chore: auto merge branches (#52047)

chore: merge master into feature
This commit is contained in:
github-actions[bot] 2024-12-19 08:12:06 +00:00 committed by GitHub
commit d20a07405f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
150 changed files with 932 additions and 423 deletions

View File

@ -66,7 +66,7 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
export interface SemanticPreviewProps { export interface SemanticPreviewProps {
semantics: { name: string; desc: string; version?: string }[]; semantics: { name: string; desc: string; version?: string }[];
children: React.ReactElement; children: React.ReactElement<any>;
height?: number; height?: number;
} }
@ -97,7 +97,7 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
// ======================== Hover ========================= // ======================== Hover =========================
const containerRef = React.useRef<HTMLDivElement>(null); const containerRef = React.useRef<HTMLDivElement>(null);
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(); const timerRef = React.useRef<ReturnType<typeof setTimeout>>(null);
const [positionMotion, setPositionMotion] = React.useState<boolean>(false); const [positionMotion, setPositionMotion] = React.useState<boolean>(false);
const [hoverSemantic, setHoverSemantic] = React.useState<string | null>(null); const [hoverSemantic, setHoverSemantic] = React.useState<string | null>(null);

View File

@ -2,7 +2,7 @@ import React, { Suspense, useContext } from 'react';
import { BugOutlined, CodeOutlined, ExperimentOutlined } from '@ant-design/icons'; import { BugOutlined, CodeOutlined, ExperimentOutlined } from '@ant-design/icons';
import { ConfigProvider, Tooltip, Button } from 'antd'; import { ConfigProvider, Tooltip, Button } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { DumiDemoGrid, FormattedMessage } from 'dumi'; import { DumiDemoGrid, FormattedMessage, DumiDemo } from 'dumi';
import { css, Global } from '@emotion/react'; import { css, Global } from '@emotion/react';
import useLayoutState from '../../../hooks/useLayoutState'; import useLayoutState from '../../../hooks/useLayoutState';
@ -114,7 +114,14 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
</span> </span>
<ConfigProvider theme={{ cssVar: enableCssVar, hashed: !enableCssVar }}> <ConfigProvider theme={{ cssVar: enableCssVar, hashed: !enableCssVar }}>
<Suspense> <Suspense>
<DumiDemoGrid items={demos} /> <DumiDemoGrid
items={demos}
demoRender={(item) => (
<Suspense>
<DumiDemo key={item.demo.id} {...item} />
</Suspense>
)}
/>
</Suspense> </Suspense>
</ConfigProvider> </ConfigProvider>
</div> </div>

View File

@ -306,7 +306,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
return ( return (
<> <>
{isValidElement(children) && {isValidElement(children) &&
cloneElement(children as React.ReactElement, { cloneElement(children as React.ReactElement<any>, {
onClick: () => setShow(true), onClick: () => setShow(true),
})} })}
<Drawer <Drawer

View File

@ -141,13 +141,23 @@ const PrevAndNext: React.FC<{ rtl?: boolean }> = ({ rtl }) => {
return ( return (
<section className={styles.prevNextNav}> <section className={styles.prevNextNav}>
{prev && {prev &&
React.cloneElement(prev.label as ReactElement, { React.cloneElement(
className: classNames(styles.pageNav, styles.prevNav, prev.className), prev.label as ReactElement<{
})} className: string;
}>,
{
className: classNames(styles.pageNav, styles.prevNav, prev.className),
},
)}
{next && {next &&
React.cloneElement(next.label as ReactElement, { React.cloneElement(
className: classNames(styles.pageNav, styles.nextNav, next.className), next.label as ReactElement<{
})} className: string;
}>,
{
className: classNames(styles.pageNav, styles.nextNav, next.className),
},
)}
</section> </section>
); );
}; };

View File

@ -37,7 +37,7 @@ const DocLayout: React.FC = () => {
const location = useLocation(); const location = useLocation();
const { pathname, search, hash } = location; const { pathname, search, hash } = location;
const [locale, lang] = useLocale(locales); const [locale, lang] = useLocale(locales);
const timerRef = useRef<ReturnType<typeof setTimeout>>(); const timerRef = useRef<ReturnType<typeof setTimeout>>(null!);
const { direction } = useContext(SiteContext); const { direction } = useContext(SiteContext);
const { loading } = useSiteData(); const { loading } = useSiteData();

View File

@ -9,10 +9,11 @@ import {
} from '@ant-design/cssinjs'; } from '@ant-design/cssinjs';
import { HappyProvider } from '@ant-design/happy-work-theme'; import { HappyProvider } from '@ant-design/happy-work-theme';
import { getSandpackCssText } from '@codesandbox/sandpack-react'; import { getSandpackCssText } from '@codesandbox/sandpack-react';
import { theme as antdTheme, App } from 'antd'; import { theme as antdTheme, App, unstableSetRender } from 'antd';
import type { MappingAlgorithm } from 'antd'; import type { MappingAlgorithm } from 'antd';
import type { DirectionType, ThemeConfig } from 'antd/es/config-provider'; import type { DirectionType, ThemeConfig } from 'antd/es/config-provider';
import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi'; import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi';
import { createRoot } from 'react-dom/client';
import { DarkContext } from '../../hooks/useDark'; import { DarkContext } from '../../hooks/useDark';
import useLayoutState from '../../hooks/useLayoutState'; import useLayoutState from '../../hooks/useLayoutState';
@ -30,6 +31,14 @@ type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
const RESPONSIVE_MOBILE = 768; const RESPONSIVE_MOBILE = 768;
export const ANT_DESIGN_NOT_SHOW_BANNER = 'ANT_DESIGN_NOT_SHOW_BANNER'; export const ANT_DESIGN_NOT_SHOW_BANNER = 'ANT_DESIGN_NOT_SHOW_BANNER';
unstableSetRender((node, container) => {
const root = createRoot(container);
root.render(node);
return async () => {
root.unmount();
};
});
// const styleCache = createCache(); // const styleCache = createCache();
// if (typeof global !== 'undefined') { // if (typeof global !== 'undefined') {
// (global as any).styleCache = styleCache; // (global as any).styleCache = styleCache;

View File

@ -8,13 +8,14 @@ import useMenu from '../../../hooks/useMenu';
import SiteContext from '../SiteContext'; import SiteContext from '../SiteContext';
const useStyle = createStyles(({ token, css }) => { const useStyle = createStyles(({ token, css }) => {
const { antCls, fontFamily, colorSplit } = token; const { antCls, fontFamily, colorSplit, marginXXL, paddingXXS } = token;
return { return {
asideContainer: css` asideContainer: css`
min-height: 100%; min-height: 100%;
padding-bottom: 48px; padding-bottom: ${marginXXL}px !important;
font-family: Avenir, ${fontFamily}, sans-serif; font-family: Avenir, ${fontFamily}, sans-serif;
padding: 0 ${paddingXXS}px;
&${antCls}-menu-inline { &${antCls}-menu-inline {
${antCls}-menu-submenu-title h4, ${antCls}-menu-submenu-title h4,
@ -94,14 +95,10 @@ const useStyle = createStyles(({ token, css }) => {
position: sticky; position: sticky;
top: ${token.headerHeight + token.contentMarginTop}px; top: ${token.headerHeight + token.contentMarginTop}px;
width: 100%; width: 100%;
height: 100%;
max-height: calc(100vh - ${token.headerHeight + token.contentMarginTop}px); max-height: calc(100vh - ${token.headerHeight + token.contentMarginTop}px);
overflow: hidden; overflow: hidden;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-gutter: stable; scrollbar-gutter: stable;
.ant-menu {
padding: 0 4px;
}
&:hover { &:hover {
overflow-y: auto; overflow-y: auto;

70
.github/workflows/auto-unassign.yml vendored Normal file
View File

@ -0,0 +1,70 @@
name: Issue Inactivity Reminder
on:
schedule:
- cron: "0 0 * * *" # Run at 00:00 every day
permissions:
issues: write # Need write permission to modify issue assignees
jobs:
reminder_job:
runs-on: ubuntu-latest
steps:
- name: Send reminders for inactive issues
uses: actions/github-script@v7
with:
script: |
const daysBeforeReminder = 14;
let page = 1;
const perPage = 100;
while (true) {
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
assignee: '*', // Filter assigned issues
per_page: perPage,
page: page,
});
if (issues.length === 0) {
break;
}
const now = new Date();
for (const issue of issues) {
if (issue.pull_request) continue;
const updatedAt = new Date(issue.updated_at);
const daysInactive = (now - updatedAt) / (1000 * 60 * 60 * 24);
const { data: timeline } = await github.rest.issues.listEventsForTimeline({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
});
const hasLinkedPR = timeline.some(event =>
event.event === 'connected' || // PR connected via keywords
event.event === 'referenced' && event.commit_id // Connected via commit
);
if (daysInactive >= daysBeforeReminder && !hasLinkedPR) {
const assigneesMentions = issue.assignees
.map(user => `@${user.login}`)
.join(' ');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `${assigneesMentions} 这个 issue 已经超过 14 天没有更新或关联 PR。如果您仍在处理这个 issue请更新进度如果您无法继续处理请联系维护者重新分配。\n\nThis issue has been inactive for more than 14 days without any updates or linked PR. If you are still working on this issue, please provide a progress update. If you are unable to continue, please contact the maintainers for reassignment.`,
});
}
}
page += 1;
}

View File

@ -14,6 +14,11 @@ jobs:
build-site: build-site:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: (startsWith(github.ref, 'refs/tags/') && (contains(github.ref_name, '-') == false)) || github.event_name == 'workflow_dispatch' if: (startsWith(github.ref, 'refs/tags/') && (contains(github.ref_name, '-') == false)) || github.event_name == 'workflow_dispatch'
# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#example-defining-outputs-for-a-job
outputs:
formatted_version: ${{ steps.shared-formatted_version.outputs.VERSION }}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -46,6 +51,11 @@ jobs:
path: _site/ path: _site/
retention-days: 1 # Not need to keep for too long retention-days: 1 # Not need to keep for too long
- name: Format version
if: ${{ always() }}
id: shared-formatted_version
run: echo "VERSION=$(echo ${{ github.ref_name }} | sed 's/\./-/g')" >> $GITHUB_OUTPUT
deploy-to-pages: deploy-to-pages:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build-site needs: build-site
@ -58,10 +68,6 @@ jobs:
name: real-site name: real-site
path: _site path: _site
- name: Get version
id: publish-version
run: echo "VERSION=$(echo ${{ github.ref_name }} | sed 's/\./-/g')" >> $GITHUB_OUTPUT
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4 uses: peaceiris/actions-gh-pages@v4
with: with:
@ -81,15 +87,15 @@ jobs:
- name: Deploy to Surge (with TAG) - name: Deploy to Surge (with TAG)
run: | run: |
export DEPLOY_DOMAIN=ant-design-${{ steps.publish-version.outputs.VERSION }}.surge.sh export DEPLOY_DOMAIN=ant-design-${{ needs.build-site.outputs.formatted_version }}.surge.sh
bunx surge --project ./_site --domain $DEPLOY_DOMAIN --token ${{ secrets.SURGE_TOKEN }} bunx surge --project ./_site --domain $DEPLOY_DOMAIN --token ${{ secrets.SURGE_TOKEN }}
- name: Create Commit Comment - name: Create Commit Comment
uses: peter-evans/commit-comment@v3 uses: peter-evans/commit-comment@v3
with: with:
body: | body: |
- Documentation site for this release: https://ant-design-${{ steps.publish-version.outputs.VERSION }}.surge.sh - Documentation site for this release: https://ant-design-${{ needs.build-site.outputs.formatted_version }}.surge.sh
- Webpack bundle analyzer report page: https://ant-design-${{ steps.publish-version.outputs.VERSION }}.surge.sh/report.html - Webpack bundle analyzer report page: https://ant-design-${{ needs.build-site.outputs.formatted_version }}.surge.sh/report.html
# https://github.com/ant-design/ant-design/pull/49213/files#r1625446496 # https://github.com/ant-design/ant-design/pull/49213/files#r1625446496
upload-to-release: upload-to-release:
@ -107,12 +113,11 @@ jobs:
- name: Tarball site - name: Tarball site
run: | run: |
cd ./_site cd ./_site
VERSION=$(echo ${{ github.ref_name }} | sed 's/\./-/g') tar -czf ../website.tar.gz --transform 's|^|antd-${{ needs.build-site.outputs.formatted_version }}-website/|' .
tar -czf ../website.tar.gz --transform 's|^|antd-${VERSION}-website/|' .
cd .. cd ..
- name: Upload to Release - name: Upload to Release
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0 uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0
with: with:
fail_on_unmatched_files: true fail_on_unmatched_files: true
files: website.tar.gz files: website.tar.gz

2
.husky/pre-commit Executable file → Normal file
View File

@ -1 +1 @@
lint-staged lint-staged

View File

@ -15,6 +15,19 @@ tag: vVERSION
--- ---
## 5.22.5
`2024-12-15`
- 🛠 Refactor Wave/Menu/Form `ref` check logic to resolve React 19 `ref` conflict (Note, this is not finally support React 19 but we will resolve step by step in future version). [#51952](https://github.com/ant-design/ant-design/pull/51952) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Dropdown cannot accept ReactNode as `children`. [#50174](https://github.com/ant-design/ant-design/pull/50174) [@coding-ice](https://github.com/coding-ice)
- 🐞 Fix Carousel cannot display correctly in Modal without icon. [#51988](https://github.com/ant-design/ant-design/pull/51988) [@quan060798](https://github.com/quan060798)
- 🐞 Fix Select label overflow issue. [#52011](https://github.com/ant-design/ant-design/pull/52011) [@OysterD3](https://github.com/OysterD3)
- 🐞 Fix Form `setFieldValue` not reset field validation. [#51993](https://github.com/ant-design/ant-design/pull/51993) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Pagination with setting `showSizeChanger.showSearch` not working. [#51962](https://github.com/ant-design/ant-design/pull/51962) [@zombieJ](https://github.com/zombieJ)
- 🇰🇷 Improve Korean locales for DatePicker. [#51983](https://github.com/ant-design/ant-design/pull/51983) [@DevLeti](https://github.com/DevLeti)
- 🤖 Export `CheckboxChangeEvent` from antd. [#52008](https://github.com/ant-design/ant-design/pull/52008) [@SpecLad](https://github.com/SpecLad)
## 5.22.4 ## 5.22.4
`2024-12-09` `2024-12-09`

View File

@ -15,6 +15,19 @@ tag: vVERSION
--- ---
## 5.22.5
`2024-12-15`
- 🛠 重构 Wave/Menu/Form `ref` 检查逻辑以解决 React 19 `ref` 部分冲突(注:该更新不会完全解决 React 19 兼容问题,后续将会持续更新)。[#51952](https://github.com/ant-design/ant-design/pull/51952) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Dropdown `children` 不支持传入 ReactNode 的问题。[#50174](https://github.com/ant-design/ant-design/pull/50174) [@coding-ice](https://github.com/coding-ice)
- 🐞 修复 Carousel 某些情况下在 Modal 中无法正确展示的问题。[#51988](https://github.com/ant-design/ant-design/pull/51988) [@quan060798](https://github.com/quan060798)
- 🐞 修复 Select 选中文本溢出的问题 。[#52011](https://github.com/ant-design/ant-design/pull/52011) [@OysterD3](https://github.com/OysterD3)
- 🐞 修复 Form `setFieldValue` 没有重置字段校验信息的问题。[#51993](https://github.com/ant-design/ant-design/pull/51993) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Pagination 配置 `showSizeChanger.showSearch` 无效的问题。[#51962](https://github.com/ant-design/ant-design/pull/51962) [@zombieJ](https://github.com/zombieJ)
- 🇰🇷 优化 DatePicker 韩语本地化文案。[#51983](https://github.com/ant-design/ant-design/pull/51983) [@DevLeti](https://github.com/DevLeti)
- 🤖 从 antd 里导出 `CheckboxChangeEvent` 类型。[#52008](https://github.com/ant-design/ant-design/pull/52008) [@SpecLad](https://github.com/SpecLad)
## 5.22.4 ## 5.22.4
`2024-12-09` `2024-12-09`

View File

@ -74,6 +74,7 @@ exports[`antd exports modules correctly 1`] = `
"message", "message",
"notification", "notification",
"theme", "theme",
"unstableSetRender",
"version", "version",
] ]
`; `;

View File

@ -0,0 +1,44 @@
import * as ReactDOM from 'react-dom';
import { Modal, unstableSetRender } from 'antd';
import { waitFakeTimer19 } from '../../tests/utils';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('unstable', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('unstableSetRender', async () => {
if (ReactDOM.version.startsWith('19')) {
unstableSetRender((node, container) => {
const root = (ReactDOM as any).createRoot(container);
root.render(node);
return async () => {
root.unmount();
};
});
Modal.info({ content: 'unstableSetRender' });
await waitFakeTimer19();
expect(document.querySelector('.ant-modal')).toBeTruthy();
}
});
});

View File

@ -28,6 +28,18 @@ import { consumerBaseZIndexOffset, containerBaseZIndexOffset, useZIndex } from '
import { resetWarned } from '../warning'; import { resetWarned } from '../warning';
import zIndexContext from '../zindexContext'; import zIndexContext from '../zindexContext';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
const WrapWithProvider: React.FC<PropsWithChildren<{ container: ZIndexContainer }>> = ({ const WrapWithProvider: React.FC<PropsWithChildren<{ container: ZIndexContainer }>> = ({
children, children,
container, container,

View File

@ -9,6 +9,18 @@ import { TARGET_CLS } from '../wave/interface';
(global as any).isVisible = true; (global as any).isVisible = true;
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
jest.mock('rc-util/lib/Dom/isVisible', () => { jest.mock('rc-util/lib/Dom/isVisible', () => {
const mockFn = () => (global as any).isVisible; const mockFn = () => (global as any).isVisible;
return mockFn; return mockFn;
@ -96,6 +108,7 @@ describe('Wave component', () => {
expect(document.querySelector('.ant-wave')).toBeFalsy(); expect(document.querySelector('.ant-wave')).toBeFalsy();
expect(errorSpy).not.toHaveBeenCalled(); expect(errorSpy).not.toHaveBeenCalled();
errorSpy.mockRestore();
unmount(); unmount();
}); });

View File

@ -0,0 +1,3 @@
const isPrimitive = (value: unknown) => (typeof value !== 'object' && typeof value !== 'function') || value === null;
export default isPrimitive;

View File

@ -2,9 +2,9 @@ import * as React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import CSSMotion from 'rc-motion'; import CSSMotion from 'rc-motion';
import raf from 'rc-util/lib/raf'; import raf from 'rc-util/lib/raf';
import { render, unmount } from 'rc-util/lib/React/render';
import { composeRef } from 'rc-util/lib/ref'; import { composeRef } from 'rc-util/lib/ref';
import { getReactRender, type UnmountType } from '../../config-provider/UnstableContext';
import { TARGET_CLS } from './interface'; import { TARGET_CLS } from './interface';
import type { ShowWaveEffect } from './interface'; import type { ShowWaveEffect } from './interface';
import { getTargetWaveColor } from './util'; import { getTargetWaveColor } from './util';
@ -17,12 +17,21 @@ export interface WaveEffectProps {
className: string; className: string;
target: HTMLElement; target: HTMLElement;
component?: string; component?: string;
registerUnmount: () => UnmountType | null;
} }
const WaveEffect: React.FC<WaveEffectProps> = (props) => { const WaveEffect = (props: WaveEffectProps) => {
const { className, target, component } = props; const { className, target, component, registerUnmount } = props;
const divRef = React.useRef<HTMLDivElement>(null); const divRef = React.useRef<HTMLDivElement>(null);
// ====================== Refs ======================
const unmountRef = React.useRef<UnmountType>(null);
React.useEffect(() => {
unmountRef.current = registerUnmount();
}, []);
// ===================== Effect =====================
const [color, setWaveColor] = React.useState<string | null>(null); const [color, setWaveColor] = React.useState<string | null>(null);
const [borderRadius, setBorderRadius] = React.useState<number[]>([]); const [borderRadius, setBorderRadius] = React.useState<number[]>([]);
const [left, setLeft] = React.useState(0); const [left, setLeft] = React.useState(0);
@ -119,7 +128,7 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
onAppearEnd={(_, event) => { onAppearEnd={(_, event) => {
if (event.deadline || (event as TransitionEvent).propertyName === 'opacity') { if (event.deadline || (event as TransitionEvent).propertyName === 'opacity') {
const holder = divRef.current?.parentElement!; const holder = divRef.current?.parentElement!;
unmount(holder).then(() => { unmountRef.current?.().then(() => {
holder?.remove(); holder?.remove();
}); });
} }
@ -140,13 +149,6 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
const showWaveEffect: ShowWaveEffect = (target, info) => { const showWaveEffect: ShowWaveEffect = (target, info) => {
const { component } = info; const { component } = info;
// Skip if not support `render` since `rc-util` render not support React 19
// TODO: remove this check in v6
/* istanbul ignore next */
if (!render) {
return;
}
// Skip for unchecked checkbox // Skip for unchecked checkbox
if (component === 'Checkbox' && !target.querySelector<HTMLInputElement>('input')?.checked) { if (component === 'Checkbox' && !target.querySelector<HTMLInputElement>('input')?.checked) {
return; return;
@ -159,7 +161,18 @@ const showWaveEffect: ShowWaveEffect = (target, info) => {
holder.style.top = '0px'; holder.style.top = '0px';
target?.insertBefore(holder, target?.firstChild); target?.insertBefore(holder, target?.firstChild);
render(<WaveEffect {...info} target={target} />, holder); const reactRender = getReactRender();
let unmountCallback: UnmountType | null = null;
function registerUnmount() {
return unmountCallback;
}
unmountCallback = reactRender(
<WaveEffect {...info} target={target} registerUnmount={registerUnmount} />,
holder,
);
}; };
export default showWaveEffect; export default showWaveEffect;

View File

@ -19,7 +19,7 @@ export interface WaveProps {
const Wave: React.FC<WaveProps> = (props) => { const Wave: React.FC<WaveProps> = (props) => {
const { children, disabled, component } = props; const { children, disabled, component } = props;
const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext); const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext);
const containerRef = useRef<HTMLElement>(null); const containerRef = useRef<HTMLElement>(null!);
// ============================== Style =============================== // ============================== Style ===============================
const prefixCls = getPrefixCls('wave'); const prefixCls = getPrefixCls('wave');

View File

@ -28,10 +28,16 @@ const useWave = (
const { showEffect } = wave || {}; const { showEffect } = wave || {};
// Customize wave effect // Customize wave effect
(showEffect || showWaveEffect)(targetNode, { className, token, component, event, hashId }); (showEffect || showWaveEffect)(targetNode, {
className,
token,
component,
event,
hashId,
});
}); });
const rafId = React.useRef<number>(); const rafId = React.useRef<number>(null);
// Merge trigger event into one for each frame // Merge trigger event into one for each frame
const showDebounceWave: ShowWave = (event) => { const showDebounceWave: ShowWave = (event) => {

View File

@ -1,5 +1,7 @@
import { imageDemoTest } from '../../../tests/shared/imageTest'; import { imageDemoTest } from '../../../tests/shared/imageTest';
describe('Affix image', () => { describe('Affix image', () => {
imageDemoTest('affix'); imageDemoTest('affix', {
onlyViewport: ['debug.tsx'],
});
}); });

View File

@ -80,7 +80,7 @@ const Affix = React.forwardRef<AffixRef, AffixProps>((props, ref) => {
const status = React.useRef<AffixStatus>(AFFIX_STATUS_NONE); const status = React.useRef<AffixStatus>(AFFIX_STATUS_NONE);
const prevTarget = React.useRef<Window | HTMLElement | null>(null); const prevTarget = React.useRef<Window | HTMLElement | null>(null);
const prevListener = React.useRef<EventListener>(); const prevListener = React.useRef<EventListener>(null);
const placeholderNodeRef = React.useRef<HTMLDivElement>(null); const placeholderNodeRef = React.useRef<HTMLDivElement>(null);
const fixedNodeRef = React.useRef<HTMLDivElement>(null); const fixedNodeRef = React.useRef<HTMLDivElement>(null);

View File

@ -76,9 +76,14 @@ const IconNode: React.FC<IconNodeProps> = (props) => {
const iconType = iconMapFilled[type!] || null; const iconType = iconMapFilled[type!] || null;
if (icon) { if (icon) {
return replaceElement(icon, <span className={`${prefixCls}-icon`}>{icon}</span>, () => ({ return replaceElement(icon, <span className={`${prefixCls}-icon`}>{icon}</span>, () => ({
className: classNames(`${prefixCls}-icon`, { className: classNames(
[(icon as ReactElement).props.className]: (icon as ReactElement).props.className, `${prefixCls}-icon`,
}), (
icon as ReactElement<{
className?: string;
}>
).props.className,
),
})) as ReactElement; })) as ReactElement;
} }
return React.createElement(iconType, { className: `${prefixCls}-icon` }); return React.createElement(iconType, { className: `${prefixCls}-icon` });

View File

@ -5,7 +5,7 @@ import { resetWarned } from 'rc-util/lib/warning';
import Alert from '..'; import Alert from '..';
import { accessibilityTest } from '../../../tests/shared/accessibilityTest'; import { accessibilityTest } from '../../../tests/shared/accessibilityTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
import { act, render, screen, waitFakeTimer } from '../../../tests/utils'; import { act, fireEvent, render, screen, waitFakeTimer } from '../../../tests/utils';
import Button from '../../button'; import Button from '../../button';
import Popconfirm from '../../popconfirm'; import Popconfirm from '../../popconfirm';
import Tooltip from '../../tooltip'; import Tooltip from '../../tooltip';
@ -28,7 +28,7 @@ describe('Alert', () => {
it('should show close button and could be closed', async () => { it('should show close button and could be closed', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const onClose = jest.fn(); const onClose = jest.fn();
render( const { container } = render(
<Alert <Alert
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text" message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
type="warning" type="warning"
@ -37,10 +37,7 @@ describe('Alert', () => {
/>, />,
); );
await act(async () => { fireEvent.click(container.querySelector('.ant-alert-close-icon')!);
await userEvent.click(screen.getByRole('button', { name: /close/i }));
jest.runAllTimers();
});
expect(onClose).toHaveBeenCalledTimes(1); expect(onClose).toHaveBeenCalledTimes(1);
expect(errSpy).not.toHaveBeenCalled(); expect(errSpy).not.toHaveBeenCalled();

View File

@ -381,8 +381,6 @@ describe('Anchor Render', () => {
}, },
]} ]}
/>, />,
// https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0
{ legacyRoot: true },
); );
expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenCalledTimes(1);
@ -556,16 +554,14 @@ describe('Anchor Render', () => {
{ key: hash2, href: `#${hash2}`, title: hash2 }, { key: hash2, href: `#${hash2}`, title: hash2 },
]} ]}
/>, />,
// https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0
{ legacyRoot: true },
); );
// Should be 2 times: // Should be 2 times:
// 1. '' // 1. ''
// 2. hash1 (Since `getCurrentAnchor` still return same hash) // 2. hash1 (Since `getCurrentAnchor` still return same hash)
expect(onChange).toHaveBeenCalledTimes(2); const calledTimes = onChange.mock.calls.length;
fireEvent.click(container.querySelector(`a[href="#${hash2}"]`)!); fireEvent.click(container.querySelector(`a[href="#${hash2}"]`)!);
expect(onChange).toHaveBeenCalledTimes(3); expect(onChange).toHaveBeenCalledTimes(calledTimes + 1);
expect(onChange).toHaveBeenLastCalledWith(`#${hash2}`); expect(onChange).toHaveBeenLastCalledWith(`#${hash2}`);
}); });

View File

@ -2267,15 +2267,7 @@ exports[`renders components/auto-complete/demo/render-panel.tsx extend context c
</div> </div>
`; `;
exports[`renders components/auto-complete/demo/render-panel.tsx extend context correctly 2`] = ` exports[`renders components/auto-complete/demo/render-panel.tsx extend context correctly 2`] = `[]`;
[
"Warning: Received \`%s\` for a non-boolean attribute \`%s\`.
If you want to write it to the DOM, pass a string instead: %s="%s" or %s={value.toString()}.
If you used to conditionally omit it with %s={condition && value}, pass %s={condition ? value : undefined} instead.%s",
]
`;
exports[`renders components/auto-complete/demo/status.tsx extend context correctly 1`] = ` exports[`renders components/auto-complete/demo/status.tsx extend context correctly 1`] = `
<div <div

View File

@ -1,3 +1,5 @@
import { extendTest } from '../../../tests/shared/demoTest'; import { extendTest } from '../../../tests/shared/demoTest';
extendTest('auto-complete'); extendTest('auto-complete', {
skip: ['row-selection-debug.tsx'],
});

View File

@ -1,5 +1,7 @@
import { imageDemoTest } from '../../../tests/shared/imageTest'; import { imageDemoTest } from '../../../tests/shared/imageTest';
describe('AutoComplete image', () => { describe('AutoComplete image', () => {
imageDemoTest('auto-complete'); imageDemoTest('auto-complete', {
skip: ['row-selection-debug.tsx'],
});
}); });

View File

@ -170,7 +170,9 @@ const RefAutoComplete = React.forwardRef<RefSelectProps, AutoCompleteProps>(
// We don't care debug panel // We don't care debug panel
/* istanbul ignore next */ /* istanbul ignore next */
const PurePanel = genPurePanel(RefAutoComplete); const PurePanel = genPurePanel(RefAutoComplete, undefined, undefined, (props: any) =>
omit(props, ['visible']),
);
RefAutoComplete.Option = Option; RefAutoComplete.Option = Option;
RefAutoComplete._InternalPanelDoNotUseOrYouWillBeFired = PurePanel; RefAutoComplete._InternalPanelDoNotUseOrYouWillBeFired = PurePanel;

View File

@ -10,7 +10,7 @@ export interface ScrollNumberProps {
className?: string; className?: string;
motionClassName?: string; motionClassName?: string;
count?: string | number | null; count?: string | number | null;
children?: React.ReactElement<HTMLElement>; children?: React.ReactElement;
component?: React.ComponentType<any>; component?: React.ComponentType<any>;
style?: React.CSSProperties; style?: React.CSSProperties;
title?: string | number | null; title?: string | number | null;

View File

@ -3,7 +3,7 @@ import React from 'react';
import type { GetRef } from '../../_util/type'; import type { GetRef } from '../../_util/type';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils'; import { act, fireEvent, render, waitFakeTimer19 } from '../../../tests/utils';
import Tooltip from '../../tooltip'; import Tooltip from '../../tooltip';
import Badge from '../index'; import Badge from '../index';
@ -50,7 +50,7 @@ describe('Badge', () => {
const { container } = render(<Comp />); const { container } = render(<Comp />);
fireEvent.click(container.querySelector('button')!); fireEvent.click(container.querySelector('button')!);
await waitFakeTimer(); await waitFakeTimer19();
expect(errSpy).not.toHaveBeenCalled(); expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore(); errSpy.mockRestore();

View File

@ -4,6 +4,18 @@ import userEvent from '@testing-library/user-event';
import Button from '..'; import Button from '..';
import { act, fireEvent, render } from '../../../tests/utils'; import { act, fireEvent, render } from '../../../tests/utils';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
jest.mock('rc-util/lib/Dom/isVisible', () => { jest.mock('rc-util/lib/Dom/isVisible', () => {
const mockFn = () => true; const mockFn = () => true;
return mockFn; return mockFn;

View File

@ -163,7 +163,7 @@ const InternalCompoundedButton = React.forwardRef<
const [hasTwoCNChar, setHasTwoCNChar] = useState<boolean>(false); const [hasTwoCNChar, setHasTwoCNChar] = useState<boolean>(false);
const buttonRef = useRef<HTMLButtonElement | HTMLAnchorElement>(); const buttonRef = useRef<HTMLButtonElement | HTMLAnchorElement>(null);
const mergedRef = useComposeRef(ref, buttonRef); const mergedRef = useComposeRef(ref, buttonRef);

View File

@ -16,7 +16,7 @@ export function convertLegacyProps(
return { type }; return { type };
} }
export function isString(str: any): str is string { export function isString(str: unknown): str is string {
return typeof str === 'string'; return typeof str === 'string';
} }
@ -35,10 +35,22 @@ function splitCNCharsBySpace(child: React.ReactElement | string | number, needIn
typeof child !== 'string' && typeof child !== 'string' &&
typeof child !== 'number' && typeof child !== 'number' &&
isString(child.type) && isString(child.type) &&
isTwoCNChar(child.props.children) isTwoCNChar(
(
child as React.ReactElement<{
children: string;
}>
).props.children,
)
) { ) {
return cloneElement(child, { return cloneElement(child, {
children: child.props.children.split('').join(SPACE), children: (
child as React.ReactElement<{
children: string;
}>
).props.children
.split('')
.join(SPACE),
}); });
} }

View File

@ -335,9 +335,11 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token) =>
token.solidTextColor, token.solidTextColor,
token.colorBgSolid, token.colorBgSolid,
{ {
color: token.solidTextColor,
background: token.colorBgSolidHover, background: token.colorBgSolidHover,
}, },
{ {
color: token.solidTextColor,
background: token.colorBgSolidActive, background: token.colorBgSolidActive,
}, },
), ),

View File

@ -153,7 +153,7 @@ export interface CalendarHeaderProps<DateType> {
} }
function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) { function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
const { prefixCls, fullscreen, mode, onChange, onModeChange } = props; const { prefixCls, fullscreen, mode, onChange, onModeChange } = props;
const divRef = React.useRef<HTMLDivElement>(null); const divRef = React.useRef<HTMLDivElement>(null!);
const formItemInputContext = useContext(FormItemInputContext); const formItemInputContext = useContext(FormItemInputContext);
const mergedFormItemInputContext = useMemo( const mergedFormItemInputContext = useMemo(

View File

@ -121,7 +121,7 @@ const App: React.FC = () => {
const displayHoliday = h?.getTarget() === h?.getDay() ? h?.getName() : undefined; const displayHoliday = h?.getTarget() === h?.getDay() ? h?.getName() : undefined;
if (info.type === 'date') { if (info.type === 'date') {
return React.cloneElement(info.originNode, { return React.cloneElement(info.originNode, {
...info.originNode.props, ...(info.originNode as React.ReactElement<any>).props,
className: classNames(styles.dateCell, { className: classNames(styles.dateCell, {
[styles.current]: selectDate.isSame(date, 'date'), [styles.current]: selectDate.isSame(date, 'date'),
[styles.today]: date.isSame(dayjs(), 'date'), [styles.today]: date.isSame(dayjs(), 'date'),

View File

@ -147,7 +147,7 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>((props, ref) => {
const isContainGrid = React.useMemo<boolean>(() => { const isContainGrid = React.useMemo<boolean>(() => {
let containGrid = false; let containGrid = false;
React.Children.forEach(children as React.ReactElement, (element: JSX.Element) => { React.Children.forEach(children as React.ReactElement, (element: React.JSX.Element) => {
if (element?.type === Grid) { if (element?.type === Grid) {
containGrid = true; containGrid = true;
} }

View File

@ -59,7 +59,7 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>((props, ref) => {
...otherProps ...otherProps
} = props; } = props;
const { getPrefixCls, direction, carousel } = React.useContext(ConfigContext); const { getPrefixCls, direction, carousel } = React.useContext(ConfigContext);
const slickRef = React.useRef<any>(); const slickRef = React.useRef<any>(null);
const goTo = (slide: number, dontAnimate = false) => { const goTo = (slide: number, dontAnimate = false) => {
slickRef.current.slickGoTo(slide, dontAnimate); slickRef.current.slickGoTo(slide, dontAnimate);

View File

@ -2585,15 +2585,7 @@ exports[`renders components/cascader/demo/render-panel.tsx extend context correc
</div> </div>
`; `;
exports[`renders components/cascader/demo/render-panel.tsx extend context correctly 2`] = ` exports[`renders components/cascader/demo/render-panel.tsx extend context correctly 2`] = `[]`;
[
"Warning: Received \`%s\` for a non-boolean attribute \`%s\`.
If you want to write it to the DOM, pass a string instead: %s="%s" or %s={value.toString()}.
If you used to conditionally omit it with %s={condition && value}, pass %s={condition ? value : undefined} instead.%s",
]
`;
exports[`renders components/cascader/demo/search.tsx extend context correctly 1`] = ` exports[`renders components/cascader/demo/search.tsx extend context correctly 1`] = `
<div <div

View File

@ -370,7 +370,9 @@ if (process.env.NODE_ENV !== 'production') {
// We don't care debug panel // We don't care debug panel
/* istanbul ignore next */ /* istanbul ignore next */
const PurePanel = genPurePanel(Cascader); const PurePanel = genPurePanel(Cascader, undefined, undefined, (props: any) =>
omit(props, ['visible']),
);
Cascader.SHOW_PARENT = SHOW_PARENT; Cascader.SHOW_PARENT = SHOW_PARENT;
Cascader.SHOW_CHILD = SHOW_CHILD; Cascader.SHOW_CHILD = SHOW_CHILD;

View File

@ -111,7 +111,14 @@ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) =>
/> />
); );
return cloneElement(icon, () => ({ return cloneElement(icon, () => ({
className: classNames((icon as React.ReactElement)?.props?.className, `${prefixCls}-arrow`), className: classNames(
(
icon as React.ReactElement<{
className?: string;
}>
)?.props?.className,
`${prefixCls}-arrow`,
),
})); }));
}, },
[mergedExpandIcon, prefixCls], [mergedExpandIcon, prefixCls],
@ -137,25 +144,30 @@ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) =>
leavedClassName: `${prefixCls}-content-hidden`, leavedClassName: `${prefixCls}-content-hidden`,
}; };
const items = React.useMemo<React.ReactNode[] | null>( const items = React.useMemo<React.ReactNode[] | null>(() => {
() => if (children) {
children return toArray(children).map((child, index) => {
? toArray(children).map<React.ReactNode>((child, index) => { const childProps = (
if (child.props?.disabled) { child as React.ReactElement<{
const key = child.key ?? String(index); disabled?: boolean;
const { disabled, collapsible } = child.props; collapsible?: CollapsibleType;
const childProps: Omit<CollapseProps, 'items'> & { key: React.Key } = { }>
...omit(child.props, ['disabled']), ).props;
key,
collapsible: collapsible ?? (disabled ? 'disabled' : undefined), if (childProps?.disabled) {
}; const key = child.key ?? String(index);
return cloneElement(child, childProps); const mergedChildProps: Omit<CollapseProps, 'items'> & { key: React.Key } = {
} ...omit(child.props as any, ['disabled']),
return child; key,
}) collapsible: childProps.collapsible ?? 'disabled',
: null, };
[children], return cloneElement(child, mergedChildProps);
); }
return child;
});
}
return null;
}, [children]);
return wrapCSSVar( return wrapCSSVar(
// @ts-ignore // @ts-ignore

View File

@ -0,0 +1,30 @@
import * as React from 'react';
import { render, unmount } from 'rc-util/lib/React/render';
export type UnmountType = () => Promise<void>;
export type RenderType = (
node: React.ReactElement,
container: Element | DocumentFragment,
) => UnmountType;
const defaultReactRender: RenderType = (node, container) => {
render(node, container);
return () => {
return unmount(container);
};
};
let unstableRender: RenderType = defaultReactRender;
/**
* @deprecated Set React render function for compatible usage.
* This is internal usage only compatible with React 19.
* And will be removed in next major version.
*/
export function unstableSetRender(render: RenderType) {
unstableRender = render;
}
export function getReactRender() {
return unstableRender;
}

View File

@ -12,6 +12,18 @@ import Modal from '../../modal';
import Pagination from '../../pagination'; import Pagination from '../../pagination';
import TimePicker from '../../time-picker'; import TimePicker from '../../time-picker';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('ConfigProvider.Locale', () => { describe('ConfigProvider.Locale', () => {
function $$(selector: string): NodeListOf<Element> { function $$(selector: string): NodeListOf<Element> {
return document.body.querySelectorAll(selector); return document.body.querySelectorAll(selector);

View File

@ -193,12 +193,26 @@ const Page: React.FC = () => {
Begin Tour Begin Tour
</Button> </Button>
<Space> <Space>
<Button ref={(node) => node && tourRefs.current.splice(0, 0, node)}> Upload</Button> <Button
<Button ref={(node) => node && tourRefs.current.splice(1, 0, node)} type="primary"> ref={(node) => {
node && tourRefs.current.splice(0, 0, node);
}}
>
{' '}
Upload
</Button>
<Button
ref={(node) => {
node && tourRefs.current.splice(1, 0, node);
}}
type="primary"
>
Save Save
</Button> </Button>
<Button <Button
ref={(node) => node && tourRefs.current.splice(2, 0, node)} ref={(node) => {
node && tourRefs.current.splice(2, 0, node);
}}
icon={<EllipsisOutlined />} icon={<EllipsisOutlined />}
/> />
</Space> </Space>

View File

@ -1,4 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import type { JSX } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
function notEmpty(val: any) { function notEmpty(val: any) {

View File

@ -11,6 +11,9 @@ export interface DescriptionsItemProps {
span?: number; span?: number;
} }
const DescriptionsItem: React.FC<DescriptionsItemProps> = ({ children }) => children as JSX.Element; // JSX Structure Syntactic Sugar. Never reach the render code.
/* istanbul ignore next */
const DescriptionsItem: React.FC<DescriptionsItemProps> = ({ children }) =>
children as React.JSX.Element;
export default DescriptionsItem; export default DescriptionsItem;

View File

@ -7,7 +7,10 @@ import type { ScreenMap } from '../../_util/responsiveObserver';
// Convert children into items // Convert children into items
const transChildren2Items = (childNodes?: React.ReactNode) => const transChildren2Items = (childNodes?: React.ReactNode) =>
toArray(childNodes).map((node) => ({ ...node?.props, key: node.key })); toArray(childNodes).map((node) => ({
...(node as React.ReactElement<any>)?.props,
key: node.key,
}));
export default function useItems( export default function useItems(
screens: ScreenMap, screens: ScreenMap,

View File

@ -352,6 +352,17 @@ describe('Dropdown', () => {
expect(container3.querySelector('button')).not.toHaveAttribute('disabled'); expect(container3.querySelector('button')).not.toHaveAttribute('disabled');
}); });
it('should support Primitive', () => {
expect(() => {
render(<Dropdown>antd</Dropdown>);
render(<Dropdown>{123}</Dropdown>);
render(<Dropdown>{undefined}</Dropdown>);
render(<Dropdown>{true}</Dropdown>);
render(<Dropdown>{false}</Dropdown>);
render(<Dropdown>{null}</Dropdown>);
}).not.toThrow();
});
it('menu item with extra prop', () => { it('menu item with extra prop', () => {
const text = '⌘P'; const text = '⌘P';
const { container } = render( const { container } = render(

View File

@ -52,7 +52,12 @@ const App: React.FC = () => {
menu={{ items }} menu={{ items }}
dropdownRender={(menu) => ( dropdownRender={(menu) => (
<div style={contentStyle}> <div style={contentStyle}>
{React.cloneElement(menu as React.ReactElement, { style: menuStyle })} {React.cloneElement(
menu as React.ReactElement<{
style: React.CSSProperties;
}>,
{ style: menuStyle },
)}
<Divider style={{ margin: 0 }} /> <Divider style={{ margin: 0 }} />
<Space style={{ padding: 8 }}> <Space style={{ padding: 8 }}>
<Button type="primary">Click me!</Button> <Button type="primary">Click me!</Button>

View File

@ -8,6 +8,7 @@ import useMergedState from 'rc-util/lib/hooks/useMergedState';
import omit from 'rc-util/lib/omit'; import omit from 'rc-util/lib/omit';
import { useZIndex } from '../_util/hooks/useZIndex'; import { useZIndex } from '../_util/hooks/useZIndex';
import isPrimitive from '../_util/isPrimitive';
import type { AdjustOverflow } from '../_util/placements'; import type { AdjustOverflow } from '../_util/placements';
import getPlacements from '../_util/placements'; import getPlacements from '../_util/placements';
import genPurePanel from '../_util/PurePanel'; import genPurePanel from '../_util/PurePanel';
@ -175,7 +176,12 @@ const Dropdown: CompoundedComponent = (props) => {
const [, token] = useToken(); const [, token] = useToken();
const child = React.Children.only(children) as React.ReactElement<any>; const child = React.Children.only(
isPrimitive(children) ? <span>{children}</span> : children,
) as React.ReactElement<{
className?: string;
disabled?: boolean;
}>;
const dropdownTrigger = cloneElement(child, { const dropdownTrigger = cloneElement(child, {
className: classNames( className: classNames(

View File

@ -8,8 +8,6 @@ import { fireEvent, render, waitFakeTimer } from '../../../tests/utils';
const { BackTop } = FloatButton; const { BackTop } = FloatButton;
describe('BackTop', () => { describe('BackTop', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
beforeEach(() => { beforeEach(() => {
jest.useFakeTimers(); jest.useFakeTimers();
}); });
@ -57,6 +55,8 @@ describe('BackTop', () => {
}); });
it('no error when BackTop work', () => { it('no error when BackTop work', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<BackTop visibilityHeight={0} />); render(<BackTop visibilityHeight={0} />);
expect(errSpy).not.toHaveBeenCalled(); expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore(); errSpy.mockRestore();

View File

@ -1,5 +1,4 @@
import * as React from 'react'; import * as React from 'react';
import { useMemo } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import type { CSSMotionProps } from 'rc-motion'; import type { CSSMotionProps } from 'rc-motion';
import CSSMotion, { CSSMotionList } from 'rc-motion'; import CSSMotion, { CSSMotionList } from 'rc-motion';
@ -58,7 +57,10 @@ const ErrorList: React.FC<ErrorListProps> = ({
const rootCls = useCSSVarCls(prefixCls); const rootCls = useCSSVarCls(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls); const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const collapseMotion: CSSMotionProps = useMemo(() => initCollapseMotion(prefixCls), [prefixCls]); const collapseMotion = React.useMemo<CSSMotionProps>(
() => initCollapseMotion(prefixCls),
[prefixCls],
);
// We have to debounce here again since somewhere use ErrorList directly still need no shaking // We have to debounce here again since somewhere use ErrorList directly still need no shaking
// ref: https://github.com/ant-design/ant-design/issues/36336 // ref: https://github.com/ant-design/ant-design/issues/36336

View File

@ -1,5 +1,4 @@
import * as React from 'react'; import * as React from 'react';
import { useMemo } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import FieldForm, { List, useWatch } from 'rc-field-form'; import FieldForm, { List, useWatch } from 'rc-field-form';
import type { FormProps as RcFormProps } from 'rc-field-form/lib/Form'; import type { FormProps as RcFormProps } from 'rc-field-form/lib/Form';
@ -93,7 +92,7 @@ const InternalForm: React.ForwardRefRenderFunction<FormRef, FormProps> = (props,
useFormWarning(props); useFormWarning(props);
} }
const mergedRequiredMark = useMemo(() => { const mergedRequiredMark = React.useMemo(() => {
if (requiredMark !== undefined) { if (requiredMark !== undefined) {
return requiredMark; return requiredMark;
} }
@ -137,7 +136,7 @@ const InternalForm: React.ForwardRefRenderFunction<FormRef, FormProps> = (props,
const { __INTERNAL__ } = wrapForm; const { __INTERNAL__ } = wrapForm;
__INTERNAL__.name = name; __INTERNAL__.name = name;
const formContextValue = useMemo<FormContextProps>( const formContextValue = React.useMemo<FormContextProps>(
() => ({ () => ({
name, name,
labelAlign, labelAlign,

View File

@ -1,4 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import type { JSX } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Field, FieldContext, ListContext } from 'rc-field-form'; import { Field, FieldContext, ListContext } from 'rc-field-form';
import type { FieldProps } from 'rc-field-form/lib/Field'; import type { FieldProps } from 'rc-field-form/lib/Field';
@ -165,7 +166,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
// ========================= MISC ========================= // ========================= MISC =========================
// Get `noStyle` required info // Get `noStyle` required info
const listContext = React.useContext(ListContext); const listContext = React.useContext(ListContext);
const fieldKeyPathRef = React.useRef<InternalNamePath>(); const fieldKeyPathRef = React.useRef<InternalNamePath>(null);
// ======================== Errors ======================== // ======================== Errors ========================
// >>>>> Collect sub field errors // >>>>> Collect sub field errors
@ -361,12 +362,19 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
); );
} else if (React.isValidElement(mergedChildren)) { } else if (React.isValidElement(mergedChildren)) {
warning( warning(
mergedChildren.props.defaultValue === undefined, (
mergedChildren as React.ReactElement<{
defaultValue?: any;
}>
).props.defaultValue === undefined,
'usage', 'usage',
'`defaultValue` will not work on controlled Field. You should use `initialValues` of Form instead.', '`defaultValue` will not work on controlled Field. You should use `initialValues` of Form instead.',
); );
const childProps = { ...mergedChildren.props, ...mergedControl }; const childProps = {
...(mergedChildren as React.ReactElement<any>).props,
...mergedControl,
};
if (!childProps.id) { if (!childProps.id) {
childProps.id = fieldId; childProps.id = fieldId;
} }
@ -403,7 +411,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
triggers.forEach((eventName) => { triggers.forEach((eventName) => {
childProps[eventName] = (...args: any[]) => { childProps[eventName] = (...args: any[]) => {
mergedControl[eventName]?.(...args); mergedControl[eventName]?.(...args);
mergedChildren.props[eventName]?.(...args); (mergedChildren as React.ReactElement<any>).props[eventName]?.(...args);
}; };
}); });

View File

@ -1,4 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import type { JSX } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { get, set } from 'rc-util'; import { get, set } from 'rc-util';
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect'; import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';

View File

@ -10,7 +10,7 @@ import Form from '..';
import { resetWarned } from '../../_util/warning'; import { resetWarned } from '../../_util/warning';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, pureRender, render, screen, waitFakeTimer } from '../../../tests/utils'; import { act, fireEvent, pureRender, render, screen, waitFakeTimer } from '../../../tests/utils';
import Button from '../../button'; import Button from '../../button';
import Cascader from '../../cascader'; import Cascader from '../../cascader';
import Checkbox from '../../checkbox'; import Checkbox from '../../checkbox';
@ -126,7 +126,9 @@ describe('Form', () => {
await waitFakeTimer(); await waitFakeTimer();
try { try {
await form.validateFields(); await act(async () => {
await form.validateFields();
});
} catch { } catch {
// do nothing // do nothing
} }
@ -2244,7 +2246,7 @@ describe('Form', () => {
await waitFakeTimer(); await waitFakeTimer();
// initial validate // initial validate
const initTriggerTime = ReactVersion.startsWith('18') ? 2 : 1; const initTriggerTime = ReactVersion.startsWith('18') || ReactVersion.startsWith('19') ? 2 : 1;
expect(onChange).toHaveBeenCalledTimes(initTriggerTime); expect(onChange).toHaveBeenCalledTimes(initTriggerTime);
let idx = 1; let idx = 1;
expect(onChange).toHaveBeenNthCalledWith(idx++, ''); expect(onChange).toHaveBeenNthCalledWith(idx++, '');

View File

@ -1,6 +1,5 @@
import type { PropsWithChildren, ReactNode } from 'react'; import type { PropsWithChildren, ReactNode } from 'react';
import * as React from 'react'; import * as React from 'react';
import { createContext, useContext, useMemo } from 'react';
import { FormProvider as RcFormProvider } from 'rc-field-form'; import { FormProvider as RcFormProvider } from 'rc-field-form';
import type { FormProviderProps as RcFormProviderProps } from 'rc-field-form/lib/FormContext'; import type { FormProviderProps as RcFormProviderProps } from 'rc-field-form/lib/FormContext';
import type { Meta } from 'rc-field-form/lib/interface'; import type { Meta } from 'rc-field-form/lib/interface';
@ -78,9 +77,9 @@ export type NoFormStyleProps = PropsWithChildren<{
}>; }>;
export const NoFormStyle: React.FC<NoFormStyleProps> = ({ children, status, override }) => { export const NoFormStyle: React.FC<NoFormStyleProps> = ({ children, status, override }) => {
const formItemInputContext = useContext(FormItemInputContext); const formItemInputContext = React.useContext(FormItemInputContext);
const newFormItemInputContext = useMemo(() => { const newFormItemInputContext = React.useMemo(() => {
const newContext = { ...formItemInputContext }; const newContext = { ...formItemInputContext };
if (override) { if (override) {
delete newContext.isFormItemInput; delete newContext.isFormItemInput;
@ -100,4 +99,4 @@ export const NoFormStyle: React.FC<NoFormStyleProps> = ({ children, status, over
); );
}; };
export const VariantContext = createContext<Variant | undefined>(undefined); export const VariantContext = React.createContext<Variant | undefined>(undefined);

View File

@ -26,7 +26,7 @@ interface ModalFormProps {
// reset form fields when modal is form, closed // reset form fields when modal is form, closed
const useResetFormOnCloseModal = ({ form, open }: { form: FormInstance; open: boolean }) => { const useResetFormOnCloseModal = ({ form, open }: { form: FormInstance; open: boolean }) => {
const prevOpenRef = useRef<boolean>(); const prevOpenRef = useRef<boolean>(null);
useEffect(() => { useEffect(() => {
prevOpenRef.current = open; prevOpenRef.current = open;
}, [open]); }, [open]);

View File

@ -1,10 +1,10 @@
import { useContext } from 'react'; import * as React from 'react';
import { FormContext } from '../context'; import { FormContext } from '../context';
import type { FormInstance } from './useForm'; import type { FormInstance } from './useForm';
export default function useFormInstance<Value = any>(): FormInstance<Value> { export default function useFormInstance<Value = any>(): FormInstance<Value> {
const { form } = useContext(FormContext); const { form } = React.useContext(FormContext);
return form!; return form!;
} }

View File

@ -1,4 +1,4 @@
import { useContext } from 'react'; import * as React from 'react';
import type { ValidateStatus } from 'antd/es/form/FormItem'; import type { ValidateStatus } from 'antd/es/form/FormItem';
import { devUseWarning } from '../../_util/warning'; import { devUseWarning } from '../../_util/warning';
@ -11,7 +11,7 @@ type UseFormItemStatus = () => {
}; };
const useFormItemStatus: UseFormItemStatus = () => { const useFormItemStatus: UseFormItemStatus = () => {
const { status, errors = [], warnings = [] } = useContext(FormItemInputContext); const { status, errors = [], warnings = [] } = React.useContext(FormItemInputContext);
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Form.Item'); const warning = devUseWarning('Form.Item');

View File

@ -1,4 +1,4 @@
import { useEffect } from 'react'; import * as React from 'react';
import { devUseWarning } from '../../_util/warning'; import { devUseWarning } from '../../_util/warning';
import type { FormProps } from '../Form'; import type { FormProps } from '../Form';
@ -8,7 +8,7 @@ const names: Record<string, number> = {};
export default function useFormWarning({ name }: FormProps) { export default function useFormWarning({ name }: FormProps) {
const warning = devUseWarning('Form'); const warning = devUseWarning('Form');
useEffect(() => { React.useEffect(() => {
if (name) { if (name) {
names[name] = (names[name] || 0) + 1; names[name] = (names[name] || 0) + 1;

View File

@ -1,5 +1,4 @@
import * as React from 'react'; import * as React from 'react';
import { useRef } from 'react';
import raf from 'rc-util/lib/raf'; import raf from 'rc-util/lib/raf';
type Updater<ValueType> = (prev?: ValueType) => ValueType; type Updater<ValueType> = (prev?: ValueType) => ValueType;
@ -8,9 +7,9 @@ export default function useFrameState<ValueType>(
defaultValue: ValueType, defaultValue: ValueType,
): [ValueType, (updater: Updater<ValueType>) => void] { ): [ValueType, (updater: Updater<ValueType>) => void] {
const [value, setValue] = React.useState(defaultValue); const [value, setValue] = React.useState(defaultValue);
const frameRef = useRef<number | null>(null); const frameRef = React.useRef<number | null>(null);
const batchRef = useRef<Updater<ValueType>[]>([]); const batchRef = React.useRef<Updater<ValueType>[]>([]);
const destroyRef = useRef(false); const destroyRef = React.useRef(false);
React.useEffect(() => { React.useEffect(() => {
destroyRef.current = false; destroyRef.current = false;

View File

@ -1,4 +1,4 @@
import { useContext } from 'react'; import * as React from 'react';
import { VariantContext } from '../context'; import { VariantContext } from '../context';
import type { Variant, ConfigProviderProps } from '../../config-provider'; import type { Variant, ConfigProviderProps } from '../../config-provider';
@ -26,8 +26,8 @@ const useVariant = (
variant: Variant | undefined, variant: Variant | undefined,
legacyBordered: boolean | undefined = undefined, legacyBordered: boolean | undefined = undefined,
): [Variant, boolean] => { ): [Variant, boolean] => {
const { variant: configVariant, [component]: componentConfig } = useContext(ConfigContext); const { variant: configVariant, [component]: componentConfig } = React.useContext(ConfigContext);
const ctxVariant = useContext(VariantContext); const ctxVariant = React.useContext(VariantContext);
const configComponentVariant = componentConfig?.variant; const configComponentVariant = componentConfig?.variant;
let mergedVariant: Variant; let mergedVariant: Variant;

View File

@ -30,7 +30,12 @@ export { default as Cascader } from './cascader';
export type { CascaderProps, CascaderAutoProps } from './cascader'; export type { CascaderProps, CascaderAutoProps } from './cascader';
export type { CascaderPanelProps, CascaderPanelAutoProps } from './cascader/Panel'; export type { CascaderPanelProps, CascaderPanelAutoProps } from './cascader/Panel';
export { default as Checkbox } from './checkbox'; export { default as Checkbox } from './checkbox';
export type { CheckboxOptionType, CheckboxProps, CheckboxRef } from './checkbox'; export type {
CheckboxChangeEvent,
CheckboxOptionType,
CheckboxProps,
CheckboxRef,
} from './checkbox';
export { default as Col } from './col'; export { default as Col } from './col';
export type { ColProps } from './col'; export type { ColProps } from './col';
export { default as Collapse } from './collapse'; export { default as Collapse } from './collapse';
@ -172,3 +177,6 @@ export { default as Watermark } from './watermark';
export type { WatermarkProps } from './watermark'; export type { WatermarkProps } from './watermark';
export { default as Splitter } from './splitter'; export { default as Splitter } from './splitter';
export type { SplitterProps } from './splitter'; export type { SplitterProps } from './splitter';
// TODO: Remove in v6
export { unstableSetRender } from './config-provider/UnstableContext';

View File

@ -8,10 +8,10 @@ import { composeRef } from 'rc-util/lib/ref';
import type { ConfigConsumerProps } from '../config-provider'; import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider'; import { ConfigContext } from '../config-provider';
import DisabledContext from '../config-provider/DisabledContext';
import useRemovePasswordTimeout from './hooks/useRemovePasswordTimeout'; import useRemovePasswordTimeout from './hooks/useRemovePasswordTimeout';
import type { InputProps, InputRef } from './Input'; import type { InputProps, InputRef } from './Input';
import Input from './Input'; import Input from './Input';
import DisabledContext from '../config-provider/DisabledContext';
const defaultIconRender = (visible: boolean): React.ReactNode => const defaultIconRender = (visible: boolean): React.ReactNode =>
visible ? <EyeOutlined /> : <EyeInvisibleOutlined />; visible ? <EyeOutlined /> : <EyeInvisibleOutlined />;
@ -70,13 +70,13 @@ const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
if (visible) { if (visible) {
removePasswordTimeout(); removePasswordTimeout();
} }
setVisible((prevState) => {
const newState = !prevState; const nextVisible = !visible;
if (typeof visibilityToggle === 'object') { setVisible(nextVisible);
visibilityToggle.onVisibleChange?.(newState);
} if (typeof visibilityToggle === 'object') {
return newState; visibilityToggle.onVisibleChange?.(nextVisible);
}); }
}; };
const getIcon = (prefixCls: string) => { const getIcon = (prefixCls: string) => {

View File

@ -98,7 +98,11 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
button = cloneElement(enterButtonAsElement, { button = cloneElement(enterButtonAsElement, {
onMouseDown, onMouseDown,
onClick: (e: React.MouseEvent<HTMLButtonElement>) => { onClick: (e: React.MouseEvent<HTMLButtonElement>) => {
enterButtonAsElement?.props?.onClick?.(e); (
enterButtonAsElement as React.ReactElement<{
onClick?: React.MouseEventHandler<HTMLButtonElement>;
}>
)?.props?.onClick?.(e);
onSearch(e); onSearch(e);
}, },
key: 'enterButton', key: 'enterButton',

View File

@ -5,7 +5,14 @@ import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import Input from '..'; import Input from '..';
import focusTest from '../../../tests/shared/focusTest'; import focusTest from '../../../tests/shared/focusTest';
import type { RenderOptions } from '../../../tests/utils'; import type { RenderOptions } from '../../../tests/utils';
import { fireEvent, pureRender, render, triggerResize, waitFakeTimer } from '../../../tests/utils'; import {
fireEvent,
pureRender,
render,
triggerResize,
waitFakeTimer,
waitFakeTimer19,
} from '../../../tests/utils';
import type { TextAreaRef } from '../TextArea'; import type { TextAreaRef } from '../TextArea';
const { TextArea } = Input; const { TextArea } = Input;
@ -50,15 +57,15 @@ describe('TextArea', () => {
); );
const { container, rerender } = pureRender(genTextArea()); const { container, rerender } = pureRender(genTextArea());
await waitFakeTimer(); await waitFakeTimer19();
expect(onInternalAutoSize).toHaveBeenCalledTimes(1); expect(onInternalAutoSize).toHaveBeenCalledTimes(1);
rerender(genTextArea({ value: '1111\n2222\n3333' })); rerender(genTextArea({ value: '1111\n2222\n3333' }));
await waitFakeTimer(); await waitFakeTimer19();
expect(onInternalAutoSize).toHaveBeenCalledTimes(2); expect(onInternalAutoSize).toHaveBeenCalledTimes(2);
rerender(genTextArea({ value: '1111' })); rerender(genTextArea({ value: '1111' }));
await waitFakeTimer(); await waitFakeTimer19();
expect(onInternalAutoSize).toHaveBeenCalledTimes(3); expect(onInternalAutoSize).toHaveBeenCalledTimes(3);
expect(container.querySelector('textarea')?.style.overflow).toBeFalsy(); expect(container.querySelector('textarea')?.style.overflow).toBeFalsy();
@ -332,7 +339,6 @@ describe('TextArea allowClear', () => {
const ref = React.createRef<TextAreaRef>(); const ref = React.createRef<TextAreaRef>();
const { container, unmount } = render(<Input.TextArea ref={ref} autoSize />, { const { container, unmount } = render(<Input.TextArea ref={ref} autoSize />, {
container: document.body, container: document.body,
legacyRoot: true,
} as RenderOptions); } as RenderOptions);
fireEvent.focus(container.querySelector('textarea')!); fireEvent.focus(container.querySelector('textarea')!);
container.querySelector('textarea')?.focus(); container.querySelector('textarea')?.focus();

View File

@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react';
import type { InputRef } from '../Input'; import type { InputRef } from '../Input';
export default function useRemovePasswordTimeout( export default function useRemovePasswordTimeout(
inputRef: React.RefObject<InputRef>, inputRef: React.RefObject<InputRef | null>,
triggerOnMount?: boolean, triggerOnMount?: boolean,
) { ) {
const removePasswordTimeoutRef = useRef<ReturnType<typeof setTimeout>[]>([]); const removePasswordTimeoutRef = useRef<ReturnType<typeof setTimeout>[]>([]);

View File

@ -105,7 +105,7 @@ const Sider = React.forwardRef<HTMLDivElement, SiderProps>((props, ref) => {
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls); const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
// ========================= Responsive ========================= // ========================= Responsive =========================
const responsiveHandlerRef = useRef<(mql: MediaQueryListEvent | MediaQueryList) => void>(); const responsiveHandlerRef = useRef<(mql: MediaQueryListEvent | MediaQueryList) => void>(null);
responsiveHandlerRef.current = (mql: MediaQueryListEvent | MediaQueryList) => { responsiveHandlerRef.current = (mql: MediaQueryListEvent | MediaQueryList) => {
setBelow(mql.matches); setBelow(mql.matches);
onBreakpoint?.(mql.matches); onBreakpoint?.(mql.matches);

View File

@ -14,6 +14,18 @@ const Demo: React.FC<{ type: string }> = ({ type }) => {
return null; return null;
}; };
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('Locale Provider demo', () => { describe('Locale Provider demo', () => {
it('change type', async () => { it('change type', async () => {
jest.useFakeTimers(); jest.useFakeTimers();

View File

@ -1,3 +1,5 @@
import { extendTest } from '../../../tests/shared/demoTest'; import { extendTest } from '../../../tests/shared/demoTest';
extendTest('mentions'); extendTest('mentions', {
skip: ['autoSize.tsx'],
});

View File

@ -5,7 +5,7 @@ import debounce from 'lodash/debounce';
const App: React.FC = () => { const App: React.FC = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [users, setUsers] = useState<{ login: string; avatar_url: string }[]>([]); const [users, setUsers] = useState<{ login: string; avatar_url: string }[]>([]);
const ref = useRef<string>(); const ref = useRef<string>(null);
const loadGithubUsers = (key: string) => { const loadGithubUsers = (key: string) => {
if (!key) { if (!key) {

View File

@ -101,7 +101,9 @@ const MenuItem: GenericComponent = (props) => {
> >
{cloneElement(icon, { {cloneElement(icon, {
className: classNames( className: classNames(
React.isValidElement(icon) ? icon.props?.className : '', React.isValidElement(icon)
? (icon as React.ReactElement<{ className?: string }>).props?.className
: '',
`${prefixCls}-item-icon`, `${prefixCls}-item-icon`,
), ),
})} })}

View File

@ -44,7 +44,14 @@ export const OverrideProvider = React.forwardRef<
return ( return (
<OverrideContext.Provider value={context}> <OverrideContext.Provider value={context}>
<ContextIsolator space> <ContextIsolator space>
{canRef ? React.cloneElement(children as React.ReactElement, { ref: mergedRef }) : children} {canRef
? React.cloneElement(
children as React.ReactElement<{
ref?: React.Ref<HTMLElement>;
}>,
{ ref: mergedRef },
)
: children}
</ContextIsolator> </ContextIsolator>
</OverrideContext.Provider> </OverrideContext.Provider>
); );

View File

@ -5,9 +5,9 @@ import omit from 'rc-util/lib/omit';
import { useZIndex } from '../_util/hooks/useZIndex'; import { useZIndex } from '../_util/hooks/useZIndex';
import { cloneElement } from '../_util/reactNode'; import { cloneElement } from '../_util/reactNode';
import type { SubMenuType } from './interface';
import type { MenuContextProps } from './MenuContext'; import type { MenuContextProps } from './MenuContext';
import MenuContext from './MenuContext'; import MenuContext from './MenuContext';
import type { SubMenuType } from './interface';
export interface SubMenuProps extends Omit<SubMenuType, 'ref' | 'key' | 'children' | 'label'> { export interface SubMenuProps extends Omit<SubMenuType, 'ref' | 'key' | 'children' | 'label'> {
title?: React.ReactNode; title?: React.ReactNode;
@ -43,7 +43,9 @@ const SubMenu: React.FC<SubMenuProps> = (props) => {
<> <>
{cloneElement(icon, { {cloneElement(icon, {
className: classNames( className: classNames(
React.isValidElement(icon) ? icon.props?.className : '', React.isValidElement(icon)
? (icon as React.ReactElement<{ className?: string }>).props?.className
: '',
`${prefixCls}-item-icon`, `${prefixCls}-item-icon`,
), ),
})} })}

View File

@ -70,18 +70,18 @@ const App: React.FC = () => {
inlineCollapsed inlineCollapsed
// Test only. Remove in future. // Test only. Remove in future.
_internalRenderMenuItem={(node) => _internalRenderMenuItem={(node) =>
React.cloneElement(node, { React.cloneElement<any>(node, {
style: { style: {
...node.props.style, ...(node as any).props.style,
textDecoration: 'underline', textDecoration: 'underline',
}, },
}) })
} }
// Test only. Remove in future. // Test only. Remove in future.
_internalRenderSubMenuItem={(node) => _internalRenderSubMenuItem={(node) =>
React.cloneElement(node, { React.cloneElement<any>(node, {
style: { style: {
...node.props.style, ...(node as any).props.style,
background: 'rgba(255, 255, 255, 0.3)', background: 'rgba(255, 255, 255, 0.3)',
}, },
}) })

View File

@ -136,7 +136,13 @@ const InternalMenu = forwardRef<RcMenuRef, InternalMenuProps>((props, ref) => {
return cloneElement(mergedIcon, { return cloneElement(mergedIcon, {
className: classNames( className: classNames(
`${prefixCls}-submenu-expand-icon`, `${prefixCls}-submenu-expand-icon`,
React.isValidElement<any>(mergedIcon) ? mergedIcon.props?.className : undefined, React.isValidElement<any>(mergedIcon)
? (
mergedIcon as React.ReactElement<{
className?: string;
}>
).props?.className
: undefined,
), ),
}); });
}, [expandIcon, overrideObj?.expandIcon, menu?.expandIcon, prefixCls]); }, [expandIcon, overrideObj?.expandIcon, menu?.expandIcon, prefixCls]);

View File

@ -6,6 +6,18 @@ import App from '../../app';
import ConfigProvider, { defaultPrefixCls } from '../../config-provider'; import ConfigProvider, { defaultPrefixCls } from '../../config-provider';
import { awaitPromise, triggerMotionEnd } from './util'; import { awaitPromise, triggerMotionEnd } from './util';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('message.config', () => { describe('message.config', () => {
beforeAll(() => { beforeAll(() => {
actWrapper(act); actWrapper(act);

View File

@ -2,6 +2,18 @@ import message, { actDestroy, actWrapper } from '..';
import { act } from '../../../tests/utils'; import { act } from '../../../tests/utils';
import { awaitPromise, triggerMotionEnd } from './util'; import { awaitPromise, triggerMotionEnd } from './util';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('call close immediately', () => { describe('call close immediately', () => {
beforeAll(() => { beforeAll(() => {
actWrapper(act); actWrapper(act);

View File

@ -5,6 +5,18 @@ import message, { actWrapper } from '..';
import { act, fireEvent, waitFakeTimer } from '../../../tests/utils'; import { act, fireEvent, waitFakeTimer } from '../../../tests/utils';
import { awaitPromise, triggerMotionEnd } from './util'; import { awaitPromise, triggerMotionEnd } from './util';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('message', () => { describe('message', () => {
beforeAll(() => { beforeAll(() => {
actWrapper(act); actWrapper(act);

View File

@ -1,10 +1,22 @@
import React from 'react'; import React from 'react';
import message, { actWrapper } from '..'; import message, { actWrapper } from '..';
import { act, render, waitFakeTimer } from '../../../tests/utils'; import { act, render, waitFakeTimer, waitFakeTimer19 } from '../../../tests/utils';
import ConfigProvider from '../../config-provider'; import ConfigProvider from '../../config-provider';
import { awaitPromise, triggerMotionEnd } from './util'; import { awaitPromise, triggerMotionEnd } from './util';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('message static warning', () => { describe('message static warning', () => {
beforeAll(() => { beforeAll(() => {
actWrapper(act); actWrapper(act);
@ -32,11 +44,12 @@ describe('message static warning', () => {
content: <div className="bamboo" />, content: <div className="bamboo" />,
duration: 0, duration: 0,
}); });
await waitFakeTimer(); await waitFakeTimer19();
expect(document.querySelector('.bamboo')).toBeTruthy(); expect(document.querySelector('.bamboo')).toBeTruthy();
expect(errSpy).not.toHaveBeenCalled(); expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
}); });
it('warning if use theme', async () => { it('warning if use theme', async () => {
@ -54,5 +67,6 @@ describe('message static warning', () => {
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
"Warning: [antd: message] Static function can not consume context like dynamic theme. Please use 'App' component instead.", "Warning: [antd: message] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
); );
errSpy.mockRestore();
}); });
}); });

View File

@ -2,6 +2,18 @@ import message, { actWrapper } from '..';
import { act } from '../../../tests/utils'; import { act } from '../../../tests/utils';
import { awaitPromise, triggerMotionEnd } from './util'; import { awaitPromise, triggerMotionEnd } from './util';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('message.typescript', () => { describe('message.typescript', () => {
beforeAll(() => { beforeAll(() => {
actWrapper(act); actWrapper(act);

View File

@ -1,8 +1,8 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { render } from 'rc-util/lib/React/render';
import { AppConfigContext } from '../app/context'; import { AppConfigContext } from '../app/context';
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider'; import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
import { getReactRender } from '../config-provider/UnstableContext';
import type { import type {
ArgsProps, ArgsProps,
ConfigOptions, ConfigOptions,
@ -132,7 +132,9 @@ function flushNotice() {
// Delay render to avoid sync issue // Delay render to avoid sync issue
act(() => { act(() => {
render( const reactRender = getReactRender();
reactRender(
<GlobalHolderWrapper <GlobalHolderWrapper
ref={(node) => { ref={(node) => {
const { instance, sync } = node || {}; const { instance, sync } = node || {};

View File

@ -18,6 +18,18 @@ const { confirm } = Modal;
jest.mock('rc-motion'); jest.mock('rc-motion');
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
(global as any).injectPromise = false; (global as any).injectPromise = false;
(global as any).rejectPromise = null; (global as any).rejectPromise = null;

View File

@ -14,6 +14,18 @@ import type { ModalFunc } from '../confirm';
jest.mock('rc-util/lib/Portal'); jest.mock('rc-util/lib/Portal');
jest.mock('rc-motion'); jest.mock('rc-motion');
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('Modal.hook', () => { describe('Modal.hook', () => {
// Inject CSSMotion to replace with No transition support // Inject CSSMotion to replace with No transition support
const MockCSSMotion = genCSSMotion(false); const MockCSSMotion = genCSSMotion(false);

View File

@ -2,9 +2,21 @@ import * as React from 'react';
import Modal from '..'; import Modal from '..';
import { resetWarned } from '../../_util/warning'; import { resetWarned } from '../../_util/warning';
import { render, waitFakeTimer } from '../../../tests/utils'; import { render, waitFakeTimer, waitFakeTimer19 } from '../../../tests/utils';
import ConfigProvider from '../../config-provider'; import ConfigProvider from '../../config-provider';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('Modal.confirm warning', () => { describe('Modal.confirm warning', () => {
beforeEach(() => { beforeEach(() => {
jest.useFakeTimers(); jest.useFakeTimers();
@ -25,11 +37,12 @@ describe('Modal.confirm warning', () => {
Modal.confirm({ Modal.confirm({
content: <div className="bamboo" />, content: <div className="bamboo" />,
}); });
await waitFakeTimer(); await waitFakeTimer19();
expect(document.querySelector('.bamboo')).toBeTruthy(); expect(document.querySelector('.bamboo')).toBeTruthy();
expect(errSpy).not.toHaveBeenCalled(); expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
}); });
it('warning if use theme', async () => { it('warning if use theme', async () => {
@ -46,5 +59,6 @@ describe('Modal.confirm warning', () => {
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
"Warning: [antd: Modal] Static function can not consume context like dynamic theme. Please use 'App' component instead.", "Warning: [antd: Modal] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
); );
errSpy.mockRestore();
}); });
}); });

View File

@ -11,12 +11,7 @@ export interface ConfirmCancelBtnProps
'cancelButtonProps' | 'isSilent' | 'rootPrefixCls' | 'close' | 'onConfirm' | 'onCancel' 'cancelButtonProps' | 'isSilent' | 'rootPrefixCls' | 'close' | 'onConfirm' | 'onCancel'
> { > {
autoFocusButton?: false | 'ok' | 'cancel' | null; autoFocusButton?: false | 'ok' | 'cancel' | null;
cancelTextLocale?: cancelTextLocale?: React.ReactNode;
| string
| number
| true
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
| Iterable<React.ReactNode>;
mergedOkCancel?: boolean; mergedOkCancel?: boolean;
} }

View File

@ -11,12 +11,7 @@ export interface ConfirmOkBtnProps
'close' | 'isSilent' | 'okType' | 'okButtonProps' | 'rootPrefixCls' | 'onConfirm' | 'onOk' 'close' | 'isSilent' | 'okType' | 'okButtonProps' | 'rootPrefixCls' | 'onConfirm' | 'onOk'
> { > {
autoFocusButton?: false | 'ok' | 'cancel' | null; autoFocusButton?: false | 'ok' | 'cancel' | null;
okTextLocale?: okTextLocale?: React.ReactNode;
| string
| number
| true
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
| Iterable<React.ReactNode>;
} }
const ConfirmOkBtn: FC = () => { const ConfirmOkBtn: FC = () => {

View File

@ -6,12 +6,7 @@ import { ModalContext } from '../context';
import type { ModalProps } from '../interface'; import type { ModalProps } from '../interface';
export interface NormalCancelBtnProps extends Pick<ModalProps, 'cancelButtonProps' | 'onCancel'> { export interface NormalCancelBtnProps extends Pick<ModalProps, 'cancelButtonProps' | 'onCancel'> {
cancelTextLocale?: cancelTextLocale?: React.ReactNode;
| string
| number
| true
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
| Iterable<React.ReactNode>;
} }
const NormalCancelBtn: FC = () => { const NormalCancelBtn: FC = () => {

View File

@ -8,12 +8,7 @@ import type { ModalProps } from '../interface';
export interface NormalOkBtnProps export interface NormalOkBtnProps
extends Pick<ModalProps, 'confirmLoading' | 'okType' | 'okButtonProps' | 'onOk'> { extends Pick<ModalProps, 'confirmLoading' | 'okType' | 'okButtonProps' | 'onOk'> {
okTextLocale?: okTextLocale?: React.ReactNode;
| string
| number
| true
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
| Iterable<React.ReactNode>;
} }
const NormalOkBtn: FC = () => { const NormalOkBtn: FC = () => {

View File

@ -1,8 +1,8 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { render as reactRender, unmount as reactUnmount } from 'rc-util/lib/React/render';
import warning from '../_util/warning'; import warning from '../_util/warning';
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider'; import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
import { getReactRender, UnmountType } from '../config-provider/UnstableContext';
import type { ConfirmDialogProps } from './ConfirmDialog'; import type { ConfirmDialogProps } from './ConfirmDialog';
import ConfirmDialog from './ConfirmDialog'; import ConfirmDialog from './ConfirmDialog';
import destroyFns from './destroyFns'; import destroyFns from './destroyFns';
@ -71,6 +71,8 @@ export default function confirm(config: ModalFuncProps) {
let currentConfig = { ...config, close, open: true } as any; let currentConfig = { ...config, close, open: true } as any;
let timeoutId: ReturnType<typeof setTimeout>; let timeoutId: ReturnType<typeof setTimeout>;
let reactUnmount: UnmountType;
function destroy(...args: any[]) { function destroy(...args: any[]) {
const triggerCancel = args.some((param) => param?.triggerCancel); const triggerCancel = args.some((param) => param?.triggerCancel);
if (triggerCancel) { if (triggerCancel) {
@ -84,7 +86,7 @@ export default function confirm(config: ModalFuncProps) {
} }
} }
reactUnmount(container); reactUnmount();
} }
function render(props: any) { function render(props: any) {
@ -102,7 +104,9 @@ export default function confirm(config: ModalFuncProps) {
const dom = <ConfirmDialogWrapper {...props} />; const dom = <ConfirmDialogWrapper {...props} />;
reactRender( const reactRender = getReactRender();
reactUnmount = reactRender(
<ConfigProvider prefixCls={rootPrefixCls} iconPrefixCls={iconPrefixCls} theme={theme}> <ConfigProvider prefixCls={rootPrefixCls} iconPrefixCls={iconPrefixCls} theme={theme}>
{global.holderRender ? global.holderRender(dom) : dom} {global.holderRender ? global.holderRender(dom) : dom}
</ConfigProvider>, </ConfigProvider>,

View File

@ -7,7 +7,7 @@ const App: React.FC = () => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [disabled, setDisabled] = useState(true); const [disabled, setDisabled] = useState(true);
const [bounds, setBounds] = useState({ left: 0, top: 0, bottom: 0, right: 0 }); const [bounds, setBounds] = useState({ left: 0, top: 0, bottom: 0, right: 0 });
const draggleRef = useRef<HTMLDivElement>(null); const draggleRef = useRef<HTMLDivElement>(null!);
const showModal = () => { const showModal = () => {
setOpen(true); setOpen(true);

View File

@ -51,7 +51,7 @@ export const Footer: React.FC<
const [locale] = useLocale('Modal', getConfirmLocale()); const [locale] = useLocale('Modal', getConfirmLocale());
// ================== Locale Text ================== // ================== Locale Text ==================
const okTextLocale = okText || locale?.okText; const okTextLocale: React.ReactNode = okText || locale?.okText;
const cancelTextLocale = cancelText || locale?.cancelText; const cancelTextLocale = cancelText || locale?.cancelText;
// ================= Context Value ================= // ================= Context Value =================

View File

@ -66,6 +66,8 @@ const genModalConfirmStyle: GenerateStyle<ModalToken> = (token) => {
flexDirection: 'column', flexDirection: 'column',
flex: 'auto', flex: 'auto',
rowGap: token.marginXS, rowGap: token.marginXS,
// https://github.com/ant-design/ant-design/issues/51912
maxWidth: `calc(100% - ${unit(token.marginSM)})`,
}, },
// https://github.com/ant-design/ant-design/issues/48159 // https://github.com/ant-design/ant-design/issues/48159

View File

@ -6,6 +6,18 @@ import App from '../../app';
import ConfigProvider from '../../config-provider'; import ConfigProvider from '../../config-provider';
import { awaitPromise, triggerMotionEnd } from './util'; import { awaitPromise, triggerMotionEnd } from './util';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('notification.config', () => { describe('notification.config', () => {
beforeAll(() => { beforeAll(() => {
actWrapper(act); actWrapper(act);

View File

@ -6,6 +6,18 @@ import { act, fireEvent } from '../../../tests/utils';
import ConfigProvider, { defaultPrefixCls } from '../../config-provider'; import ConfigProvider, { defaultPrefixCls } from '../../config-provider';
import { awaitPromise, triggerMotionEnd } from './util'; import { awaitPromise, triggerMotionEnd } from './util';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('notification', () => { describe('notification', () => {
beforeAll(() => { beforeAll(() => {
actWrapper(act); actWrapper(act);

View File

@ -3,6 +3,18 @@ import { act, fireEvent } from '../../../tests/utils';
import type { ArgsProps, GlobalConfigProps } from '../interface'; import type { ArgsProps, GlobalConfigProps } from '../interface';
import { awaitPromise, triggerMotionEnd } from './util'; import { awaitPromise, triggerMotionEnd } from './util';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('Notification.placement', () => { describe('Notification.placement', () => {
function open(args?: Partial<ArgsProps>) { function open(args?: Partial<ArgsProps>) {
notification.open({ notification.open({

View File

@ -1,10 +1,22 @@
import React from 'react'; import React from 'react';
import notification, { actWrapper } from '..'; import notification, { actWrapper } from '..';
import { act, render, waitFakeTimer } from '../../../tests/utils'; import { act, render, waitFakeTimer, waitFakeTimer19 } from '../../../tests/utils';
import ConfigProvider from '../../config-provider'; import ConfigProvider from '../../config-provider';
import { awaitPromise, triggerMotionEnd } from './util'; import { awaitPromise, triggerMotionEnd } from './util';
// TODO: Remove this. Mock for React 19
jest.mock('react-dom', () => {
const realReactDOM = jest.requireActual('react-dom');
if (realReactDOM.version.startsWith('19')) {
const realReactDOMClient = jest.requireActual('react-dom/client');
realReactDOM.createRoot = realReactDOMClient.createRoot;
}
return realReactDOM;
});
describe('notification static warning', () => { describe('notification static warning', () => {
beforeAll(() => { beforeAll(() => {
actWrapper(act); actWrapper(act);
@ -32,11 +44,12 @@ describe('notification static warning', () => {
message: <div className="bamboo" />, message: <div className="bamboo" />,
duration: 0, duration: 0,
}); });
await waitFakeTimer(); await waitFakeTimer19();
expect(document.querySelector('.bamboo')).toBeTruthy(); expect(document.querySelector('.bamboo')).toBeTruthy();
expect(errSpy).not.toHaveBeenCalled(); expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
}); });
it('warning if use theme', async () => { it('warning if use theme', async () => {
@ -54,5 +67,6 @@ describe('notification static warning', () => {
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
"Warning: [antd: notification] Static function can not consume context like dynamic theme. Please use 'App' component instead.", "Warning: [antd: notification] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
); );
errSpy.mockRestore();
}); });
}); });

View File

@ -1,8 +1,8 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { render } from 'rc-util/lib/React/render';
import { AppConfigContext } from '../app/context'; import { AppConfigContext } from '../app/context';
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider'; import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
import { getReactRender } from '../config-provider/UnstableContext';
import type { ArgsProps, GlobalConfigProps, NotificationInstance } from './interface'; import type { ArgsProps, GlobalConfigProps, NotificationInstance } from './interface';
import PurePanel from './PurePanel'; import PurePanel from './PurePanel';
import useNotification, { useInternalNotification } from './useNotification'; import useNotification, { useInternalNotification } from './useNotification';
@ -126,7 +126,9 @@ function flushNotice() {
// Delay render to avoid sync issue // Delay render to avoid sync issue
act(() => { act(() => {
render( const reactRender = getReactRender();
reactRender(
<GlobalHolderWrapper <GlobalHolderWrapper
ref={(node) => { ref={(node) => {
const { instance, sync } = node || {}; const { instance, sync } = node || {};

Some files were not shown because too many files have changed in this diff Show More