diff --git a/core/src/components/UnifiedSearch/UnifiedSearchModal.vue b/core/src/components/UnifiedSearch/UnifiedSearchModal.vue
index c28d02cfcefd4..a1a36a78e69e7 100644
--- a/core/src/components/UnifiedSearch/UnifiedSearchModal.vue
+++ b/core/src/components/UnifiedSearch/UnifiedSearchModal.vue
@@ -159,7 +159,7 @@
ref="resultsContainer"
class="unified-search-modal__results"
:class="{ 'unified-search-modal__results--held': heldHeight !== null }"
- :style="heldHeight !== null ? { blockSize: `${heldHeight}px`, boxSizing: 'border-box' } : undefined">
+ :style="heldHeight !== null ? { minBlockSize: `${heldHeight}px`, boxSizing: 'border-box' } : undefined">
{{ t('core', 'Results') }}
@@ -1807,20 +1807,30 @@ export default defineComponent({
min-height: 0;
overflow: hidden auto;
- // The placeholders deliberately overfill, so the bottom fades out over the cut.
+ // The reserved height is a floor, not a size. Column layout so the placeholders can
+ // take what the results leave.
&--held {
+ display: flex;
+ flex-direction: column;
flex-grow: 0;
- overflow: clip;
- // Capped, so a short box does not spend a third of itself fading.
- mask-image: linear-gradient(to bottom, #000 calc(100% - min(2lh, 25%)), transparent);
+
+ > *:not(.search-result-skeleton) {
+ flex: none;
+ }
}
// Adjust padding to match container but keep the scrollbar on the very end
padding-inline: calc(var(--default-grid-baseline) * 4);
padding-block: 0 calc(var(--default-grid-baseline) * 4);
- // Matches the gap a category title keeps above itself.
.search-result-skeleton {
+ // Matches the gap a category title keeps above itself.
margin-block-start: 14px;
+ // The placeholders deliberately overfill, so the bottom fades out over the cut.
+ flex: 1 1 0;
+ min-block-size: 0;
+ overflow: clip;
+ // Capped, so a short box does not spend a third of itself fading.
+ mask-image: linear-gradient(to bottom, #000 calc(100% - min(2lh, 25%)), transparent);
}
.result {
@@ -1891,7 +1901,7 @@ export default defineComponent({
// Ensure modal is accessible on small devices
@media only screen and (max-height: 400px) {
- .unified-search-modal__results:not(.unified-search-modal__results--held) {
+ .unified-search-modal__results {
overflow: unset;
}
}
diff --git a/core/src/tests/components/UnifiedSearchModal.spec.ts b/core/src/tests/components/UnifiedSearchModal.spec.ts
index 604f1a5e10c39..1c6597b671778 100644
--- a/core/src/tests/components/UnifiedSearchModal.spec.ts
+++ b/core/src/tests/components/UnifiedSearchModal.spec.ts
@@ -1267,13 +1267,35 @@ describe('UnifiedSearchModal loading skeleton', () => {
expect(wrapper.vm.skeletonRows * 60).toBeGreaterThanOrEqual(300)
})
+ // isBusy stays true until the last provider answers, so the box carries real results meanwhile.
+ it('reserves the height as a floor, so landed results are never cut off', async () => {
+ const wrapper = await withResults()
+ measureResultsAt(wrapper, 300)
+ wrapper.vm.searchQuery = 'querying'
+ await wrapper.vm.$nextTick()
+ wrapper.vm.find('querying')
+
+ searchStates.value = {
+ files: loaded([{ resourceUrl: '/a' }, { resourceUrl: '/b' }, { resourceUrl: '/c' }]),
+ talk: { status: 'loading', entries: [], cursor: null, hasMore: false, loadMoreFailed: false },
+ }
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.vm.isBusy).toBe(true)
+ expect(wrapper.vm.hasVisibleResults).toBe(true)
+
+ const style = wrapper.find('.unified-search-modal__results').attributes('style')
+ expect(style).toContain('min-block-size: 300px')
+ expect(style).not.toMatch(/(^|[^-])block-size: 300px/)
+ })
+
it('holds the same height through repeated keystrokes rather than creeping taller', async () => {
const wrapper = await withResults()
const box = wrapper.vm.$refs.resultsContainer as HTMLElement
// Stand in for the browser: report the padding on top of whatever height is set.
const padding = 16
vi.spyOn(box, 'getBoundingClientRect').mockImplementation(() => ({
- height: (parseFloat(box.style.blockSize) || 300) + (box.style.boxSizing === 'border-box' ? 0 : padding),
+ height: (parseFloat(box.style.minBlockSize) || 300) + (box.style.boxSizing === 'border-box' ? 0 : padding),
}) as DOMRect)
wrapper.vm.searchQuery = 'q1'