openvibers/openvibe.live Commits

Fix hamburger: the desktop-dropdown outside-click no longer closes the mobile drawer; chat button steps aside while a panel is open (no dead space at the end of the user menu)

OP OpenVibers committed 18 Sept 2026 · 79240bd799 · 3 files, +27 −5


Changes

ddiff --git a/public/css/style.css b/public/css/style.css
iindex 6a2b4352..95dae2bf 100644
---- a/public/css/style.css
++++ b/public/css/style.css
@@@ -11864,12 +11864,14 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
/* Below tablet width the user menu is a sheet pinned under the bar, as wide as the phone allows. */
@media (max-width: 900px) {
.user-dropdown { position: fixed; top: calc(var(--navbar-h) + 6px); right: 8px; left: auto; min-width: 0; width: min(340px, calc(100vw - 16px)); }
-- /* The floating chat button sits over the bottom-right corner: let the last row scroll clear of it. */
-- .user-dropdown > :last-child { padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)); }
-- .user-dropdown::after { content: ''; display: block; height: 64px; }
++ /* The floating chat button steps aside while a navbar panel is open (body.ov-panel-open), so the menu
++ needs no dead space at its end to clear it. */
++ .user-dropdown > :last-child { padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)); }
}
/* Short screens (landscape phones, split view): tighter rows so more of the menu is visible at once. */
@media (max-height: 560px) {
.user-dropdown .ud-group a, .nav-links.show .nav-link { padding-top: 7px; padding-bottom: 7px; font-size: 0.86rem; }
.user-dropdown .ud-label { padding-top: 8px; padding-bottom: 2px; }
}
++
++body.ov-panel-open #floating-chat-fab { opacity: 0; pointer-events: none; transform: scale(.85); transition: opacity .15s, transform .15s; }
ddiff --git a/public/js/app.js b/public/js/app.js
iindex 27338a21..95d6dcc1 100644
---- a/public/js/app.js
++++ b/public/js/app.js
@@@ -1426,7 +1426,6 @@ function toggleNavDropdown(id) {
function closeNavDropdowns() {
document.querySelectorAll('.nav-dropdown.open').forEach(d => d.classList.remove('open'));
-- closeMobileNav();
}
// Close nav dropdowns when clicking outside
@@@ -1476,10 +1475,20 @@ function positionNavDropdownMenu(dropdown) {
menu.style.maxHeight = `${Math.max(120, vh - rect.bottom - pad)}px`;
}
++// While the user menu or the mobile drawer is open, the floating chat button steps aside (it used to sit on
++// top of the menu's last rows). Driven by the panels' own classes, so every open/close path is covered.
++{
++ const sync = () => document.body.classList.toggle('ov-panel-open',
++ !!document.querySelector('#user-dropdown.show, .nav-links.show'));
++ const watch = () => { for (const el of [document.getElementById('user-dropdown'), document.querySelector('.nav-links')]) if (el) new MutationObserver(sync).observe(el, { attributes: true, attributeFilter: ['class'] }); };
++ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', watch); else watch();
++}
++
/** Close every navbar panel (used when the viewport changes under an open menu). */
function closeNavPanels() {
document.getElementById('user-dropdown')?.classList.remove('show');
-- document.querySelectorAll('.nav-dropdown.open').forEach(d => d.classList.remove('open'));
++ closeNavDropdowns();
++ closeMobileNav();
}
// A rotated phone, a resized window or an opened keyboard invalidates a fixed menu's position: reposition
// what is open, and close panels on orientation change. Escape closes them too.
ddiff --git a/test/nav-panels.test.js b/test/nav-panels.test.js
nnew file mode 100644
iindex 00000000..4bc5a3e6
---- /dev/null
++++ b/test/nav-panels.test.js
@@@ -0,0 +1,11 @@
++'use strict';
++// Regression: a desktop-dropdown "click outside" handler must never close the mobile drawer, or the hamburger
++// opens and closes it in the same click (shipped once, 2026-09-18).
++const assert = require('assert');
++const fs = require('fs'); const path = require('path');
++const src = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'app.js'), 'utf8');
++const body = (name) => { const i = src.indexOf(`function ${name}(`); assert.ok(i >= 0, `${name} exists`); let d = 0, j = src.indexOf('{', i); const s = j; for (; j < src.length; j++) { if (src[j] === '{') d++; else if (src[j] === '}' && --d === 0) break; } return src.slice(s, j + 1); };
++assert.ok(!/closeMobileNav\s*\(/.test(body('closeNavDropdowns')), 'closeNavDropdowns leaves the mobile drawer alone');
++assert.ok(/closeMobileNav\s*\(/.test(body('closeNavPanels')) && /closeNavDropdowns\s*\(/.test(body('closeNavPanels')), 'closeNavPanels closes everything');
++assert.ok(/classList\.toggle\('show'\)/.test(body('toggleMobileNav')), 'hamburger toggles the drawer');
++console.log('nav panels: all checks passed');