mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 14:45:36 +00:00
fix(dialog): keep PrimeVue overlays clickable inside Reka modal Settings
Reka's modal DialogContent sets body { pointer-events: none } via
DismissableLayer while the dialog is open. Body-portaled PrimeVue
overlays (Select, ColorPicker, Popover, Autocomplete) inherit that and
become unclickable, so Settings entries like Language or Model library
name format break after the Phase 3 flip. Reka also fires
pointer-down-outside for clicks on those overlays and would auto-dismiss
the Settings dialog mid-interaction.
- design-system: opt PrimeVue overlay classes back into pointer-events
- GlobalDialog: prevent dismiss when pointer-down originates inside any
PrimeVue overlay, extracted as rekaPrimeVueBridge for unit tests
- Tests cover each overlay class plus the dismissableMask fallback
This commit is contained in:
@@ -1894,3 +1894,17 @@ audio.comfy-audio.empty-audio-widget {
|
||||
300% 14px;
|
||||
background-attachment: local, local, scroll, scroll;
|
||||
}
|
||||
|
||||
/*
|
||||
PrimeVue overlays teleport to body. When a Reka modal dialog is open it sets
|
||||
body { pointer-events: none } via DismissableLayer, which propagates to the
|
||||
body-portaled overlays and makes them unclickable. PrimeVue's own Dialog
|
||||
sets pointer-events inline, but Select / ColorPicker / Popover / Autocomplete
|
||||
overlays do not, so they need to opt in here.
|
||||
*/
|
||||
.p-select-overlay,
|
||||
.p-colorpicker-panel,
|
||||
.p-popover,
|
||||
.p-autocomplete-overlay {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { defineComponent, h } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import GlobalDialog from '@/components/dialog/GlobalDialog.vue'
|
||||
import { onRekaPointerDownOutside } from '@/components/dialog/rekaPrimeVueBridge'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const i18n = createI18n({
|
||||
@@ -190,3 +191,53 @@ describe('GlobalDialog Reka parity with PrimeVue', () => {
|
||||
expect(store.isDialogOpen('reka-esc-blocked')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('shouldPreventRekaDismiss', () => {
|
||||
function makeEvent(target: Element | null) {
|
||||
let prevented = false
|
||||
return {
|
||||
detail: { originalEvent: { target } },
|
||||
preventDefault: () => {
|
||||
prevented = true
|
||||
},
|
||||
get defaultPrevented() {
|
||||
return prevented
|
||||
}
|
||||
} as unknown as CustomEvent<{ originalEvent: PointerEvent }> & {
|
||||
defaultPrevented: boolean
|
||||
}
|
||||
}
|
||||
|
||||
it.for([
|
||||
'p-select-overlay',
|
||||
'p-colorpicker-panel',
|
||||
'p-popover',
|
||||
'p-autocomplete-overlay',
|
||||
'p-overlay-mask',
|
||||
'p-dialog'
|
||||
])('prevents dismiss when target is inside %s', (className) => {
|
||||
const overlay = document.createElement('div')
|
||||
overlay.className = className
|
||||
const inner = document.createElement('button')
|
||||
overlay.appendChild(inner)
|
||||
document.body.appendChild(overlay)
|
||||
|
||||
const event = makeEvent(inner)
|
||||
onRekaPointerDownOutside({ dismissableMask: undefined }, event)
|
||||
|
||||
expect(event.defaultPrevented).toBe(true)
|
||||
overlay.remove()
|
||||
})
|
||||
|
||||
it('allows dismiss when target is outside any PrimeVue overlay', () => {
|
||||
const event = makeEvent(document.body)
|
||||
onRekaPointerDownOutside({ dismissableMask: undefined }, event)
|
||||
expect(event.defaultPrevented).toBe(false)
|
||||
})
|
||||
|
||||
it('prevents dismiss when dismissableMask is false even outside an overlay', () => {
|
||||
const event = makeEvent(document.body)
|
||||
onRekaPointerDownOutside({ dismissableMask: false }, event)
|
||||
expect(event.defaultPrevented).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
e.preventDefault()
|
||||
"
|
||||
@pointer-down-outside="
|
||||
(e) =>
|
||||
item.dialogComponentProps.dismissableMask === false &&
|
||||
e.preventDefault()
|
||||
(e) => onRekaPointerDownOutside(item.dialogComponentProps, e)
|
||||
"
|
||||
@mousedown="() => dialogStore.riseDialog({ key: item.key })"
|
||||
>
|
||||
@@ -115,6 +113,7 @@ import DialogMaximize from '@/components/ui/dialog/DialogMaximize.vue'
|
||||
import DialogOverlay from '@/components/ui/dialog/DialogOverlay.vue'
|
||||
import DialogPortal from '@/components/ui/dialog/DialogPortal.vue'
|
||||
import DialogTitle from '@/components/ui/dialog/DialogTitle.vue'
|
||||
import { onRekaPointerDownOutside } from '@/components/dialog/rekaPrimeVueBridge'
|
||||
import type { DialogInstance } from '@/stores/dialogStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
|
||||
23
src/components/dialog/rekaPrimeVueBridge.ts
Normal file
23
src/components/dialog/rekaPrimeVueBridge.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// PrimeVue overlays (Select, ColorPicker, Popover, Autocomplete, stacked
|
||||
// PrimeVue Dialogs) teleport to body. Reka treats clicks on body-portaled
|
||||
// elements as outside its dialog and would auto-dismiss on the first
|
||||
// interaction, tearing the overlay down mid-interaction. Treat any
|
||||
// PrimeVue overlay click as inside.
|
||||
const PRIMEVUE_OVERLAY_SELECTORS =
|
||||
'.p-select-overlay, .p-colorpicker-panel, .p-popover, .p-autocomplete-overlay, .p-overlay, .p-overlay-mask, .p-dialog'
|
||||
|
||||
type PointerDownOutsideEvent = CustomEvent<{ originalEvent: PointerEvent }>
|
||||
|
||||
export function onRekaPointerDownOutside(
|
||||
options: { dismissableMask?: boolean },
|
||||
event: PointerDownOutsideEvent
|
||||
) {
|
||||
const target = event.detail.originalEvent.target
|
||||
if (target instanceof Element && target.closest(PRIMEVUE_OVERLAY_SELECTORS)) {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
if (options.dismissableMask === false) {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user