fix: nodes2.0's combo widget does not respect "scale menus" setting

This commit is contained in:
Rizumu Ayaka
2025-12-15 22:40:29 +08:00
parent c414635ead
commit c69bc74713
2 changed files with 80 additions and 10 deletions

View File

@@ -1,6 +1,8 @@
import type { HintedString } from '@primevue/core'
import { computed } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
/**
* Options for configuring transform-compatible overlay props
*/
@@ -21,8 +23,11 @@ interface TransformCompatOverlayOptions {
*
* Vue nodes use CSS transforms for positioning/scaling. PrimeVue overlay
* components (Select, MultiSelect, TreeSelect, etc.) teleport to document
* body by default, breaking transform inheritance. This composable provides
* the necessary props to keep overlays within their component elements.
* body by default, breaking transform inheritance.
*
* When LiteGraph.ContextMenu.Scaling is enabled, overlays are appended to
* 'self' to inherit canvas transforms and scale with the canvas. When disabled,
* overlays are appended to 'body' to maintain fixed size regardless of canvas zoom.
*
* @param overrides - Optional overrides for specific use cases
* @returns Computed props object to spread on PrimeVue overlay components
@@ -41,8 +46,15 @@ interface TransformCompatOverlayOptions {
export function useTransformCompatOverlayProps(
overrides: TransformCompatOverlayOptions = {}
) {
return computed(() => ({
appendTo: 'self' as const,
...overrides
}))
const settingStore = useSettingStore()
return computed(() => {
const contextMenuScaling = settingStore.get('LiteGraph.ContextMenu.Scaling')
const appendTo = contextMenuScaling ? ('self' as const) : ('body' as const)
return {
appendTo,
...overrides
}
})
}

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { refDebounced } from '@vueuse/core'
import type { HintedString } from '@primevue/core'
import { onClickOutside, refDebounced } from '@vueuse/core'
import Popover from 'primevue/popover'
import { computed, ref, useTemplateRef, watch } from 'vue'
@@ -42,6 +43,15 @@ interface Props {
items: DropdownItem[],
onCleanup: (cleanupFn: () => void) => void
) => Promise<DropdownItem[]>
/**
* Where to append the dropdown overlay. 'self' keeps it within component
* for transform inheritance (scales with canvas), 'body' keeps fixed size.
*
* When Popover handles appendTo set to 'self', it still tries to
* apply offset to the component. Therefore, if in 'self' mode,
* render the dropdown directly inside the component.
*/
appendTo?: HintedString<'body' | 'self'> | HTMLElement
}
const props = withDefaults(defineProps<Props>(), {
@@ -52,7 +62,8 @@ const props = withDefaults(defineProps<Props>(), {
filterOptions: () => [],
sortOptions: () => getDefaultSortOptions(),
isSelected: (selected, item, _index) => selected.has(item.id),
searcher: defaultSearcher
searcher: defaultSearcher,
appendTo: 'body'
})
const selected = defineModel<Set<SelectedKey>>('selected', {
@@ -75,6 +86,7 @@ const isQuerying = ref(false)
const toastStore = useToastStore()
const popoverRef = ref<InstanceType<typeof Popover>>()
const triggerRef = useTemplateRef('triggerRef')
const dropdownContainerRef = useTemplateRef('dropdownContainerRef')
const isOpen = ref(false)
const maxSelectable = computed(() => {
@@ -132,23 +144,38 @@ const sortedItems = computed(() => {
return selectedSorter.value({ items: filteredItems.value }) || []
})
// Close dropdown when clicking outside (only for appendTo === 'self' mode)
onClickOutside(
dropdownContainerRef,
() => {
if (props.appendTo === 'self' && isOpen.value) {
closeDropdown()
}
},
{ ignore: [triggerRef] }
)
function internalIsSelected(item: DropdownItem, index: number): boolean {
return props.isSelected?.(selected.value, item, index) ?? false
}
const toggleDropdown = (event: Event) => {
if (props.disabled) return
// In 'appendTo === "self"' mode, the Popover component is not used.
// Therefore, set isOpen directly.
if (popoverRef.value && triggerRef.value) {
popoverRef.value.toggle(event, triggerRef.value)
isOpen.value = !isOpen.value
}
isOpen.value = !isOpen.value
}
const closeDropdown = () => {
// In 'appendTo === "self"' mode, the Popover component is not used.
// Therefore, set isOpen directly.
if (popoverRef.value) {
popoverRef.value.hide()
isOpen.value = false
}
isOpen.value = false
}
function handleFileChange(event: Event) {
@@ -200,10 +227,41 @@ function handleSelection(item: DropdownItem, index: number) {
@select-click="toggleDropdown"
@file-change="handleFileChange"
/>
<template v-if="appendTo === 'self'">
<!--
When Popover handles appendTo set to 'self', it still tries to
apply offset to the component. Therefore, if in 'self' mode,
render the dropdown directly inside the div element.
-->
<div
v-if="isOpen"
ref="dropdownContainerRef"
class="absolute z-1001 top-8 left-0"
>
<FormDropdownMenu
v-model:filter-selected="filterSelected"
v-model:layout-mode="layoutMode"
v-model:sort-selected="sortSelected"
v-model:search-query="searchQuery"
:filter-options="filterOptions"
:sort-options="sortOptions"
:disabled="disabled"
:is-querying="isQuerying"
:items="sortedItems"
:is-selected="internalIsSelected"
:max-selectable="maxSelectable"
@close="closeDropdown"
@item-click="handleSelection"
/>
</div>
</template>
<Popover
v-else
ref="popoverRef"
:dismissable="true"
:close-on-escape="true"
:append-to="appendTo"
unstyled
:pt="{
root: {