chore: auto merge branches (#51642)
Some checks failed
Publish Any Commit / build (push) Has been cancelled
🔀 Sync mirror to Gitee / mirror (push) Has been cancelled
✅ test / lint (push) Has been cancelled
✅ test / test-react-legacy (16, 1/2) (push) Has been cancelled
✅ test / test-react-legacy (16, 2/2) (push) Has been cancelled
✅ test / test-react-legacy (17, 1/2) (push) Has been cancelled
✅ test / test-react-legacy (17, 2/2) (push) Has been cancelled
✅ test / test-node (push) Has been cancelled
✅ test / test-react-latest (dom, 1/2) (push) Has been cancelled
✅ test / test-react-latest (dom, 2/2) (push) Has been cancelled
✅ test / build (push) Has been cancelled
✅ test / test lib/es module (es, 1/2) (push) Has been cancelled
✅ test / test lib/es module (es, 2/2) (push) Has been cancelled
✅ test / test lib/es module (lib, 1/2) (push) Has been cancelled
✅ test / test lib/es module (lib, 2/2) (push) Has been cancelled
👁️ Visual Regression Persist Start / test image (push) Has been cancelled
✅ test / test-react-latest-dist (dist, 1/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist, 2/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist-min, 1/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist-min, 2/2) (push) Has been cancelled
✅ test / test-coverage (push) Has been cancelled

chore: merge master into feature
This commit is contained in:
github-actions[bot] 2024-11-15 03:03:42 +00:00 committed by GitHub
commit 1b8e956a62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 157 additions and 35 deletions

View File

@ -58,7 +58,13 @@ jobs:
if (comment.body.includes('VISUAL_DIFF_FAILED')) { if (comment.body.includes('VISUAL_DIFF_FAILED')) {
hasDiffFailed = true; hasDiffFailed = true;
} }
if (comment.body.includes('- [x] Visual diff is acceptable')) {
// https://regex101.com/r/kLjudz/1
const RE = /(?<=\>\s\[!IMPORTANT\].*?- \[ \])/s;
if (
comment.body.includes('- [x] Visual diff is acceptable') &&
comment.body.match(RE) == null /** 检查 IMPORTANT 是否存在未勾选的 */
) {
hasMemberApprove = true; hasMemberApprove = true;
} }
} }

View File

@ -474,4 +474,10 @@ describe('Button', () => {
'--ant-button-solid-text-color': '#000', '--ant-button-solid-text-color': '#000',
}); });
}); });
it('autoFocus should work', () => {
const { container } = render(<Button autoFocus>button</Button>);
expect(container.querySelector('button')).toBe(document.activeElement);
});
}); });

View File

@ -1,7 +1,7 @@
import React, { Children, createRef, useContext, useEffect, useMemo, useState } from 'react'; import React, { Children, useContext, useEffect, useMemo, useRef, useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import omit from 'rc-util/lib/omit'; import omit from 'rc-util/lib/omit';
import { composeRef } from 'rc-util/lib/ref'; import { useComposeRef } from 'rc-util/lib/ref';
import { devUseWarning } from '../_util/warning'; import { devUseWarning } from '../_util/warning';
import Wave from '../_util/wave'; import Wave from '../_util/wave';
@ -119,6 +119,7 @@ const InternalCompoundedButton = React.forwardRef<
classNames: customClassNames, classNames: customClassNames,
style: customStyle = {}, style: customStyle = {},
autoInsertSpace, autoInsertSpace,
autoFocus,
...rest ...rest
} = props; } = props;
@ -162,13 +163,15 @@ const InternalCompoundedButton = React.forwardRef<
const [hasTwoCNChar, setHasTwoCNChar] = useState<boolean>(false); const [hasTwoCNChar, setHasTwoCNChar] = useState<boolean>(false);
const internalRef = createRef<HTMLButtonElement | HTMLAnchorElement>(); const buttonRef = useRef<HTMLButtonElement | HTMLAnchorElement>();
const buttonRef = composeRef(ref, internalRef); const mergedRef = useComposeRef(ref, buttonRef);
const needInserted = const needInserted =
Children.count(children) === 1 && !icon && !isUnBorderedButtonVariant(mergedVariant); Children.count(children) === 1 && !icon && !isUnBorderedButtonVariant(mergedVariant);
// ========================= Effect =========================
// Loading
useEffect(() => { useEffect(() => {
let delayTimer: ReturnType<typeof setTimeout> | null = null; let delayTimer: ReturnType<typeof setTimeout> | null = null;
if (loadingOrDelay.delay > 0) { if (loadingOrDelay.delay > 0) {
@ -190,12 +193,13 @@ const InternalCompoundedButton = React.forwardRef<
return cleanupTimer; return cleanupTimer;
}, [loadingOrDelay]); }, [loadingOrDelay]);
// Two chinese characters check
useEffect(() => { useEffect(() => {
// FIXME: for HOC usage like <FormatMessage /> // FIXME: for HOC usage like <FormatMessage />
if (!buttonRef || !(buttonRef as any).current || !mergedInsertSpace) { if (!buttonRef.current || !mergedInsertSpace) {
return; return;
} }
const buttonText = (buttonRef as any).current.textContent; const buttonText = buttonRef.current.textContent || '';
if (needInserted && isTwoCNChar(buttonText)) { if (needInserted && isTwoCNChar(buttonText)) {
if (!hasTwoCNChar) { if (!hasTwoCNChar) {
setHasTwoCNChar(true); setHasTwoCNChar(true);
@ -203,8 +207,16 @@ const InternalCompoundedButton = React.forwardRef<
} else if (hasTwoCNChar) { } else if (hasTwoCNChar) {
setHasTwoCNChar(false); setHasTwoCNChar(false);
} }
}, [buttonRef]); });
// Auto focus
useEffect(() => {
if (autoFocus && buttonRef.current) {
buttonRef.current.focus();
}
}, []);
// ========================= Events =========================
const handleClick = React.useCallback( const handleClick = React.useCallback(
(e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement, MouseEvent>) => { (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement, MouseEvent>) => {
// FIXME: https://github.com/ant-design/ant-design/issues/30207 // FIXME: https://github.com/ant-design/ant-design/issues/30207
@ -217,6 +229,7 @@ const InternalCompoundedButton = React.forwardRef<
[props.onClick, innerLoading, mergedDisabled], [props.onClick, innerLoading, mergedDisabled],
); );
// ========================== Warn ==========================
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Button'); const warning = devUseWarning('Button');
@ -233,6 +246,7 @@ const InternalCompoundedButton = React.forwardRef<
); );
} }
// ========================== Size ==========================
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction); const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
const sizeClassNameMap = { large: 'lg', small: 'sm', middle: undefined }; const sizeClassNameMap = { large: 'lg', small: 'sm', middle: undefined };
@ -245,6 +259,7 @@ const InternalCompoundedButton = React.forwardRef<
const linkButtonRestProps = omit(rest as ButtonProps & { navigate: any }, ['navigate']); const linkButtonRestProps = omit(rest as ButtonProps & { navigate: any }, ['navigate']);
// ========================= Render =========================
const classes = classNames( const classes = classNames(
prefixCls, prefixCls,
hashId, hashId,
@ -301,7 +316,7 @@ const InternalCompoundedButton = React.forwardRef<
href={mergedDisabled ? undefined : linkButtonRestProps.href} href={mergedDisabled ? undefined : linkButtonRestProps.href}
style={fullStyle} style={fullStyle}
onClick={handleClick} onClick={handleClick}
ref={buttonRef as React.Ref<HTMLAnchorElement>} ref={mergedRef as React.Ref<HTMLAnchorElement>}
tabIndex={mergedDisabled ? -1 : 0} tabIndex={mergedDisabled ? -1 : 0}
> >
{iconNode} {iconNode}
@ -318,7 +333,7 @@ const InternalCompoundedButton = React.forwardRef<
style={fullStyle} style={fullStyle}
onClick={handleClick} onClick={handleClick}
disabled={mergedDisabled} disabled={mergedDisabled}
ref={buttonRef as React.Ref<HTMLButtonElement>} ref={mergedRef as React.Ref<HTMLButtonElement>}
> >
{iconNode} {iconNode}
{kids} {kids}

View File

@ -78,6 +78,17 @@ const ErrorList: React.FC<ErrorListProps> = ({
]; ];
}, [help, helpStatus, debounceErrors, debounceWarnings]); }, [help, helpStatus, debounceErrors, debounceWarnings]);
const filledKeyFullKeyList = React.useMemo<ErrorEntity[]>(() => {
const keysCount: Record<string, number> = {};
fullKeyList.forEach(({ key }) => {
keysCount[key] = (keysCount[key] || 0) + 1;
});
return fullKeyList.map((entity, index) => ({
...entity,
key: keysCount[entity.key] > 1 ? `${entity.key}-fallback-${index}` : entity.key,
}));
}, [fullKeyList]);
const helpProps: { id?: string } = {}; const helpProps: { id?: string } = {};
if (fieldId) { if (fieldId) {
@ -88,7 +99,7 @@ const ErrorList: React.FC<ErrorListProps> = ({
<CSSMotion <CSSMotion
motionDeadline={collapseMotion.motionDeadline} motionDeadline={collapseMotion.motionDeadline}
motionName={`${prefixCls}-show-help`} motionName={`${prefixCls}-show-help`}
visible={!!fullKeyList.length} visible={!!filledKeyFullKeyList.length}
onVisibleChanged={onVisibleChanged} onVisibleChanged={onVisibleChanged}
> >
{(holderProps) => { {(holderProps) => {
@ -109,7 +120,7 @@ const ErrorList: React.FC<ErrorListProps> = ({
role="alert" role="alert"
> >
<CSSMotionList <CSSMotionList
keys={fullKeyList} keys={filledKeyFullKeyList}
{...initCollapseMotion(prefixCls)} {...initCollapseMotion(prefixCls)}
motionName={`${prefixCls}-show-help-item`} motionName={`${prefixCls}-show-help-item`}
component={false} component={false}

View File

@ -2459,4 +2459,60 @@ describe('Form', () => {
fireEvent.click(container.querySelector('input')!); fireEvent.click(container.querySelector('input')!);
expect(container.querySelector('input')?.checked).toBeTruthy(); expect(container.querySelector('input')?.checked).toBeTruthy();
}); });
it('not warning for react key', async () => {
const MockInput = (props: { onChange?: (value: number[]) => void }) => (
<Input
onChange={({ target: { value } }) => {
props.onChange?.(value.split(',').map(Number));
}}
/>
);
const { container } = render(
<Form>
<Form.Item>
<Form.Item
name="test"
rules={[
{
type: 'array',
defaultField: {
type: 'number',
min: 10,
message: 'LESS_THAN_10',
},
},
]}
>
<MockInput />
</Form.Item>
</Form.Item>
</Form>,
);
function expectErrors(errors: string[]) {
expect(container.querySelectorAll('.ant-form-item-explain-error')).toHaveLength(
errors.length,
);
errors.forEach((error, index) => {
expect(container.querySelectorAll('.ant-form-item-explain-error')[index]).toHaveTextContent(
error,
);
});
}
// user type something and clear
await changeValue(0, '1');
expectErrors(['LESS_THAN_10']);
await changeValue(0, '1,1');
expectErrors(['LESS_THAN_10', 'LESS_THAN_10']);
await changeValue(0, '1');
expectErrors(['LESS_THAN_10']);
await changeValue(0, '100');
expectErrors([]);
});
}); });

View File

@ -52,13 +52,13 @@ Common props ref[Common props](/docs/react/common-props)
### Timeline ### Timeline
| Property | Description | Type | Default | | Property | Description | Type | Default | Version |
| --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| mode | By sending `alternate` the timeline will distribute the nodes to the left and right | `left` \| `alternate` \| `right` | - | | mode | By sending `alternate` the timeline will distribute the nodes to the left and right | `left` \| `alternate` \| `right` | - | |
| pending | Set the last ghost node's existence or its content | ReactNode | false | | pending | Set the last ghost node's existence or its content | ReactNode | false | |
| pendingDot | Set the dot of the last ghost node when pending is true | ReactNode | &lt;LoadingOutlined /&gt; | | pendingDot | Set the dot of the last ghost node when pending is true | ReactNode | &lt;LoadingOutlined /&gt; | |
| reverse | Whether reverse nodes or not | boolean | false | | reverse | Whether reverse nodes or not | boolean | false | |
| items | Each node of timeline | [Items](#Items)[] | 5.2.0 | | items | Each node of timeline | [Items](#Items)[] | - | 5.2.0 |
### Items ### Items

View File

@ -53,13 +53,13 @@ return (
### Timeline ### Timeline
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| mode | 通过设置 `mode` 可以改变时间轴和内容的相对位置 | `left` \| `alternate` \| `right` | - | | mode | 通过设置 `mode` 可以改变时间轴和内容的相对位置 | `left` \| `alternate` \| `right` | - | |
| pending | 指定最后一个幽灵节点是否存在或内容 | ReactNode | false | | pending | 指定最后一个幽灵节点是否存在或内容 | ReactNode | false | |
| pendingDot | 当最后一个幽灵节点存在時,指定其时间图点 | ReactNode | &lt;LoadingOutlined /&gt; | | pendingDot | 当最后一个幽灵节点存在時,指定其时间图点 | ReactNode | &lt;LoadingOutlined /&gt; | |
| reverse | 节点排序 | boolean | false | | reverse | 节点排序 | boolean | false | |
| items | 选项配置 | [Items](#Items)[] | 5.2.0 | | items | 选项配置 | [Items](#Items)[] | - | 5.2.0 |
### Items ### Items

View File

@ -12,6 +12,7 @@ import pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs'; import { PNG } from 'pngjs';
import sharp from 'sharp'; import sharp from 'sharp';
import simpleGit from 'simple-git'; import simpleGit from 'simple-git';
import filter from 'lodash/filter';
import markdown2Html from './convert'; import markdown2Html from './convert';
@ -283,9 +284,17 @@ ${fullReport}
let diffCount = 0; let diffCount = 0;
// Summary
const badCount = badCases.length;
const commentReportLimit = isLocalEnv ? badCount : 8;
const changedCount = filter(badCases, { type: 'changed' }).length;
const removedCount = filter(badCases, { type: 'removed' }).length;
const addedCount = filter(badCases, { type: 'added' }).length;
for (const badCase of badCases) { for (const badCase of badCases) {
diffCount += 1; diffCount += 1;
if (diffCount <= 10) { if (diffCount <= commentReportLimit) {
// 将图片下方增加文件名 // 将图片下方增加文件名
reportMdStr += generateLineReport(badCase, publicPath, currentRef, true); reportMdStr += generateLineReport(badCase, publicPath, currentRef, true);
} }
@ -293,18 +302,37 @@ ${fullReport}
fullVersionMd += generateLineReport(badCase, publicPath, currentRef, false); fullVersionMd += generateLineReport(badCase, publicPath, currentRef, false);
} }
reportMdStr += `\n\nCheck <a href="${htmlReportLink}" target="_blank">Full Report</a> for details`; const hasMore = badCount > commentReportLimit;
if (hasMore) {
reportMdStr += [
'\r',
'> [!WARNING]',
`> There are more diffs not shown in the table. Please check the <a href="${htmlReportLink}" target="_blank">Full Report</a> for details.`,
'\r',
].join('\n');
}
// tips for comment `Pass Visual Diff` will pass the CI // tips for comment `Pass Visual Diff` will pass the CI
if (!passed) { if (!passed) {
reportMdStr += ` const summaryLine = [
changedCount > 0 && `🔄 **${changedCount}** changed`,
removedCount > 0 && `🛑 **${removedCount}** removed`,
addedCount > 0 && `🆕 **${addedCount}** added`,
]
.filter(Boolean)
.join(', ');
----- reportMdStr += [
'\n---\n',
If you think the visual diff is acceptable, please check: '> [!IMPORTANT]',
`> There are **${badCount}** diffs found in this PR: ${summaryLine}.`,
- [ ] Visual diff is acceptable '> **Please check all items:**',
`; hasMore && '> - [ ] Checked all diffs in the full report',
'> - [ ] Visual diff is acceptable',
]
.filter(Boolean)
.join('\n');
} }
// convert fullVersionMd to html // convert fullVersionMd to html