fix(#5583): keep mobile nav actions visible

This commit is contained in:
Rod Boev
2026-07-05 18:40:41 -04:00
parent f650b1b725
commit 9f9ebc9746
3 changed files with 65 additions and 9 deletions
+1 -1
View File
@@ -3100,7 +3100,7 @@
.sidebar-nav .nav-tab svg{width:20px;height:20px;}
.sidebar-nav .nav-tab.active{background:var(--accent-bg);}
.sidebar-nav .nav-tab.active::before{left:-4px;top:50%;bottom:auto;transform:translateY(-50%);width:3px;height:16px;border-radius:0 2px 2px 0;}
.sidebar-nav .dashboard-link:not(.nav-action-visible){display:none!important;}
.sidebar-nav .dashboard-link:not(.nav-action-visible),.sidebar-nav [data-nav-action-mirror]:not(.nav-action-visible){display:none!important;}
.sidebar .panel-view{height:100%;margin-left:52px;}
.sidebar .panel-head{padding-right:52px;}
.mobile-sidebar-close{display:inline-flex!important;position:absolute;right:4px;top:calc(4px + var(--app-titlebar-safe-top));width:44px;height:44px;z-index:4;background:var(--surface);border:1px solid var(--border);}
+23 -4
View File
@@ -1312,16 +1312,29 @@ function _syncNavActionMirrors(){
if(!sourceIds.has(mirror.getAttribute('data-nav-action-mirror')))mirror.remove();
});
sources.forEach(source=>{
source.classList.add('nav-action-visible');
const sourceVisible=(()=>{
if(source.hidden||source.getAttribute('aria-hidden')==='true')return false;
if(source.classList.contains('nav-tab-hidden'))return false;
if(source.style&&(source.style.display==='none'||source.style.visibility==='hidden'))return false;
if(typeof window!=='undefined'&&typeof window.getComputedStyle==='function'){
const computed=window.getComputedStyle(source);
if(computed&&(computed.display==='none'||computed.visibility==='hidden'))return false;
}
return true;
})();
let mirror=mirrors.find(el=>el.getAttribute('data-nav-action-mirror')===source.id);
if(!mirror){
mirror=source.cloneNode(true);
_stripInlineEventHandlers(mirror);
mirror.id=source.id+'Mobile';
mirror.classList.remove('rail-btn');
mirror.classList.add('nav-action-visible','has-tooltip--bottom');
mirror.classList.add('has-tooltip--bottom');
mirror.setAttribute('data-nav-action-mirror',source.id);
mirror.addEventListener('click',e=>{e.preventDefault();if(mirror._navActionSource)mirror._navActionSource.click();});
mirror.addEventListener('click',e=>{
e.preventDefault();
if(mirror._navActionSource)mirror._navActionSource.click();
if(typeof closeMobileSidebar==='function')closeMobileSidebar();
});
const anchor=sidebar.querySelector('.dashboard-link,[data-dashboard-link]')||sidebar.querySelector('[data-panel="logs"]');
sidebar.insertBefore(mirror,anchor||null);
}else{
@@ -1329,6 +1342,7 @@ function _syncNavActionMirrors(){
_stripInlineEventHandlers(mirror);
}
mirror._navActionSource=source;
mirror.classList.toggle('nav-action-visible',sourceVisible);
const label=source.getAttribute('data-tooltip')||source.getAttribute('aria-label')||'';
if(label)mirror.setAttribute('data-label',label);
});
@@ -1336,7 +1350,12 @@ function _syncNavActionMirrors(){
function _initNavActionMirrors(){
_syncNavActionMirrors();
const rail=document.querySelector('.rail');
if(rail&&window.MutationObserver)new MutationObserver(_syncNavActionMirrors).observe(rail,{childList:true});
if(rail&&window.MutationObserver)new MutationObserver(_syncNavActionMirrors).observe(rail,{
childList:true,
subtree:true,
attributes:true,
attributeFilter:['class','style','hidden','aria-hidden','data-tooltip','aria-label'],
});
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',_initNavActionMirrors,{once:true});
else _initNavActionMirrors();
+41 -4
View File
@@ -402,7 +402,8 @@ def test_mobile_dashboard_link_uses_shared_visible_action_class():
assert match is not None
mobile_css = match.group(1)
assert re.search(r"\.dashboard-link-visible,\s*\.nav-action-visible\{display:flex!important;\}", STYLE_CSS)
assert ".sidebar-nav .dashboard-link:not(.nav-action-visible){display:none!important;}" in mobile_css
assert ".sidebar-nav .dashboard-link:not(.nav-action-visible)" in mobile_css
assert ".sidebar-nav [data-nav-action-mirror]:not(.nav-action-visible){display:none!important;}" in mobile_css
assert ".sidebar-nav .dashboard-link.dashboard-link-visible{display:none!important;}" not in mobile_css
assert ".sidebar-nav .dashboard-link:not(.dashboard-link-visible){display:none!important;}" not in mobile_css
@@ -445,6 +446,19 @@ def test_extension_rail_actions_are_mirrored_to_mobile_nav():
add: (...classes) => classes.forEach(c => this.classList._set.add(c)),
remove: (...classes) => classes.forEach(c => this.classList._set.delete(c)),
contains: c => this.classList._set.has(c),
toggle: (cls, force) => {
if (force === undefined) {
if (this.classList._set.has(cls)) {
this.classList._set.delete(cls);
return false;
}
this.classList._set.add(cls);
return true;
}
if (force) this.classList._set.add(cls);
else this.classList._set.delete(cls);
return !!force;
},
};
}
appendChild(child) { child.parentNode = this; this.children.push(child); return child; }
@@ -508,6 +522,7 @@ def test_extension_rail_actions_are_mirrored_to_mobile_nav():
const source = new El('button');
const dashboard = new El('button');
let clicked = 0;
let sidebarClosed = 0;
source.id = 'hwxThemeCreatorRailBtn';
source.classList.add('rail-btn', 'nav-tab', 'has-tooltip');
source.setAttribute('data-tooltip', 'Theme Creator');
@@ -519,6 +534,7 @@ def test_extension_rail_actions_are_mirrored_to_mobile_nav():
sidebar.appendChild(dashboard);
let observerCallback = null;
let observedRail = null;
let observedOptions = null;
global.document = {
readyState: 'complete',
@@ -528,10 +544,18 @@ def test_extension_rail_actions_are_mirrored_to_mobile_nav():
return null;
},
};
global.window = {};
global.closeMobileSidebar = () => { sidebarClosed += 1; };
global.window = {
getComputedStyle(el) {
return {
display: el.hidden || (el.style && el.style.display === 'none') ? 'none' : '',
visibility: (el.style && el.style.visibility) || '',
};
},
};
global.MutationObserver = window.MutationObserver = class {
constructor(callback) { observerCallback = callback; }
observe(target) { observedRail = target; }
observe(target, options) { observedRail = target; observedOptions = options; }
};
const helpers = Function(extractFn('_stripInlineEventHandlers') + '\\n' + extractFn('_syncNavActionMirrors') + '\\n' + extractFn('_initNavActionMirrors') + '; return { _initNavActionMirrors };')();
@@ -539,14 +563,22 @@ def test_extension_rail_actions_are_mirrored_to_mobile_nav():
rail.appendChild(source);
observerCallback();
const mirror = sidebar.children.find(el => el.getAttribute('data-nav-action-mirror') === 'hwxThemeCreatorRailBtn');
source.hidden = true;
observerCallback();
const mirrorHiddenWhenSourceHidden = !mirror.classList.contains('nav-action-visible');
source.hidden = false;
observerCallback();
mirror.click();
const mirrorBeforeDashboard = sidebar.children[0] === mirror;
source.remove();
observerCallback();
console.log(JSON.stringify({
observerArmed: observedRail === rail,
observerAttributes: !!(observedOptions && observedOptions.attributes),
observerSubtree: !!(observedOptions && observedOptions.subtree),
sourceVisible: source.classList.contains('nav-action-visible'),
mirrorVisible: mirror.classList.contains('nav-action-visible'),
mirrorHiddenWhenSourceHidden,
mirrorRailClass: mirror.classList.contains('rail-btn'),
mirrorLabel: mirror.getAttribute('data-label'),
mirrorOnclickAttribute: mirror.getAttribute('onclick'),
@@ -554,6 +586,7 @@ def test_extension_rail_actions_are_mirrored_to_mobile_nav():
mirrorBeforeDashboard,
mirrorRemoved: !sidebar.children.some(el => el.getAttribute('data-nav-action-mirror') === 'hwxThemeCreatorRailBtn'),
clicked,
sidebarClosed,
}));
"""
)
@@ -567,8 +600,11 @@ def test_extension_rail_actions_are_mirrored_to_mobile_nav():
pathlib.Path(path).unlink(missing_ok=True)
assert out == {
"observerArmed": True,
"sourceVisible": True,
"observerAttributes": True,
"observerSubtree": True,
"sourceVisible": False,
"mirrorVisible": True,
"mirrorHiddenWhenSourceHidden": True,
"mirrorRailClass": False,
"mirrorLabel": "Theme Creator",
"mirrorOnclickAttribute": None,
@@ -576,6 +612,7 @@ def test_extension_rail_actions_are_mirrored_to_mobile_nav():
"mirrorBeforeDashboard": True,
"mirrorRemoved": True,
"clicked": 1,
"sidebarClosed": 1,
}