feat(Modal/Popover/Slideover): add close:prevent event (#3958)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
kyyy
2025-04-23 22:38:44 +07:00
committed by GitHub
parent 975331a7b1
commit f486423381
6 changed files with 39 additions and 26 deletions

View File

@@ -276,7 +276,7 @@ This allows you to move the trigger outside of the Modal or remove it entirely.
### Prevent closing ### Prevent closing
Set the `dismissible` prop to `false` to prevent the Modal from being closed when clicking outside of it or pressing escape. Set the `dismissible` prop to `false` to prevent the Modal from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
::component-code ::component-code
--- ---

View File

@@ -183,7 +183,7 @@ In this example, leveraging [`defineShortcuts`](/composables/define-shortcuts),
### Prevent closing ### Prevent closing
Set the `dismissible` prop to `false` to prevent the Popover from being closed when clicking outside of it or pressing escape. Set the `dismissible` prop to `false` to prevent the Popover from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
::component-example ::component-example
--- ---

View File

@@ -275,7 +275,7 @@ This allows you to move the trigger outside of the Slideover or remove it entire
### Prevent closing ### Prevent closing
Set the `dismissible` prop to `false` to prevent the Slideover from being closed when clicking outside of it or pressing escape. Set the `dismissible` prop to `false` to prevent the Slideover from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it.
::component-code ::component-code
--- ---

View File

@@ -55,6 +55,7 @@ export interface ModalProps extends DialogRootProps {
export interface ModalEmits extends DialogRootEmits { export interface ModalEmits extends DialogRootEmits {
'after:leave': [] 'after:leave': []
'close:prevent': []
} }
export interface ModalSlots { export interface ModalSlots {
@@ -97,20 +98,23 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen'
const portalProps = usePortal(toRef(() => props.portal)) const portalProps = usePortal(toRef(() => props.portal))
const contentProps = toRef(() => props.content) const contentProps = toRef(() => props.content)
const contentEvents = computed(() => { const contentEvents = computed(() => {
const events = { const defaultEvents = {
closeAutoFocus: (e: Event) => e.preventDefault() closeAutoFocus: (e: Event) => e.preventDefault()
} }
if (!props.dismissible) { if (!props.dismissible) {
return { const events = ['pointerDownOutside', 'interactOutside', 'escapeKeyDown', 'closeAutoFocus'] as const
pointerDownOutside: (e: Event) => e.preventDefault(), type EventType = typeof events[number]
interactOutside: (e: Event) => e.preventDefault(), return events.reduce((acc, curr) => {
escapeKeyDown: (e: Event) => e.preventDefault(), acc[curr] = (e: Event) => {
...events e.preventDefault()
} emits('close:prevent')
}
return acc
}, {} as Record<EventType, (e: Event) => void>)
} }
return events return defaultEvents
}) })
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.modal || {}) })({ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.modal || {}) })({

View File

@@ -36,7 +36,9 @@ export interface PopoverProps extends PopoverRootProps, Pick<HoverCardRootProps,
ui?: Popover['slots'] ui?: Popover['slots']
} }
export interface PopoverEmits extends PopoverRootEmits {} export interface PopoverEmits extends PopoverRootEmits {
'close:prevent': []
}
export interface PopoverSlots { export interface PopoverSlots {
default(props: { open: boolean }): any default(props: { open: boolean }): any
@@ -72,11 +74,15 @@ const portalProps = usePortal(toRef(() => props.portal))
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8 }) as PopoverContentProps) const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8 }) as PopoverContentProps)
const contentEvents = computed(() => { const contentEvents = computed(() => {
if (!props.dismissible) { if (!props.dismissible) {
return { const events = ['pointerDownOutside', 'interactOutside', 'escapeKeyDown'] as const
pointerDownOutside: (e: Event) => e.preventDefault(), type EventType = typeof events[number]
interactOutside: (e: Event) => e.preventDefault(), return events.reduce((acc, curr) => {
escapeKeyDown: (e: Event) => e.preventDefault() acc[curr] = (e: Event) => {
} e.preventDefault()
emits('close:prevent')
}
return acc
}, {} as Record<EventType, (e: Event) => void>)
} }
return {} return {}

View File

@@ -55,6 +55,7 @@ export interface SlideoverProps extends DialogRootProps {
export interface SlideoverEmits extends DialogRootEmits { export interface SlideoverEmits extends DialogRootEmits {
'after:leave': [] 'after:leave': []
'close:prevent': []
} }
export interface SlideoverSlots { export interface SlideoverSlots {
@@ -98,20 +99,22 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen'
const portalProps = usePortal(toRef(() => props.portal)) const portalProps = usePortal(toRef(() => props.portal))
const contentProps = toRef(() => props.content) const contentProps = toRef(() => props.content)
const contentEvents = computed(() => { const contentEvents = computed(() => {
const events = { const defaultEvents = {
closeAutoFocus: (e: Event) => e.preventDefault() closeAutoFocus: (e: Event) => e.preventDefault()
} }
if (!props.dismissible) { if (!props.dismissible) {
return { const events = ['pointerDownOutside', 'interactOutside', 'escapeKeyDown', 'closeAutoFocus'] as const
pointerDownOutside: (e: Event) => e.preventDefault(), type EventType = typeof events[number]
interactOutside: (e: Event) => e.preventDefault(), return events.reduce((acc, curr) => {
escapeKeyDown: (e: Event) => e.preventDefault(), acc[curr] = (e: Event) => {
...events e.preventDefault()
} emits('close:prevent')
}
return acc
}, {} as Record<EventType, (e: Event) => void>)
} }
return events return defaultEvents
}) })
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.slideover || {}) })({ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.slideover || {}) })({