From 422227d2fc65e572ac8e67e7d4003492c8981c5b Mon Sep 17 00:00:00 2001 From: Csongor Czezar <44126075+csongorczezar@users.noreply.github.com> Date: Tue, 10 Feb 2026 18:29:44 -0800 Subject: [PATCH] fix: viewport overflow in manager (#7775) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### **Summary** Fixes viewport overflow in version selector dropdown when Manager dialog is positioned near edges **Changes** Removed popover arrow for visual consistency across all positions Implemented dialog boundary detection to constrain popover within Manager viewport **Testing** All existing unit tests pass (17/17) Visually tested across different screen positions ![after-fix-viewport-all-positions](https://github.com/user-attachments/assets/287952f1-eda3-4388-9d6a-8f4316acea7f) ![before-fix-viewport-low](https://github.com/user-attachments/assets/b88dc61d-896b-48af-870f-2b5d52a11a98) ![before-fix-viewport-high](https://github.com/user-attachments/assets/7a39c845-0593-480e-843e-d5da30b48661) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7775-fix-viewport-overflow-in-manager-2d76d73d365081a88c1df2a103c5925e) by [Unito](https://www.unito.io) --- .../components/manager/PackVersionBadge.vue | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/workbench/extensions/manager/components/manager/PackVersionBadge.vue b/src/workbench/extensions/manager/components/manager/PackVersionBadge.vue index 3d22daaeb6..a648fed05f 100644 --- a/src/workbench/extensions/manager/components/manager/PackVersionBadge.vue +++ b/src/workbench/extensions/manager/components/manager/PackVersionBadge.vue @@ -27,9 +27,14 @@ import Popover from 'primevue/popover' import { valid as validSemver } from 'semver' -import { computed, ref, watch } from 'vue' +import { computed, nextTick, ref, watch } from 'vue' import type { components } from '@/types/comfyRegistryTypes' import PackVersionSelectorPopover from '@/workbench/extensions/manager/components/manager/PackVersionSelectorPopover.vue' @@ -65,6 +70,7 @@ const { const { isUpdateAvailable } = usePackUpdateStatus(() => nodePack) const popoverRef = ref() +const lastTargetEl = ref(null) const managerStore = useComfyManagerStore() @@ -87,6 +93,7 @@ const installedVersion = computed(() => { }) const toggleVersionSelector = (event: Event) => { + lastTargetEl.value = event.currentTarget as HTMLElement popoverRef.value.toggle(event) } @@ -94,6 +101,59 @@ const closeVersionSelector = () => { popoverRef.value.hide() } +const fixPopoverIntoViewport = async () => { + await nextTick() + + const popoverEl: HTMLElement | undefined = + popoverRef.value?.container ?? popoverRef.value?.$el + const targetEl = lastTargetEl.value + if (!popoverEl || !targetEl) return + + requestAnimationFrame(() => { + const boundaryEl = + targetEl.closest('.p-dialog') ?? + targetEl.closest('.manager-dialog') ?? + targetEl.closest('[role="dialog"]') ?? + document.documentElement + + const boundary = boundaryEl.getBoundingClientRect() + const targetRect = targetEl.getBoundingClientRect() + + popoverEl.style.transform = '' + + const rect = popoverEl.getBoundingClientRect() + + const M = 8 // keep away from dialog edge + const GAP = 10 + + const spaceBelow = boundary.bottom - targetRect.bottom + const spaceAbove = targetRect.top - boundary.top + + // Decide side (below by default; flip to above if needed) + const placeAbove = spaceBelow < rect.height + GAP && spaceAbove > spaceBelow + + // 1) Top with a GAP (prevents covering trigger) + let top = placeAbove + ? targetRect.top - rect.height - GAP + : targetRect.bottom + GAP + + // Clamp vertically + top = Math.min(top, boundary.bottom - rect.height - M) + top = Math.max(top, boundary.top + M) + + // 2) Left (align to trigger, then clamp) + let left = targetRect.left + left = Math.min(left, boundary.right - rect.width - M) + left = Math.max(left, boundary.left + M) + + // Apply position + popoverEl.style.top = `${Math.round(top)}px` + popoverEl.style.left = `${Math.round(left)}px` + + popoverEl.classList.toggle('p-popover-flipped', placeAbove) + }) +} + // If the card is unselected, automatically close the version selector popover watch( () => isSelected,