mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 09:26:06 +08:00
commit
d20a07405f
@ -66,7 +66,7 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
|
|||||||
|
|
||||||
export interface SemanticPreviewProps {
|
export interface SemanticPreviewProps {
|
||||||
semantics: { name: string; desc: string; version?: string }[];
|
semantics: { name: string; desc: string; version?: string }[];
|
||||||
children: React.ReactElement;
|
children: React.ReactElement<any>;
|
||||||
height?: number;
|
height?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
|
|||||||
// ======================== Hover =========================
|
// ======================== Hover =========================
|
||||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>();
|
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(null);
|
||||||
|
|
||||||
const [positionMotion, setPositionMotion] = React.useState<boolean>(false);
|
const [positionMotion, setPositionMotion] = React.useState<boolean>(false);
|
||||||
const [hoverSemantic, setHoverSemantic] = React.useState<string | null>(null);
|
const [hoverSemantic, setHoverSemantic] = React.useState<string | null>(null);
|
||||||
|
@ -2,7 +2,7 @@ import React, { Suspense, useContext } from 'react';
|
|||||||
import { BugOutlined, CodeOutlined, ExperimentOutlined } from '@ant-design/icons';
|
import { BugOutlined, CodeOutlined, ExperimentOutlined } from '@ant-design/icons';
|
||||||
import { ConfigProvider, Tooltip, Button } from 'antd';
|
import { ConfigProvider, Tooltip, Button } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { DumiDemoGrid, FormattedMessage } from 'dumi';
|
import { DumiDemoGrid, FormattedMessage, DumiDemo } from 'dumi';
|
||||||
import { css, Global } from '@emotion/react';
|
import { css, Global } from '@emotion/react';
|
||||||
|
|
||||||
import useLayoutState from '../../../hooks/useLayoutState';
|
import useLayoutState from '../../../hooks/useLayoutState';
|
||||||
@ -114,7 +114,14 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
|
|||||||
</span>
|
</span>
|
||||||
<ConfigProvider theme={{ cssVar: enableCssVar, hashed: !enableCssVar }}>
|
<ConfigProvider theme={{ cssVar: enableCssVar, hashed: !enableCssVar }}>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<DumiDemoGrid items={demos} />
|
<DumiDemoGrid
|
||||||
|
items={demos}
|
||||||
|
demoRender={(item) => (
|
||||||
|
<Suspense>
|
||||||
|
<DumiDemo key={item.demo.id} {...item} />
|
||||||
|
</Suspense>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</div>
|
</div>
|
||||||
|
@ -306,7 +306,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isValidElement(children) &&
|
{isValidElement(children) &&
|
||||||
cloneElement(children as React.ReactElement, {
|
cloneElement(children as React.ReactElement<any>, {
|
||||||
onClick: () => setShow(true),
|
onClick: () => setShow(true),
|
||||||
})}
|
})}
|
||||||
<Drawer
|
<Drawer
|
||||||
|
@ -141,13 +141,23 @@ const PrevAndNext: React.FC<{ rtl?: boolean }> = ({ rtl }) => {
|
|||||||
return (
|
return (
|
||||||
<section className={styles.prevNextNav}>
|
<section className={styles.prevNextNav}>
|
||||||
{prev &&
|
{prev &&
|
||||||
React.cloneElement(prev.label as ReactElement, {
|
React.cloneElement(
|
||||||
className: classNames(styles.pageNav, styles.prevNav, prev.className),
|
prev.label as ReactElement<{
|
||||||
})}
|
className: string;
|
||||||
|
}>,
|
||||||
|
{
|
||||||
|
className: classNames(styles.pageNav, styles.prevNav, prev.className),
|
||||||
|
},
|
||||||
|
)}
|
||||||
{next &&
|
{next &&
|
||||||
React.cloneElement(next.label as ReactElement, {
|
React.cloneElement(
|
||||||
className: classNames(styles.pageNav, styles.nextNav, next.className),
|
next.label as ReactElement<{
|
||||||
})}
|
className: string;
|
||||||
|
}>,
|
||||||
|
{
|
||||||
|
className: classNames(styles.pageNav, styles.nextNav, next.className),
|
||||||
|
},
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,7 @@ const DocLayout: React.FC = () => {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { pathname, search, hash } = location;
|
const { pathname, search, hash } = location;
|
||||||
const [locale, lang] = useLocale(locales);
|
const [locale, lang] = useLocale(locales);
|
||||||
const timerRef = useRef<ReturnType<typeof setTimeout>>();
|
const timerRef = useRef<ReturnType<typeof setTimeout>>(null!);
|
||||||
const { direction } = useContext(SiteContext);
|
const { direction } = useContext(SiteContext);
|
||||||
const { loading } = useSiteData();
|
const { loading } = useSiteData();
|
||||||
|
|
||||||
|
@ -9,10 +9,11 @@ import {
|
|||||||
} from '@ant-design/cssinjs';
|
} from '@ant-design/cssinjs';
|
||||||
import { HappyProvider } from '@ant-design/happy-work-theme';
|
import { HappyProvider } from '@ant-design/happy-work-theme';
|
||||||
import { getSandpackCssText } from '@codesandbox/sandpack-react';
|
import { getSandpackCssText } from '@codesandbox/sandpack-react';
|
||||||
import { theme as antdTheme, App } from 'antd';
|
import { theme as antdTheme, App, unstableSetRender } from 'antd';
|
||||||
import type { MappingAlgorithm } from 'antd';
|
import type { MappingAlgorithm } from 'antd';
|
||||||
import type { DirectionType, ThemeConfig } from 'antd/es/config-provider';
|
import type { DirectionType, ThemeConfig } from 'antd/es/config-provider';
|
||||||
import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi';
|
import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import { DarkContext } from '../../hooks/useDark';
|
import { DarkContext } from '../../hooks/useDark';
|
||||||
import useLayoutState from '../../hooks/useLayoutState';
|
import useLayoutState from '../../hooks/useLayoutState';
|
||||||
@ -30,6 +31,14 @@ type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
|
|||||||
const RESPONSIVE_MOBILE = 768;
|
const RESPONSIVE_MOBILE = 768;
|
||||||
export const ANT_DESIGN_NOT_SHOW_BANNER = 'ANT_DESIGN_NOT_SHOW_BANNER';
|
export const ANT_DESIGN_NOT_SHOW_BANNER = 'ANT_DESIGN_NOT_SHOW_BANNER';
|
||||||
|
|
||||||
|
unstableSetRender((node, container) => {
|
||||||
|
const root = createRoot(container);
|
||||||
|
root.render(node);
|
||||||
|
return async () => {
|
||||||
|
root.unmount();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// const styleCache = createCache();
|
// const styleCache = createCache();
|
||||||
// if (typeof global !== 'undefined') {
|
// if (typeof global !== 'undefined') {
|
||||||
// (global as any).styleCache = styleCache;
|
// (global as any).styleCache = styleCache;
|
||||||
|
@ -8,13 +8,14 @@ import useMenu from '../../../hooks/useMenu';
|
|||||||
import SiteContext from '../SiteContext';
|
import SiteContext from '../SiteContext';
|
||||||
|
|
||||||
const useStyle = createStyles(({ token, css }) => {
|
const useStyle = createStyles(({ token, css }) => {
|
||||||
const { antCls, fontFamily, colorSplit } = token;
|
const { antCls, fontFamily, colorSplit, marginXXL, paddingXXS } = token;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
asideContainer: css`
|
asideContainer: css`
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
padding-bottom: 48px;
|
padding-bottom: ${marginXXL}px !important;
|
||||||
font-family: Avenir, ${fontFamily}, sans-serif;
|
font-family: Avenir, ${fontFamily}, sans-serif;
|
||||||
|
padding: 0 ${paddingXXS}px;
|
||||||
|
|
||||||
&${antCls}-menu-inline {
|
&${antCls}-menu-inline {
|
||||||
${antCls}-menu-submenu-title h4,
|
${antCls}-menu-submenu-title h4,
|
||||||
@ -94,14 +95,10 @@ const useStyle = createStyles(({ token, css }) => {
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: ${token.headerHeight + token.contentMarginTop}px;
|
top: ${token.headerHeight + token.contentMarginTop}px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
max-height: calc(100vh - ${token.headerHeight + token.contentMarginTop}px);
|
max-height: calc(100vh - ${token.headerHeight + token.contentMarginTop}px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
.ant-menu {
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
70
.github/workflows/auto-unassign.yml
vendored
Normal file
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:
|
build-site:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: (startsWith(github.ref, 'refs/tags/') && (contains(github.ref_name, '-') == false)) || github.event_name == 'workflow_dispatch'
|
if: (startsWith(github.ref, 'refs/tags/') && (contains(github.ref_name, '-') == false)) || github.event_name == 'workflow_dispatch'
|
||||||
|
|
||||||
|
# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#example-defining-outputs-for-a-job
|
||||||
|
outputs:
|
||||||
|
formatted_version: ${{ steps.shared-formatted_version.outputs.VERSION }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -46,6 +51,11 @@ jobs:
|
|||||||
path: _site/
|
path: _site/
|
||||||
retention-days: 1 # Not need to keep for too long
|
retention-days: 1 # Not need to keep for too long
|
||||||
|
|
||||||
|
- name: Format version
|
||||||
|
if: ${{ always() }}
|
||||||
|
id: shared-formatted_version
|
||||||
|
run: echo "VERSION=$(echo ${{ github.ref_name }} | sed 's/\./-/g')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
deploy-to-pages:
|
deploy-to-pages:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build-site
|
needs: build-site
|
||||||
@ -58,10 +68,6 @@ jobs:
|
|||||||
name: real-site
|
name: real-site
|
||||||
path: _site
|
path: _site
|
||||||
|
|
||||||
- name: Get version
|
|
||||||
id: publish-version
|
|
||||||
run: echo "VERSION=$(echo ${{ github.ref_name }} | sed 's/\./-/g')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
uses: peaceiris/actions-gh-pages@v4
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
with:
|
with:
|
||||||
@ -81,15 +87,15 @@ jobs:
|
|||||||
|
|
||||||
- name: Deploy to Surge (with TAG)
|
- name: Deploy to Surge (with TAG)
|
||||||
run: |
|
run: |
|
||||||
export DEPLOY_DOMAIN=ant-design-${{ steps.publish-version.outputs.VERSION }}.surge.sh
|
export DEPLOY_DOMAIN=ant-design-${{ needs.build-site.outputs.formatted_version }}.surge.sh
|
||||||
bunx surge --project ./_site --domain $DEPLOY_DOMAIN --token ${{ secrets.SURGE_TOKEN }}
|
bunx surge --project ./_site --domain $DEPLOY_DOMAIN --token ${{ secrets.SURGE_TOKEN }}
|
||||||
|
|
||||||
- name: Create Commit Comment
|
- name: Create Commit Comment
|
||||||
uses: peter-evans/commit-comment@v3
|
uses: peter-evans/commit-comment@v3
|
||||||
with:
|
with:
|
||||||
body: |
|
body: |
|
||||||
- Documentation site for this release: https://ant-design-${{ steps.publish-version.outputs.VERSION }}.surge.sh
|
- Documentation site for this release: https://ant-design-${{ needs.build-site.outputs.formatted_version }}.surge.sh
|
||||||
- Webpack bundle analyzer report page: https://ant-design-${{ steps.publish-version.outputs.VERSION }}.surge.sh/report.html
|
- Webpack bundle analyzer report page: https://ant-design-${{ needs.build-site.outputs.formatted_version }}.surge.sh/report.html
|
||||||
|
|
||||||
# https://github.com/ant-design/ant-design/pull/49213/files#r1625446496
|
# https://github.com/ant-design/ant-design/pull/49213/files#r1625446496
|
||||||
upload-to-release:
|
upload-to-release:
|
||||||
@ -107,12 +113,11 @@ jobs:
|
|||||||
- name: Tarball site
|
- name: Tarball site
|
||||||
run: |
|
run: |
|
||||||
cd ./_site
|
cd ./_site
|
||||||
VERSION=$(echo ${{ github.ref_name }} | sed 's/\./-/g')
|
tar -czf ../website.tar.gz --transform 's|^|antd-${{ needs.build-site.outputs.formatted_version }}-website/|' .
|
||||||
tar -czf ../website.tar.gz --transform 's|^|antd-${VERSION}-website/|' .
|
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
- name: Upload to Release
|
- name: Upload to Release
|
||||||
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
|
uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0
|
||||||
with:
|
with:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
files: website.tar.gz
|
files: website.tar.gz
|
||||||
|
2
.husky/pre-commit
Executable file → Normal file
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
|
## 5.22.4
|
||||||
|
|
||||||
`2024-12-09`
|
`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
|
## 5.22.4
|
||||||
|
|
||||||
`2024-12-09`
|
`2024-12-09`
|
||||||
|
@ -74,6 +74,7 @@ exports[`antd exports modules correctly 1`] = `
|
|||||||
"message",
|
"message",
|
||||||
"notification",
|
"notification",
|
||||||
"theme",
|
"theme",
|
||||||
|
"unstableSetRender",
|
||||||
"version",
|
"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 { resetWarned } from '../warning';
|
||||||
import zIndexContext from '../zindexContext';
|
import zIndexContext from '../zindexContext';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
const WrapWithProvider: React.FC<PropsWithChildren<{ container: ZIndexContainer }>> = ({
|
const WrapWithProvider: React.FC<PropsWithChildren<{ container: ZIndexContainer }>> = ({
|
||||||
children,
|
children,
|
||||||
container,
|
container,
|
||||||
|
@ -9,6 +9,18 @@ import { TARGET_CLS } from '../wave/interface';
|
|||||||
|
|
||||||
(global as any).isVisible = true;
|
(global as any).isVisible = true;
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
||||||
const mockFn = () => (global as any).isVisible;
|
const mockFn = () => (global as any).isVisible;
|
||||||
return mockFn;
|
return mockFn;
|
||||||
@ -96,6 +108,7 @@ describe('Wave component', () => {
|
|||||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
||||||
|
|
||||||
expect(errorSpy).not.toHaveBeenCalled();
|
expect(errorSpy).not.toHaveBeenCalled();
|
||||||
|
errorSpy.mockRestore();
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
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 classNames from 'classnames';
|
||||||
import CSSMotion from 'rc-motion';
|
import CSSMotion from 'rc-motion';
|
||||||
import raf from 'rc-util/lib/raf';
|
import raf from 'rc-util/lib/raf';
|
||||||
import { render, unmount } from 'rc-util/lib/React/render';
|
|
||||||
import { composeRef } from 'rc-util/lib/ref';
|
import { composeRef } from 'rc-util/lib/ref';
|
||||||
|
|
||||||
|
import { getReactRender, type UnmountType } from '../../config-provider/UnstableContext';
|
||||||
import { TARGET_CLS } from './interface';
|
import { TARGET_CLS } from './interface';
|
||||||
import type { ShowWaveEffect } from './interface';
|
import type { ShowWaveEffect } from './interface';
|
||||||
import { getTargetWaveColor } from './util';
|
import { getTargetWaveColor } from './util';
|
||||||
@ -17,12 +17,21 @@ export interface WaveEffectProps {
|
|||||||
className: string;
|
className: string;
|
||||||
target: HTMLElement;
|
target: HTMLElement;
|
||||||
component?: string;
|
component?: string;
|
||||||
|
registerUnmount: () => UnmountType | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
const WaveEffect = (props: WaveEffectProps) => {
|
||||||
const { className, target, component } = props;
|
const { className, target, component, registerUnmount } = props;
|
||||||
const divRef = React.useRef<HTMLDivElement>(null);
|
const divRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// ====================== Refs ======================
|
||||||
|
const unmountRef = React.useRef<UnmountType>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
unmountRef.current = registerUnmount();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ===================== Effect =====================
|
||||||
const [color, setWaveColor] = React.useState<string | null>(null);
|
const [color, setWaveColor] = React.useState<string | null>(null);
|
||||||
const [borderRadius, setBorderRadius] = React.useState<number[]>([]);
|
const [borderRadius, setBorderRadius] = React.useState<number[]>([]);
|
||||||
const [left, setLeft] = React.useState(0);
|
const [left, setLeft] = React.useState(0);
|
||||||
@ -119,7 +128,7 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
|||||||
onAppearEnd={(_, event) => {
|
onAppearEnd={(_, event) => {
|
||||||
if (event.deadline || (event as TransitionEvent).propertyName === 'opacity') {
|
if (event.deadline || (event as TransitionEvent).propertyName === 'opacity') {
|
||||||
const holder = divRef.current?.parentElement!;
|
const holder = divRef.current?.parentElement!;
|
||||||
unmount(holder).then(() => {
|
unmountRef.current?.().then(() => {
|
||||||
holder?.remove();
|
holder?.remove();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -140,13 +149,6 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
|||||||
const showWaveEffect: ShowWaveEffect = (target, info) => {
|
const showWaveEffect: ShowWaveEffect = (target, info) => {
|
||||||
const { component } = info;
|
const { component } = info;
|
||||||
|
|
||||||
// Skip if not support `render` since `rc-util` render not support React 19
|
|
||||||
// TODO: remove this check in v6
|
|
||||||
/* istanbul ignore next */
|
|
||||||
if (!render) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip for unchecked checkbox
|
// Skip for unchecked checkbox
|
||||||
if (component === 'Checkbox' && !target.querySelector<HTMLInputElement>('input')?.checked) {
|
if (component === 'Checkbox' && !target.querySelector<HTMLInputElement>('input')?.checked) {
|
||||||
return;
|
return;
|
||||||
@ -159,7 +161,18 @@ const showWaveEffect: ShowWaveEffect = (target, info) => {
|
|||||||
holder.style.top = '0px';
|
holder.style.top = '0px';
|
||||||
target?.insertBefore(holder, target?.firstChild);
|
target?.insertBefore(holder, target?.firstChild);
|
||||||
|
|
||||||
render(<WaveEffect {...info} target={target} />, holder);
|
const reactRender = getReactRender();
|
||||||
|
|
||||||
|
let unmountCallback: UnmountType | null = null;
|
||||||
|
|
||||||
|
function registerUnmount() {
|
||||||
|
return unmountCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
unmountCallback = reactRender(
|
||||||
|
<WaveEffect {...info} target={target} registerUnmount={registerUnmount} />,
|
||||||
|
holder,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default showWaveEffect;
|
export default showWaveEffect;
|
||||||
|
@ -19,7 +19,7 @@ export interface WaveProps {
|
|||||||
const Wave: React.FC<WaveProps> = (props) => {
|
const Wave: React.FC<WaveProps> = (props) => {
|
||||||
const { children, disabled, component } = props;
|
const { children, disabled, component } = props;
|
||||||
const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext);
|
const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext);
|
||||||
const containerRef = useRef<HTMLElement>(null);
|
const containerRef = useRef<HTMLElement>(null!);
|
||||||
|
|
||||||
// ============================== Style ===============================
|
// ============================== Style ===============================
|
||||||
const prefixCls = getPrefixCls('wave');
|
const prefixCls = getPrefixCls('wave');
|
||||||
|
@ -28,10 +28,16 @@ const useWave = (
|
|||||||
const { showEffect } = wave || {};
|
const { showEffect } = wave || {};
|
||||||
|
|
||||||
// Customize wave effect
|
// Customize wave effect
|
||||||
(showEffect || showWaveEffect)(targetNode, { className, token, component, event, hashId });
|
(showEffect || showWaveEffect)(targetNode, {
|
||||||
|
className,
|
||||||
|
token,
|
||||||
|
component,
|
||||||
|
event,
|
||||||
|
hashId,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const rafId = React.useRef<number>();
|
const rafId = React.useRef<number>(null);
|
||||||
|
|
||||||
// Merge trigger event into one for each frame
|
// Merge trigger event into one for each frame
|
||||||
const showDebounceWave: ShowWave = (event) => {
|
const showDebounceWave: ShowWave = (event) => {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
||||||
|
|
||||||
describe('Affix image', () => {
|
describe('Affix image', () => {
|
||||||
imageDemoTest('affix');
|
imageDemoTest('affix', {
|
||||||
|
onlyViewport: ['debug.tsx'],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -80,7 +80,7 @@ const Affix = React.forwardRef<AffixRef, AffixProps>((props, ref) => {
|
|||||||
const status = React.useRef<AffixStatus>(AFFIX_STATUS_NONE);
|
const status = React.useRef<AffixStatus>(AFFIX_STATUS_NONE);
|
||||||
|
|
||||||
const prevTarget = React.useRef<Window | HTMLElement | null>(null);
|
const prevTarget = React.useRef<Window | HTMLElement | null>(null);
|
||||||
const prevListener = React.useRef<EventListener>();
|
const prevListener = React.useRef<EventListener>(null);
|
||||||
|
|
||||||
const placeholderNodeRef = React.useRef<HTMLDivElement>(null);
|
const placeholderNodeRef = React.useRef<HTMLDivElement>(null);
|
||||||
const fixedNodeRef = React.useRef<HTMLDivElement>(null);
|
const fixedNodeRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
@ -76,9 +76,14 @@ const IconNode: React.FC<IconNodeProps> = (props) => {
|
|||||||
const iconType = iconMapFilled[type!] || null;
|
const iconType = iconMapFilled[type!] || null;
|
||||||
if (icon) {
|
if (icon) {
|
||||||
return replaceElement(icon, <span className={`${prefixCls}-icon`}>{icon}</span>, () => ({
|
return replaceElement(icon, <span className={`${prefixCls}-icon`}>{icon}</span>, () => ({
|
||||||
className: classNames(`${prefixCls}-icon`, {
|
className: classNames(
|
||||||
[(icon as ReactElement).props.className]: (icon as ReactElement).props.className,
|
`${prefixCls}-icon`,
|
||||||
}),
|
(
|
||||||
|
icon as ReactElement<{
|
||||||
|
className?: string;
|
||||||
|
}>
|
||||||
|
).props.className,
|
||||||
|
),
|
||||||
})) as ReactElement;
|
})) as ReactElement;
|
||||||
}
|
}
|
||||||
return React.createElement(iconType, { className: `${prefixCls}-icon` });
|
return React.createElement(iconType, { className: `${prefixCls}-icon` });
|
||||||
|
@ -5,7 +5,7 @@ import { resetWarned } from 'rc-util/lib/warning';
|
|||||||
import Alert from '..';
|
import Alert from '..';
|
||||||
import { accessibilityTest } from '../../../tests/shared/accessibilityTest';
|
import { accessibilityTest } from '../../../tests/shared/accessibilityTest';
|
||||||
import rtlTest from '../../../tests/shared/rtlTest';
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
import { act, render, screen, waitFakeTimer } from '../../../tests/utils';
|
import { act, fireEvent, render, screen, waitFakeTimer } from '../../../tests/utils';
|
||||||
import Button from '../../button';
|
import Button from '../../button';
|
||||||
import Popconfirm from '../../popconfirm';
|
import Popconfirm from '../../popconfirm';
|
||||||
import Tooltip from '../../tooltip';
|
import Tooltip from '../../tooltip';
|
||||||
@ -28,7 +28,7 @@ describe('Alert', () => {
|
|||||||
it('should show close button and could be closed', async () => {
|
it('should show close button and could be closed', async () => {
|
||||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
const onClose = jest.fn();
|
const onClose = jest.fn();
|
||||||
render(
|
const { container } = render(
|
||||||
<Alert
|
<Alert
|
||||||
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||||
type="warning"
|
type="warning"
|
||||||
@ -37,10 +37,7 @@ describe('Alert', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
await act(async () => {
|
fireEvent.click(container.querySelector('.ant-alert-close-icon')!);
|
||||||
await userEvent.click(screen.getByRole('button', { name: /close/i }));
|
|
||||||
jest.runAllTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(onClose).toHaveBeenCalledTimes(1);
|
expect(onClose).toHaveBeenCalledTimes(1);
|
||||||
expect(errSpy).not.toHaveBeenCalled();
|
expect(errSpy).not.toHaveBeenCalled();
|
||||||
|
@ -381,8 +381,6 @@ describe('Anchor Render', () => {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
// https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0
|
|
||||||
{ legacyRoot: true },
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(onChange).toHaveBeenCalledTimes(1);
|
expect(onChange).toHaveBeenCalledTimes(1);
|
||||||
@ -556,16 +554,14 @@ describe('Anchor Render', () => {
|
|||||||
{ key: hash2, href: `#${hash2}`, title: hash2 },
|
{ key: hash2, href: `#${hash2}`, title: hash2 },
|
||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
// https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0
|
|
||||||
{ legacyRoot: true },
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Should be 2 times:
|
// Should be 2 times:
|
||||||
// 1. ''
|
// 1. ''
|
||||||
// 2. hash1 (Since `getCurrentAnchor` still return same hash)
|
// 2. hash1 (Since `getCurrentAnchor` still return same hash)
|
||||||
expect(onChange).toHaveBeenCalledTimes(2);
|
const calledTimes = onChange.mock.calls.length;
|
||||||
fireEvent.click(container.querySelector(`a[href="#${hash2}"]`)!);
|
fireEvent.click(container.querySelector(`a[href="#${hash2}"]`)!);
|
||||||
expect(onChange).toHaveBeenCalledTimes(3);
|
expect(onChange).toHaveBeenCalledTimes(calledTimes + 1);
|
||||||
expect(onChange).toHaveBeenLastCalledWith(`#${hash2}`);
|
expect(onChange).toHaveBeenLastCalledWith(`#${hash2}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2267,15 +2267,7 @@ exports[`renders components/auto-complete/demo/render-panel.tsx extend context c
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders components/auto-complete/demo/render-panel.tsx extend context correctly 2`] = `
|
exports[`renders components/auto-complete/demo/render-panel.tsx extend context correctly 2`] = `[]`;
|
||||||
[
|
|
||||||
"Warning: Received \`%s\` for a non-boolean attribute \`%s\`.
|
|
||||||
|
|
||||||
If you want to write it to the DOM, pass a string instead: %s="%s" or %s={value.toString()}.
|
|
||||||
|
|
||||||
If you used to conditionally omit it with %s={condition && value}, pass %s={condition ? value : undefined} instead.%s",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`renders components/auto-complete/demo/status.tsx extend context correctly 1`] = `
|
exports[`renders components/auto-complete/demo/status.tsx extend context correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
import { extendTest } from '../../../tests/shared/demoTest';
|
import { extendTest } from '../../../tests/shared/demoTest';
|
||||||
|
|
||||||
extendTest('auto-complete');
|
extendTest('auto-complete', {
|
||||||
|
skip: ['row-selection-debug.tsx'],
|
||||||
|
});
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
||||||
|
|
||||||
describe('AutoComplete image', () => {
|
describe('AutoComplete image', () => {
|
||||||
imageDemoTest('auto-complete');
|
imageDemoTest('auto-complete', {
|
||||||
|
skip: ['row-selection-debug.tsx'],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -170,7 +170,9 @@ const RefAutoComplete = React.forwardRef<RefSelectProps, AutoCompleteProps>(
|
|||||||
|
|
||||||
// We don't care debug panel
|
// We don't care debug panel
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
const PurePanel = genPurePanel(RefAutoComplete);
|
const PurePanel = genPurePanel(RefAutoComplete, undefined, undefined, (props: any) =>
|
||||||
|
omit(props, ['visible']),
|
||||||
|
);
|
||||||
|
|
||||||
RefAutoComplete.Option = Option;
|
RefAutoComplete.Option = Option;
|
||||||
RefAutoComplete._InternalPanelDoNotUseOrYouWillBeFired = PurePanel;
|
RefAutoComplete._InternalPanelDoNotUseOrYouWillBeFired = PurePanel;
|
||||||
|
@ -10,7 +10,7 @@ export interface ScrollNumberProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
motionClassName?: string;
|
motionClassName?: string;
|
||||||
count?: string | number | null;
|
count?: string | number | null;
|
||||||
children?: React.ReactElement<HTMLElement>;
|
children?: React.ReactElement;
|
||||||
component?: React.ComponentType<any>;
|
component?: React.ComponentType<any>;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
title?: string | number | null;
|
title?: string | number | null;
|
||||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import type { GetRef } from '../../_util/type';
|
import type { GetRef } from '../../_util/type';
|
||||||
import mountTest from '../../../tests/shared/mountTest';
|
import mountTest from '../../../tests/shared/mountTest';
|
||||||
import rtlTest from '../../../tests/shared/rtlTest';
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
import { act, fireEvent, render, waitFakeTimer19 } from '../../../tests/utils';
|
||||||
import Tooltip from '../../tooltip';
|
import Tooltip from '../../tooltip';
|
||||||
import Badge from '../index';
|
import Badge from '../index';
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ describe('Badge', () => {
|
|||||||
const { container } = render(<Comp />);
|
const { container } = render(<Comp />);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector('button')!);
|
fireEvent.click(container.querySelector('button')!);
|
||||||
await waitFakeTimer();
|
await waitFakeTimer19();
|
||||||
|
|
||||||
expect(errSpy).not.toHaveBeenCalled();
|
expect(errSpy).not.toHaveBeenCalled();
|
||||||
errSpy.mockRestore();
|
errSpy.mockRestore();
|
||||||
|
@ -4,6 +4,18 @@ import userEvent from '@testing-library/user-event';
|
|||||||
import Button from '..';
|
import Button from '..';
|
||||||
import { act, fireEvent, render } from '../../../tests/utils';
|
import { act, fireEvent, render } from '../../../tests/utils';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
||||||
const mockFn = () => true;
|
const mockFn = () => true;
|
||||||
return mockFn;
|
return mockFn;
|
||||||
|
@ -163,7 +163,7 @@ const InternalCompoundedButton = React.forwardRef<
|
|||||||
|
|
||||||
const [hasTwoCNChar, setHasTwoCNChar] = useState<boolean>(false);
|
const [hasTwoCNChar, setHasTwoCNChar] = useState<boolean>(false);
|
||||||
|
|
||||||
const buttonRef = useRef<HTMLButtonElement | HTMLAnchorElement>();
|
const buttonRef = useRef<HTMLButtonElement | HTMLAnchorElement>(null);
|
||||||
|
|
||||||
const mergedRef = useComposeRef(ref, buttonRef);
|
const mergedRef = useComposeRef(ref, buttonRef);
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ export function convertLegacyProps(
|
|||||||
return { type };
|
return { type };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isString(str: any): str is string {
|
export function isString(str: unknown): str is string {
|
||||||
return typeof str === 'string';
|
return typeof str === 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,10 +35,22 @@ function splitCNCharsBySpace(child: React.ReactElement | string | number, needIn
|
|||||||
typeof child !== 'string' &&
|
typeof child !== 'string' &&
|
||||||
typeof child !== 'number' &&
|
typeof child !== 'number' &&
|
||||||
isString(child.type) &&
|
isString(child.type) &&
|
||||||
isTwoCNChar(child.props.children)
|
isTwoCNChar(
|
||||||
|
(
|
||||||
|
child as React.ReactElement<{
|
||||||
|
children: string;
|
||||||
|
}>
|
||||||
|
).props.children,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return cloneElement(child, {
|
return cloneElement(child, {
|
||||||
children: child.props.children.split('').join(SPACE),
|
children: (
|
||||||
|
child as React.ReactElement<{
|
||||||
|
children: string;
|
||||||
|
}>
|
||||||
|
).props.children
|
||||||
|
.split('')
|
||||||
|
.join(SPACE),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,9 +335,11 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token) =>
|
|||||||
token.solidTextColor,
|
token.solidTextColor,
|
||||||
token.colorBgSolid,
|
token.colorBgSolid,
|
||||||
{
|
{
|
||||||
|
color: token.solidTextColor,
|
||||||
background: token.colorBgSolidHover,
|
background: token.colorBgSolidHover,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
color: token.solidTextColor,
|
||||||
background: token.colorBgSolidActive,
|
background: token.colorBgSolidActive,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -153,7 +153,7 @@ export interface CalendarHeaderProps<DateType> {
|
|||||||
}
|
}
|
||||||
function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
|
function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
|
||||||
const { prefixCls, fullscreen, mode, onChange, onModeChange } = props;
|
const { prefixCls, fullscreen, mode, onChange, onModeChange } = props;
|
||||||
const divRef = React.useRef<HTMLDivElement>(null);
|
const divRef = React.useRef<HTMLDivElement>(null!);
|
||||||
|
|
||||||
const formItemInputContext = useContext(FormItemInputContext);
|
const formItemInputContext = useContext(FormItemInputContext);
|
||||||
const mergedFormItemInputContext = useMemo(
|
const mergedFormItemInputContext = useMemo(
|
||||||
|
@ -121,7 +121,7 @@ const App: React.FC = () => {
|
|||||||
const displayHoliday = h?.getTarget() === h?.getDay() ? h?.getName() : undefined;
|
const displayHoliday = h?.getTarget() === h?.getDay() ? h?.getName() : undefined;
|
||||||
if (info.type === 'date') {
|
if (info.type === 'date') {
|
||||||
return React.cloneElement(info.originNode, {
|
return React.cloneElement(info.originNode, {
|
||||||
...info.originNode.props,
|
...(info.originNode as React.ReactElement<any>).props,
|
||||||
className: classNames(styles.dateCell, {
|
className: classNames(styles.dateCell, {
|
||||||
[styles.current]: selectDate.isSame(date, 'date'),
|
[styles.current]: selectDate.isSame(date, 'date'),
|
||||||
[styles.today]: date.isSame(dayjs(), 'date'),
|
[styles.today]: date.isSame(dayjs(), 'date'),
|
||||||
|
@ -147,7 +147,7 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>((props, ref) => {
|
|||||||
|
|
||||||
const isContainGrid = React.useMemo<boolean>(() => {
|
const isContainGrid = React.useMemo<boolean>(() => {
|
||||||
let containGrid = false;
|
let containGrid = false;
|
||||||
React.Children.forEach(children as React.ReactElement, (element: JSX.Element) => {
|
React.Children.forEach(children as React.ReactElement, (element: React.JSX.Element) => {
|
||||||
if (element?.type === Grid) {
|
if (element?.type === Grid) {
|
||||||
containGrid = true;
|
containGrid = true;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>((props, ref) => {
|
|||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
const { getPrefixCls, direction, carousel } = React.useContext(ConfigContext);
|
const { getPrefixCls, direction, carousel } = React.useContext(ConfigContext);
|
||||||
const slickRef = React.useRef<any>();
|
const slickRef = React.useRef<any>(null);
|
||||||
|
|
||||||
const goTo = (slide: number, dontAnimate = false) => {
|
const goTo = (slide: number, dontAnimate = false) => {
|
||||||
slickRef.current.slickGoTo(slide, dontAnimate);
|
slickRef.current.slickGoTo(slide, dontAnimate);
|
||||||
|
@ -2585,15 +2585,7 @@ exports[`renders components/cascader/demo/render-panel.tsx extend context correc
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders components/cascader/demo/render-panel.tsx extend context correctly 2`] = `
|
exports[`renders components/cascader/demo/render-panel.tsx extend context correctly 2`] = `[]`;
|
||||||
[
|
|
||||||
"Warning: Received \`%s\` for a non-boolean attribute \`%s\`.
|
|
||||||
|
|
||||||
If you want to write it to the DOM, pass a string instead: %s="%s" or %s={value.toString()}.
|
|
||||||
|
|
||||||
If you used to conditionally omit it with %s={condition && value}, pass %s={condition ? value : undefined} instead.%s",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`renders components/cascader/demo/search.tsx extend context correctly 1`] = `
|
exports[`renders components/cascader/demo/search.tsx extend context correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
|
@ -370,7 +370,9 @@ if (process.env.NODE_ENV !== 'production') {
|
|||||||
|
|
||||||
// We don't care debug panel
|
// We don't care debug panel
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
const PurePanel = genPurePanel(Cascader);
|
const PurePanel = genPurePanel(Cascader, undefined, undefined, (props: any) =>
|
||||||
|
omit(props, ['visible']),
|
||||||
|
);
|
||||||
|
|
||||||
Cascader.SHOW_PARENT = SHOW_PARENT;
|
Cascader.SHOW_PARENT = SHOW_PARENT;
|
||||||
Cascader.SHOW_CHILD = SHOW_CHILD;
|
Cascader.SHOW_CHILD = SHOW_CHILD;
|
||||||
|
@ -111,7 +111,14 @@ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) =>
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
return cloneElement(icon, () => ({
|
return cloneElement(icon, () => ({
|
||||||
className: classNames((icon as React.ReactElement)?.props?.className, `${prefixCls}-arrow`),
|
className: classNames(
|
||||||
|
(
|
||||||
|
icon as React.ReactElement<{
|
||||||
|
className?: string;
|
||||||
|
}>
|
||||||
|
)?.props?.className,
|
||||||
|
`${prefixCls}-arrow`,
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
[mergedExpandIcon, prefixCls],
|
[mergedExpandIcon, prefixCls],
|
||||||
@ -137,25 +144,30 @@ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) =>
|
|||||||
leavedClassName: `${prefixCls}-content-hidden`,
|
leavedClassName: `${prefixCls}-content-hidden`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const items = React.useMemo<React.ReactNode[] | null>(
|
const items = React.useMemo<React.ReactNode[] | null>(() => {
|
||||||
() =>
|
if (children) {
|
||||||
children
|
return toArray(children).map((child, index) => {
|
||||||
? toArray(children).map<React.ReactNode>((child, index) => {
|
const childProps = (
|
||||||
if (child.props?.disabled) {
|
child as React.ReactElement<{
|
||||||
const key = child.key ?? String(index);
|
disabled?: boolean;
|
||||||
const { disabled, collapsible } = child.props;
|
collapsible?: CollapsibleType;
|
||||||
const childProps: Omit<CollapseProps, 'items'> & { key: React.Key } = {
|
}>
|
||||||
...omit(child.props, ['disabled']),
|
).props;
|
||||||
key,
|
|
||||||
collapsible: collapsible ?? (disabled ? 'disabled' : undefined),
|
if (childProps?.disabled) {
|
||||||
};
|
const key = child.key ?? String(index);
|
||||||
return cloneElement(child, childProps);
|
const mergedChildProps: Omit<CollapseProps, 'items'> & { key: React.Key } = {
|
||||||
}
|
...omit(child.props as any, ['disabled']),
|
||||||
return child;
|
key,
|
||||||
})
|
collapsible: childProps.collapsible ?? 'disabled',
|
||||||
: null,
|
};
|
||||||
[children],
|
return cloneElement(child, mergedChildProps);
|
||||||
);
|
}
|
||||||
|
return child;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [children]);
|
||||||
|
|
||||||
return wrapCSSVar(
|
return wrapCSSVar(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
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 Pagination from '../../pagination';
|
||||||
import TimePicker from '../../time-picker';
|
import TimePicker from '../../time-picker';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('ConfigProvider.Locale', () => {
|
describe('ConfigProvider.Locale', () => {
|
||||||
function $$(selector: string): NodeListOf<Element> {
|
function $$(selector: string): NodeListOf<Element> {
|
||||||
return document.body.querySelectorAll(selector);
|
return document.body.querySelectorAll(selector);
|
||||||
|
@ -193,12 +193,26 @@ const Page: React.FC = () => {
|
|||||||
Begin Tour
|
Begin Tour
|
||||||
</Button>
|
</Button>
|
||||||
<Space>
|
<Space>
|
||||||
<Button ref={(node) => node && tourRefs.current.splice(0, 0, node)}> Upload</Button>
|
<Button
|
||||||
<Button ref={(node) => node && tourRefs.current.splice(1, 0, node)} type="primary">
|
ref={(node) => {
|
||||||
|
node && tourRefs.current.splice(0, 0, node);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
ref={(node) => {
|
||||||
|
node && tourRefs.current.splice(1, 0, node);
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
ref={(node) => node && tourRefs.current.splice(2, 0, node)}
|
ref={(node) => {
|
||||||
|
node && tourRefs.current.splice(2, 0, node);
|
||||||
|
}}
|
||||||
icon={<EllipsisOutlined />}
|
icon={<EllipsisOutlined />}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import type { JSX } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
function notEmpty(val: any) {
|
function notEmpty(val: any) {
|
||||||
|
@ -11,6 +11,9 @@ export interface DescriptionsItemProps {
|
|||||||
span?: number;
|
span?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DescriptionsItem: React.FC<DescriptionsItemProps> = ({ children }) => children as JSX.Element;
|
// JSX Structure Syntactic Sugar. Never reach the render code.
|
||||||
|
/* istanbul ignore next */
|
||||||
|
const DescriptionsItem: React.FC<DescriptionsItemProps> = ({ children }) =>
|
||||||
|
children as React.JSX.Element;
|
||||||
|
|
||||||
export default DescriptionsItem;
|
export default DescriptionsItem;
|
||||||
|
@ -7,7 +7,10 @@ import type { ScreenMap } from '../../_util/responsiveObserver';
|
|||||||
|
|
||||||
// Convert children into items
|
// Convert children into items
|
||||||
const transChildren2Items = (childNodes?: React.ReactNode) =>
|
const transChildren2Items = (childNodes?: React.ReactNode) =>
|
||||||
toArray(childNodes).map((node) => ({ ...node?.props, key: node.key }));
|
toArray(childNodes).map((node) => ({
|
||||||
|
...(node as React.ReactElement<any>)?.props,
|
||||||
|
key: node.key,
|
||||||
|
}));
|
||||||
|
|
||||||
export default function useItems(
|
export default function useItems(
|
||||||
screens: ScreenMap,
|
screens: ScreenMap,
|
||||||
|
@ -352,6 +352,17 @@ describe('Dropdown', () => {
|
|||||||
expect(container3.querySelector('button')).not.toHaveAttribute('disabled');
|
expect(container3.querySelector('button')).not.toHaveAttribute('disabled');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support Primitive', () => {
|
||||||
|
expect(() => {
|
||||||
|
render(<Dropdown>antd</Dropdown>);
|
||||||
|
render(<Dropdown>{123}</Dropdown>);
|
||||||
|
render(<Dropdown>{undefined}</Dropdown>);
|
||||||
|
render(<Dropdown>{true}</Dropdown>);
|
||||||
|
render(<Dropdown>{false}</Dropdown>);
|
||||||
|
render(<Dropdown>{null}</Dropdown>);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
it('menu item with extra prop', () => {
|
it('menu item with extra prop', () => {
|
||||||
const text = '⌘P';
|
const text = '⌘P';
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
|
@ -52,7 +52,12 @@ const App: React.FC = () => {
|
|||||||
menu={{ items }}
|
menu={{ items }}
|
||||||
dropdownRender={(menu) => (
|
dropdownRender={(menu) => (
|
||||||
<div style={contentStyle}>
|
<div style={contentStyle}>
|
||||||
{React.cloneElement(menu as React.ReactElement, { style: menuStyle })}
|
{React.cloneElement(
|
||||||
|
menu as React.ReactElement<{
|
||||||
|
style: React.CSSProperties;
|
||||||
|
}>,
|
||||||
|
{ style: menuStyle },
|
||||||
|
)}
|
||||||
<Divider style={{ margin: 0 }} />
|
<Divider style={{ margin: 0 }} />
|
||||||
<Space style={{ padding: 8 }}>
|
<Space style={{ padding: 8 }}>
|
||||||
<Button type="primary">Click me!</Button>
|
<Button type="primary">Click me!</Button>
|
||||||
|
@ -8,6 +8,7 @@ import useMergedState from 'rc-util/lib/hooks/useMergedState';
|
|||||||
import omit from 'rc-util/lib/omit';
|
import omit from 'rc-util/lib/omit';
|
||||||
|
|
||||||
import { useZIndex } from '../_util/hooks/useZIndex';
|
import { useZIndex } from '../_util/hooks/useZIndex';
|
||||||
|
import isPrimitive from '../_util/isPrimitive';
|
||||||
import type { AdjustOverflow } from '../_util/placements';
|
import type { AdjustOverflow } from '../_util/placements';
|
||||||
import getPlacements from '../_util/placements';
|
import getPlacements from '../_util/placements';
|
||||||
import genPurePanel from '../_util/PurePanel';
|
import genPurePanel from '../_util/PurePanel';
|
||||||
@ -175,7 +176,12 @@ const Dropdown: CompoundedComponent = (props) => {
|
|||||||
|
|
||||||
const [, token] = useToken();
|
const [, token] = useToken();
|
||||||
|
|
||||||
const child = React.Children.only(children) as React.ReactElement<any>;
|
const child = React.Children.only(
|
||||||
|
isPrimitive(children) ? <span>{children}</span> : children,
|
||||||
|
) as React.ReactElement<{
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
const dropdownTrigger = cloneElement(child, {
|
const dropdownTrigger = cloneElement(child, {
|
||||||
className: classNames(
|
className: classNames(
|
||||||
|
@ -8,8 +8,6 @@ import { fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
|||||||
const { BackTop } = FloatButton;
|
const { BackTop } = FloatButton;
|
||||||
|
|
||||||
describe('BackTop', () => {
|
describe('BackTop', () => {
|
||||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
});
|
});
|
||||||
@ -57,6 +55,8 @@ describe('BackTop', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('no error when BackTop work', () => {
|
it('no error when BackTop work', () => {
|
||||||
|
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
|
||||||
render(<BackTop visibilityHeight={0} />);
|
render(<BackTop visibilityHeight={0} />);
|
||||||
expect(errSpy).not.toHaveBeenCalled();
|
expect(errSpy).not.toHaveBeenCalled();
|
||||||
errSpy.mockRestore();
|
errSpy.mockRestore();
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { CSSMotionProps } from 'rc-motion';
|
import type { CSSMotionProps } from 'rc-motion';
|
||||||
import CSSMotion, { CSSMotionList } from 'rc-motion';
|
import CSSMotion, { CSSMotionList } from 'rc-motion';
|
||||||
@ -58,7 +57,10 @@ const ErrorList: React.FC<ErrorListProps> = ({
|
|||||||
const rootCls = useCSSVarCls(prefixCls);
|
const rootCls = useCSSVarCls(prefixCls);
|
||||||
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
|
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
|
||||||
|
|
||||||
const collapseMotion: CSSMotionProps = useMemo(() => initCollapseMotion(prefixCls), [prefixCls]);
|
const collapseMotion = React.useMemo<CSSMotionProps>(
|
||||||
|
() => initCollapseMotion(prefixCls),
|
||||||
|
[prefixCls],
|
||||||
|
);
|
||||||
|
|
||||||
// We have to debounce here again since somewhere use ErrorList directly still need no shaking
|
// We have to debounce here again since somewhere use ErrorList directly still need no shaking
|
||||||
// ref: https://github.com/ant-design/ant-design/issues/36336
|
// ref: https://github.com/ant-design/ant-design/issues/36336
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import FieldForm, { List, useWatch } from 'rc-field-form';
|
import FieldForm, { List, useWatch } from 'rc-field-form';
|
||||||
import type { FormProps as RcFormProps } from 'rc-field-form/lib/Form';
|
import type { FormProps as RcFormProps } from 'rc-field-form/lib/Form';
|
||||||
@ -93,7 +92,7 @@ const InternalForm: React.ForwardRefRenderFunction<FormRef, FormProps> = (props,
|
|||||||
useFormWarning(props);
|
useFormWarning(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergedRequiredMark = useMemo(() => {
|
const mergedRequiredMark = React.useMemo(() => {
|
||||||
if (requiredMark !== undefined) {
|
if (requiredMark !== undefined) {
|
||||||
return requiredMark;
|
return requiredMark;
|
||||||
}
|
}
|
||||||
@ -137,7 +136,7 @@ const InternalForm: React.ForwardRefRenderFunction<FormRef, FormProps> = (props,
|
|||||||
const { __INTERNAL__ } = wrapForm;
|
const { __INTERNAL__ } = wrapForm;
|
||||||
__INTERNAL__.name = name;
|
__INTERNAL__.name = name;
|
||||||
|
|
||||||
const formContextValue = useMemo<FormContextProps>(
|
const formContextValue = React.useMemo<FormContextProps>(
|
||||||
() => ({
|
() => ({
|
||||||
name,
|
name,
|
||||||
labelAlign,
|
labelAlign,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import type { JSX } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Field, FieldContext, ListContext } from 'rc-field-form';
|
import { Field, FieldContext, ListContext } from 'rc-field-form';
|
||||||
import type { FieldProps } from 'rc-field-form/lib/Field';
|
import type { FieldProps } from 'rc-field-form/lib/Field';
|
||||||
@ -165,7 +166,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
|
|||||||
// ========================= MISC =========================
|
// ========================= MISC =========================
|
||||||
// Get `noStyle` required info
|
// Get `noStyle` required info
|
||||||
const listContext = React.useContext(ListContext);
|
const listContext = React.useContext(ListContext);
|
||||||
const fieldKeyPathRef = React.useRef<InternalNamePath>();
|
const fieldKeyPathRef = React.useRef<InternalNamePath>(null);
|
||||||
|
|
||||||
// ======================== Errors ========================
|
// ======================== Errors ========================
|
||||||
// >>>>> Collect sub field errors
|
// >>>>> Collect sub field errors
|
||||||
@ -361,12 +362,19 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
|
|||||||
);
|
);
|
||||||
} else if (React.isValidElement(mergedChildren)) {
|
} else if (React.isValidElement(mergedChildren)) {
|
||||||
warning(
|
warning(
|
||||||
mergedChildren.props.defaultValue === undefined,
|
(
|
||||||
|
mergedChildren as React.ReactElement<{
|
||||||
|
defaultValue?: any;
|
||||||
|
}>
|
||||||
|
).props.defaultValue === undefined,
|
||||||
'usage',
|
'usage',
|
||||||
'`defaultValue` will not work on controlled Field. You should use `initialValues` of Form instead.',
|
'`defaultValue` will not work on controlled Field. You should use `initialValues` of Form instead.',
|
||||||
);
|
);
|
||||||
|
|
||||||
const childProps = { ...mergedChildren.props, ...mergedControl };
|
const childProps = {
|
||||||
|
...(mergedChildren as React.ReactElement<any>).props,
|
||||||
|
...mergedControl,
|
||||||
|
};
|
||||||
if (!childProps.id) {
|
if (!childProps.id) {
|
||||||
childProps.id = fieldId;
|
childProps.id = fieldId;
|
||||||
}
|
}
|
||||||
@ -403,7 +411,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
|
|||||||
triggers.forEach((eventName) => {
|
triggers.forEach((eventName) => {
|
||||||
childProps[eventName] = (...args: any[]) => {
|
childProps[eventName] = (...args: any[]) => {
|
||||||
mergedControl[eventName]?.(...args);
|
mergedControl[eventName]?.(...args);
|
||||||
mergedChildren.props[eventName]?.(...args);
|
(mergedChildren as React.ReactElement<any>).props[eventName]?.(...args);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import type { JSX } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { get, set } from 'rc-util';
|
import { get, set } from 'rc-util';
|
||||||
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
|
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
|
||||||
|
@ -10,7 +10,7 @@ import Form from '..';
|
|||||||
import { resetWarned } from '../../_util/warning';
|
import { resetWarned } from '../../_util/warning';
|
||||||
import mountTest from '../../../tests/shared/mountTest';
|
import mountTest from '../../../tests/shared/mountTest';
|
||||||
import rtlTest from '../../../tests/shared/rtlTest';
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
import { fireEvent, pureRender, render, screen, waitFakeTimer } from '../../../tests/utils';
|
import { act, fireEvent, pureRender, render, screen, waitFakeTimer } from '../../../tests/utils';
|
||||||
import Button from '../../button';
|
import Button from '../../button';
|
||||||
import Cascader from '../../cascader';
|
import Cascader from '../../cascader';
|
||||||
import Checkbox from '../../checkbox';
|
import Checkbox from '../../checkbox';
|
||||||
@ -126,7 +126,9 @@ describe('Form', () => {
|
|||||||
await waitFakeTimer();
|
await waitFakeTimer();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await form.validateFields();
|
await act(async () => {
|
||||||
|
await form.validateFields();
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
@ -2244,7 +2246,7 @@ describe('Form', () => {
|
|||||||
await waitFakeTimer();
|
await waitFakeTimer();
|
||||||
|
|
||||||
// initial validate
|
// initial validate
|
||||||
const initTriggerTime = ReactVersion.startsWith('18') ? 2 : 1;
|
const initTriggerTime = ReactVersion.startsWith('18') || ReactVersion.startsWith('19') ? 2 : 1;
|
||||||
expect(onChange).toHaveBeenCalledTimes(initTriggerTime);
|
expect(onChange).toHaveBeenCalledTimes(initTriggerTime);
|
||||||
let idx = 1;
|
let idx = 1;
|
||||||
expect(onChange).toHaveBeenNthCalledWith(idx++, '');
|
expect(onChange).toHaveBeenNthCalledWith(idx++, '');
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import type { PropsWithChildren, ReactNode } from 'react';
|
import type { PropsWithChildren, ReactNode } from 'react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { createContext, useContext, useMemo } from 'react';
|
|
||||||
import { FormProvider as RcFormProvider } from 'rc-field-form';
|
import { FormProvider as RcFormProvider } from 'rc-field-form';
|
||||||
import type { FormProviderProps as RcFormProviderProps } from 'rc-field-form/lib/FormContext';
|
import type { FormProviderProps as RcFormProviderProps } from 'rc-field-form/lib/FormContext';
|
||||||
import type { Meta } from 'rc-field-form/lib/interface';
|
import type { Meta } from 'rc-field-form/lib/interface';
|
||||||
@ -78,9 +77,9 @@ export type NoFormStyleProps = PropsWithChildren<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const NoFormStyle: React.FC<NoFormStyleProps> = ({ children, status, override }) => {
|
export const NoFormStyle: React.FC<NoFormStyleProps> = ({ children, status, override }) => {
|
||||||
const formItemInputContext = useContext(FormItemInputContext);
|
const formItemInputContext = React.useContext(FormItemInputContext);
|
||||||
|
|
||||||
const newFormItemInputContext = useMemo(() => {
|
const newFormItemInputContext = React.useMemo(() => {
|
||||||
const newContext = { ...formItemInputContext };
|
const newContext = { ...formItemInputContext };
|
||||||
if (override) {
|
if (override) {
|
||||||
delete newContext.isFormItemInput;
|
delete newContext.isFormItemInput;
|
||||||
@ -100,4 +99,4 @@ export const NoFormStyle: React.FC<NoFormStyleProps> = ({ children, status, over
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VariantContext = createContext<Variant | undefined>(undefined);
|
export const VariantContext = React.createContext<Variant | undefined>(undefined);
|
||||||
|
@ -26,7 +26,7 @@ interface ModalFormProps {
|
|||||||
|
|
||||||
// reset form fields when modal is form, closed
|
// reset form fields when modal is form, closed
|
||||||
const useResetFormOnCloseModal = ({ form, open }: { form: FormInstance; open: boolean }) => {
|
const useResetFormOnCloseModal = ({ form, open }: { form: FormInstance; open: boolean }) => {
|
||||||
const prevOpenRef = useRef<boolean>();
|
const prevOpenRef = useRef<boolean>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
prevOpenRef.current = open;
|
prevOpenRef.current = open;
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useContext } from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { FormContext } from '../context';
|
import { FormContext } from '../context';
|
||||||
import type { FormInstance } from './useForm';
|
import type { FormInstance } from './useForm';
|
||||||
|
|
||||||
export default function useFormInstance<Value = any>(): FormInstance<Value> {
|
export default function useFormInstance<Value = any>(): FormInstance<Value> {
|
||||||
const { form } = useContext(FormContext);
|
const { form } = React.useContext(FormContext);
|
||||||
|
|
||||||
return form!;
|
return form!;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useContext } from 'react';
|
import * as React from 'react';
|
||||||
import type { ValidateStatus } from 'antd/es/form/FormItem';
|
import type { ValidateStatus } from 'antd/es/form/FormItem';
|
||||||
|
|
||||||
import { devUseWarning } from '../../_util/warning';
|
import { devUseWarning } from '../../_util/warning';
|
||||||
@ -11,7 +11,7 @@ type UseFormItemStatus = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useFormItemStatus: UseFormItemStatus = () => {
|
const useFormItemStatus: UseFormItemStatus = () => {
|
||||||
const { status, errors = [], warnings = [] } = useContext(FormItemInputContext);
|
const { status, errors = [], warnings = [] } = React.useContext(FormItemInputContext);
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
const warning = devUseWarning('Form.Item');
|
const warning = devUseWarning('Form.Item');
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect } from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { devUseWarning } from '../../_util/warning';
|
import { devUseWarning } from '../../_util/warning';
|
||||||
import type { FormProps } from '../Form';
|
import type { FormProps } from '../Form';
|
||||||
@ -8,7 +8,7 @@ const names: Record<string, number> = {};
|
|||||||
export default function useFormWarning({ name }: FormProps) {
|
export default function useFormWarning({ name }: FormProps) {
|
||||||
const warning = devUseWarning('Form');
|
const warning = devUseWarning('Form');
|
||||||
|
|
||||||
useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (name) {
|
if (name) {
|
||||||
names[name] = (names[name] || 0) + 1;
|
names[name] = (names[name] || 0) + 1;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useRef } from 'react';
|
|
||||||
import raf from 'rc-util/lib/raf';
|
import raf from 'rc-util/lib/raf';
|
||||||
|
|
||||||
type Updater<ValueType> = (prev?: ValueType) => ValueType;
|
type Updater<ValueType> = (prev?: ValueType) => ValueType;
|
||||||
@ -8,9 +7,9 @@ export default function useFrameState<ValueType>(
|
|||||||
defaultValue: ValueType,
|
defaultValue: ValueType,
|
||||||
): [ValueType, (updater: Updater<ValueType>) => void] {
|
): [ValueType, (updater: Updater<ValueType>) => void] {
|
||||||
const [value, setValue] = React.useState(defaultValue);
|
const [value, setValue] = React.useState(defaultValue);
|
||||||
const frameRef = useRef<number | null>(null);
|
const frameRef = React.useRef<number | null>(null);
|
||||||
const batchRef = useRef<Updater<ValueType>[]>([]);
|
const batchRef = React.useRef<Updater<ValueType>[]>([]);
|
||||||
const destroyRef = useRef(false);
|
const destroyRef = React.useRef(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
destroyRef.current = false;
|
destroyRef.current = false;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useContext } from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { VariantContext } from '../context';
|
import { VariantContext } from '../context';
|
||||||
import type { Variant, ConfigProviderProps } from '../../config-provider';
|
import type { Variant, ConfigProviderProps } from '../../config-provider';
|
||||||
@ -26,8 +26,8 @@ const useVariant = (
|
|||||||
variant: Variant | undefined,
|
variant: Variant | undefined,
|
||||||
legacyBordered: boolean | undefined = undefined,
|
legacyBordered: boolean | undefined = undefined,
|
||||||
): [Variant, boolean] => {
|
): [Variant, boolean] => {
|
||||||
const { variant: configVariant, [component]: componentConfig } = useContext(ConfigContext);
|
const { variant: configVariant, [component]: componentConfig } = React.useContext(ConfigContext);
|
||||||
const ctxVariant = useContext(VariantContext);
|
const ctxVariant = React.useContext(VariantContext);
|
||||||
const configComponentVariant = componentConfig?.variant;
|
const configComponentVariant = componentConfig?.variant;
|
||||||
|
|
||||||
let mergedVariant: Variant;
|
let mergedVariant: Variant;
|
||||||
|
@ -30,7 +30,12 @@ export { default as Cascader } from './cascader';
|
|||||||
export type { CascaderProps, CascaderAutoProps } from './cascader';
|
export type { CascaderProps, CascaderAutoProps } from './cascader';
|
||||||
export type { CascaderPanelProps, CascaderPanelAutoProps } from './cascader/Panel';
|
export type { CascaderPanelProps, CascaderPanelAutoProps } from './cascader/Panel';
|
||||||
export { default as Checkbox } from './checkbox';
|
export { default as Checkbox } from './checkbox';
|
||||||
export type { CheckboxOptionType, CheckboxProps, CheckboxRef } from './checkbox';
|
export type {
|
||||||
|
CheckboxChangeEvent,
|
||||||
|
CheckboxOptionType,
|
||||||
|
CheckboxProps,
|
||||||
|
CheckboxRef,
|
||||||
|
} from './checkbox';
|
||||||
export { default as Col } from './col';
|
export { default as Col } from './col';
|
||||||
export type { ColProps } from './col';
|
export type { ColProps } from './col';
|
||||||
export { default as Collapse } from './collapse';
|
export { default as Collapse } from './collapse';
|
||||||
@ -172,3 +177,6 @@ export { default as Watermark } from './watermark';
|
|||||||
export type { WatermarkProps } from './watermark';
|
export type { WatermarkProps } from './watermark';
|
||||||
export { default as Splitter } from './splitter';
|
export { default as Splitter } from './splitter';
|
||||||
export type { SplitterProps } from './splitter';
|
export type { SplitterProps } from './splitter';
|
||||||
|
|
||||||
|
// TODO: Remove in v6
|
||||||
|
export { unstableSetRender } from './config-provider/UnstableContext';
|
||||||
|
@ -8,10 +8,10 @@ import { composeRef } from 'rc-util/lib/ref';
|
|||||||
|
|
||||||
import type { ConfigConsumerProps } from '../config-provider';
|
import type { ConfigConsumerProps } from '../config-provider';
|
||||||
import { ConfigContext } from '../config-provider';
|
import { ConfigContext } from '../config-provider';
|
||||||
|
import DisabledContext from '../config-provider/DisabledContext';
|
||||||
import useRemovePasswordTimeout from './hooks/useRemovePasswordTimeout';
|
import useRemovePasswordTimeout from './hooks/useRemovePasswordTimeout';
|
||||||
import type { InputProps, InputRef } from './Input';
|
import type { InputProps, InputRef } from './Input';
|
||||||
import Input from './Input';
|
import Input from './Input';
|
||||||
import DisabledContext from '../config-provider/DisabledContext';
|
|
||||||
|
|
||||||
const defaultIconRender = (visible: boolean): React.ReactNode =>
|
const defaultIconRender = (visible: boolean): React.ReactNode =>
|
||||||
visible ? <EyeOutlined /> : <EyeInvisibleOutlined />;
|
visible ? <EyeOutlined /> : <EyeInvisibleOutlined />;
|
||||||
@ -70,13 +70,13 @@ const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
|
|||||||
if (visible) {
|
if (visible) {
|
||||||
removePasswordTimeout();
|
removePasswordTimeout();
|
||||||
}
|
}
|
||||||
setVisible((prevState) => {
|
|
||||||
const newState = !prevState;
|
const nextVisible = !visible;
|
||||||
if (typeof visibilityToggle === 'object') {
|
setVisible(nextVisible);
|
||||||
visibilityToggle.onVisibleChange?.(newState);
|
|
||||||
}
|
if (typeof visibilityToggle === 'object') {
|
||||||
return newState;
|
visibilityToggle.onVisibleChange?.(nextVisible);
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIcon = (prefixCls: string) => {
|
const getIcon = (prefixCls: string) => {
|
||||||
|
@ -98,7 +98,11 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
|
|||||||
button = cloneElement(enterButtonAsElement, {
|
button = cloneElement(enterButtonAsElement, {
|
||||||
onMouseDown,
|
onMouseDown,
|
||||||
onClick: (e: React.MouseEvent<HTMLButtonElement>) => {
|
onClick: (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
enterButtonAsElement?.props?.onClick?.(e);
|
(
|
||||||
|
enterButtonAsElement as React.ReactElement<{
|
||||||
|
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
}>
|
||||||
|
)?.props?.onClick?.(e);
|
||||||
onSearch(e);
|
onSearch(e);
|
||||||
},
|
},
|
||||||
key: 'enterButton',
|
key: 'enterButton',
|
||||||
|
@ -5,7 +5,14 @@ import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
|
|||||||
import Input from '..';
|
import Input from '..';
|
||||||
import focusTest from '../../../tests/shared/focusTest';
|
import focusTest from '../../../tests/shared/focusTest';
|
||||||
import type { RenderOptions } from '../../../tests/utils';
|
import type { RenderOptions } from '../../../tests/utils';
|
||||||
import { fireEvent, pureRender, render, triggerResize, waitFakeTimer } from '../../../tests/utils';
|
import {
|
||||||
|
fireEvent,
|
||||||
|
pureRender,
|
||||||
|
render,
|
||||||
|
triggerResize,
|
||||||
|
waitFakeTimer,
|
||||||
|
waitFakeTimer19,
|
||||||
|
} from '../../../tests/utils';
|
||||||
import type { TextAreaRef } from '../TextArea';
|
import type { TextAreaRef } from '../TextArea';
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
@ -50,15 +57,15 @@ describe('TextArea', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { container, rerender } = pureRender(genTextArea());
|
const { container, rerender } = pureRender(genTextArea());
|
||||||
await waitFakeTimer();
|
await waitFakeTimer19();
|
||||||
expect(onInternalAutoSize).toHaveBeenCalledTimes(1);
|
expect(onInternalAutoSize).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
rerender(genTextArea({ value: '1111\n2222\n3333' }));
|
rerender(genTextArea({ value: '1111\n2222\n3333' }));
|
||||||
await waitFakeTimer();
|
await waitFakeTimer19();
|
||||||
expect(onInternalAutoSize).toHaveBeenCalledTimes(2);
|
expect(onInternalAutoSize).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
rerender(genTextArea({ value: '1111' }));
|
rerender(genTextArea({ value: '1111' }));
|
||||||
await waitFakeTimer();
|
await waitFakeTimer19();
|
||||||
expect(onInternalAutoSize).toHaveBeenCalledTimes(3);
|
expect(onInternalAutoSize).toHaveBeenCalledTimes(3);
|
||||||
|
|
||||||
expect(container.querySelector('textarea')?.style.overflow).toBeFalsy();
|
expect(container.querySelector('textarea')?.style.overflow).toBeFalsy();
|
||||||
@ -332,7 +339,6 @@ describe('TextArea allowClear', () => {
|
|||||||
const ref = React.createRef<TextAreaRef>();
|
const ref = React.createRef<TextAreaRef>();
|
||||||
const { container, unmount } = render(<Input.TextArea ref={ref} autoSize />, {
|
const { container, unmount } = render(<Input.TextArea ref={ref} autoSize />, {
|
||||||
container: document.body,
|
container: document.body,
|
||||||
legacyRoot: true,
|
|
||||||
} as RenderOptions);
|
} as RenderOptions);
|
||||||
fireEvent.focus(container.querySelector('textarea')!);
|
fireEvent.focus(container.querySelector('textarea')!);
|
||||||
container.querySelector('textarea')?.focus();
|
container.querySelector('textarea')?.focus();
|
||||||
|
@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react';
|
|||||||
import type { InputRef } from '../Input';
|
import type { InputRef } from '../Input';
|
||||||
|
|
||||||
export default function useRemovePasswordTimeout(
|
export default function useRemovePasswordTimeout(
|
||||||
inputRef: React.RefObject<InputRef>,
|
inputRef: React.RefObject<InputRef | null>,
|
||||||
triggerOnMount?: boolean,
|
triggerOnMount?: boolean,
|
||||||
) {
|
) {
|
||||||
const removePasswordTimeoutRef = useRef<ReturnType<typeof setTimeout>[]>([]);
|
const removePasswordTimeoutRef = useRef<ReturnType<typeof setTimeout>[]>([]);
|
||||||
|
@ -105,7 +105,7 @@ const Sider = React.forwardRef<HTMLDivElement, SiderProps>((props, ref) => {
|
|||||||
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
||||||
|
|
||||||
// ========================= Responsive =========================
|
// ========================= Responsive =========================
|
||||||
const responsiveHandlerRef = useRef<(mql: MediaQueryListEvent | MediaQueryList) => void>();
|
const responsiveHandlerRef = useRef<(mql: MediaQueryListEvent | MediaQueryList) => void>(null);
|
||||||
responsiveHandlerRef.current = (mql: MediaQueryListEvent | MediaQueryList) => {
|
responsiveHandlerRef.current = (mql: MediaQueryListEvent | MediaQueryList) => {
|
||||||
setBelow(mql.matches);
|
setBelow(mql.matches);
|
||||||
onBreakpoint?.(mql.matches);
|
onBreakpoint?.(mql.matches);
|
||||||
|
@ -14,6 +14,18 @@ const Demo: React.FC<{ type: string }> = ({ type }) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('Locale Provider demo', () => {
|
describe('Locale Provider demo', () => {
|
||||||
it('change type', async () => {
|
it('change type', async () => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
import { extendTest } from '../../../tests/shared/demoTest';
|
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 App: React.FC = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [users, setUsers] = useState<{ login: string; avatar_url: string }[]>([]);
|
const [users, setUsers] = useState<{ login: string; avatar_url: string }[]>([]);
|
||||||
const ref = useRef<string>();
|
const ref = useRef<string>(null);
|
||||||
|
|
||||||
const loadGithubUsers = (key: string) => {
|
const loadGithubUsers = (key: string) => {
|
||||||
if (!key) {
|
if (!key) {
|
||||||
|
@ -101,7 +101,9 @@ const MenuItem: GenericComponent = (props) => {
|
|||||||
>
|
>
|
||||||
{cloneElement(icon, {
|
{cloneElement(icon, {
|
||||||
className: classNames(
|
className: classNames(
|
||||||
React.isValidElement(icon) ? icon.props?.className : '',
|
React.isValidElement(icon)
|
||||||
|
? (icon as React.ReactElement<{ className?: string }>).props?.className
|
||||||
|
: '',
|
||||||
`${prefixCls}-item-icon`,
|
`${prefixCls}-item-icon`,
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
|
@ -44,7 +44,14 @@ export const OverrideProvider = React.forwardRef<
|
|||||||
return (
|
return (
|
||||||
<OverrideContext.Provider value={context}>
|
<OverrideContext.Provider value={context}>
|
||||||
<ContextIsolator space>
|
<ContextIsolator space>
|
||||||
{canRef ? React.cloneElement(children as React.ReactElement, { ref: mergedRef }) : children}
|
{canRef
|
||||||
|
? React.cloneElement(
|
||||||
|
children as React.ReactElement<{
|
||||||
|
ref?: React.Ref<HTMLElement>;
|
||||||
|
}>,
|
||||||
|
{ ref: mergedRef },
|
||||||
|
)
|
||||||
|
: children}
|
||||||
</ContextIsolator>
|
</ContextIsolator>
|
||||||
</OverrideContext.Provider>
|
</OverrideContext.Provider>
|
||||||
);
|
);
|
||||||
|
@ -5,9 +5,9 @@ import omit from 'rc-util/lib/omit';
|
|||||||
|
|
||||||
import { useZIndex } from '../_util/hooks/useZIndex';
|
import { useZIndex } from '../_util/hooks/useZIndex';
|
||||||
import { cloneElement } from '../_util/reactNode';
|
import { cloneElement } from '../_util/reactNode';
|
||||||
|
import type { SubMenuType } from './interface';
|
||||||
import type { MenuContextProps } from './MenuContext';
|
import type { MenuContextProps } from './MenuContext';
|
||||||
import MenuContext from './MenuContext';
|
import MenuContext from './MenuContext';
|
||||||
import type { SubMenuType } from './interface';
|
|
||||||
|
|
||||||
export interface SubMenuProps extends Omit<SubMenuType, 'ref' | 'key' | 'children' | 'label'> {
|
export interface SubMenuProps extends Omit<SubMenuType, 'ref' | 'key' | 'children' | 'label'> {
|
||||||
title?: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
@ -43,7 +43,9 @@ const SubMenu: React.FC<SubMenuProps> = (props) => {
|
|||||||
<>
|
<>
|
||||||
{cloneElement(icon, {
|
{cloneElement(icon, {
|
||||||
className: classNames(
|
className: classNames(
|
||||||
React.isValidElement(icon) ? icon.props?.className : '',
|
React.isValidElement(icon)
|
||||||
|
? (icon as React.ReactElement<{ className?: string }>).props?.className
|
||||||
|
: '',
|
||||||
`${prefixCls}-item-icon`,
|
`${prefixCls}-item-icon`,
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
|
@ -70,18 +70,18 @@ const App: React.FC = () => {
|
|||||||
inlineCollapsed
|
inlineCollapsed
|
||||||
// Test only. Remove in future.
|
// Test only. Remove in future.
|
||||||
_internalRenderMenuItem={(node) =>
|
_internalRenderMenuItem={(node) =>
|
||||||
React.cloneElement(node, {
|
React.cloneElement<any>(node, {
|
||||||
style: {
|
style: {
|
||||||
...node.props.style,
|
...(node as any).props.style,
|
||||||
textDecoration: 'underline',
|
textDecoration: 'underline',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Test only. Remove in future.
|
// Test only. Remove in future.
|
||||||
_internalRenderSubMenuItem={(node) =>
|
_internalRenderSubMenuItem={(node) =>
|
||||||
React.cloneElement(node, {
|
React.cloneElement<any>(node, {
|
||||||
style: {
|
style: {
|
||||||
...node.props.style,
|
...(node as any).props.style,
|
||||||
background: 'rgba(255, 255, 255, 0.3)',
|
background: 'rgba(255, 255, 255, 0.3)',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -136,7 +136,13 @@ const InternalMenu = forwardRef<RcMenuRef, InternalMenuProps>((props, ref) => {
|
|||||||
return cloneElement(mergedIcon, {
|
return cloneElement(mergedIcon, {
|
||||||
className: classNames(
|
className: classNames(
|
||||||
`${prefixCls}-submenu-expand-icon`,
|
`${prefixCls}-submenu-expand-icon`,
|
||||||
React.isValidElement<any>(mergedIcon) ? mergedIcon.props?.className : undefined,
|
React.isValidElement<any>(mergedIcon)
|
||||||
|
? (
|
||||||
|
mergedIcon as React.ReactElement<{
|
||||||
|
className?: string;
|
||||||
|
}>
|
||||||
|
).props?.className
|
||||||
|
: undefined,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}, [expandIcon, overrideObj?.expandIcon, menu?.expandIcon, prefixCls]);
|
}, [expandIcon, overrideObj?.expandIcon, menu?.expandIcon, prefixCls]);
|
||||||
|
@ -6,6 +6,18 @@ import App from '../../app';
|
|||||||
import ConfigProvider, { defaultPrefixCls } from '../../config-provider';
|
import ConfigProvider, { defaultPrefixCls } from '../../config-provider';
|
||||||
import { awaitPromise, triggerMotionEnd } from './util';
|
import { awaitPromise, triggerMotionEnd } from './util';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('message.config', () => {
|
describe('message.config', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
actWrapper(act);
|
actWrapper(act);
|
||||||
|
@ -2,6 +2,18 @@ import message, { actDestroy, actWrapper } from '..';
|
|||||||
import { act } from '../../../tests/utils';
|
import { act } from '../../../tests/utils';
|
||||||
import { awaitPromise, triggerMotionEnd } from './util';
|
import { awaitPromise, triggerMotionEnd } from './util';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('call close immediately', () => {
|
describe('call close immediately', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
actWrapper(act);
|
actWrapper(act);
|
||||||
|
@ -5,6 +5,18 @@ import message, { actWrapper } from '..';
|
|||||||
import { act, fireEvent, waitFakeTimer } from '../../../tests/utils';
|
import { act, fireEvent, waitFakeTimer } from '../../../tests/utils';
|
||||||
import { awaitPromise, triggerMotionEnd } from './util';
|
import { awaitPromise, triggerMotionEnd } from './util';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('message', () => {
|
describe('message', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
actWrapper(act);
|
actWrapper(act);
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import message, { actWrapper } from '..';
|
import message, { actWrapper } from '..';
|
||||||
import { act, render, waitFakeTimer } from '../../../tests/utils';
|
import { act, render, waitFakeTimer, waitFakeTimer19 } from '../../../tests/utils';
|
||||||
import ConfigProvider from '../../config-provider';
|
import ConfigProvider from '../../config-provider';
|
||||||
import { awaitPromise, triggerMotionEnd } from './util';
|
import { awaitPromise, triggerMotionEnd } from './util';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('message static warning', () => {
|
describe('message static warning', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
actWrapper(act);
|
actWrapper(act);
|
||||||
@ -32,11 +44,12 @@ describe('message static warning', () => {
|
|||||||
content: <div className="bamboo" />,
|
content: <div className="bamboo" />,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
});
|
});
|
||||||
await waitFakeTimer();
|
await waitFakeTimer19();
|
||||||
|
|
||||||
expect(document.querySelector('.bamboo')).toBeTruthy();
|
expect(document.querySelector('.bamboo')).toBeTruthy();
|
||||||
|
|
||||||
expect(errSpy).not.toHaveBeenCalled();
|
expect(errSpy).not.toHaveBeenCalled();
|
||||||
|
errSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warning if use theme', async () => {
|
it('warning if use theme', async () => {
|
||||||
@ -54,5 +67,6 @@ describe('message static warning', () => {
|
|||||||
expect(errSpy).toHaveBeenCalledWith(
|
expect(errSpy).toHaveBeenCalledWith(
|
||||||
"Warning: [antd: message] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
|
"Warning: [antd: message] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
|
||||||
);
|
);
|
||||||
|
errSpy.mockRestore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,18 @@ import message, { actWrapper } from '..';
|
|||||||
import { act } from '../../../tests/utils';
|
import { act } from '../../../tests/utils';
|
||||||
import { awaitPromise, triggerMotionEnd } from './util';
|
import { awaitPromise, triggerMotionEnd } from './util';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('message.typescript', () => {
|
describe('message.typescript', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
actWrapper(act);
|
actWrapper(act);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { render } from 'rc-util/lib/React/render';
|
|
||||||
|
|
||||||
import { AppConfigContext } from '../app/context';
|
import { AppConfigContext } from '../app/context';
|
||||||
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
|
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
|
||||||
|
import { getReactRender } from '../config-provider/UnstableContext';
|
||||||
import type {
|
import type {
|
||||||
ArgsProps,
|
ArgsProps,
|
||||||
ConfigOptions,
|
ConfigOptions,
|
||||||
@ -132,7 +132,9 @@ function flushNotice() {
|
|||||||
|
|
||||||
// Delay render to avoid sync issue
|
// Delay render to avoid sync issue
|
||||||
act(() => {
|
act(() => {
|
||||||
render(
|
const reactRender = getReactRender();
|
||||||
|
|
||||||
|
reactRender(
|
||||||
<GlobalHolderWrapper
|
<GlobalHolderWrapper
|
||||||
ref={(node) => {
|
ref={(node) => {
|
||||||
const { instance, sync } = node || {};
|
const { instance, sync } = node || {};
|
||||||
|
@ -18,6 +18,18 @@ const { confirm } = Modal;
|
|||||||
|
|
||||||
jest.mock('rc-motion');
|
jest.mock('rc-motion');
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
(global as any).injectPromise = false;
|
(global as any).injectPromise = false;
|
||||||
(global as any).rejectPromise = null;
|
(global as any).rejectPromise = null;
|
||||||
|
|
||||||
|
@ -14,6 +14,18 @@ import type { ModalFunc } from '../confirm';
|
|||||||
jest.mock('rc-util/lib/Portal');
|
jest.mock('rc-util/lib/Portal');
|
||||||
jest.mock('rc-motion');
|
jest.mock('rc-motion');
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('Modal.hook', () => {
|
describe('Modal.hook', () => {
|
||||||
// Inject CSSMotion to replace with No transition support
|
// Inject CSSMotion to replace with No transition support
|
||||||
const MockCSSMotion = genCSSMotion(false);
|
const MockCSSMotion = genCSSMotion(false);
|
||||||
|
@ -2,9 +2,21 @@ import * as React from 'react';
|
|||||||
|
|
||||||
import Modal from '..';
|
import Modal from '..';
|
||||||
import { resetWarned } from '../../_util/warning';
|
import { resetWarned } from '../../_util/warning';
|
||||||
import { render, waitFakeTimer } from '../../../tests/utils';
|
import { render, waitFakeTimer, waitFakeTimer19 } from '../../../tests/utils';
|
||||||
import ConfigProvider from '../../config-provider';
|
import ConfigProvider from '../../config-provider';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('Modal.confirm warning', () => {
|
describe('Modal.confirm warning', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
@ -25,11 +37,12 @@ describe('Modal.confirm warning', () => {
|
|||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
content: <div className="bamboo" />,
|
content: <div className="bamboo" />,
|
||||||
});
|
});
|
||||||
await waitFakeTimer();
|
await waitFakeTimer19();
|
||||||
|
|
||||||
expect(document.querySelector('.bamboo')).toBeTruthy();
|
expect(document.querySelector('.bamboo')).toBeTruthy();
|
||||||
|
|
||||||
expect(errSpy).not.toHaveBeenCalled();
|
expect(errSpy).not.toHaveBeenCalled();
|
||||||
|
errSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warning if use theme', async () => {
|
it('warning if use theme', async () => {
|
||||||
@ -46,5 +59,6 @@ describe('Modal.confirm warning', () => {
|
|||||||
expect(errSpy).toHaveBeenCalledWith(
|
expect(errSpy).toHaveBeenCalledWith(
|
||||||
"Warning: [antd: Modal] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
|
"Warning: [antd: Modal] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
|
||||||
);
|
);
|
||||||
|
errSpy.mockRestore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -11,12 +11,7 @@ export interface ConfirmCancelBtnProps
|
|||||||
'cancelButtonProps' | 'isSilent' | 'rootPrefixCls' | 'close' | 'onConfirm' | 'onCancel'
|
'cancelButtonProps' | 'isSilent' | 'rootPrefixCls' | 'close' | 'onConfirm' | 'onCancel'
|
||||||
> {
|
> {
|
||||||
autoFocusButton?: false | 'ok' | 'cancel' | null;
|
autoFocusButton?: false | 'ok' | 'cancel' | null;
|
||||||
cancelTextLocale?:
|
cancelTextLocale?: React.ReactNode;
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| true
|
|
||||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
|
||||||
| Iterable<React.ReactNode>;
|
|
||||||
mergedOkCancel?: boolean;
|
mergedOkCancel?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,12 +11,7 @@ export interface ConfirmOkBtnProps
|
|||||||
'close' | 'isSilent' | 'okType' | 'okButtonProps' | 'rootPrefixCls' | 'onConfirm' | 'onOk'
|
'close' | 'isSilent' | 'okType' | 'okButtonProps' | 'rootPrefixCls' | 'onConfirm' | 'onOk'
|
||||||
> {
|
> {
|
||||||
autoFocusButton?: false | 'ok' | 'cancel' | null;
|
autoFocusButton?: false | 'ok' | 'cancel' | null;
|
||||||
okTextLocale?:
|
okTextLocale?: React.ReactNode;
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| true
|
|
||||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
|
||||||
| Iterable<React.ReactNode>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfirmOkBtn: FC = () => {
|
const ConfirmOkBtn: FC = () => {
|
||||||
|
@ -6,12 +6,7 @@ import { ModalContext } from '../context';
|
|||||||
import type { ModalProps } from '../interface';
|
import type { ModalProps } from '../interface';
|
||||||
|
|
||||||
export interface NormalCancelBtnProps extends Pick<ModalProps, 'cancelButtonProps' | 'onCancel'> {
|
export interface NormalCancelBtnProps extends Pick<ModalProps, 'cancelButtonProps' | 'onCancel'> {
|
||||||
cancelTextLocale?:
|
cancelTextLocale?: React.ReactNode;
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| true
|
|
||||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
|
||||||
| Iterable<React.ReactNode>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const NormalCancelBtn: FC = () => {
|
const NormalCancelBtn: FC = () => {
|
||||||
|
@ -8,12 +8,7 @@ import type { ModalProps } from '../interface';
|
|||||||
|
|
||||||
export interface NormalOkBtnProps
|
export interface NormalOkBtnProps
|
||||||
extends Pick<ModalProps, 'confirmLoading' | 'okType' | 'okButtonProps' | 'onOk'> {
|
extends Pick<ModalProps, 'confirmLoading' | 'okType' | 'okButtonProps' | 'onOk'> {
|
||||||
okTextLocale?:
|
okTextLocale?: React.ReactNode;
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| true
|
|
||||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
|
||||||
| Iterable<React.ReactNode>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const NormalOkBtn: FC = () => {
|
const NormalOkBtn: FC = () => {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { render as reactRender, unmount as reactUnmount } from 'rc-util/lib/React/render';
|
|
||||||
|
|
||||||
import warning from '../_util/warning';
|
import warning from '../_util/warning';
|
||||||
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
|
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
|
||||||
|
import { getReactRender, UnmountType } from '../config-provider/UnstableContext';
|
||||||
import type { ConfirmDialogProps } from './ConfirmDialog';
|
import type { ConfirmDialogProps } from './ConfirmDialog';
|
||||||
import ConfirmDialog from './ConfirmDialog';
|
import ConfirmDialog from './ConfirmDialog';
|
||||||
import destroyFns from './destroyFns';
|
import destroyFns from './destroyFns';
|
||||||
@ -71,6 +71,8 @@ export default function confirm(config: ModalFuncProps) {
|
|||||||
let currentConfig = { ...config, close, open: true } as any;
|
let currentConfig = { ...config, close, open: true } as any;
|
||||||
let timeoutId: ReturnType<typeof setTimeout>;
|
let timeoutId: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
|
let reactUnmount: UnmountType;
|
||||||
|
|
||||||
function destroy(...args: any[]) {
|
function destroy(...args: any[]) {
|
||||||
const triggerCancel = args.some((param) => param?.triggerCancel);
|
const triggerCancel = args.some((param) => param?.triggerCancel);
|
||||||
if (triggerCancel) {
|
if (triggerCancel) {
|
||||||
@ -84,7 +86,7 @@ export default function confirm(config: ModalFuncProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reactUnmount(container);
|
reactUnmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
function render(props: any) {
|
function render(props: any) {
|
||||||
@ -102,7 +104,9 @@ export default function confirm(config: ModalFuncProps) {
|
|||||||
|
|
||||||
const dom = <ConfirmDialogWrapper {...props} />;
|
const dom = <ConfirmDialogWrapper {...props} />;
|
||||||
|
|
||||||
reactRender(
|
const reactRender = getReactRender();
|
||||||
|
|
||||||
|
reactUnmount = reactRender(
|
||||||
<ConfigProvider prefixCls={rootPrefixCls} iconPrefixCls={iconPrefixCls} theme={theme}>
|
<ConfigProvider prefixCls={rootPrefixCls} iconPrefixCls={iconPrefixCls} theme={theme}>
|
||||||
{global.holderRender ? global.holderRender(dom) : dom}
|
{global.holderRender ? global.holderRender(dom) : dom}
|
||||||
</ConfigProvider>,
|
</ConfigProvider>,
|
||||||
|
@ -7,7 +7,7 @@ const App: React.FC = () => {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [disabled, setDisabled] = useState(true);
|
const [disabled, setDisabled] = useState(true);
|
||||||
const [bounds, setBounds] = useState({ left: 0, top: 0, bottom: 0, right: 0 });
|
const [bounds, setBounds] = useState({ left: 0, top: 0, bottom: 0, right: 0 });
|
||||||
const draggleRef = useRef<HTMLDivElement>(null);
|
const draggleRef = useRef<HTMLDivElement>(null!);
|
||||||
|
|
||||||
const showModal = () => {
|
const showModal = () => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
|
@ -51,7 +51,7 @@ export const Footer: React.FC<
|
|||||||
const [locale] = useLocale('Modal', getConfirmLocale());
|
const [locale] = useLocale('Modal', getConfirmLocale());
|
||||||
|
|
||||||
// ================== Locale Text ==================
|
// ================== Locale Text ==================
|
||||||
const okTextLocale = okText || locale?.okText;
|
const okTextLocale: React.ReactNode = okText || locale?.okText;
|
||||||
const cancelTextLocale = cancelText || locale?.cancelText;
|
const cancelTextLocale = cancelText || locale?.cancelText;
|
||||||
|
|
||||||
// ================= Context Value =================
|
// ================= Context Value =================
|
||||||
|
@ -66,6 +66,8 @@ const genModalConfirmStyle: GenerateStyle<ModalToken> = (token) => {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
flex: 'auto',
|
flex: 'auto',
|
||||||
rowGap: token.marginXS,
|
rowGap: token.marginXS,
|
||||||
|
// https://github.com/ant-design/ant-design/issues/51912
|
||||||
|
maxWidth: `calc(100% - ${unit(token.marginSM)})`,
|
||||||
},
|
},
|
||||||
|
|
||||||
// https://github.com/ant-design/ant-design/issues/48159
|
// https://github.com/ant-design/ant-design/issues/48159
|
||||||
|
@ -6,6 +6,18 @@ import App from '../../app';
|
|||||||
import ConfigProvider from '../../config-provider';
|
import ConfigProvider from '../../config-provider';
|
||||||
import { awaitPromise, triggerMotionEnd } from './util';
|
import { awaitPromise, triggerMotionEnd } from './util';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('notification.config', () => {
|
describe('notification.config', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
actWrapper(act);
|
actWrapper(act);
|
||||||
|
@ -6,6 +6,18 @@ import { act, fireEvent } from '../../../tests/utils';
|
|||||||
import ConfigProvider, { defaultPrefixCls } from '../../config-provider';
|
import ConfigProvider, { defaultPrefixCls } from '../../config-provider';
|
||||||
import { awaitPromise, triggerMotionEnd } from './util';
|
import { awaitPromise, triggerMotionEnd } from './util';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('notification', () => {
|
describe('notification', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
actWrapper(act);
|
actWrapper(act);
|
||||||
|
@ -3,6 +3,18 @@ import { act, fireEvent } from '../../../tests/utils';
|
|||||||
import type { ArgsProps, GlobalConfigProps } from '../interface';
|
import type { ArgsProps, GlobalConfigProps } from '../interface';
|
||||||
import { awaitPromise, triggerMotionEnd } from './util';
|
import { awaitPromise, triggerMotionEnd } from './util';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('Notification.placement', () => {
|
describe('Notification.placement', () => {
|
||||||
function open(args?: Partial<ArgsProps>) {
|
function open(args?: Partial<ArgsProps>) {
|
||||||
notification.open({
|
notification.open({
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import notification, { actWrapper } from '..';
|
import notification, { actWrapper } from '..';
|
||||||
import { act, render, waitFakeTimer } from '../../../tests/utils';
|
import { act, render, waitFakeTimer, waitFakeTimer19 } from '../../../tests/utils';
|
||||||
import ConfigProvider from '../../config-provider';
|
import ConfigProvider from '../../config-provider';
|
||||||
import { awaitPromise, triggerMotionEnd } from './util';
|
import { awaitPromise, triggerMotionEnd } from './util';
|
||||||
|
|
||||||
|
// TODO: Remove this. Mock for React 19
|
||||||
|
jest.mock('react-dom', () => {
|
||||||
|
const realReactDOM = jest.requireActual('react-dom');
|
||||||
|
|
||||||
|
if (realReactDOM.version.startsWith('19')) {
|
||||||
|
const realReactDOMClient = jest.requireActual('react-dom/client');
|
||||||
|
realReactDOM.createRoot = realReactDOMClient.createRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realReactDOM;
|
||||||
|
});
|
||||||
|
|
||||||
describe('notification static warning', () => {
|
describe('notification static warning', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
actWrapper(act);
|
actWrapper(act);
|
||||||
@ -32,11 +44,12 @@ describe('notification static warning', () => {
|
|||||||
message: <div className="bamboo" />,
|
message: <div className="bamboo" />,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
});
|
});
|
||||||
await waitFakeTimer();
|
await waitFakeTimer19();
|
||||||
|
|
||||||
expect(document.querySelector('.bamboo')).toBeTruthy();
|
expect(document.querySelector('.bamboo')).toBeTruthy();
|
||||||
|
|
||||||
expect(errSpy).not.toHaveBeenCalled();
|
expect(errSpy).not.toHaveBeenCalled();
|
||||||
|
errSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warning if use theme', async () => {
|
it('warning if use theme', async () => {
|
||||||
@ -54,5 +67,6 @@ describe('notification static warning', () => {
|
|||||||
expect(errSpy).toHaveBeenCalledWith(
|
expect(errSpy).toHaveBeenCalledWith(
|
||||||
"Warning: [antd: notification] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
|
"Warning: [antd: notification] Static function can not consume context like dynamic theme. Please use 'App' component instead.",
|
||||||
);
|
);
|
||||||
|
errSpy.mockRestore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { render } from 'rc-util/lib/React/render';
|
|
||||||
|
|
||||||
import { AppConfigContext } from '../app/context';
|
import { AppConfigContext } from '../app/context';
|
||||||
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
|
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
|
||||||
|
import { getReactRender } from '../config-provider/UnstableContext';
|
||||||
import type { ArgsProps, GlobalConfigProps, NotificationInstance } from './interface';
|
import type { ArgsProps, GlobalConfigProps, NotificationInstance } from './interface';
|
||||||
import PurePanel from './PurePanel';
|
import PurePanel from './PurePanel';
|
||||||
import useNotification, { useInternalNotification } from './useNotification';
|
import useNotification, { useInternalNotification } from './useNotification';
|
||||||
@ -126,7 +126,9 @@ function flushNotice() {
|
|||||||
|
|
||||||
// Delay render to avoid sync issue
|
// Delay render to avoid sync issue
|
||||||
act(() => {
|
act(() => {
|
||||||
render(
|
const reactRender = getReactRender();
|
||||||
|
|
||||||
|
reactRender(
|
||||||
<GlobalHolderWrapper
|
<GlobalHolderWrapper
|
||||||
ref={(node) => {
|
ref={(node) => {
|
||||||
const { instance, sync } = node || {};
|
const { instance, sync } = node || {};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user