mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 01:13:58 +08:00
commit
d20a07405f
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
70
.github/workflows/auto-unassign.yml
vendored
Normal 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;
|
||||
}
|
25
.github/workflows/site-deploy.yml
vendored
25
.github/workflows/site-deploy.yml
vendored
@ -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
2
.husky/pre-commit
Executable file → Normal file
@ -1 +1 @@
|
||||
lint-staged
|
||||
lint-staged
|
@ -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`
|
||||
|
@ -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`
|
||||
|
@ -74,6 +74,7 @@ exports[`antd exports modules correctly 1`] = `
|
||||
"message",
|
||||
"notification",
|
||||
"theme",
|
||||
"unstableSetRender",
|
||||
"version",
|
||||
]
|
||||
`;
|
||||
|
44
components/__tests__/unstable.test.ts
Normal file
44
components/__tests__/unstable.test.ts
Normal 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();
|
||||
}
|
||||
});
|
||||
});
|
@ -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,
|
||||
|
@ -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();
|
||||
});
|
||||
|
3
components/_util/isPrimitive.ts
Normal file
3
components/_util/isPrimitive.ts
Normal file
@ -0,0 +1,3 @@
|
||||
const isPrimitive = (value: unknown) => (typeof value !== 'object' && typeof value !== 'function') || value === null;
|
||||
|
||||
export default isPrimitive;
|
@ -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;
|
||||
|
@ -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');
|
||||
|
@ -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) => {
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
||||
|
||||
describe('Affix image', () => {
|
||||
imageDemoTest('affix');
|
||||
imageDemoTest('affix', {
|
||||
onlyViewport: ['debug.tsx'],
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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` });
|
||||
|
@ -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();
|
||||
|
@ -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}`);
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { extendTest } from '../../../tests/shared/demoTest';
|
||||
|
||||
extendTest('auto-complete');
|
||||
extendTest('auto-complete', {
|
||||
skip: ['row-selection-debug.tsx'],
|
||||
});
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
||||
|
||||
describe('AutoComplete image', () => {
|
||||
imageDemoTest('auto-complete');
|
||||
imageDemoTest('auto-complete', {
|
||||
skip: ['row-selection-debug.tsx'],
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
),
|
||||
|
@ -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(
|
||||
|
@ -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'),
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
30
components/config-provider/UnstableContext.tsx
Normal file
30
components/config-provider/UnstableContext.tsx
Normal 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;
|
||||
}
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import type { JSX } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
function notEmpty(val: any) {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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>
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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++, '');
|
||||
|
@ -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);
|
||||
|
@ -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]);
|
||||
|
@ -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!;
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
@ -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) => {
|
||||
|
@ -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',
|
||||
|
@ -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();
|
||||
|
@ -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>[]>([]);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { extendTest } from '../../../tests/shared/demoTest';
|
||||
|
||||
extendTest('mentions');
|
||||
extendTest('mentions', {
|
||||
skip: ['autoSize.tsx'],
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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`,
|
||||
),
|
||||
})}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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`,
|
||||
),
|
||||
})}
|
||||
|
@ -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)',
|
||||
},
|
||||
})
|
||||
|
@ -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]);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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 || {};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 = () => {
|
||||
|
@ -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 = () => {
|
||||
|
@ -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 = () => {
|
||||
|
@ -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>,
|
||||
|
@ -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);
|
||||
|
@ -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 =================
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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({
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user