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} />