mirror of
https://github.com/ant-design/ant-design.git
synced 2025-08-05 23:46:28 +08:00
fix: input work with Upload (#51874)
Some checks are pending
Publish Any Commit / build (push) Waiting to run
🔀 Sync mirror to Gitee / mirror (push) Waiting to run
✅ test / lint (push) Waiting to run
✅ test / test-react-legacy (16, 1/2) (push) Waiting to run
✅ test / test-react-legacy (16, 2/2) (push) Waiting to run
✅ test / test-react-legacy (17, 1/2) (push) Waiting to run
✅ test / test-react-legacy (17, 2/2) (push) Waiting to run
✅ test / test-node (push) Waiting to run
✅ test / test-react-latest (dom, 1/2) (push) Waiting to run
✅ test / test-react-latest (dom, 2/2) (push) Waiting to run
✅ test / test-react-latest-dist (dist, 1/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist, 2/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist-min, 1/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist-min, 2/2) (push) Blocked by required conditions
✅ test / test-coverage (push) Blocked by required conditions
✅ test / build (push) Waiting to run
✅ test / test lib/es module (es, 1/2) (push) Waiting to run
✅ test / test lib/es module (es, 2/2) (push) Waiting to run
✅ test / test lib/es module (lib, 1/2) (push) Waiting to run
✅ test / test lib/es module (lib, 2/2) (push) Waiting to run
👁️ Visual Regression Persist Start / test image (push) Waiting to run
Some checks are pending
Publish Any Commit / build (push) Waiting to run
🔀 Sync mirror to Gitee / mirror (push) Waiting to run
✅ test / lint (push) Waiting to run
✅ test / test-react-legacy (16, 1/2) (push) Waiting to run
✅ test / test-react-legacy (16, 2/2) (push) Waiting to run
✅ test / test-react-legacy (17, 1/2) (push) Waiting to run
✅ test / test-react-legacy (17, 2/2) (push) Waiting to run
✅ test / test-node (push) Waiting to run
✅ test / test-react-latest (dom, 1/2) (push) Waiting to run
✅ test / test-react-latest (dom, 2/2) (push) Waiting to run
✅ test / test-react-latest-dist (dist, 1/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist, 2/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist-min, 1/2) (push) Blocked by required conditions
✅ test / test-react-latest-dist (dist-min, 2/2) (push) Blocked by required conditions
✅ test / test-coverage (push) Blocked by required conditions
✅ test / build (push) Waiting to run
✅ test / test lib/es module (es, 1/2) (push) Waiting to run
✅ test / test lib/es module (es, 2/2) (push) Waiting to run
✅ test / test lib/es module (lib, 1/2) (push) Waiting to run
✅ test / test lib/es module (lib, 2/2) (push) Waiting to run
👁️ Visual Regression Persist Start / test image (push) Waiting to run
* fix: input work with Upload * test: update snapshot
This commit is contained in:
parent
f94261d1c9
commit
0e9ac35e3c
@ -13,6 +13,7 @@ import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
|
||||
import { FormItemInputContext } from '../form/context';
|
||||
import GroupContext from './GroupContext';
|
||||
import useStyle from './style';
|
||||
import useBubbleLock from './useBubbleLock';
|
||||
|
||||
export interface AbstractCheckboxProps<T> {
|
||||
prefixCls?: string;
|
||||
@ -153,6 +154,11 @@ const InternalCheckbox: React.ForwardRefRenderFunction<CheckboxRef, CheckboxProp
|
||||
TARGET_CLS,
|
||||
hashId,
|
||||
);
|
||||
|
||||
// ============================ Event Lock ============================
|
||||
const [onLabelClick, onInputClick] = useBubbleLock(checkboxProps.onClick);
|
||||
|
||||
// ============================== Render ==============================
|
||||
return wrapCSSVar(
|
||||
<Wave component="Checkbox" disabled={mergedDisabled}>
|
||||
<label
|
||||
@ -160,10 +166,12 @@ const InternalCheckbox: React.ForwardRefRenderFunction<CheckboxRef, CheckboxProp
|
||||
style={{ ...checkbox?.style, ...style }}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onClick={onLabelClick}
|
||||
>
|
||||
{/* @ts-ignore */}
|
||||
<RcCheckbox
|
||||
{...checkboxProps}
|
||||
onClick={onInputClick}
|
||||
prefixCls={prefixCls}
|
||||
className={checkboxClass}
|
||||
disabled={mergedDisabled}
|
||||
|
@ -5,13 +5,21 @@ import { resetWarned } from '../../_util/warning';
|
||||
import focusTest from '../../../tests/shared/focusTest';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { fireEvent, render } from '../../../tests/utils';
|
||||
import { act, fireEvent, render } from '../../../tests/utils';
|
||||
|
||||
describe('Checkbox', () => {
|
||||
focusTest(Checkbox, { refFocus: true });
|
||||
mountTest(Checkbox);
|
||||
rtlTest(Checkbox);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('responses hover events', () => {
|
||||
const onMouseEnter = jest.fn();
|
||||
const onMouseLeave = jest.fn();
|
||||
@ -63,4 +71,36 @@ describe('Checkbox', () => {
|
||||
|
||||
expect(checkboxInput.indeterminate).toBe(false);
|
||||
});
|
||||
|
||||
it('event bubble should not trigger twice', () => {
|
||||
const onClick = jest.fn();
|
||||
const onRootClick = jest.fn();
|
||||
|
||||
const { container } = render(
|
||||
<div onClick={onRootClick}>
|
||||
<Checkbox onClick={onClick} />
|
||||
</div>,
|
||||
);
|
||||
|
||||
// Click on label
|
||||
fireEvent.click(container.querySelector('label')!);
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
expect(onRootClick).toHaveBeenCalledTimes(1);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
// Click on input
|
||||
fireEvent.click(container.querySelector('input')!);
|
||||
expect(onClick).toHaveBeenCalledTimes(2);
|
||||
expect(onRootClick).toHaveBeenCalledTimes(2);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
// Click on input again
|
||||
fireEvent.click(container.querySelector('input')!);
|
||||
expect(onClick).toHaveBeenCalledTimes(3);
|
||||
expect(onRootClick).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
37
components/checkbox/useBubbleLock.ts
Normal file
37
components/checkbox/useBubbleLock.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import raf from 'rc-util/lib/raf';
|
||||
|
||||
/**
|
||||
* When click on the label,
|
||||
* the event will be stopped to prevent the label from being clicked twice.
|
||||
* label click -> input click -> label click again
|
||||
*/
|
||||
export default function useBubbleLock(
|
||||
onOriginInputClick?: React.MouseEventHandler<HTMLInputElement>,
|
||||
) {
|
||||
const labelClickLockRef = React.useRef<number | null>(null);
|
||||
|
||||
const clearLock = () => {
|
||||
raf.cancel(labelClickLockRef.current!);
|
||||
labelClickLockRef.current = null;
|
||||
};
|
||||
|
||||
const onLabelClick: React.MouseEventHandler<HTMLLabelElement> = () => {
|
||||
clearLock();
|
||||
|
||||
labelClickLockRef.current = raf(() => {
|
||||
labelClickLockRef.current = null;
|
||||
});
|
||||
};
|
||||
|
||||
const onInputClick: React.MouseEventHandler<HTMLInputElement> = (e) => {
|
||||
if (labelClickLockRef.current) {
|
||||
e.stopPropagation();
|
||||
clearLock();
|
||||
}
|
||||
|
||||
onOriginInputClick?.(e);
|
||||
};
|
||||
|
||||
return [onLabelClick, onInputClick] as const;
|
||||
}
|
@ -429,6 +429,101 @@ exports[`renders components/radio/demo/component-token.tsx extend context correc
|
||||
|
||||
exports[`renders components/radio/demo/component-token.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/radio/demo/debug-upload.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span
|
||||
class="ant-upload-wrapper"
|
||||
>
|
||||
<div
|
||||
class="ant-upload ant-upload-select"
|
||||
>
|
||||
<span
|
||||
class="ant-upload"
|
||||
>
|
||||
<input
|
||||
accept=""
|
||||
name="file"
|
||||
style="display: none;"
|
||||
type="file"
|
||||
/>
|
||||
<label
|
||||
class="ant-radio-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio ant-wave-target"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-input"
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
Radio
|
||||
</span>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-upload-list ant-upload-list-text"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span
|
||||
class="ant-upload-wrapper"
|
||||
>
|
||||
<div
|
||||
class="ant-upload ant-upload-select"
|
||||
>
|
||||
<span
|
||||
class="ant-upload"
|
||||
>
|
||||
<input
|
||||
accept=""
|
||||
name="file"
|
||||
style="display: none;"
|
||||
type="file"
|
||||
/>
|
||||
<label
|
||||
class="ant-checkbox-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target"
|
||||
>
|
||||
<input
|
||||
class="ant-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
class="ant-checkbox-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
Checkbox
|
||||
</span>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-upload-list ant-upload-list-text"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/radio/demo/debug-upload.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/radio/demo/disabled.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<label
|
||||
|
@ -423,6 +423,99 @@ exports[`renders components/radio/demo/component-token.tsx correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/radio/demo/debug-upload.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span
|
||||
class="ant-upload-wrapper"
|
||||
>
|
||||
<div
|
||||
class="ant-upload ant-upload-select"
|
||||
>
|
||||
<span
|
||||
class="ant-upload"
|
||||
>
|
||||
<input
|
||||
accept=""
|
||||
name="file"
|
||||
style="display:none"
|
||||
type="file"
|
||||
/>
|
||||
<label
|
||||
class="ant-radio-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio ant-wave-target"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-input"
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
Radio
|
||||
</span>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-upload-list ant-upload-list-text"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span
|
||||
class="ant-upload-wrapper"
|
||||
>
|
||||
<div
|
||||
class="ant-upload ant-upload-select"
|
||||
>
|
||||
<span
|
||||
class="ant-upload"
|
||||
>
|
||||
<input
|
||||
accept=""
|
||||
name="file"
|
||||
style="display:none"
|
||||
type="file"
|
||||
/>
|
||||
<label
|
||||
class="ant-checkbox-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-wave-target"
|
||||
>
|
||||
<input
|
||||
class="ant-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
class="ant-checkbox-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
Checkbox
|
||||
</span>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-upload-list ant-upload-list-text"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/radio/demo/disabled.tsx correctly 1`] = `
|
||||
Array [
|
||||
<label
|
||||
|
@ -4,7 +4,7 @@ import Radio, { Button, Group } from '..';
|
||||
import focusTest from '../../../tests/shared/focusTest';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { fireEvent, render } from '../../../tests/utils';
|
||||
import { act, fireEvent, render } from '../../../tests/utils';
|
||||
import Form from '../../form';
|
||||
|
||||
describe('Radio', () => {
|
||||
@ -17,6 +17,14 @@ describe('Radio', () => {
|
||||
rtlTest(Group);
|
||||
rtlTest(Button);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should render correctly', () => {
|
||||
const { container } = render(<Radio className="customized">Test</Radio>);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
@ -58,4 +66,36 @@ describe('Radio', () => {
|
||||
it('have static property for type detecting', () => {
|
||||
expect(Radio.__ANT_RADIO).toBeTruthy();
|
||||
});
|
||||
|
||||
it('event bubble should not trigger twice', () => {
|
||||
const onClick = jest.fn();
|
||||
const onRootClick = jest.fn();
|
||||
|
||||
const { container } = render(
|
||||
<div onClick={onRootClick}>
|
||||
<Radio onClick={onClick} />
|
||||
</div>,
|
||||
);
|
||||
|
||||
// Click on label
|
||||
fireEvent.click(container.querySelector('label')!);
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
expect(onRootClick).toHaveBeenCalledTimes(1);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
// Click on input
|
||||
fireEvent.click(container.querySelector('input')!);
|
||||
expect(onClick).toHaveBeenCalledTimes(2);
|
||||
expect(onRootClick).toHaveBeenCalledTimes(2);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
// Click on input again
|
||||
fireEvent.click(container.querySelector('input')!);
|
||||
expect(onClick).toHaveBeenCalledTimes(3);
|
||||
expect(onRootClick).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
7
components/radio/demo/debug-upload.md
Normal file
7
components/radio/demo/debug-upload.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
最简单的用法。
|
||||
|
||||
## en-US
|
||||
|
||||
The simplest use.
|
15
components/radio/demo/debug-upload.tsx
Normal file
15
components/radio/demo/debug-upload.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Checkbox, Radio, Space, Upload } from 'antd';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space>
|
||||
<Upload>
|
||||
<Radio>Radio</Radio>
|
||||
</Upload>
|
||||
<Upload>
|
||||
<Checkbox>Checkbox</Checkbox>
|
||||
</Upload>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
@ -30,6 +30,7 @@ demo:
|
||||
<code src="./demo/badge.tsx" debug>Badge style</code>
|
||||
<code src="./demo/wireframe.tsx" debug>Wireframe</code>
|
||||
<code src="./demo/component-token.tsx" debug>Component Token</code>
|
||||
<code src="./demo/debug-upload.tsx" debug>Upload Debug</code>
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## API
|
||||
|
@ -31,6 +31,7 @@ demo:
|
||||
<code src="./demo/badge.tsx" debug>测试 Badge 的样式</code>
|
||||
<code src="./demo/wireframe.tsx" debug>线框风格</code>
|
||||
<code src="./demo/component-token.tsx" debug>组件 Token</code>
|
||||
<code src="./demo/debug-upload.tsx" debug>Upload Debug</code>
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## API
|
||||
|
@ -6,6 +6,7 @@ import { composeRef } from 'rc-util/lib/ref';
|
||||
import { devUseWarning } from '../_util/warning';
|
||||
import Wave from '../_util/wave';
|
||||
import { TARGET_CLS } from '../_util/wave/interface';
|
||||
import useBubbleLock from '../checkbox/useBubbleLock';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import DisabledContext from '../config-provider/DisabledContext';
|
||||
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
|
||||
@ -82,6 +83,10 @@ const InternalRadio: React.ForwardRefRenderFunction<RadioRef, RadioProps> = (pro
|
||||
rootCls,
|
||||
);
|
||||
|
||||
// ============================ Event Lock ============================
|
||||
const [onLabelClick, onInputClick] = useBubbleLock(radioProps.onClick);
|
||||
|
||||
// ============================== Render ==============================
|
||||
return wrapCSSVar(
|
||||
<Wave component="Radio" disabled={radioProps.disabled}>
|
||||
<label
|
||||
@ -90,6 +95,7 @@ const InternalRadio: React.ForwardRefRenderFunction<RadioRef, RadioProps> = (pro
|
||||
onMouseEnter={props.onMouseEnter}
|
||||
onMouseLeave={props.onMouseLeave}
|
||||
title={title}
|
||||
onClick={onLabelClick}
|
||||
>
|
||||
{/* @ts-ignore */}
|
||||
<RcCheckbox
|
||||
@ -98,6 +104,7 @@ const InternalRadio: React.ForwardRefRenderFunction<RadioRef, RadioProps> = (pro
|
||||
type="radio"
|
||||
prefixCls={prefixCls}
|
||||
ref={mergedRef}
|
||||
onClick={onInputClick}
|
||||
/>
|
||||
{children !== undefined ? <span>{children}</span> : null}
|
||||
</label>
|
||||
|
@ -231,7 +231,7 @@
|
||||
"cross-fetch": "^4.0.0",
|
||||
"dekko": "^0.2.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"dumi": "~2.4.13",
|
||||
"dumi": "~2.4.14",
|
||||
"dumi-plugin-color-chunk": "^1.1.2",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-plugin-compat": "^6.0.1",
|
||||
|
Loading…
Reference in New Issue
Block a user