From fb13353a67e136e402e27e06ef6abda74c0b564d Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Fri, 29 May 2026 15:07:54 +0530 Subject: [PATCH] fix: prevent layout shift when ComboBox dropdown opens inside modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two root causes of the shift were identified and fixed: 1. Melt UI's `usePopper` applies `position: absolute` to the dropdown via floating-ui after a `tick()` delay. During that one frame the `ul` was briefly in normal document flow, momentarily growing the `` element and causing a reflow. Pre-setting `[data-melt-combobox-menu] { position: absolute }` in base.css removes the element from flow immediately at mount. 2. Melt UI's ComboBox has `preventScroll: true` by default. On open it calls `removeScroll()` which sets `body.overflow: hidden` and adds compensatory `padding-right` for the scrollbar width — a body reflow that shifts the dialog. Pre-setting `data-melt-scroll-lock` on the body in `modal.svelte` tells Melt UI the lock is already active so it returns early without touching the body. Also fix `on:search` on `Input.ComboBox` (which the component never dispatches) by replacing it with native `oninput`/`onfocusin` handlers on the wrapper `
`, so typed text actually triggers debounced server-side project search. Projects are now ordered newest-first (`Query.orderDesc('')`) matching the org projects page. Co-Authored-By: Claude Sonnet 4.5 --- src/lib/components/modal.svelte | 10 ++++++++++ src/lib/profiles/css/base.css | 12 ++++++++++++ .../projectAccessSelector.svelte | 18 ++++++++++++++---- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/lib/components/modal.svelte b/src/lib/components/modal.svelte index 062639340..ace5aa01c 100644 --- a/src/lib/components/modal.svelte +++ b/src/lib/components/modal.svelte @@ -27,6 +27,16 @@ $: if (error) { alert?.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' }); } + + // Melt UI's ComboBox (and other listbox-based components) call removeScroll() + // when they open, which adds overflow:hidden + compensatory padding-right + $: if (typeof document !== 'undefined') { + if (show) { + document.body.setAttribute('data-melt-scroll-lock', ''); + } else { + document.body.removeAttribute('data-melt-scroll-lock'); + } + }
diff --git a/src/lib/profiles/css/base.css b/src/lib/profiles/css/base.css index ae0218c27..b88c0e1c1 100644 --- a/src/lib/profiles/css/base.css +++ b/src/lib/profiles/css/base.css @@ -4,3 +4,15 @@ scrollbar-width: thin; position: relative; } + +/* + * Melt UI applies `position: absolute` to the combobox menu via floating-ui, + * but only after `tick()` — a one-frame delay. During that initial frame the + * menu is in the normal document flow, momentarily expanding any ancestor with + * `overflow: visible` (e.g. the element) and causing a visible layout + * shift. Pre-setting `position: absolute` here removes the element from flow + * immediately at mount, so there is no reflow before floating-ui takes over. + */ +[data-melt-combobox-menu] { + position: absolute; +} diff --git a/src/routes/(console)/organization-[organization]/projectAccessSelector.svelte b/src/routes/(console)/organization-[organization]/projectAccessSelector.svelte index 8d513a2b2..1a6cb0122 100644 --- a/src/routes/(console)/organization-[organization]/projectAccessSelector.svelte +++ b/src/routes/(console)/organization-[organization]/projectAccessSelector.svelte @@ -35,6 +35,7 @@ rowSearching[index] = true; const queries: string[] = [ Query.limit(25), + Query.orderDesc(''), Query.equal('teamId', $organization.$id) ]; if (search) { @@ -83,7 +84,16 @@ {#each projectAccess as access, i} -
+
{ + if (!rowOptions[i]?.length) loadProjects(i); + }} + oninput={(e) => { + if (e.target instanceof HTMLInputElement) { + getDebouncedSearch(i)(e.target.value); + } + }}> getDebouncedSearch(i)(e.detail)} - on:focus={() => { if (!rowOptions[i]?.length) loadProjects(i); }} /> + noResultsOption={rowSearching[i] + ? { disabled: true, message: 'Searching...' } + : undefined} />