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:
dante01yoon
2026-05-18 21:23:39 +09:00
parent 30cfec5a83
commit b01a7b5494
4 changed files with 90 additions and 3 deletions

View File

@@ -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;
}

View File

@@ -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)
})
})

View File

@@ -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'

View 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()
}
}