From c1f12e508340558c8c211b56ae2989fc1ba3fb01 Mon Sep 17 00:00:00 2001 From: "Michael E. Karpeles (Mek)" Date: Sun, 26 Apr 2026 21:37:17 -0700 Subject: [PATCH 1/3] fix: ac-scroll fills remaining viewport height in mobile overlay Replaced max-height: 40vh cap with flex: 1; min-height: 0 so the autocomplete list expands to fill whatever height remains after the back bar, search input, chip bar, and facet bar. Panel becomes a flex column while keeping overflow: visible so facet dropdowns aren't clipped. --- frontend/src/components/ol-search-bar.js | 4 ++-- frontend/src/components/ol-search-bar.mobile-overlay.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/ol-search-bar.js b/frontend/src/components/ol-search-bar.js index f478e8b..e795939 100644 --- a/frontend/src/components/ol-search-bar.js +++ b/frontend/src/components/ol-search-bar.js @@ -671,12 +671,12 @@ export class OlSearchBar extends LitElement { :host(.mobile-exp) .panel { position: static; flex: 1; width: 100%; box-sizing: border-box; - overflow: visible; max-height: none; + display: flex; flex-direction: column; overflow: visible; max-height: none; border: none; box-shadow: none; border-radius: 0; border-top: none; } :host(.mobile-exp) .panel-chips { max-height: none; } - :host(.mobile-exp) .ac-scroll { max-height: 40vh; } + :host(.mobile-exp) .ac-scroll { flex: 1; min-height: 0; max-height: none; overflow-y: auto; } :host(.mobile-exp) .pf-bar { overflow: visible; flex-wrap: wrap; } /* Back button shown at top of the expanded panel */ diff --git a/frontend/src/components/ol-search-bar.mobile-overlay.test.js b/frontend/src/components/ol-search-bar.mobile-overlay.test.js index 517d918..c3bd3a2 100644 --- a/frontend/src/components/ol-search-bar.mobile-overlay.test.js +++ b/frontend/src/components/ol-search-bar.mobile-overlay.test.js @@ -37,8 +37,8 @@ describe('ol-search-bar mobile overlay CSS contract', () => { expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.panel[^}]*max-height\s*:\s*none/); }); - it(':host(.mobile-exp) .ac-scroll has a vh-based max-height for results scrolling', () => { - expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.ac-scroll[^}]*max-height\s*:\s*\d+vh/); + it(':host(.mobile-exp) .ac-scroll grows to fill remaining panel height via flex: 1', () => { + expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.ac-scroll[^}]*flex\s*:\s*1/); }); it(':host(.mobile-exp) .panel sets width:100% to override the desktop CSS var', () => { From cbfaa6f9b023b54c2dcfd314c2dd0f54b888cf1b Mon Sep 17 00:00:00 2001 From: "Michael E. Karpeles (Mek)" Date: Sun, 26 Apr 2026 21:48:11 -0700 Subject: [PATCH 2/3] fix: prevent body scroll and fix flex height chain in mobile overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs reported after the initial flex fill: 1. Body behind position:fixed overlay was still scrollable — visually a full-page scrollbar appeared. Fix: lock document.body.style.overflow when _mobileExpanded, restore it on close or disconnect. 2. Shrinking the browser window clipped content at the bottom rather than scrolling — .search-outer and .panel lacked min-height:0 so the flex chain couldn't constrain their heights. Fix: add min-height:0 to both; change .panel overflow to hidden (safe in full-screen mode since facet dropdowns stay within the panel's viewport-sized bounds). --- frontend/src/components/ol-search-bar.js | 11 ++++++--- .../ol-search-bar.mobile-overlay.test.js | 23 +++++++++++++++++-- .../ol-search-bar.panel-overlay.test.js | 2 +- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/ol-search-bar.js b/frontend/src/components/ol-search-bar.js index e795939..9facf36 100644 --- a/frontend/src/components/ol-search-bar.js +++ b/frontend/src/components/ol-search-bar.js @@ -124,6 +124,7 @@ export class OlSearchBar extends LitElement { super.disconnectedCallback(); document.removeEventListener('click', this._onDoc, true); window.removeEventListener('resize', this._onWinResize); + if (this._mobileExpanded) document.body.style.overflow = ''; this._acAbort?.abort(); this._authorAbort?.abort(); this._subjectAbort?.abort(); @@ -147,6 +148,10 @@ export class OlSearchBar extends LitElement { } // Sync full-screen overlay class on the host element. this.classList.toggle('mobile-exp', this._mobileExpanded); + // Prevent body scroll while overlay is active so the page behind doesn't scroll. + if (changed.has('_mobileExpanded')) { + document.body.style.overflow = this._mobileExpanded ? 'hidden' : ''; + } // Anchor the panel to the trigger and focus the panel-input when it opens. if (changed.has('_open') && this._open && this.showFacets) { if (!this._mobileExpanded) this._positionPanel(); @@ -665,13 +670,13 @@ export class OlSearchBar extends LitElement { background: white; overflow: hidden; } :host(.mobile-exp) .search-outer { - flex: 1; display: flex; flex-direction: column; + flex: 1; min-height: 0; display: flex; flex-direction: column; } :host(.mobile-exp) .input-row { display: none; } :host(.mobile-exp) .panel { - position: static; flex: 1; + position: static; flex: 1; min-height: 0; width: 100%; box-sizing: border-box; - display: flex; flex-direction: column; overflow: visible; max-height: none; + display: flex; flex-direction: column; overflow: hidden; max-height: none; border: none; box-shadow: none; border-radius: 0; border-top: none; } diff --git a/frontend/src/components/ol-search-bar.mobile-overlay.test.js b/frontend/src/components/ol-search-bar.mobile-overlay.test.js index c3bd3a2..3ddcb4e 100644 --- a/frontend/src/components/ol-search-bar.mobile-overlay.test.js +++ b/frontend/src/components/ol-search-bar.mobile-overlay.test.js @@ -45,8 +45,16 @@ describe('ol-search-bar mobile overlay CSS contract', () => { expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.panel[^}]*width\s*:\s*100%/); }); - it(':host(.mobile-exp) .panel uses overflow:visible so facet dropdowns are not clipped', () => { - expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.panel[^}]*overflow\s*:\s*visible/); + it(':host(.mobile-exp) .panel uses overflow:hidden to bound the flex scroll region', () => { + // In full-screen mode the panel IS the viewport, so facet dropdowns remain + // within its bounds and are not clipped; overflow:hidden is needed so that + // flex children (ac-scroll) are properly height-constrained. + expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.panel[^}]*overflow\s*:\s*hidden/); + }); + + it(':host(.mobile-exp) .panel and .search-outer have min-height:0 so flex chain can shrink', () => { + expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.panel[^}]*min-height\s*:\s*0/); + expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.search-outer[^}]*min-height\s*:\s*0/); }); }); @@ -82,4 +90,15 @@ describe('ol-search-bar mobile overlay JS contract', () => { it('toggles mobile-exp class on :host via updated()', () => { expect(src).toMatch(/classList\.toggle\s*\(\s*['"]mobile-exp['"]\s*,\s*this\._mobileExpanded\s*\)/); }); + + it('locks body scroll when mobile overlay opens and restores it when it closes', () => { + const updatedFn = src.slice(src.indexOf('updated(changed)'), src.indexOf('updated(changed)') + 1200); + expect(updatedFn).toMatch(/document\.body\.style\.overflow/); + expect(updatedFn).toMatch(/_mobileExpanded.*hidden|hidden.*_mobileExpanded/s); + }); + + it('restores body scroll in disconnectedCallback in case component is removed while expanded', () => { + const dcFn = src.slice(src.indexOf('disconnectedCallback()'), src.indexOf('disconnectedCallback()') + 400); + expect(dcFn).toMatch(/document\.body\.style\.overflow/); + }); }); diff --git a/frontend/src/components/ol-search-bar.panel-overlay.test.js b/frontend/src/components/ol-search-bar.panel-overlay.test.js index f1b8e02..ce51141 100644 --- a/frontend/src/components/ol-search-bar.panel-overlay.test.js +++ b/frontend/src/components/ol-search-bar.panel-overlay.test.js @@ -99,7 +99,7 @@ describe('ol-search-bar panel overlay — panel-input contract', () => { }); it('updated() focuses the panel-input when the panel opens', () => { - const updFn = src.slice(src.indexOf('updated(changed)'), src.indexOf('updated(changed)') + 1000); + const updFn = src.slice(src.indexOf('updated(changed)'), src.indexOf('updated(changed)') + 1200); expect(updFn).toMatch(/panel-input/); }); }); From 93316330bd427b79afb5e5dcf2e9523439b790cb Mon Sep 17 00:00:00 2001 From: "Michael E. Karpeles (Mek)" Date: Sun, 26 Apr 2026 21:49:36 -0700 Subject: [PATCH 3/3] fix: scope 600px ac-scroll rule to :not(.mobile-exp); add flex/min-height tests Address Copilot review comments on PR #43: - Scope @media(max-width:600px) .ac-scroll to :host(:not(.mobile-exp)) so overlay and non-overlay modes are explicitly separate rather than relying on selector specificity to determine which max-height wins. - Add contract tests asserting .panel is display:flex/flex-direction:column and .ac-scroll has min-height:0 in mobile-exp, protecting the full flex layout intent against future regressions. --- frontend/src/components/ol-search-bar.js | 2 +- .../ol-search-bar.mobile-overlay.test.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/ol-search-bar.js b/frontend/src/components/ol-search-bar.js index 9facf36..22618d0 100644 --- a/frontend/src/components/ol-search-bar.js +++ b/frontend/src/components/ol-search-bar.js @@ -702,7 +702,7 @@ export class OlSearchBar extends LitElement { .pf-bar::-webkit-scrollbar { display: none; } .pf-btn { font-size: 10px; padding: 11px 4px; } .submit { padding: 6px 10px; } - .ac-scroll { max-height: 40vh; } + :host(:not(.mobile-exp)) .ac-scroll { max-height: 40vh; } .panel-chips { max-height: 72px; overflow-y: auto; } } `; diff --git a/frontend/src/components/ol-search-bar.mobile-overlay.test.js b/frontend/src/components/ol-search-bar.mobile-overlay.test.js index 3ddcb4e..b6ad2f8 100644 --- a/frontend/src/components/ol-search-bar.mobile-overlay.test.js +++ b/frontend/src/components/ol-search-bar.mobile-overlay.test.js @@ -41,6 +41,21 @@ describe('ol-search-bar mobile overlay CSS contract', () => { expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.ac-scroll[^}]*flex\s*:\s*1/); }); + it(':host(.mobile-exp) .ac-scroll has min-height:0 so it can shrink below content size', () => { + expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.ac-scroll[^}]*min-height\s*:\s*0/); + }); + + it(':host(.mobile-exp) .panel is a flex column so children stack and ac-scroll can flex-grow', () => { + expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.panel[^}]*display\s*:\s*flex/); + expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.panel[^}]*flex-direction\s*:\s*column/); + }); + + it('@media 600px .ac-scroll rule is scoped to :host(:not(.mobile-exp)) — no specificity conflict', () => { + const mediaBlock = src.slice(src.indexOf('@media (max-width: 600px)'), src.indexOf('@media (max-width: 600px)') + 400); + expect(mediaBlock).toMatch(/:host\(:not\(\.mobile-exp\)\)\s+\.ac-scroll/); + expect(mediaBlock).not.toMatch(/[^)]\s+\.ac-scroll\s*\{[^}]*max-height/); // plain .ac-scroll should not appear + }); + it(':host(.mobile-exp) .panel sets width:100% to override the desktop CSS var', () => { expect(src).toMatch(/:host\(\.mobile-exp\)\s+\.panel[^}]*width\s*:\s*100%/); });