diff --git a/src/format/html/format-html-shared.ts b/src/format/html/format-html-shared.ts
index 6d63e73abd5..dbba131dc03 100644
--- a/src/format/html/format-html-shared.ts
+++ b/src/format/html/format-html-shared.ts
@@ -317,7 +317,8 @@ export const quartoDefaults = (format: Format) => {
"code-copy-selector",
format.metadata[kCodeCopy] === undefined ||
format.metadata[kCodeCopy] === "hover"
- ? '"pre.sourceCode:hover > "'
+ // ? '"div.sourceCode:hover > "'
+ ? '"div.code-copy-outer-scaffold:hover > "'
: '""',
),
),
diff --git a/src/format/html/format-html.ts b/src/format/html/format-html.ts
index 0116419ce82..305de562c8c 100644
--- a/src/format/html/format-html.ts
+++ b/src/format/html/format-html.ts
@@ -750,12 +750,25 @@ function htmlFormatPostprocessor(
// insert code copy button (with specfic attribute when inside a modal)
if (codeCopy) {
- code.classList.add("code-with-copy");
+ // the interaction of code copy button fixed position
+ // and scrolling overflow behavior requires a scaffold div to be inserted
+ // as a parent of the code block and the copy button both
+ // (see #13009, #5538, and #12787)
+ const outerScaffold = doc.createElement("div");
+ outerScaffold.classList.add("code-copy-outer-scaffold");
+
const copyButton = createCodeCopyButton(doc, format);
if (EmbedSourceModal && EmbedSourceModal.contains(code)) {
copyButton.setAttribute("data-in-quarto-modal", "");
}
- code.appendChild(copyButton);
+ code.classList.add("code-with-copy");
+
+ const sourceCodeDiv = code.parentElement!;
+ const sourceCodeDivParent = code.parentElement?.parentElement;
+ sourceCodeDivParent!.replaceChild(outerScaffold, sourceCodeDiv);
+
+ outerScaffold.appendChild(sourceCodeDiv);
+ outerScaffold.appendChild(copyButton);
}
// insert example iframe
diff --git a/src/resources/formats/html/_quarto-rules.scss b/src/resources/formats/html/_quarto-rules.scss
index 4a4e9959f5e..f10cadb5a86 100644
--- a/src/resources/formats/html/_quarto-rules.scss
+++ b/src/resources/formats/html/_quarto-rules.scss
@@ -282,7 +282,7 @@ details > summary > p:only-child {
}
// codeCopy
-div.sourceCode {
+div.code-copy-outer-scaffold {
position: relative;
}
diff --git a/src/resources/formats/html/bootstrap/_bootstrap-rules.scss b/src/resources/formats/html/bootstrap/_bootstrap-rules.scss
index ed6dd280b9b..07d982073db 100644
--- a/src/resources/formats/html/bootstrap/_bootstrap-rules.scss
+++ b/src/resources/formats/html/bootstrap/_bootstrap-rules.scss
@@ -877,7 +877,8 @@ pre.sourceCode {
border: none;
}
font-size: $code-block-font-size;
- overflow-y: auto !important;
+ overflow-y: visible !important;
+ // overflow-y: auto !important;
@if $code-block-bg {
padding: $code-block-bg-padding;
}
@@ -890,7 +891,8 @@ pre.sourceCode > code.sourceCode {
}
div.sourceCode {
- position: relative;
+ overflow-y: hidden;
+ // position: relative;
}
.callout div.sourceCode {