diff --git a/.gitignore b/.gitignore
index 16c86a873c..eeed7b1d34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,6 +48,7 @@ server/
# Docs templates
scripts/previewEditor/index.html
components/version/version.ts
+components/version/version.tsx
components/version/token.json
components/version/token-meta.json
.dumi/tmp
diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx
index c47fa6055e..b6829855b1 100644
--- a/components/_util/__tests__/wave.test.tsx
+++ b/components/_util/__tests__/wave.test.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import mountTest from '../../../tests/shared/mountTest';
-import { render, fireEvent, getByText, waitFakeTimer } from '../../../tests/utils';
+import { render, fireEvent, getByText, waitFakeTimer, act } from '../../../tests/utils';
import Wave from '../wave';
(global as any).isVisible = true;
@@ -13,12 +13,29 @@ jest.mock('rc-util/lib/Dom/isVisible', () => {
describe('Wave component', () => {
mountTest(Wave);
+ let obCnt = 0;
+ let disCnt = 0;
+
beforeAll(() => {
+ /* eslint-disable class-methods-use-this */
+ class FakeResizeObserver {
+ observe = () => {
+ obCnt += 1;
+ };
+
+ disconnect = () => {
+ disCnt += 1;
+ };
+ }
+
+ (window as any).ResizeObserver = FakeResizeObserver;
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
+ expect(obCnt).not.toBe(0);
+ expect(disCnt).not.toBe(0);
});
beforeEach(() => {
@@ -47,6 +64,12 @@ describe('Wave component', () => {
return styleObj;
}
+ function waitRaf() {
+ act(() => {
+ jest.advanceTimersByTime(100);
+ });
+ }
+
it('work', async () => {
const { container, unmount } = render(
@@ -55,6 +78,7 @@ describe('Wave component', () => {
);
fireEvent.click(container.querySelector('button')!);
+ waitRaf();
expect(document.querySelector('.ant-wave')).toBeTruthy();
// Match deadline
@@ -74,6 +98,7 @@ describe('Wave component', () => {
);
fireEvent.click(container.querySelector('button')!);
+ waitRaf();
expect(document.querySelector('.ant-wave')).toBeFalsy();
unmount();
@@ -92,6 +117,7 @@ describe('Wave component', () => {
);
fireEvent.click(container.querySelector('button')!);
+ waitRaf();
const style = getWaveStyle();
@@ -110,6 +136,7 @@ describe('Wave component', () => {
);
fireEvent.click(container.querySelector('button')!);
+ waitRaf();
const style = getWaveStyle();
expect(style['--wave-color']).toEqual('red');
@@ -125,6 +152,7 @@ describe('Wave component', () => {
);
fireEvent.click(getByText(container, 'button')!);
+ waitRaf();
const style = getWaveStyle();
expect(style['--wave-color']).toEqual('blue');
@@ -140,6 +168,7 @@ describe('Wave component', () => {
);
fireEvent.click(getByText(container, 'button')!);
+ waitRaf();
const style = getWaveStyle();
expect(style['--wave-color']).toEqual('green');
@@ -155,6 +184,7 @@ describe('Wave component', () => {
);
fireEvent.click(getByText(container, 'button')!);
+ waitRaf();
const style = getWaveStyle();
expect(style['--wave-color']).toEqual('yellow');
@@ -172,6 +202,7 @@ describe('Wave component', () => {
);
fireEvent.click(container.querySelector('button')!);
+ waitRaf();
expect(document.querySelector('.ant-wave')).toBeFalsy();
unmount();
@@ -202,6 +233,7 @@ describe('Wave component', () => {
);
fireEvent.click(container.querySelector('button')!);
+ waitRaf();
expect(document.querySelector('.ant-wave')).toBeFalsy();
});
@@ -213,6 +245,7 @@ describe('Wave component', () => {
);
fireEvent.click(container.querySelector('input')!);
+ waitRaf();
expect(document.querySelector('.ant-wave')).toBeFalsy();
});
@@ -224,11 +257,15 @@ describe('Wave component', () => {
,
);
fireEvent.click(container);
+ waitRaf();
}).not.toThrow();
});
it('should not throw when no children', () => {
- expect(() => render()).not.toThrow();
+ expect(() => {
+ render();
+ waitRaf();
+ }).not.toThrow();
});
it('wave color should inferred if border is transparent and background is not', () => {
@@ -240,6 +277,7 @@ describe('Wave component', () => {
,
);
fireEvent.click(container.querySelector('button')!);
+ waitRaf();
const style = getWaveStyle();
expect(style['--wave-color']).toEqual('red');
@@ -257,6 +295,7 @@ describe('Wave component', () => {
);
fireEvent.click(container.querySelector('button')!);
+ waitRaf();
const style = getWaveStyle();
expect(style['--wave-color']).toEqual('red');
@@ -282,6 +321,7 @@ describe('Wave component', () => {
// Click should not throw
fireEvent.click(elem);
+ waitRaf();
expect(container.querySelector('.ant-wave')).toBeTruthy();
});
diff --git a/components/_util/wave/WaveEffect.tsx b/components/_util/wave/WaveEffect.tsx
index 905c6bb0aa..9bdbb938d6 100644
--- a/components/_util/wave/WaveEffect.tsx
+++ b/components/_util/wave/WaveEffect.tsx
@@ -1,23 +1,31 @@
import * as React from 'react';
import CSSMotion from 'rc-motion';
+import raf from 'rc-util/lib/raf';
import { render, unmount } from 'rc-util/lib/React/render';
import classNames from 'classnames';
import { getTargetWaveColor } from './util';
+function validateNum(value: number) {
+ return Number.isNaN(value) ? 0 : value;
+}
+
export interface WaveEffectProps {
- left: number;
- top: number;
- width: number;
- height: number;
- color: string | null;
className: string;
- borderRadius: number[];
+ target: HTMLElement;
}
const WaveEffect: React.FC = (props) => {
- const { className, left, top, width, height, color, borderRadius } = props;
+ const { className, target } = props;
const divRef = React.useRef(null);
+ const [color, setWaveColor] = React.useState(null);
+ const [borderRadius, setBorderRadius] = React.useState([]);
+ const [left, setLeft] = React.useState(0);
+ const [top, setTop] = React.useState(0);
+ const [width, setWidth] = React.useState(0);
+ const [height, setHeight] = React.useState(0);
+ const [enabled, setEnabled] = React.useState(false);
+
const waveStyle = {
left,
top,
@@ -32,6 +40,65 @@ const WaveEffect: React.FC = (props) => {
waveStyle['--wave-color'] = color;
}
+ function syncPos() {
+ const nodeStyle = getComputedStyle(target);
+
+ // Get wave color from target
+ setWaveColor(getTargetWaveColor(target));
+
+ // Rect
+ setLeft(target.offsetLeft);
+ setTop(target.offsetTop);
+ setWidth(target.offsetWidth);
+ setHeight(target.offsetHeight);
+
+ // Get border radius
+ const {
+ borderTopLeftRadius,
+ borderTopRightRadius,
+ borderBottomLeftRadius,
+ borderBottomRightRadius,
+ } = nodeStyle;
+
+ setBorderRadius(
+ [
+ borderTopLeftRadius,
+ borderTopRightRadius,
+ borderBottomRightRadius,
+ borderBottomLeftRadius,
+ ].map((radius) => validateNum(parseFloat(radius))),
+ );
+ }
+
+ React.useEffect(() => {
+ if (target) {
+ // We need delay to check position here
+ // since UI may change after click
+ const id = raf(() => {
+ syncPos();
+
+ setEnabled(true);
+ });
+
+ // Add resize observer to follow size
+ let resizeObserver: ResizeObserver;
+ if (typeof ResizeObserver !== 'undefined') {
+ resizeObserver = new ResizeObserver(syncPos);
+
+ resizeObserver.observe(target);
+ }
+
+ return () => {
+ raf.cancel(id);
+ resizeObserver?.disconnect();
+ };
+ }
+ }, []);
+
+ if (!enabled) {
+ return null;
+ }
+
return (
= (props) => {
);
};
-function validateNum(value: number) {
- return Number.isNaN(value) ? 0 : value;
-}
-
export default function showWaveEffect(node: HTMLElement, className: string) {
- const nodeStyle = getComputedStyle(node);
-
- // Get wave color from target
- const waveColor = getTargetWaveColor(node);
-
- // Get border radius
- const {
- borderTopLeftRadius,
- borderTopRightRadius,
- borderBottomLeftRadius,
- borderBottomRightRadius,
- } = nodeStyle;
-
// Create holder
const holder = document.createElement('div');
holder.style.position = 'absolute';
- holder.style.left = `${node.offsetLeft}px`;
- holder.style.top = `${node.offsetTop}px`;
+ holder.style.left = `0px`;
+ holder.style.top = `0px`;
node.parentElement?.appendChild(holder);
- render(
- validateNum(parseFloat(radius)))}
- />,
- holder,
- );
+ render(, holder);
}
diff --git a/components/button/__tests__/wave.test.tsx b/components/button/__tests__/wave.test.tsx
index 71d76e28ae..91ee5e21d1 100644
--- a/components/button/__tests__/wave.test.tsx
+++ b/components/button/__tests__/wave.test.tsx
@@ -1,7 +1,7 @@
import userEvent from '@testing-library/user-event';
import React from 'react';
import Button from '..';
-import { fireEvent, render } from '../../../tests/utils';
+import { act, fireEvent, render } from '../../../tests/utils';
jest.mock('rc-util/lib/Dom/isVisible', () => {
const mockFn = () => true;
@@ -23,6 +23,10 @@ describe('click wave effect', () => {
const element = container.firstChild;
// https://github.com/testing-library/user-event/issues/833
await userEvent.setup({ advanceTimers: jest.advanceTimersByTime }).click(element as Element);
+ act(() => {
+ jest.advanceTimersByTime(100);
+ });
+
fireEvent(element!, new Event('transitionstart'));
fireEvent(element!, new Event('animationend'));
}
diff --git a/components/switch/__tests__/index.test.tsx b/components/switch/__tests__/index.test.tsx
index 47c1c476f0..844463ac7c 100644
--- a/components/switch/__tests__/index.test.tsx
+++ b/components/switch/__tests__/index.test.tsx
@@ -3,7 +3,7 @@ import Switch 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 { resetWarned } from '../../_util/warning';
jest.mock('rc-util/lib/Dom/isVisible', () => {
@@ -20,6 +20,9 @@ describe('Switch', () => {
jest.useFakeTimers();
const { container } = render();
fireEvent.click(container.querySelector('.ant-switch')!);
+ act(() => {
+ jest.advanceTimersByTime(100);
+ });
expect(document.querySelector('.ant-wave')).toBeTruthy();
jest.clearAllTimers();
jest.useRealTimers();