From 32e47d2112eff765bec2156f66166ffa74fcd65e Mon Sep 17 00:00:00 2001 From: Sebastien Powell Date: Mon, 5 Oct 2026 15:35:31 +0100 Subject: [PATCH] WIP --- .../src/v2/components/Select/Select.test.ts | 19 +++++++++++++++++++ .../src/v2/components/Select/Select.types.ts | 8 ++++++++ .../src/v2/components/Select/Select.vue | 3 ++- .../v2/components/Select/component-select.md | 1 + 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.test.ts b/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.test.ts index 26984c903ed..f9aa1452e55 100644 --- a/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.test.ts +++ b/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.test.ts @@ -847,4 +847,23 @@ describe('v2/components/Select', () => { }); }); }); + + describe('portal', () => { + it('should keep the menu in place when teleported is false', async () => { + const wrapper = render(Select, { + props: { + items: [ + { value: 'Option 1', label: 'Option 1' }, + { value: 'Option 2', label: 'Option 2' }, + ], + defaultOpen: true, + teleported: false, + }, + }); + + const { popover } = await getPopoverContainer(wrapper.getByTestId('select-trigger')); + expect(wrapper.container.contains(popover)).toBe(true); + expect(popover.parentElement).not.toBe(document.body); + }); + }); }); diff --git a/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.types.ts b/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.types.ts index 59e0b05dff6..add96b5b052 100644 --- a/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.types.ts +++ b/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.types.ts @@ -79,6 +79,14 @@ export type SelectProps = Omit< /** Additional CSS class(es) applied to the dropdown content container (portaled). */ contentClass?: string; + + /** + * Render the menu in a portal on `document.body`. + * Set `false` inside a dialog so the menu stays in the dialog. + * A portaled menu sits outside a focus trap, and a dialog can treat a click on it as an outside click. + * @defaultValue true + */ + teleported?: boolean; }; export type SelectEmits = Omit & { diff --git a/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.vue b/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.vue index 9e38d5c2032..c28ae332f0e 100644 --- a/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.vue +++ b/packages/frontend/@n8n/design-system/src/v2/components/Select/Select.vue @@ -58,6 +58,7 @@ const props = withDefaults(defineProps>(), { sideOffset: 4, align: 'start', clearable: false, + teleported: true, }); const emit = defineEmits>(); defineSlots>(); @@ -406,7 +407,7 @@ function resolveDisplayValue(value: unknown): string | undefined { /> - +