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 {
semantics: { name: string; desc: string; version?: string }[];
children: React.ReactElement;
children: React.ReactElement<any>;
height?: number;
}
@ -97,7 +97,7 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
// ======================== Hover =========================
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 [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 { ConfigProvider, Tooltip, Button } from 'antd';
import classNames from 'classnames';
import { DumiDemoGrid, FormattedMessage } from 'dumi';
import { DumiDemoGrid, FormattedMessage, DumiDemo } from 'dumi';
import { css, Global } from '@emotion/react';
import useLayoutState from '../../../hooks/useLayoutState';
@ -114,7 +114,14 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
</span>
<ConfigProvider theme={{ cssVar: enableCssVar, hashed: !enableCssVar }}>
<Suspense>
<DumiDemoGrid items={demos} />
<DumiDemoGrid
items={demos}
demoRender={(item) => (
<Suspense>
<DumiDemo key={item.demo.id} {...item} />
</Suspense>
)}
/>
</Suspense>
</ConfigProvider>
</div>

View File

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

View File

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

View File

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

View File

@ -9,10 +9,11 @@ import {
} from '@ant-design/cssinjs';
import { HappyProvider } from '@ant-design/happy-work-theme';
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 { DirectionType, ThemeConfig } from 'antd/es/config-provider';
import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi';
import { createRoot } from 'react-dom/client';
import { DarkContext } from '../../hooks/useDark';
import useLayoutState from '../../hooks/useLayoutState';
@ -30,6 +31,14 @@ type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
const RESPONSIVE_MOBILE = 768;
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();
// if (typeof global !== 'undefined') {
// (global as any).styleCache = styleCache;

View File

@ -8,13 +8,14 @@ import useMenu from '../../../hooks/useMenu';
import SiteContext from '../SiteContext';
const useStyle = createStyles(({ token, css }) => {
const { antCls, fontFamily, colorSplit } = token;
const { antCls, fontFamily, colorSplit, marginXXL, paddingXXS } = token;
return {
asideContainer: css`
min-height: 100%;
padding-bottom: 48px;
padding-bottom: ${marginXXL}px !important;
font-family: Avenir, ${fontFamily}, sans-serif;
padding: 0 ${paddingXXS}px;
&${antCls}-menu-inline {
${antCls}-menu-submenu-title h4,
@ -94,14 +95,10 @@ const useStyle = createStyles(({ token, css }) => {
position: sticky;
top: ${token.headerHeight + token.contentMarginTop}px;
width: 100%;
height: 100%;
max-height: calc(100vh - ${token.headerHeight + token.contentMarginTop}px);
overflow: hidden;
scrollbar-width: thin;
scrollbar-gutter: stable;
.ant-menu {
padding: 0 4px;
}
&:hover {
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:
runs-on: ubuntu-latest
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:
- name: checkout
uses: actions/checkout@v4
@ -46,6 +51,11 @@ jobs:
path: _site/
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:
runs-on: ubuntu-latest
needs: build-site
@ -58,10 +68,6 @@ jobs:
name: real-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
uses: peaceiris/actions-gh-pages@v4
with:
@ -81,15 +87,15 @@ jobs:
- name: Deploy to Surge (with TAG)
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 }}
- name: Create Commit Comment
uses: peter-evans/commit-comment@v3
with:
body: |
- Documentation site for this release: https://ant-design-${{ steps.publish-version.outputs.VERSION }}.surge.sh
- Webpack bundle analyzer report page: https://ant-design-${{ steps.publish-version.outputs.VERSION }}.surge.sh/report.html
- Documentation site for this release: https://ant-design-${{ needs.build-site.outputs.formatted_version }}.surge.sh
- 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
upload-to-release:
@ -107,12 +113,11 @@ jobs:
- name: Tarball site
run: |
cd ./_site
VERSION=$(echo ${{ github.ref_name }} | sed 's/\./-/g')
tar -czf ../website.tar.gz --transform 's|^|antd-${VERSION}-website/|' .
tar -czf ../website.tar.gz --transform 's|^|antd-${{ needs.build-site.outputs.formatted_version }}-website/|' .
cd ..
- name: Upload to Release
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0
with:
fail_on_unmatched_files: true
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
`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
`2024-12-09`

View File

@ -74,6 +74,7 @@ exports[`antd exports modules correctly 1`] = `
"message",
"notification",
"theme",
"unstableSetRender",
"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 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 }>> = ({
children,
container,

View File

@ -9,6 +9,18 @@ import { TARGET_CLS } from '../wave/interface';
(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', () => {
const mockFn = () => (global as any).isVisible;
return mockFn;
@ -96,6 +108,7 @@ describe('Wave component', () => {
expect(document.querySelector('.ant-wave')).toBeFalsy();
expect(errorSpy).not.toHaveBeenCalled();
errorSpy.mockRestore();
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 CSSMotion from 'rc-motion';
import raf from 'rc-util/lib/raf';
import { render, unmount } from 'rc-util/lib/React/render';
import { composeRef } from 'rc-util/lib/ref';
import { getReactRender, type UnmountType } from '../../config-provider/UnstableContext';
import { TARGET_CLS } from './interface';
import type { ShowWaveEffect } from './interface';
import { getTargetWaveColor } from './util';
@ -17,12 +17,21 @@ export interface WaveEffectProps {
className: string;
target: HTMLElement;
component?: string;
registerUnmount: () => UnmountType | null;
}
const WaveEffect: React.FC<WaveEffectProps> = (props) => {
const { className, target, component } = props;
const WaveEffect = (props: WaveEffectProps) => {
const { className, target, component, registerUnmount } = props;
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 [borderRadius, setBorderRadius] = React.useState<number[]>([]);
const [left, setLeft] = React.useState(0);
@ -119,7 +128,7 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
onAppearEnd={(_, event) => {
if (event.deadline || (event as TransitionEvent).propertyName === 'opacity') {
const holder = divRef.current?.parentElement!;
unmount(holder).then(() => {
unmountRef.current?.().then(() => {
holder?.remove();
});
}
@ -140,13 +149,6 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
const showWaveEffect: ShowWaveEffect = (target, 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
if (component === 'Checkbox' && !target.querySelector<HTMLInputElement>('input')?.checked) {
return;
@ -159,7 +161,18 @@ const showWaveEffect: ShowWaveEffect = (target, info) => {
holder.style.top = '0px';
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;

View File

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

View File

@ -28,10 +28,16 @@ const useWave = (
const { showEffect } = wave || {};
// 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
const showDebounceWave: ShowWave = (event) => {

View File

@ -1,5 +1,7 @@
import { imageDemoTest } from '../../../tests/shared/imageTest';
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 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 fixedNodeRef = React.useRef<HTMLDivElement>(null);

View File

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

View File

@ -5,7 +5,7 @@ import { resetWarned } from 'rc-util/lib/warning';
import Alert from '..';
import { accessibilityTest } from '../../../tests/shared/accessibilityTest';
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 Popconfirm from '../../popconfirm';
import Tooltip from '../../tooltip';
@ -28,7 +28,7 @@ describe('Alert', () => {
it('should show close button and could be closed', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const onClose = jest.fn();
render(
const { container } = render(
<Alert
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
type="warning"
@ -37,10 +37,7 @@ describe('Alert', () => {
/>,
);
await act(async () => {
await userEvent.click(screen.getByRole('button', { name: /close/i }));
jest.runAllTimers();
});
fireEvent.click(container.querySelector('.ant-alert-close-icon')!);
expect(onClose).toHaveBeenCalledTimes(1);
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);
@ -556,16 +554,14 @@ describe('Anchor Render', () => {
{ 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:
// 1. ''
// 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}"]`)!);
expect(onChange).toHaveBeenCalledTimes(3);
expect(onChange).toHaveBeenCalledTimes(calledTimes + 1);
expect(onChange).toHaveBeenLastCalledWith(`#${hash2}`);
});

View File

@ -2267,15 +2267,7 @@ exports[`renders components/auto-complete/demo/render-panel.tsx extend context c
</div>
`;
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/render-panel.tsx extend context correctly 2`] = `[]`;
exports[`renders components/auto-complete/demo/status.tsx extend context correctly 1`] = `
<div

View File

@ -1,3 +1,5 @@
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';
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
/* istanbul ignore next */
const PurePanel = genPurePanel(RefAutoComplete);
const PurePanel = genPurePanel(RefAutoComplete, undefined, undefined, (props: any) =>
omit(props, ['visible']),
);
RefAutoComplete.Option = Option;
RefAutoComplete._InternalPanelDoNotUseOrYouWillBeFired = PurePanel;

View File

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

View File

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

View File

@ -4,6 +4,18 @@ import userEvent from '@testing-library/user-event';
import Button from '..';
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', () => {
const mockFn = () => true;
return mockFn;

View File

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

View File

@ -16,7 +16,7 @@ export function convertLegacyProps(
return { type };
}
export function isString(str: any): str is string {
export function isString(str: unknown): str is string {
return typeof str === 'string';
}
@ -35,10 +35,22 @@ function splitCNCharsBySpace(child: React.ReactElement | string | number, needIn
typeof child !== 'string' &&
typeof child !== 'number' &&
isString(child.type) &&
isTwoCNChar(child.props.children)
isTwoCNChar(
(
child as React.ReactElement<{
children: string;
}>
).props.children,
)
) {
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.colorBgSolid,
{
color: token.solidTextColor,
background: token.colorBgSolidHover,
},
{
color: token.solidTextColor,
background: token.colorBgSolidActive,
},
),

View File

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

View File

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

View File

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

View File

@ -2585,15 +2585,7 @@ exports[`renders components/cascader/demo/render-panel.tsx extend context correc
</div>
`;
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/render-panel.tsx extend context correctly 2`] = `[]`;
exports[`renders components/cascader/demo/search.tsx extend context correctly 1`] = `
<div

View File

@ -370,7 +370,9 @@ if (process.env.NODE_ENV !== 'production') {
// We don't care debug panel
/* 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_CHILD = SHOW_CHILD;

View File

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

View File

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

View File

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

View File

@ -11,6 +11,9 @@ export interface DescriptionsItemProps {
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;

View File

@ -7,7 +7,10 @@ import type { ScreenMap } from '../../_util/responsiveObserver';
// Convert children into items
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(
screens: ScreenMap,

View File

@ -352,6 +352,17 @@ describe('Dropdown', () => {
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', () => {
const text = '⌘P';
const { container } = render(

View File

@ -52,7 +52,12 @@ const App: React.FC = () => {
menu={{ items }}
dropdownRender={(menu) => (
<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 }} />
<Space style={{ padding: 8 }}>
<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 { useZIndex } from '../_util/hooks/useZIndex';
import isPrimitive from '../_util/isPrimitive';
import type { AdjustOverflow } from '../_util/placements';
import getPlacements from '../_util/placements';
import genPurePanel from '../_util/PurePanel';
@ -175,7 +176,12 @@ const Dropdown: CompoundedComponent = (props) => {
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, {
className: classNames(

View File

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

View File

@ -1,5 +1,4 @@
import * as React from 'react';
import { useMemo } from 'react';
import classNames from 'classnames';
import type { CSSMotionProps } from 'rc-motion';
import CSSMotion, { CSSMotionList } from 'rc-motion';
@ -58,7 +57,10 @@ const ErrorList: React.FC<ErrorListProps> = ({
const rootCls = useCSSVarCls(prefixCls);
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
// ref: https://github.com/ant-design/ant-design/issues/36336

View File

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

View File

@ -1,4 +1,5 @@
import * as React from 'react';
import type { JSX } from 'react';
import classNames from 'classnames';
import { Field, FieldContext, ListContext } from 'rc-field-form';
import type { FieldProps } from 'rc-field-form/lib/Field';
@ -165,7 +166,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
// ========================= MISC =========================
// Get `noStyle` required info
const listContext = React.useContext(ListContext);
const fieldKeyPathRef = React.useRef<InternalNamePath>();
const fieldKeyPathRef = React.useRef<InternalNamePath>(null);
// ======================== Errors ========================
// >>>>> Collect sub field errors
@ -361,12 +362,19 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
);
} else if (React.isValidElement(mergedChildren)) {
warning(
mergedChildren.props.defaultValue === undefined,
(
mergedChildren as React.ReactElement<{
defaultValue?: any;
}>
).props.defaultValue === undefined,
'usage',
'`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) {
childProps.id = fieldId;
}
@ -403,7 +411,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
triggers.forEach((eventName) => {
childProps[eventName] = (...args: any[]) => {
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 type { JSX } from 'react';
import classNames from 'classnames';
import { get, set } from 'rc-util';
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';

View File

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

View File

@ -1,6 +1,5 @@
import type { PropsWithChildren, ReactNode } from 'react';
import * as React from 'react';
import { createContext, useContext, useMemo } from 'react';
import { FormProvider as RcFormProvider } from 'rc-field-form';
import type { FormProviderProps as RcFormProviderProps } from 'rc-field-form/lib/FormContext';
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 }) => {
const formItemInputContext = useContext(FormItemInputContext);
const formItemInputContext = React.useContext(FormItemInputContext);
const newFormItemInputContext = useMemo(() => {
const newFormItemInputContext = React.useMemo(() => {
const newContext = { ...formItemInputContext };
if (override) {
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
const useResetFormOnCloseModal = ({ form, open }: { form: FormInstance; open: boolean }) => {
const prevOpenRef = useRef<boolean>();
const prevOpenRef = useRef<boolean>(null);
useEffect(() => {
prevOpenRef.current = open;
}, [open]);

View File

@ -1,10 +1,10 @@
import { useContext } from 'react';
import * as React from 'react';
import { FormContext } from '../context';
import type { FormInstance } from './useForm';
export default function useFormInstance<Value = any>(): FormInstance<Value> {
const { form } = useContext(FormContext);
const { form } = React.useContext(FormContext);
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 { devUseWarning } from '../../_util/warning';
@ -11,7 +11,7 @@ type UseFormItemStatus = () => {
};
const useFormItemStatus: UseFormItemStatus = () => {
const { status, errors = [], warnings = [] } = useContext(FormItemInputContext);
const { status, errors = [], warnings = [] } = React.useContext(FormItemInputContext);
if (process.env.NODE_ENV !== 'production') {
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 type { FormProps } from '../Form';
@ -8,7 +8,7 @@ const names: Record<string, number> = {};
export default function useFormWarning({ name }: FormProps) {
const warning = devUseWarning('Form');
useEffect(() => {
React.useEffect(() => {
if (name) {
names[name] = (names[name] || 0) + 1;

View File

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

View File

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

View File

@ -30,7 +30,12 @@ export { default as Cascader } from './cascader';
export type { CascaderProps, CascaderAutoProps } from './cascader';
export type { CascaderPanelProps, CascaderPanelAutoProps } from './cascader/Panel';
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 type { ColProps } from './col';
export { default as Collapse } from './collapse';
@ -172,3 +177,6 @@ export { default as Watermark } from './watermark';
export type { WatermarkProps } from './watermark';
export { default as Splitter } 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 { ConfigContext } from '../config-provider';
import DisabledContext from '../config-provider/DisabledContext';
import useRemovePasswordTimeout from './hooks/useRemovePasswordTimeout';
import type { InputProps, InputRef } from './Input';
import Input from './Input';
import DisabledContext from '../config-provider/DisabledContext';
const defaultIconRender = (visible: boolean): React.ReactNode =>
visible ? <EyeOutlined /> : <EyeInvisibleOutlined />;
@ -70,13 +70,13 @@ const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
if (visible) {
removePasswordTimeout();
}
setVisible((prevState) => {
const newState = !prevState;
if (typeof visibilityToggle === 'object') {
visibilityToggle.onVisibleChange?.(newState);
}
return newState;
});
const nextVisible = !visible;
setVisible(nextVisible);
if (typeof visibilityToggle === 'object') {
visibilityToggle.onVisibleChange?.(nextVisible);
}
};
const getIcon = (prefixCls: string) => {

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react';
import type { InputRef } from '../Input';
export default function useRemovePasswordTimeout(
inputRef: React.RefObject<InputRef>,
inputRef: React.RefObject<InputRef | null>,
triggerOnMount?: boolean,
) {
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);
// ========================= Responsive =========================
const responsiveHandlerRef = useRef<(mql: MediaQueryListEvent | MediaQueryList) => void>();
const responsiveHandlerRef = useRef<(mql: MediaQueryListEvent | MediaQueryList) => void>(null);
responsiveHandlerRef.current = (mql: MediaQueryListEvent | MediaQueryList) => {
setBelow(mql.matches);
onBreakpoint?.(mql.matches);

View File

@ -14,6 +14,18 @@ const Demo: React.FC<{ type: string }> = ({ type }) => {
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', () => {
it('change type', async () => {
jest.useFakeTimers();

View File

@ -1,3 +1,5 @@
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 [loading, setLoading] = useState(false);
const [users, setUsers] = useState<{ login: string; avatar_url: string }[]>([]);
const ref = useRef<string>();
const ref = useRef<string>(null);
const loadGithubUsers = (key: string) => {
if (!key) {

View File

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

View File

@ -44,7 +44,14 @@ export const OverrideProvider = React.forwardRef<
return (
<OverrideContext.Provider value={context}>
<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>
</OverrideContext.Provider>
);

View File

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

View File

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

View File

@ -136,7 +136,13 @@ const InternalMenu = forwardRef<RcMenuRef, InternalMenuProps>((props, ref) => {
return cloneElement(mergedIcon, {
className: classNames(
`${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]);

View File

@ -6,6 +6,18 @@ import App from '../../app';
import ConfigProvider, { defaultPrefixCls } from '../../config-provider';
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', () => {
beforeAll(() => {
actWrapper(act);

View File

@ -2,6 +2,18 @@ import message, { actDestroy, actWrapper } from '..';
import { act } from '../../../tests/utils';
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', () => {
beforeAll(() => {
actWrapper(act);

View File

@ -5,6 +5,18 @@ import message, { actWrapper } from '..';
import { act, fireEvent, waitFakeTimer } from '../../../tests/utils';
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', () => {
beforeAll(() => {
actWrapper(act);

View File

@ -1,10 +1,22 @@
import React from 'react';
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 { 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', () => {
beforeAll(() => {
actWrapper(act);
@ -32,11 +44,12 @@ describe('message static warning', () => {
content: <div className="bamboo" />,
duration: 0,
});
await waitFakeTimer();
await waitFakeTimer19();
expect(document.querySelector('.bamboo')).toBeTruthy();
expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
});
it('warning if use theme', async () => {
@ -54,5 +67,6 @@ describe('message static warning', () => {
expect(errSpy).toHaveBeenCalledWith(
"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 { 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', () => {
beforeAll(() => {
actWrapper(act);

View File

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

View File

@ -18,6 +18,18 @@ const { confirm } = Modal;
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).rejectPromise = null;

View File

@ -14,6 +14,18 @@ import type { ModalFunc } from '../confirm';
jest.mock('rc-util/lib/Portal');
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', () => {
// Inject CSSMotion to replace with No transition support
const MockCSSMotion = genCSSMotion(false);

View File

@ -2,9 +2,21 @@ import * as React from 'react';
import Modal from '..';
import { resetWarned } from '../../_util/warning';
import { render, waitFakeTimer } from '../../../tests/utils';
import { render, waitFakeTimer, waitFakeTimer19 } from '../../../tests/utils';
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', () => {
beforeEach(() => {
jest.useFakeTimers();
@ -25,11 +37,12 @@ describe('Modal.confirm warning', () => {
Modal.confirm({
content: <div className="bamboo" />,
});
await waitFakeTimer();
await waitFakeTimer19();
expect(document.querySelector('.bamboo')).toBeTruthy();
expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
});
it('warning if use theme', async () => {
@ -46,5 +59,6 @@ describe('Modal.confirm warning', () => {
expect(errSpy).toHaveBeenCalledWith(
"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'
> {
autoFocusButton?: false | 'ok' | 'cancel' | null;
cancelTextLocale?:
| string
| number
| true
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
| Iterable<React.ReactNode>;
cancelTextLocale?: React.ReactNode;
mergedOkCancel?: boolean;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,6 +66,8 @@ const genModalConfirmStyle: GenerateStyle<ModalToken> = (token) => {
flexDirection: 'column',
flex: 'auto',
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

View File

@ -6,6 +6,18 @@ import App from '../../app';
import ConfigProvider from '../../config-provider';
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', () => {
beforeAll(() => {
actWrapper(act);

View File

@ -6,6 +6,18 @@ import { act, fireEvent } from '../../../tests/utils';
import ConfigProvider, { defaultPrefixCls } from '../../config-provider';
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', () => {
beforeAll(() => {
actWrapper(act);

View File

@ -3,6 +3,18 @@ import { act, fireEvent } from '../../../tests/utils';
import type { ArgsProps, GlobalConfigProps } from '../interface';
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', () => {
function open(args?: Partial<ArgsProps>) {
notification.open({

View File

@ -1,10 +1,22 @@
import React from 'react';
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 { 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', () => {
beforeAll(() => {
actWrapper(act);
@ -32,11 +44,12 @@ describe('notification static warning', () => {
message: <div className="bamboo" />,
duration: 0,
});
await waitFakeTimer();
await waitFakeTimer19();
expect(document.querySelector('.bamboo')).toBeTruthy();
expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
});
it('warning if use theme', async () => {
@ -54,5 +67,6 @@ describe('notification static warning', () => {
expect(errSpy).toHaveBeenCalledWith(
"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 { render } from 'rc-util/lib/React/render';
import { AppConfigContext } from '../app/context';
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
import { getReactRender } from '../config-provider/UnstableContext';
import type { ArgsProps, GlobalConfigProps, NotificationInstance } from './interface';
import PurePanel from './PurePanel';
import useNotification, { useInternalNotification } from './useNotification';
@ -126,7 +126,9 @@ function flushNotice() {
// Delay render to avoid sync issue
act(() => {
render(
const reactRender = getReactRender();
reactRender(
<GlobalHolderWrapper
ref={(node) => {
const { instance, sync } = node || {};

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