Skip to content

Commit 8ef1489

Browse files
committed
Media editor: intercept ESC, nest ConfirmDialog inside Modal
Address review feedback on PR #77730: - Restore ESC-to-discard-dialog: keep shouldCloseOnEsc default (true) and intercept ESC via Modal's onKeyDown when there are pending changes, preventDefault() so Modal's own overlay handler skips the close. - Render ConfirmDialog inside Modal's children so it inherits the ModalContext dismisser tree. As a top-level sibling it would, on mount, request the parent Modal close. Backdrop click remains a no-op when dirty since Modal does not expose a hook to intercept it before the close animation.
1 parent 310bb23 commit 8ef1489

1 file changed

Lines changed: 91 additions & 77 deletions

File tree

  • packages/media-editor/src/components/media-editor-modal

packages/media-editor/src/components/media-editor-modal/index.tsx

Lines changed: 91 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -342,87 +342,101 @@ function MediaEditorModalContent( {
342342
};
343343

344344
return (
345-
<>
346-
<Modal
347-
className="media-editor-modal"
348-
title={ __( 'Edit media' ) }
349-
size="fill"
350-
isDismissible={ false }
351-
shouldCloseOnEsc={ ! hasChanges }
352-
shouldCloseOnClickOutside={ ! hasChanges }
353-
onRequestClose={ handleRequestClose }
354-
headerActions={
355-
<HeaderActions
356-
isSaving={ isSaving }
357-
hasMedia={ !! media }
358-
hasChanges={ hasChanges }
359-
onCancel={ handleRequestClose }
360-
onSave={ handleSave }
361-
/>
345+
<Modal
346+
className="media-editor-modal"
347+
title={ __( 'Edit media' ) }
348+
size="fill"
349+
isDismissible={ false }
350+
shouldCloseOnClickOutside={ ! hasChanges }
351+
onKeyDown={ ( event ) => {
352+
// When there are pending changes, intercept ESC and
353+
// open the confirm dialog ourselves. `preventDefault`
354+
// short-circuits Modal's own ESC-to-close handler on
355+
// the overlay so the modal doesn't animate out before
356+
// the dialog appears.
357+
if (
358+
hasChanges &&
359+
( event.code === 'Escape' || event.key === 'Escape' )
360+
) {
361+
event.preventDefault();
362+
setIsDiscardDialogOpen( true );
362363
}
364+
} }
365+
onRequestClose={ handleRequestClose }
366+
headerActions={
367+
<HeaderActions
368+
isSaving={ isSaving }
369+
hasMedia={ !! media }
370+
hasChanges={ hasChanges }
371+
onCancel={ handleRequestClose }
372+
onSave={ handleSave }
373+
/>
374+
}
375+
>
376+
<MediaEditorProvider
377+
value={ media ?? undefined }
378+
onChange={ handleChange }
379+
settings={ { fields } }
363380
>
364-
<MediaEditorProvider
365-
value={ media ?? undefined }
366-
onChange={ handleChange }
367-
settings={ { fields } }
368-
>
369-
{ ! media ? (
370-
<Spinner />
371-
) : (
372-
<>
373-
<Tabs>
374-
<MediaEditorModalSidebar tabs={ tabs } />
375-
</Tabs>
376-
<InterfaceSkeleton
377-
className="media-editor-modal__skeleton"
378-
content={
379-
<div className="media-editor-modal__canvas">
380-
{ isImage ? (
381-
<MediaEditorCanvas
382-
aspectRatio={ resolveAspectRatio(
383-
aspectRatioValue,
384-
imageAspectRatio
385-
) }
386-
freeformCrop={ freeformCrop }
387-
/>
388-
) : (
389-
<MediaPreview />
390-
) }
391-
</div>
392-
}
393-
footer={
394-
isImage ? (
395-
<MediaEditorToolbar
396-
onReset={ () => {
397-
setAspectRatioValue( '0' );
398-
setFreeformCrop( true );
399-
} }
381+
{ ! media ? (
382+
<Spinner />
383+
) : (
384+
<>
385+
<Tabs>
386+
<MediaEditorModalSidebar tabs={ tabs } />
387+
</Tabs>
388+
<InterfaceSkeleton
389+
className="media-editor-modal__skeleton"
390+
content={
391+
<div className="media-editor-modal__canvas">
392+
{ isImage ? (
393+
<MediaEditorCanvas
394+
aspectRatio={ resolveAspectRatio(
395+
aspectRatioValue,
396+
imageAspectRatio
397+
) }
398+
freeformCrop={ freeformCrop }
400399
/>
401-
) : undefined
402-
}
403-
sidebar={
404-
<ComplementaryArea.Slot scope="media-editor" />
405-
}
406-
/>
407-
</>
408-
) }
409-
</MediaEditorProvider>
410-
</Modal>
411-
<ConfirmDialog
412-
isOpen={ isDiscardDialogOpen }
413-
confirmButtonText={ __( 'Discard' ) }
414-
cancelButtonText={ __( 'Keep editing' ) }
415-
onCancel={ () => setIsDiscardDialogOpen( false ) }
416-
onConfirm={ () => {
417-
setIsDiscardDialogOpen( false );
418-
discardAndClose();
419-
} }
420-
>
421-
{ __(
422-
'Are you sure you want to discard your unsaved changes?'
400+
) : (
401+
<MediaPreview />
402+
) }
403+
</div>
404+
}
405+
footer={
406+
isImage ? (
407+
<MediaEditorToolbar
408+
onReset={ () => {
409+
setAspectRatioValue( '0' );
410+
setFreeformCrop( true );
411+
} }
412+
/>
413+
) : undefined
414+
}
415+
sidebar={
416+
<ComplementaryArea.Slot scope="media-editor" />
417+
}
418+
/>
419+
</>
423420
) }
424-
</ConfirmDialog>
425-
</>
421+
{ /* Rendered inside the parent Modal so it's tracked as
422+
a nested dismisser. As a top-level sibling it would,
423+
on mount, request the parent Modal close. */ }
424+
<ConfirmDialog
425+
isOpen={ isDiscardDialogOpen }
426+
confirmButtonText={ __( 'Discard' ) }
427+
cancelButtonText={ __( 'Keep editing' ) }
428+
onCancel={ () => setIsDiscardDialogOpen( false ) }
429+
onConfirm={ () => {
430+
setIsDiscardDialogOpen( false );
431+
discardAndClose();
432+
} }
433+
>
434+
{ __(
435+
'Are you sure you want to discard your unsaved changes?'
436+
) }
437+
</ConfirmDialog>
438+
</MediaEditorProvider>
439+
</Modal>
426440
);
427441
}
428442

0 commit comments

Comments
 (0)