Skip to content

Commit 53df080

Browse files
authored
fix: Free lock if motion not start (#472)
* chore: tmp of it * fix: motion lock * chore: revert demo * chore: clean up * chore: fix lint * chore: rm console
1 parent ffa313a commit 53df080

File tree

8 files changed

+81
-37
lines changed

8 files changed

+81
-37
lines changed

docs/examples/multiple-Portal.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ const Demo: React.FC = () => {
4545
<button type="button" onClick={onToggleDialog}>
4646
open dialog
4747
</button>
48+
<button
49+
type="button"
50+
onClick={() => {
51+
setShowDialog(true);
52+
setTimeout(() => {
53+
setShowDialog(false);
54+
}, 0);
55+
}}
56+
>
57+
quick
58+
</button>
4859
{dialog}
4960
{drawer}
5061
</div>

package.json

+4-5
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"lint:tsc": "tsc -p tsconfig.json --noEmit",
3838
"now-build": "npm run docs:build",
3939
"prepare": "husky install",
40-
"prepublishOnly": "npm run compile && np --yolo --no-publish",
40+
"prepublishOnly": "npm run compile && rc-np",
4141
"prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
4242
"start": "dumi dev",
4343
"test": "rc-test"
@@ -49,14 +49,14 @@
4949
]
5050
},
5151
"dependencies": {
52-
"@babel/runtime": "^7.10.1",
5352
"@rc-component/portal": "^1.0.0-8",
5453
"@rc-component/util": "^1.0.1",
5554
"classnames": "^2.2.6",
56-
"rc-motion": "^2.3.0"
55+
"@rc-component/motion": "^1.1.3"
5756
},
5857
"devDependencies": {
59-
"@rc-component/father-plugin": "^2.0.1",
58+
"@rc-component/father-plugin": "^2.0.2",
59+
"@rc-component/np": "^1.0.3",
6060
"@testing-library/jest-dom": "^6.1.6",
6161
"@testing-library/react": "^13.0.0",
6262
"@types/jest": "^29.4.0",
@@ -77,7 +77,6 @@
7777
"husky": "^8.0.3",
7878
"less": "^4.1.3",
7979
"lint-staged": "^15.2.0",
80-
"np": "^10.0.5",
8180
"prettier": "^3.2.1",
8281
"rc-drawer": "^7.0.0",
8382
"rc-select": "^14.11.0",

src/Dialog/Content/Panel.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ export interface PanelProps extends Omit<IDialogPropTypes, 'getOpenCount'> {
1717
holderRef?: React.Ref<HTMLDivElement>;
1818
}
1919

20-
export type ContentRef = {
20+
export type PanelRef = {
2121
focus: () => void;
2222
changeActive: (next: boolean) => void;
2323
};
2424

25-
const Panel = React.forwardRef<ContentRef, PanelProps>((props, ref) => {
25+
const Panel = React.forwardRef<PanelRef, PanelProps>((props, ref) => {
2626
const {
2727
prefixCls,
2828
className,

src/Dialog/Content/index.tsx

+23-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import * as React from 'react';
22
import { useRef } from 'react';
33
import classNames from 'classnames';
4-
import CSSMotion from 'rc-motion';
4+
import CSSMotion from '@rc-component/motion';
55
import { offset } from '../../util';
6-
import type { PanelProps, ContentRef } from './Panel';
6+
import type { PanelProps, PanelRef } from './Panel';
77
import Panel from './Panel';
8+
import type { CSSMotionRef } from '@rc-component/motion/es/CSSMotion';
9+
10+
export type CSSMotionStateRef = Pick<CSSMotionRef, 'inMotion' | 'enableMotion'>;
11+
12+
export type ContentRef = PanelRef & CSSMotionStateRef;
813

914
export type ContentProps = {
1015
motionName: string;
@@ -27,7 +32,20 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
2732
mousePosition,
2833
} = props;
2934

30-
const dialogRef = useRef<HTMLDivElement>();
35+
const dialogRef = useRef<
36+
{
37+
nativeElement: HTMLElement;
38+
} & CSSMotionStateRef
39+
>();
40+
41+
const panelRef = useRef<PanelRef>();
42+
43+
// ============================== Refs ==============================
44+
React.useImperativeHandle(ref, () => ({
45+
...panelRef.current,
46+
inMotion: dialogRef.current.inMotion,
47+
enableMotion: dialogRef.current.enableMotion,
48+
}));
3149

3250
// ============================= Style ==============================
3351
const [transformOrigin, setTransformOrigin] = React.useState<string>();
@@ -38,7 +56,7 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
3856
}
3957

4058
function onPrepare() {
41-
const elementOffset = offset(dialogRef.current);
59+
const elementOffset = offset(dialogRef.current.nativeElement);
4260

4361
setTransformOrigin(
4462
mousePosition && (mousePosition.x || mousePosition.y)
@@ -62,7 +80,7 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
6280
{({ className: motionClassName, style: motionStyle }, motionRef) => (
6381
<Panel
6482
{...props}
65-
ref={ref}
83+
ref={panelRef}
6684
title={title}
6785
ariaId={ariaId}
6886
prefixCls={prefixCls}

src/Dialog/Mask.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import classNames from 'classnames';
3-
import CSSMotion from 'rc-motion';
3+
import CSSMotion from '@rc-component/motion';
44

55
export type MaskProps = {
66
prefixCls: string;

src/Dialog/index.tsx

+30-18
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import * as React from 'react';
77
import { useEffect, useRef } from 'react';
88
import type { IDialogPropTypes } from '../IDialogPropTypes';
99
import { getMotionName } from '../util';
10-
import Content from './Content';
11-
import type { ContentRef } from './Content/Panel';
10+
import Content, { type ContentRef } from './Content';
1211
import Mask from './Mask';
1312
import { warning } from '@rc-component/util/lib/warning';
1413

@@ -78,27 +77,34 @@ const Dialog: React.FC<IDialogPropTypes> = (props) => {
7877
}
7978

8079
// ========================= Events =========================
80+
// Close action will trigger by:
81+
// 1. When hide motion end
82+
// 2. Controlled `open` to `false` immediately after set to `true` which will not trigger motion
83+
function doClose() {
84+
// Clean up scroll bar & focus back
85+
setAnimatedVisible(false);
86+
87+
if (mask && lastOutSideActiveElementRef.current && focusTriggerAfterClose) {
88+
try {
89+
lastOutSideActiveElementRef.current.focus({ preventScroll: true });
90+
} catch (e) {
91+
// Do nothing
92+
}
93+
lastOutSideActiveElementRef.current = null;
94+
}
95+
96+
// Trigger afterClose only when change visible from true to false
97+
if (animatedVisible) {
98+
afterClose?.();
99+
}
100+
}
101+
81102
function onDialogVisibleChanged(newVisible: boolean) {
82103
// Try to focus
83104
if (newVisible) {
84105
focusDialogContent();
85106
} else {
86-
// Clean up scroll bar & focus back
87-
setAnimatedVisible(false);
88-
89-
if (mask && lastOutSideActiveElementRef.current && focusTriggerAfterClose) {
90-
try {
91-
lastOutSideActiveElementRef.current.focus({ preventScroll: true });
92-
} catch (e) {
93-
// Do nothing
94-
}
95-
lastOutSideActiveElementRef.current = null;
96-
}
97-
98-
// Trigger afterClose only when change visible from true to false
99-
if (animatedVisible) {
100-
afterClose?.();
101-
}
107+
doClose();
102108
}
103109
afterOpenChange?.(newVisible);
104110
}
@@ -154,6 +160,12 @@ const Dialog: React.FC<IDialogPropTypes> = (props) => {
154160
if (visible) {
155161
setAnimatedVisible(true);
156162
saveLastOutSideActiveElementRef();
163+
} else if (
164+
animatedVisible &&
165+
contentRef.current.enableMotion() &&
166+
!contentRef.current.inMotion()
167+
) {
168+
doClose();
157169
}
158170
}, [visible]);
159171

tests/index.spec.tsx

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
/* eslint-disable react/no-render-return-value, max-classes-per-file, func-names, no-console */
22
import { fireEvent, render, act } from '@testing-library/react';
3-
import { Provider } from 'rc-motion';
3+
import { Provider } from '@rc-component/motion';
44
import KeyCode from '@rc-component/util/lib/KeyCode';
55
import React, { cloneElement, useEffect } from 'react';
66
import type { DialogProps } from '../src';
77
import Dialog from '../src';
88

9-
jest.mock('rc-motion', () => {
9+
jest.mock('@rc-component/motion', () => {
1010
const OriReact = jest.requireActual('react');
11-
const origin = jest.requireActual('rc-motion');
11+
const origin = jest.requireActual('@rc-component/motion');
1212
const OriCSSMotion = origin.default;
1313

1414
const ProxyCSSMotion = OriReact.forwardRef((props: any, ref: any) => {
@@ -515,10 +515,14 @@ describe('dialog', () => {
515515
const afterClose = jest.fn();
516516

517517
const { rerender } = render(<Dialog afterClose={afterClose} visible />);
518-
jest.runAllTimers();
518+
act(() => {
519+
jest.runAllTimers();
520+
});
519521

520522
rerender(<Dialog afterClose={afterClose} visible={false} />);
521-
jest.runAllTimers();
523+
act(() => {
524+
jest.runAllTimers();
525+
});
522526

523527
expect(afterClose).toHaveBeenCalledTimes(1);
524528
});

tests/ref.spec.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable react/no-render-return-value, max-classes-per-file, func-names, no-console */
22
import { render } from '@testing-library/react';
3-
import { Provider } from 'rc-motion';
3+
import { Provider } from '@rc-component/motion';
44
import React from 'react';
55
import Dialog from '../src';
66

0 commit comments

Comments
 (0)