fix(editor): Make selector dropdowns usable in Instance AI workflow setup (#33971)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Riqwan Thamir
2026-07-15 16:35:48 +02:00
committed by GitHub
parent 5f3d139648
commit 487594cfcd
7 changed files with 51 additions and 8 deletions
@@ -37,6 +37,16 @@ export const ExecutionDataStoreKey: InjectionKey<
// derived from it via injectWorkflowExecutionStateStore(), so a subtree's
// document scope and execution scope can never diverge.
export const CanvasRenderDataKey: InjectionKey<Ref<CanvasRenderData>> = Symbol('CanvasRenderData');
/**
* Opts resource-locator dropdowns into teleporting to `<body>`. Defaults to
* `false` (stay in the local stacking context, e.g. inside the NDV dialog).
* Hosts that render parameters inside a scroll container overlaid by sticky
* elements (e.g. the Instance AI workflow setup card above the chat input)
* provide `true` so the dropdown isn't painted underneath those overlays.
*/
export const ResourceLocatorDropdownTeleportedKey: InjectionKey<boolean> = Symbol(
'ResourceLocatorDropdownTeleported',
);
export const ChatHubToolContextKey: InjectionKey<boolean> = Symbol('ChatHubToolContext');
export const AiBuilderScrollToBottomKey: InjectionKey<() => void> = Symbol('ChatScrollToBottom');
/**
@@ -1,9 +1,10 @@
<script lang="ts" setup>
import { toRef } from 'vue';
import { provide, toRef } from 'vue';
import type { InstanceAiCredentialFlow, InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import WorkflowSetupWizard from './components/WorkflowSetupWizard.vue';
import WorkflowSetupStatus from './components/WorkflowSetupStatus.vue';
import { provideWorkflowSetupContext } from './composables/useWorkflowSetupContext';
import { ResourceLocatorDropdownTeleportedKey } from '@/app/constants';
const props = defineProps<{
requestId: string;
@@ -13,6 +14,8 @@ const props = defineProps<{
credentialFlow?: InstanceAiCredentialFlow;
}>();
provide(ResourceLocatorDropdownTeleportedKey, true);
const ctx = provideWorkflowSetupContext({
requestId: toRef(props, 'requestId'),
setupRequests: toRef(props, 'setupRequests'),
@@ -951,11 +951,27 @@ async function onQuickConnectSignIn(credentialTypeName: string) {
data-test-id="node-credentials-empty-state"
>
<N8nSelect
ref="selectRefs"
:class="$style.emptySelect"
size="small"
disabled
:disabled="!canCreateCredentials"
:placeholder="i18n.baseText('nodeCredentials.emptyState.noCredentials')"
/>
:popper-class="$style.selectPopper"
>
<template #empty> </template>
<template #footer>
<button
type="button"
data-test-id="node-credentials-select-item-new"
:class="[$style.newCredential]"
:disabled="!canCreateCredentials"
@click="onClickCreateCredential(type)"
>
<N8nIcon size="xsmall" icon="plus" />
{{ NEW_CREDENTIALS_TEXT }}
</button>
</template>
</N8nSelect>
<N8nButton
v-if="canCreateCredentials"
variant="subtle"
@@ -264,7 +264,10 @@ watch(
},
);
onClickOutside(dropdown, () => {
onClickOutside(dropdown, (event) => {
if (event.target instanceof HTMLElement && dropdown.value?.isWithinDropdown(event.target)) {
return;
}
isDropdownVisible.value = false;
});
@@ -636,7 +636,12 @@ onBeforeUnmount(() => {
}
});
onClickOutside(dropdownRef as Ref<VueInstance>, hideResourceDropdown);
onClickOutside(dropdownRef as Ref<VueInstance>, (event) => {
if (event.target instanceof HTMLElement && dropdownRef.value?.isWithinDropdown(event.target)) {
return;
}
hideResourceDropdown();
});
function setWidth() {
if (containerRef.value) {
@@ -6,7 +6,8 @@ import { useI18n } from '@n8n/i18n';
import type { EventBus } from '@n8n/utils/event-bus';
import { createEventBus } from '@n8n/utils/event-bus';
import type { INodeParameterResourceLocator } from 'n8n-workflow';
import { computed, onBeforeUnmount, onMounted, ref, useCssModule, watch } from 'vue';
import { computed, inject, onBeforeUnmount, onMounted, ref, useCssModule, watch } from 'vue';
import { ResourceLocatorDropdownTeleportedKey } from '@/app/constants';
const SEARCH_BAR_HEIGHT_PX = 40;
const SCROLL_MARGIN_PX = 10;
@@ -65,6 +66,8 @@ const debouncedLoadMore = debounce(
const i18n = useI18n();
const $style = useCssModule();
const teleported = inject(ResourceLocatorDropdownTeleportedKey, false);
const hoverIndex = ref(0);
const showHoverUrl = ref(false);
const searchRef = ref<HTMLInputElement>();
@@ -269,7 +272,7 @@ watch(
:width="props.width ? `${props.width}px` : undefined"
:content-class="$style.popover"
:open="props.show"
:teleported="false"
:teleported="teleported"
:enable-scrolling="false"
data-test-id="resource-locator-dropdown"
>
@@ -277,7 +277,10 @@ watch(
},
);
onClickOutside(dropdown, () => {
onClickOutside(dropdown, (event) => {
if (event.target instanceof HTMLElement && dropdown.value?.isWithinDropdown(event.target)) {
return;
}
isDropdownVisible.value = false;
});