Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: allows user to opt-out of uncontrolled open changes",
"packageName": "@fluentui/react-dialog",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export type DialogOpenChangeData = {
export type DialogOpenChangeEvent = DialogOpenChangeData['event'];

// @public
export type DialogOpenChangeEventHandler = (event: DialogOpenChangeEvent, data: DialogOpenChangeData) => void;
export type DialogOpenChangeEventHandler = (event: DialogOpenChangeEvent, data: DialogOpenChangeData) => void | boolean;

// @public (undocumented)
export type DialogProps = ComponentProps<Partial<DialogSlots>> & {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/// <reference types="cypress" />
/// <reference types="cypress-real-events" />

import * as React from 'react';
import { mount as mountBase } from '@cypress/react';

Expand Down Expand Up @@ -659,4 +662,40 @@ describe('Dialog', () => {
cy.get('#second-dialog').should('not.exist');
cy.get('#first-dialog').should('not.exist');
});
it('should be able to stop uncontrolled dialog from closing', () => {
mount(
<Dialog
onOpenChange={(e, data) => {
if (data.type === 'escapeKeyDown') {
return false;
}
}}
>
<DialogTrigger disableButtonEnhancement>
<Button id={dialogTriggerOpenId}>Open dialog</Button>
</DialogTrigger>
<DialogSurface>
<DialogBody>
<DialogTitle>Dialog title</DialogTitle>
<DialogContent>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam exercitationem cumque repellendus eaque
est dolor eius expedita nulla ullam? Tenetur reprehenderit aut voluptatum impedit voluptates in natus iure
cumque eaque?
</DialogContent>
<DialogActions>
<DialogTrigger disableButtonEnhancement>
<Button id={dialogTriggerCloseId} appearance="secondary">
Close
</Button>
</DialogTrigger>
<Button appearance="primary">Do Something</Button>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>,
);
cy.get(dialogTriggerOpenSelector).realClick();
cy.focused().realType('{esc}');
cy.get(dialogSurfaceSelector).should('exist');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@ export type DialogModalType = 'modal' | 'non-modal' | 'alert';
/**
* Callback fired when the component changes value from open state.
*
* If a boolean is returned, it will be used to indicate if the opening should be allowed.
* If false, the opening will be prevented.
* If true or undefined, the opening will be allowed.
*
* @param event - a React's Synthetic event or a KeyboardEvent in case of `documentEscapeKeyDown`
* @param data - A data object with relevant information,
* such as open value and type of interaction that created the event
*/
export type DialogOpenChangeEventHandler = (event: DialogOpenChangeEvent, data: DialogOpenChangeData) => void;
export type DialogOpenChangeEventHandler = (event: DialogOpenChangeEvent, data: DialogOpenChangeData) => void | boolean;

export type DialogContextValues = {
dialog: DialogContextValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ export const useDialog_unstable = (props: DialogProps): DialogState => {
});

const requestOpenChange = useEventCallback((data: DialogOpenChangeData) => {
onOpenChange?.(data.event, data);
const isInternalsAllowed = onOpenChange?.(data.event, data) ?? true;

// if user prevents default then do not change state value
// otherwise updates state value and trigger reference to the element that caused the opening
if (!data.event.isDefaultPrevented()) {
if (isInternalsAllowed && !data.event.isDefaultPrevented()) {
setOpen(data.open);
}
});
Expand Down