Skip to content

Commit 3cd45a8

Browse files
Fix copy menu positioning
1 parent ef98216 commit 3cd45a8

3 files changed

Lines changed: 45 additions & 6 deletions

File tree

_components/CopyPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default function CopyPage({ file }: { file: string | undefined }) {
5757
</svg>
5858
</button>
5959

60-
{/* Popover panel — lives in top layer, positioned via JS */}
60+
{/* Popover panel — positioned via CSS anchor positioning, JS fallback */}
6161
<div id="copy-page-menu" popover="auto" class="copy-page-panel">
6262
<a
6363
href={file}

js/copy-page.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
document.querySelectorAll<HTMLButtonElement>(".copy-page-main-btn").forEach(
33
(btn) => {
44
btn.addEventListener("click", () => {
5-
navigator?.clipboard?.writeText(window.location.href).then(() => {
5+
navigator?.clipboard?.writeText(globalThis.location.href).then(() => {
66
const label = btn.querySelector<HTMLElement>(".copy-page-main-label");
77
if (label) {
88
const original = label.textContent;
@@ -31,14 +31,43 @@ const toggleBtn = document.querySelector<HTMLButtonElement>(
3131
".copy-page-toggle-btn",
3232
);
3333

34+
const supportsAnchor = CSS.supports("anchor-name", "--a");
35+
36+
if (!supportsAnchor && panel) {
37+
panel.style.setProperty("position-anchor", "unset");
38+
panel.style.setProperty("position-area", "unset");
39+
panel.style.setProperty("position-try-fallbacks", "none");
40+
}
41+
3442
panel?.addEventListener("toggle", (event) => {
3543
const e = event as ToggleEvent;
3644
const chevron = toggleBtn?.querySelector<SVGElement>(".copy-page-chevron");
3745

38-
if (e.newState === "open" && toggleBtn) {
39-
const rect = toggleBtn.getBoundingClientRect();
40-
panel.style.top = `${rect.bottom + 4}px`;
41-
panel.style.right = `${window.innerWidth - rect.right}px`;
46+
const splitBtn = toggleBtn?.closest<HTMLElement>(".copy-page-split");
47+
if (e.newState === "open" && splitBtn && toggleBtn) {
48+
// Only position via JS when CSS anchor positioning isn't supported
49+
if (!supportsAnchor) {
50+
// Use requestAnimationFrame to ensure the panel is rendered before measuring
51+
requestAnimationFrame(() => {
52+
// Use toggleBtn for vertical position (splitBtn may include popover in measurement)
53+
// Use splitBtn for horizontal alignment (left edge of whole button)
54+
const btnRect = toggleBtn.getBoundingClientRect();
55+
const splitRect = splitBtn.getBoundingClientRect();
56+
const panelWidth = panel.offsetWidth;
57+
58+
const top = btnRect.bottom + 4;
59+
let right = globalThis.innerWidth - splitRect.right;
60+
61+
// Clamp so the panel doesn't overflow the left edge
62+
const left = globalThis.innerWidth - right - panelWidth;
63+
if (left < 8) {
64+
right = globalThis.innerWidth - panelWidth - 8;
65+
}
66+
67+
panel.style.top = `${top}px`;
68+
panel.style.right = `${right}px`;
69+
});
70+
}
4271
}
4372
if (chevron) {
4473
chevron.style.transform = e.newState === "open" ? "rotate(180deg)" : "";

style.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,13 +1227,23 @@
12271227
}
12281228
}
12291229

1230+
.copy-page-split {
1231+
anchor-name: --copy-page-anchor;
1232+
}
1233+
12301234
.copy-page-panel {
12311235
/* Reset browser popover defaults */
12321236
inset: unset;
12331237
margin: 0;
12341238
padding: 0;
12351239
@apply w-72 rounded-md overflow-hidden bg-white dark:bg-gray-900 border
12361240
border-foreground-tertiary shadow-lg;
1241+
1242+
/* CSS anchor positioning (supported browsers) */
1243+
position-anchor: --copy-page-anchor;
1244+
position-area: bottom span-left;
1245+
margin-top: 4px;
1246+
position-try-fallbacks: flip-inline;
12371247
}
12381248

12391249
#feedback-no:focus + label,

0 commit comments

Comments
 (0)