Skip to content
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
26 changes: 26 additions & 0 deletions packages/design-system/src/components/OcModal/OcModal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,21 @@ describe('OcModal', () => {
expect(wrapper.emitted('cancel')).toBeTruthy()
})

it('emits cancel when pressing escape after focus left the dialog', async () => {
const wrapper = mountModal()
await wrapper.find('.oc-modal-background').trigger('click')
pressEscapeFromBody()

expect(wrapper.emitted('cancel')).toBeTruthy()
})

it('does not emit cancel from the document while inactive', () => {
const wrapper = mountModal({ active: false })
pressEscapeFromBody()

expect(wrapper.emitted('cancel')).toBeFalsy()
})

// the cancel button is disabled while loading, so escape has to be blocked as
// well. Otherwise an in-flight handler gets discarded without its result ever
// reaching the user.
Expand All @@ -119,6 +134,13 @@ describe('OcModal', () => {
await wrapper.find('[role="dialog"]').trigger('keydown.esc')
expect(wrapper.emitted('cancel')).toBeFalsy()
})

it('does not emit cancel from the document while loading', () => {
const wrapper = mountModal({ isLoading: true })
pressEscapeFromBody()

expect(wrapper.emitted('cancel')).toBeFalsy()
})
})

it('displays loading state', async () => {
Expand Down Expand Up @@ -164,3 +186,7 @@ describe('OcModal', () => {
expect(wrapper.html()).toMatchSnapshot()
})
})

const pressEscapeFromBody = () => {
document.body.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
}
17 changes: 16 additions & 1 deletion packages/design-system/src/components/OcModal/OcModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
class="oc-modal-background fixed left-0 top-0 z-[var(--z-index-modal)] flex items-center justify-center flex-row flex-wrap size-full"
:class="{ 'bg-black/40': active }"
>
<focus-trap :active="true" :initial-focus="initialFocusRef" :tabbable-options="tabbableOptions">
<focus-trap
:active="active"
:initial-focus="initialFocusRef"
:tabbable-options="tabbableOptions"
>
<div
:id="elementId"
:class="classes"
Expand Down Expand Up @@ -113,6 +117,7 @@ import OcTextInput from '../OcTextInput/OcTextInput.vue'
import { FocusTargetOrFalse, FocusTrapTabbableOptions } from 'focus-trap'
import { ContextualHelperData, isComposingEvent } from '../../helpers'
import { useGettext } from 'vue3-gettext'
import { useEventListener } from '@vueuse/core'

export interface Props {
/**
Expand Down Expand Up @@ -357,6 +362,16 @@ const cancelModalAction = () => {
emit('cancel')
}

const onDocumentKeydown = (event: KeyboardEvent) => {
if (!active || event.key !== 'Escape') {
return
}
event.stopPropagation()
cancelModalAction()
}

useEventListener(document, 'keydown', onDocumentKeydown)

const confirm = () => {
if (buttonConfirmDisabled || inputError) {
return
Expand Down
Loading