Skip to content

update destroyOnClose to destroyOnHidden #475

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 4 commits into from
May 19, 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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ coverage
/android/
yarn.lock
package-lock.json
pnpm-lock.yaml
.storybook
.doc

Expand All @@ -48,4 +49,4 @@ package-lock.json
.dumi/tmp
.dumi/tmp-production

bun.lockb
bun.lockb
59 changes: 28 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,34 +49,34 @@ ReactDOM.render(

### rc-dialog

| Name | Type | Default | Description | Version |
| ---------------------- | ------------------------------ | --------- | ------------------------------------------------------------------------------- | ------- |
| prefixCls | String | rc-dialog | The dialog dom node's prefixCls | |
| className | String | | additional className for dialog | |
| classNames | { header?: string; body?: string; footer?: string; mask?: string; content?: string; wrapper?: string; } | | pass className to target area | |
| styles | { header?: CSSProperties; body?: CSSProperties; footer?: CSSProperties; mask?: CSSProperties; content?: CSSProperties; wrapper?: CSSProperties; } | | pass styles to target area | |
| style | Object | {} | Root style for dialog element.Such as width, height | |
| zIndex | Number | | | |
| visible | Boolean | false | current dialog's visible status | |
| animation | String | | part of dialog animation css class name | |
| maskAnimation | String | | part of dialog's mask animation css class name | |
| transitionName | String | | dialog animation css class name | |
| maskTransitionName | String | | mask animation css class name | |
| title | String\|React.Element | | Title of the dialog | |
| footer | React.Element | | footer of the dialog | |
| closable | Boolean \| ({ closeIcon?: React.ReactNode; disabled?: boolean } & React.AriaAttributes | true | whether show close button | |
| mask | Boolean | true | whether show mask | |
| maskClosable | Boolean | true | whether click mask to close | |
| keyboard | Boolean | true | whether support press esc to close | |
| mousePosition | {x:number,y:number} | | set pageX and pageY of current mouse(it will cause transform origin to be set). | |
| onClose | function() | | called when click close button or mask | |
| afterClose | function() | | called when close animation end | |
| getContainer | function(): HTMLElement | | to determine where Dialog will be mounted | |
| destroyOnClose | Boolean | false | to unmount child compenents on onClose | |
| closeIcon | ReactNode | | specific the close icon. | |
| forceRender | Boolean | false | Create dialog dom node before dialog first show | |
| focusTriggerAfterClose | Boolean | true | focus trigger element when dialog closed | |
| modalRender | (node: ReactNode) => ReactNode | | Custom modal content render | 8.3.0 |
| Name | Type | Default | Description | Version |
| --- | --- | --- | --- | --- |
| prefixCls | String | rc-dialog | The dialog dom node's prefixCls | |
| className | String | | additional className for dialog | |
| classNames | { header?: string; body?: string; footer?: string; mask?: string; content?: string; wrapper?: string; } | | pass className to target area | |
| styles | { header?: CSSProperties; body?: CSSProperties; footer?: CSSProperties; mask?: CSSProperties; content?: CSSProperties; wrapper?: CSSProperties; } | | pass styles to target area | |
| style | Object | {} | Root style for dialog element.Such as width, height | |
| zIndex | Number | | | |
| visible | Boolean | false | current dialog's visible status | |
| animation | String | | part of dialog animation css class name | |
| maskAnimation | String | | part of dialog's mask animation css class name | |
| transitionName | String | | dialog animation css class name | |
| maskTransitionName | String | | mask animation css class name | |
| title | String\|React.Element | | Title of the dialog | |
| footer | React.Element | | footer of the dialog | |
| closable | Boolean \| ({ closeIcon?: React.ReactNode; disabled?: boolean } & React.AriaAttributes | true | whether show close button | |
| mask | Boolean | true | whether show mask | |
| maskClosable | Boolean | true | whether click mask to close | |
| keyboard | Boolean | true | whether support press esc to close | |
| mousePosition | {x:number,y:number} | | set pageX and pageY of current mouse(it will cause transform origin to be set). | |
| onClose | function() | | called when click close button or mask | |
| afterClose | function() | | called when close animation end | |
| getContainer | function(): HTMLElement | | to determine where Dialog will be mounted | |
| destroyOnHidden | Boolean | false | to unmount child compenents on onClose | |
| closeIcon | ReactNode | | specific the close icon. | |
| forceRender | Boolean | false | Create dialog dom node before dialog first show | |
| focusTriggerAfterClose | Boolean | true | focus trigger element when dialog closed | |
| modalRender | (node: ReactNode) => ReactNode | | Custom modal content render | 8.3.0 |

## Development

Expand All @@ -85,8 +85,6 @@ npm install
npm start
```



## Test Case

```
Expand All @@ -102,7 +100,6 @@ npm run coverage

open coverage/ dir


## License

rc-dialog is released under the MIT license.
12 changes: 6 additions & 6 deletions docs/examples/ant-design.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const MyControl: React.FC = () => {
const [visible2, setVisible2] = React.useState(false);
const [visible3, setVisible3] = React.useState(false);
const [width, setWidth] = React.useState(600);
const [destroyOnClose, setDestroyOnClose] = React.useState(false);
const [destroyOnHidden, setDestroyOnHidden] = React.useState(false);
const [center, setCenter] = React.useState(false);
const [mousePosition, setMousePosition] = React.useState({ x: null, y: null });
const [useIcon, setUseIcon] = React.useState(false);
Expand Down Expand Up @@ -60,8 +60,8 @@ const MyControl: React.FC = () => {
setVisible3(false);
};

const onDestroyOnCloseChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setDestroyOnClose(e.target.checked);
const onDestroyOnHiddenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setDestroyOnHidden(e.target.checked);
};

const onForceRenderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -97,7 +97,7 @@ const MyControl: React.FC = () => {
style={style}
title="dialog1"
mousePosition={mousePosition}
destroyOnClose={destroyOnClose}
destroyOnHidden={destroyOnHidden}
closeIcon={useIcon ? getSvg(clearPath, {}, true) : undefined}
forceRender={forceRender}
focusTriggerAfterClose={false}
Expand Down Expand Up @@ -210,8 +210,8 @@ const MyControl: React.FC = () => {
</button>
&nbsp;
<label>
destroy on close:
<input type="checkbox" checked={destroyOnClose} onChange={onDestroyOnCloseChange} />
destroy on hidden:
<input type="checkbox" checked={destroyOnHidden} onChange={onDestroyOnHiddenChange} />
</label>
&nbsp;
<label>
Expand Down
12 changes: 6 additions & 6 deletions docs/examples/bootstrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const InnerRender: React.FC = () => {

const MyControl: React.FC = () => {
const [visible, setVisible] = React.useState(false);
const [destroyOnClose, setDestroyOnClose] = React.useState(false);
const [destroyOnHidden, setDestroyOnHidden] = React.useState(false);

const onClick = () => {
setVisible(true);
Expand All @@ -21,14 +21,14 @@ const MyControl: React.FC = () => {
setVisible(false);
};

const onDestroyOnCloseChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setDestroyOnClose(e.target.checked);
const onDestroyOnHiddenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setDestroyOnHidden(e.target.checked);
};

const dialog = (
<Dialog
visible={visible}
destroyOnClose={destroyOnClose}
destroyOnHidden={destroyOnHidden}
animation="slide-fade"
maskAnimation="fade"
onClose={onClose}
Expand Down Expand Up @@ -120,8 +120,8 @@ const MyControl: React.FC = () => {
</button>
&nbsp;
<label>
destroy on close:
<input type="checkbox" checked={destroyOnClose} onChange={onDestroyOnCloseChange} />
destroy on hidden:
<input type="checkbox" checked={destroyOnHidden} onChange={onDestroyOnHiddenChange} />
</label>
</p>
{dialog}
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,16 @@
},
"devDependencies": {
"@rc-component/drawer": "^1.0.0",
"@rc-component/select": "^1.0.0",
"@rc-component/father-plugin": "^2.0.2",
"@rc-component/np": "^1.0.3",
"@rc-component/select": "^1.0.0",
"@testing-library/jest-dom": "^6.1.6",
"@testing-library/react": "^13.0.0",
"@types/jest": "^29.4.0",
"@types/keyv": "3.1.4",
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8",
"@types/node": "^22.15.18",
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.5",
"@umijs/fabric": "^3.0.0",
"bootstrap": "^4.3.1",
"cheerio": "1.0.0-rc.12",
Expand Down
2 changes: 1 addition & 1 deletion src/Dialog/Content/MemoChildren.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export type MemoChildrenProps = {
};

export default React.memo(
({ children }: MemoChildrenProps) => children as React.ReactElement,
({ children }: MemoChildrenProps) => children as React.ReactElement<any>,
(_, { shouldUpdate }) => !shouldUpdate,
);
16 changes: 12 additions & 4 deletions src/Dialog/Content/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@ import type { IDialogPropTypes } from '../../IDialogPropTypes';
import MemoChildren from './MemoChildren';
import pickAttrs from '@rc-component/util/lib/pickAttrs';

const sentinelStyle = { width: 0, height: 0, overflow: 'hidden', outline: 'none' };
const entityStyle = { outline: 'none' };
const sentinelStyle: React.CSSProperties = {
width: 0,
height: 0,
overflow: 'hidden',
outline: 'none',
};

const entityStyle: React.CSSProperties = {
outline: 'none',
};

export interface PanelProps extends Omit<IDialogPropTypes, 'getOpenCount'> {
prefixCls: string;
Expand Down Expand Up @@ -53,8 +61,8 @@ const Panel = React.forwardRef<PanelRef, PanelProps>((props, ref) => {

const mergedRef = useComposeRef(holderRef, panelRef);

const sentinelStartRef = useRef<HTMLDivElement>();
const sentinelEndRef = useRef<HTMLDivElement>();
const sentinelStartRef = useRef<HTMLDivElement>(null);
const sentinelEndRef = useRef<HTMLDivElement>(null);

React.useImperativeHandle(ref, () => ({
focus: () => {
Expand Down
16 changes: 7 additions & 9 deletions src/Dialog/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,16 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
className,
visible,
forceRender,
destroyOnClose,
destroyOnHidden,
motionName,
ariaId,
onVisibleChanged,
mousePosition,
} = props;

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

const panelRef = useRef<PanelRef>();
const panelRef = useRef<PanelRef>(null);

// ============================== Refs ==============================
React.useImperativeHandle(ref, () => ({
Expand Down Expand Up @@ -74,7 +70,7 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
onEnterPrepare={onPrepare}
forceRender={forceRender}
motionName={motionName}
removeOnLeave={destroyOnClose}
removeOnLeave={destroyOnHidden}
ref={dialogRef}
>
{({ className: motionClassName, style: motionStyle }, motionRef) => (
Expand All @@ -93,6 +89,8 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
);
});

Content.displayName = 'Content';
if (process.env.NODE_ENV !== 'production') {
Content.displayName = 'Content';
}

export default Content;
8 changes: 4 additions & 4 deletions src/Dialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ const Dialog: React.FC<IDialogPropTypes> = (props) => {
}
}

const lastOutSideActiveElementRef = useRef<HTMLElement>();
const wrapperRef = useRef<HTMLDivElement>();
const contentRef = useRef<ContentRef>();
const lastOutSideActiveElementRef = useRef<HTMLElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<ContentRef>(null);

const [animatedVisible, setAnimatedVisible] = React.useState(visible);

Expand Down Expand Up @@ -115,7 +115,7 @@ const Dialog: React.FC<IDialogPropTypes> = (props) => {

// >>> Content
const contentClickRef = useRef(false);
const contentTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const contentTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null);

// We need record content click incase content popup out of dialog
const onContentMouseDown: React.MouseEventHandler = () => {
Expand Down
10 changes: 6 additions & 4 deletions src/DialogWrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const DialogWrap: React.FC<IDialogPropTypes> = (props) => {
visible,
getContainer,
forceRender,
destroyOnClose = false,
destroyOnHidden = false,
afterClose,
panelRef,
} = props;
Expand All @@ -33,7 +33,7 @@ const DialogWrap: React.FC<IDialogPropTypes> = (props) => {
}, [visible]);

// Destroy on close will remove wrapped div
if (!forceRender && destroyOnClose && !animatedVisible) {
if (!forceRender && destroyOnHidden && !animatedVisible) {
return null;
}

Expand All @@ -47,7 +47,7 @@ const DialogWrap: React.FC<IDialogPropTypes> = (props) => {
>
<Dialog
{...props}
destroyOnClose={destroyOnClose}
destroyOnHidden={destroyOnHidden}
afterClose={() => {
afterClose?.();
setAnimatedVisible(false);
Expand All @@ -58,6 +58,8 @@ const DialogWrap: React.FC<IDialogPropTypes> = (props) => {
);
};

DialogWrap.displayName = 'Dialog';
if (process.env.NODE_ENV !== 'production') {
DialogWrap.displayName = 'Dialog';
}

export default DialogWrap;
2 changes: 1 addition & 1 deletion src/IDialogPropTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type IDialogPropTypes = {
closable?: boolean | ({ closeIcon?: React.ReactNode; disabled?: boolean } & React.AriaAttributes);
maskClosable?: boolean;
visible?: boolean;
destroyOnClose?: boolean;
destroyOnHidden?: boolean;
mousePosition?: {
x: number;
y: number;
Expand Down
14 changes: 7 additions & 7 deletions tests/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ describe('dialog', () => {
expect(onClose).toHaveBeenCalledTimes(1);
});

describe('destroyOnClose', () => {
describe('destroyOnHidden', () => {
it('default is false', () => {
const { rerender } = render(
<Dialog visible>
Expand All @@ -128,8 +128,8 @@ describe('dialog', () => {
});

it('destroy on hide should unmount child components on close', () => {
const Demo = (props?: Partial<DialogProps>) => (
<Dialog destroyOnClose {...props}>
const Demo: React.FC<Partial<DialogProps>> = (props) => (
<Dialog destroyOnHidden {...props}>
<input className="test-input" />
</Dialog>
);
Expand Down Expand Up @@ -384,7 +384,7 @@ describe('dialog', () => {
render(
<Dialog
visible
modalRender={(node: React.ReactElement) =>
modalRender={(node: React.ReactElement<any>) =>
cloneElement(node, { ...node.props, style: { background: '#1890ff' } })
}
/>,
Expand All @@ -394,7 +394,7 @@ describe('dialog', () => {

describe('focusTriggerAfterClose', () => {
it('should focus trigger after close dialog', () => {
const Demo = () => {
const Demo: React.FC = () => {
const [visible, setVisible] = React.useState(false);
return (
<>
Expand All @@ -417,9 +417,9 @@ describe('dialog', () => {
});

it('should focus trigger after close dialog when contains focusable element', () => {
const Demo = () => {
const Demo: React.FC = () => {
const [visible, setVisible] = React.useState(false);
const inputRef = React.useRef(null);
const inputRef = React.useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
Expand Down
Loading