fix: dropdown menus use fixed positioning to escape overflow clipping

Dropdown menus in app-switcher and space-switcher were clipped by
overflow:hidden on .rstack-header__left (mobile). Changed from
position:absolute to position:fixed with dynamic getBoundingClientRect
positioning. Bumped shell asset versions to v=5 to bypass CF cache.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-28 21:36:18 +00:00
parent 08928a9f8e
commit d9bd7557fa
3 changed files with 24 additions and 14 deletions

View File

@ -54,7 +54,7 @@ export function renderShell(opts: ShellOptions): string {
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🌌</text></svg>">
<title>${escapeHtml(title)}</title>
<link rel="stylesheet" href="/shell.css?v=4">
<link rel="stylesheet" href="/shell.css?v=5">
<style>
/* When loaded inside an iframe (e.g. standalone domain redirecting back),
hide the shell chrome the parent rSpace page already provides it. */
@ -94,7 +94,7 @@ export function renderShell(opts: ShellOptions): string {
${renderWelcomeOverlay()}
<script type="module">
import '/shell.js?v=4';
import '/shell.js?v=5';
// Provide module list to app switcher
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
@ -355,7 +355,7 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🌌</text></svg>">
<title>${escapeHtml(title)}</title>
<link rel="stylesheet" href="/shell.css?v=4">
<link rel="stylesheet" href="/shell.css?v=5">
<style>
html.rspace-embedded .rstack-header { display: none !important; }
html.rspace-embedded .rstack-tab-row { display: none !important; }
@ -398,7 +398,7 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
</div>
<script type="module">
import '/shell.js?v=4';
import '/shell.js?v=5';
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
const tabBar = document.querySelector('rstack-tab-bar');
@ -581,7 +581,7 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string {
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>${mod.icon}</text></svg>">
<title>${escapeHtml(mod.name)} rSpace</title>
<link rel="stylesheet" href="/shell.css?v=4">
<link rel="stylesheet" href="/shell.css?v=5">
${cssBlock}
<script defer src="https://rdata.online/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
</head>
@ -601,7 +601,7 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string {
</header>
${bodyContent}
<script type="module">
import '/shell.js?v=4';
import '/shell.js?v=5';
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
try {
var raw = localStorage.getItem('encryptid_session');

View File

@ -241,7 +241,12 @@ export class RStackAppSwitcher extends HTMLElement {
trigger.addEventListener("click", (e) => {
e.stopPropagation();
menu.classList.toggle("open");
const isOpen = menu.classList.toggle("open");
if (isOpen) {
const rect = trigger.getBoundingClientRect();
menu.style.top = `${rect.bottom + 6}px`;
menu.style.left = `${Math.max(4, rect.left)}px`;
}
});
// Prevent external links from closing the menu prematurely
@ -298,10 +303,10 @@ const STYLES = `
.caret { font-size: 0.7em; opacity: 0.6; }
.menu {
position: absolute; top: 100%; left: 0; margin-top: 6px;
position: fixed; margin-top: 0;
min-width: 300px; border-radius: 12px; overflow: hidden;
overflow-y: auto; max-height: 70vh;
box-shadow: 0 8px 30px rgba(0,0,0,0.25); display: none; z-index: 200;
box-shadow: 0 8px 30px rgba(0,0,0,0.25); display: none; z-index: 10001;
}
.menu.open { display: block; }
:host-context([data-theme="light"]) .menu {

View File

@ -100,9 +100,14 @@ export class RStackSpaceSwitcher extends HTMLElement {
trigger.addEventListener("click", async (e) => {
e.stopPropagation();
const isOpen = menu.classList.toggle("open");
if (isOpen && !this.#loaded) {
await this.#loadSpaces();
this.#renderMenu(menu, current);
if (isOpen) {
const rect = trigger.getBoundingClientRect();
menu.style.top = `${rect.bottom + 6}px`;
menu.style.left = `${Math.max(4, rect.left)}px`;
if (!this.#loaded) {
await this.#loadSpaces();
this.#renderMenu(menu, current);
}
}
});
@ -689,10 +694,10 @@ const STYLES = `
.caret { font-size: 0.7em; opacity: 0.6; }
.menu {
position: absolute; top: 100%; left: 0; margin-top: 6px;
position: fixed; margin-top: 0;
min-width: 260px; max-height: 400px; overflow-y: auto;
border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.25);
display: none; z-index: 200;
display: none; z-index: 10001;
}
.menu.open { display: block; }
:host-context([data-theme="light"]) .menu { background: white; border: 1px solid rgba(0,0,0,0.1); }