Skip to content

fix: Free lock if motion not start #472

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/examples/multiple-Portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ const Demo: React.FC = () => {
<button type="button" onClick={onToggleDialog}>
open dialog
</button>
<button
type="button"
onClick={() => {
setShowDialog(true);
setTimeout(() => {
setShowDialog(false);
}, 0);
}}
>
quick
</button>
{dialog}
{drawer}
</div>
Expand Down
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"lint:tsc": "tsc -p tsconfig.json --noEmit",
"now-build": "npm run docs:build",
"prepare": "husky install",
"prepublishOnly": "npm run compile && np --yolo --no-publish",
"prepublishOnly": "npm run compile && rc-np",
"prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
"start": "dumi dev",
"test": "rc-test"
Expand All @@ -49,14 +49,14 @@
]
},
"dependencies": {
"@babel/runtime": "^7.10.1",
"@rc-component/portal": "^1.0.0-8",
"@rc-component/util": "^1.0.1",
"classnames": "^2.2.6",
"rc-motion": "^2.3.0"
"@rc-component/motion": "^1.1.3"
},
"devDependencies": {
"@rc-component/father-plugin": "^2.0.1",
"@rc-component/father-plugin": "^2.0.2",
"@rc-component/np": "^1.0.3",
"@testing-library/jest-dom": "^6.1.6",
"@testing-library/react": "^13.0.0",
"@types/jest": "^29.4.0",
Expand All @@ -77,7 +77,6 @@
"husky": "^8.0.3",
"less": "^4.1.3",
"lint-staged": "^15.2.0",
"np": "^10.0.5",
"prettier": "^3.2.1",
"rc-drawer": "^7.0.0",
"rc-select": "^14.11.0",
Expand Down
4 changes: 2 additions & 2 deletions src/Dialog/Content/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ export interface PanelProps extends Omit<IDialogPropTypes, 'getOpenCount'> {
holderRef?: React.Ref<HTMLDivElement>;
}

export type ContentRef = {
export type PanelRef = {
focus: () => void;
changeActive: (next: boolean) => void;
};

const Panel = React.forwardRef<ContentRef, PanelProps>((props, ref) => {
const Panel = React.forwardRef<PanelRef, PanelProps>((props, ref) => {
const {
prefixCls,
className,
Expand Down
28 changes: 23 additions & 5 deletions src/Dialog/Content/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import * as React from 'react';
import { useRef } from 'react';
import classNames from 'classnames';
import CSSMotion from 'rc-motion';
import CSSMotion from '@rc-component/motion';
import { offset } from '../../util';
import type { PanelProps, ContentRef } from './Panel';
import type { PanelProps, PanelRef } from './Panel';
import Panel from './Panel';
import type { CSSMotionRef } from '@rc-component/motion/es/CSSMotion';

export type CSSMotionStateRef = Pick<CSSMotionRef, 'inMotion' | 'enableMotion'>;

export type ContentRef = PanelRef & CSSMotionStateRef;

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

const dialogRef = useRef<HTMLDivElement>();
const dialogRef = useRef<
{
nativeElement: HTMLElement;
} & CSSMotionStateRef
>();

const panelRef = useRef<PanelRef>();

// ============================== Refs ==============================
React.useImperativeHandle(ref, () => ({
...panelRef.current,
inMotion: dialogRef.current.inMotion,
enableMotion: dialogRef.current.enableMotion,
}));

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

function onPrepare() {
const elementOffset = offset(dialogRef.current);
const elementOffset = offset(dialogRef.current.nativeElement);

setTransformOrigin(
mousePosition && (mousePosition.x || mousePosition.y)
Expand All @@ -62,7 +80,7 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
{({ className: motionClassName, style: motionStyle }, motionRef) => (
<Panel
{...props}
ref={ref}
ref={panelRef}
title={title}
ariaId={ariaId}
prefixCls={prefixCls}
Expand Down
2 changes: 1 addition & 1 deletion src/Dialog/Mask.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import classNames from 'classnames';
import CSSMotion from 'rc-motion';
import CSSMotion from '@rc-component/motion';

export type MaskProps = {
prefixCls: string;
Expand Down
48 changes: 30 additions & 18 deletions src/Dialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import { useEffect, useRef } from 'react';
import type { IDialogPropTypes } from '../IDialogPropTypes';
import { getMotionName } from '../util';
import Content from './Content';
import type { ContentRef } from './Content/Panel';
import Content, { type ContentRef } from './Content';
import Mask from './Mask';
import { warning } from '@rc-component/util/lib/warning';

Expand Down Expand Up @@ -78,27 +77,34 @@
}

// ========================= Events =========================
// Close action will trigger by:
// 1. When hide motion end
// 2. Controlled `open` to `false` immediately after set to `true` which will not trigger motion
function doClose() {
// Clean up scroll bar & focus back
setAnimatedVisible(false);

if (mask && lastOutSideActiveElementRef.current && focusTriggerAfterClose) {
try {
lastOutSideActiveElementRef.current.focus({ preventScroll: true });
} catch (e) {
// Do nothing
}
lastOutSideActiveElementRef.current = null;
}

// Trigger afterClose only when change visible from true to false
if (animatedVisible) {
afterClose?.();
}
}

function onDialogVisibleChanged(newVisible: boolean) {
// Try to focus
if (newVisible) {
focusDialogContent();
} else {
// Clean up scroll bar & focus back
setAnimatedVisible(false);

if (mask && lastOutSideActiveElementRef.current && focusTriggerAfterClose) {
try {
lastOutSideActiveElementRef.current.focus({ preventScroll: true });
} catch (e) {
// Do nothing
}
lastOutSideActiveElementRef.current = null;
}

// Trigger afterClose only when change visible from true to false
if (animatedVisible) {
afterClose?.();
}
doClose();
}
afterOpenChange?.(newVisible);
}
Expand Down Expand Up @@ -154,6 +160,12 @@
if (visible) {
setAnimatedVisible(true);
saveLastOutSideActiveElementRef();
} else if (
animatedVisible &&
contentRef.current.enableMotion() &&
!contentRef.current.inMotion()

Check warning on line 166 in src/Dialog/index.tsx

View check run for this annotation

Codecov / codecov/patch

src/Dialog/index.tsx#L166

Added line #L166 was not covered by tests
) {
doClose();

Check warning on line 168 in src/Dialog/index.tsx

View check run for this annotation

Codecov / codecov/patch

src/Dialog/index.tsx#L168

Added line #L168 was not covered by tests
}
}, [visible]);

Expand Down
14 changes: 9 additions & 5 deletions tests/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/* eslint-disable react/no-render-return-value, max-classes-per-file, func-names, no-console */
import { fireEvent, render, act } from '@testing-library/react';
import { Provider } from 'rc-motion';
import { Provider } from '@rc-component/motion';
import KeyCode from '@rc-component/util/lib/KeyCode';
import React, { cloneElement, useEffect } from 'react';
import type { DialogProps } from '../src';
import Dialog from '../src';

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

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

const { rerender } = render(<Dialog afterClose={afterClose} visible />);
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});

rerender(<Dialog afterClose={afterClose} visible={false} />);
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});

expect(afterClose).toHaveBeenCalledTimes(1);
});
Expand Down
2 changes: 1 addition & 1 deletion tests/ref.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable react/no-render-return-value, max-classes-per-file, func-names, no-console */
import { render } from '@testing-library/react';
import { Provider } from 'rc-motion';
import { Provider } from '@rc-component/motion';
import React from 'react';
import Dialog from '../src';

Expand Down
Loading