Skip to content

Conversation

@csala
Copy link
Contributor

@csala csala commented Sep 2, 2025

Attempt at #109 via browser-level fullscreen toggling instead of in-app maximization.

IMO this is not the best solution yet, because fullscreen may be "too much" compared to modal-level maximization.
Also the current implementation forces re-rendering and suffers the same problem described in #108 about zoom changes.

On the other hand, I suppose that this is better than nothing, so it can be considered as an interim feature until proper maximization can be implemented. And it can also be a reference for future implementations.

Up to the maintainers to decide what to do with this.

Summary by CodeRabbit

  • New Features

    • Added fullscreen/maximize viewing with a toggle and preserved scroll/resize behavior.
    • Added a public option to show or hide the fullscreen toggle (enabled by default).
    • Controls consolidated into a top toolbar; zoom control retained and zoom panel closes on outside click.
  • Documentation

    • README updated to document the fullscreen toggle and parameter notes.
  • Style/Refactor

    • Refreshed control layout, styling, transitions, and alignment handling.
  • Tests

    • Updated tests to wait for iframe content and to target the new toolbar control selector.

@csala
Copy link
Contributor Author

csala commented Sep 2, 2025

I just noticed that my version is not based on the latest upstream one and is missing one conflicting commit. Let me fix that

@csala csala force-pushed the fullscreen-toggle branch from 6bac75c to 469edb8 Compare September 2, 2025 09:53
@csala
Copy link
Contributor Author

csala commented Sep 2, 2025

@lfoppiano I restored the div surrounding all the control buttons and recovered the zoom-buttons class (but renamed to control-buttons) to keep everything in place (see my comment here https://github.com/lfoppiano/streamlit-pdf-viewer/pull/96/files#r2315512519).

I believe that now everything is working as expected here, but if this PR is not going to be merged maybe the div and class restoration should be replicated in the main branch.

@lfoppiano
Copy link
Owner

@csala Thanks for your PR and for the issues, and for checking the current code. Either myself or @t29mato will try to check this out as best as we can.

@csala
Copy link
Contributor Author

csala commented Sep 2, 2025

Perfect, no pressure. I just had done a dirtier version of this for my own use case and figured I could just clean it up and contribute it.

@lfoppiano
Copy link
Owner

I fixed the issue with the zoom-button class missing, I'm fine to keep the class name you use in this branch.

@lfoppiano lfoppiano requested a review from Copilot September 11, 2025 21:08
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a fullscreen toggle feature to the PDF viewer component, allowing users to view PDFs in browser-level fullscreen mode instead of modal-level maximization.

  • Introduces a new show_fullscreen_toggle parameter to control visibility of the fullscreen button
  • Adds fullscreen toggle functionality with appropriate UI controls and event handling
  • Updates component styling to accommodate the new control button layout

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
streamlit_pdf_viewer/frontend/src/PdfViewer.vue Implements fullscreen toggle UI, event handlers, and updates control button layout
streamlit_pdf_viewer/init.py Adds show_fullscreen_toggle parameter to the pdf_viewer function with documentation
README.md Documents the new show_fullscreen_toggle parameter in the parameters table
Comments suppressed due to low confidence (1)

streamlit_pdf_viewer/frontend/src/PdfViewer.vue:1

  • [nitpick] The inline SVG icons are quite verbose and duplicated. Consider extracting them into separate components or using a more maintainable icon system to improve code readability.
<template>

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 416 to 419
const enterFullscreen = () => {
const el = pdfContainer.value;
if (el && el.requestFullscreen) {
el.requestFullscreen();
Copy link

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fullscreen API requires error handling as requestFullscreen() can fail due to user gesture requirements or browser policies. Consider adding a try-catch block and graceful fallback handling.

Suggested change
const enterFullscreen = () => {
const el = pdfContainer.value;
if (el && el.requestFullscreen) {
el.requestFullscreen();
const enterFullscreen = async () => {
const el = pdfContainer.value;
if (el && el.requestFullscreen) {
try {
await el.requestFullscreen();
} catch (err) {
console.error("Failed to enter fullscreen mode:", err);
// Optionally, show a user-friendly message here
}

Copilot uses AI. Check for mistakes.
Comment on lines 423 to 425
const exitFullscreen = () => {
if (document.exitFullscreen) {
document.exitFullscreen();
Copy link

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to enterFullscreen, document.exitFullscreen() can fail and should include error handling with a try-catch block for robust implementation.

Suggested change
const exitFullscreen = () => {
if (document.exitFullscreen) {
document.exitFullscreen();
const exitFullscreen = async () => {
if (document.exitFullscreen) {
try {
await document.exitFullscreen();
} catch (error) {
console.error("Failed to exit fullscreen:", error);
}

Copilot uses AI. Check for mistakes.
@lfoppiano
Copy link
Owner

@csala I think I "fixed" the tests, or at least I removed the one that weren't running consistently. Could you update from main and check whether all the tests are running?

@coderabbitai
Copy link

coderabbitai bot commented Sep 15, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add fullscreen toggle' directly and concisely describes the main change: a new fullscreen/maximize toggle feature. It aligns with the core objective of implementing a fullscreen toggle as documented in the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
streamlit_pdf_viewer/frontend/src/PdfViewer.vue (1)

416-436: Harden Fullscreen API calls (errors, promises, prefixes).

Handle rejections, await promises, and support WebKit-prefixed APIs for broader compatibility.

-    const enterFullscreen = () => {
-      const el = pdfContainer.value;
-      if (el && el.requestFullscreen) {
-        el.requestFullscreen();
-      }
-    };
-
-    const exitFullscreen = () => {
-      if (document.exitFullscreen) {
-        document.exitFullscreen();
-      }
-    };
+    const enterFullscreen = async () => {
+      const el = pdfContainer.value;
+      if (!el) return;
+      try {
+        const req = el.requestFullscreen || el.webkitRequestFullscreen;
+        if (req) {
+          // navigationUI hint is ignored where unsupported
+          await req.call(el, { navigationUI: "hide" });
+        } else {
+          console.warn("Fullscreen API not supported on this element.");
+        }
+      } catch (err) {
+        console.error("Failed to enter fullscreen:", err);
+      }
+    };
+
+    const exitFullscreen = async () => {
+      try {
+        const exit = document.exitFullscreen || document.webkitExitFullscreen;
+        if (exit) {
+          await exit.call(document);
+        }
+      } catch (err) {
+        console.error("Failed to exit fullscreen:", err);
+      }
+    };
 
     const toggleFullscreen = () => {
       if (!isFullscreen.value) {
         enterFullscreen();
       } else {
         exitFullscreen();
       }
     };
🧹 Nitpick comments (6)
README.md (1)

105-105: Document iframe/browser constraints for Fullscreen.

Fullscreen may be blocked inside iframes unless the parent grants allow="fullscreen". Add a short caveat so users know why the button might do nothing in some deployments (e.g., Streamlit-hosted iframes).

Proposed wording to add under “Caveats”:

  • Browser fullscreen requires permission in embedded contexts (iframes). If the host does not allow fullscreen, the toggle will have no effect. Check your container/host settings (e.g., iframe allow="fullscreen").
streamlit_pdf_viewer/frontend/src/PdfViewer.vue (5)

6-15: Improve button a11y and semantics.

Add type="button" to avoid implicit submit and expose accessible names/state for the fullscreen toggle.

Apply:

-        <button v-if="showFullscreen" class="control-button" @click.stop="toggleFullscreen" title="Toggle Fullscreen">
+        <button
+          v-if="showFullscreen"
+          type="button"
+          class="control-button"
+          @click.stop="toggleFullscreen"
+          :aria-pressed="isFullscreen"
+          aria-label="Toggle fullscreen"
+          title="Toggle Fullscreen"
+        >
-        <button class="control-button" @click.stop="toggleZoomPanel">
+        <button
+          type="button"
+          class="control-button"
+          @click.stop="toggleZoomPanel"
+          aria-label="Zoom options"
+          title="Zoom options"
+        >

19-23: Constrain manual zoom input at the element level.

Reflect the 10–1000% clamp in the input attributes for immediate validation.

-          <input
+          <input
             type="number"
             class="zoom-input"
             v-model="manualZoomInput"
             @keyup.enter="applyManualZoom"
             @blur="applyManualZoom"
+            min="10"
+            max="1000"
+            inputmode="numeric"
           />

85-87: Make showFullscreen reactive to prop updates.

Use a computed instead of capturing the initial value only.

-    const isFullscreen = ref(false);
-    const showFullscreen = props.args.show_fullscreen_toggle === true;
+    const isFullscreen = ref(false);
+    const showFullscreen = computed(() => props.args.show_fullscreen_toggle === true);

491-494: Track the correct fullscreen element and listen to prefixed events.

Scope isFullscreen to this component’s container and react on Safari’s webkitfullscreenchange as well.

-    const onFullscreenChange = () => {
-      isFullscreen.value = !!document.fullscreenElement;
-    };
+    const getFullscreenElement = () =>
+      document.fullscreenElement || document.webkitFullscreenElement;
+    const onFullscreenChange = () => {
+      isFullscreen.value = getFullscreenElement() === pdfContainer.value;
+      // Ensure layout recalculates promptly when entering/exiting FS
+      debouncedHandleResize();
+    };
@@
-      document.addEventListener('fullscreenchange', onFullscreenChange);
+      document.addEventListener('fullscreenchange', onFullscreenChange);
+      document.addEventListener('webkitfullscreenchange', onFullscreenChange);
@@
-      document.removeEventListener('fullscreenchange', onFullscreenChange);
+      document.removeEventListener('fullscreenchange', onFullscreenChange);
+      document.removeEventListener('webkitfullscreenchange', onFullscreenChange);

Also applies to: 501-502, 507-508


543-551: Minor a11y polish for controls.

Increase hit area to ~40×40px and ensure contrast meets WCAG AA.

 .control-button {
-  padding: 8px 16px;
-  font-size: 14px;
+  min-width: 40px;
+  min-height: 40px;
+  padding: 8px 12px;
+  font-size: 14px;
 }
 
 .control-button:hover {
-  background: rgba(50, 50, 50, 0.9);
+  background: rgba(60, 60, 60, 0.95);
 }

Also applies to: 553-557, 559-571, 572-575

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d631017 and 0f228bf.

📒 Files selected for processing (3)
  • README.md (1 hunks)
  • streamlit_pdf_viewer/__init__.py (3 hunks)
  • streamlit_pdf_viewer/frontend/src/PdfViewer.vue (8 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: build (ubuntu-latest, 3.13, 20)
  • GitHub Check: build (ubuntu-latest, 3.10, 18)
  • GitHub Check: build (ubuntu-latest, 3.12, 18)
  • GitHub Check: build (ubuntu-latest, 3.10, 20)
  • GitHub Check: build (ubuntu-latest, 3.11, 18)
  • GitHub Check: build (ubuntu-latest, 3.13, 18)
  • GitHub Check: build (ubuntu-latest, 3.12, 20)
  • GitHub Check: build (ubuntu-latest, 3.11, 20)
🔇 Additional comments (5)
README.md (1)

99-101: Reformatting looks good.

The updated descriptions for zoom_level and viewer_align are accurate and consistent with the code.

streamlit_pdf_viewer/__init__.py (3)

42-43: Public API: new flag placement and default look good.

Adding show_fullscreen_toggle with a True default is backwards compatible and well placed after the callback.


63-64: Docstring clarity LGTM.

Description and default documented correctly.


137-139: Prop forwarding to frontend verified.

Argument is passed through to the component. No further changes needed here.

streamlit_pdf_viewer/frontend/src/PdfViewer.vue (1)

486-489: Outside-click containment LGTM.

Switching to .control-buttons correctly keeps the zoom panel open when interacting inside the controls.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
tests/test_zoom_numeric.py (1)

44-45: Replace fixed timeout with element-based wait.

Same concern as in test_zoom_auto_height.py: the hard-coded 2-second timeout is duplicated across multiple test files and should be replaced with a more reliable wait strategy.

Consider creating a shared test helper function:

# In tests/conftest.py or tests/e2e_utils.py
def wait_for_pdf_content(page: Page, iframe_index: int = 0):
    """Wait for PDF content to be loaded in the iframe."""
    iframe_frame = page.frame_locator('iframe[title="streamlit_pdf_viewer.streamlit_pdf_viewer"]').nth(iframe_index)
    iframe_frame.locator('div[id="pdfViewer"] canvas').first.wait_for(timeout=5000, state='visible')

Then use it in tests:

wait_for_pdf_content(page)
tests/test_zoom_auto.py (2)

53-54: Same fixed timeout pattern across all test files.

This is the third test file with the identical hard-coded 2-second wait. The concern and suggested refactor mentioned in test_zoom_auto_height.py applies here as well.


61-61: Same button selector brittleness.

The selector concern raised in test_zoom_auto_height.py applies here. Consider the shared helper function approach suggested in test_zoom_numeric.py to reduce duplication.

🧹 Nitpick comments (4)
tests/test_zoom_auto_height.py (1)

54-55: Consider replacing fixed timeout with element-based wait.

The hard-coded 2-second timeout is fragile and may cause flakiness (too short) or slowness (too long). Consider waiting for a specific element that indicates content is loaded instead.

For example:

-    # Wait for iframe to be properly loaded and have content
-    page.wait_for_timeout(2000)
+    # Wait for PDF content to load
+    iframe_frame = page.frame_locator('iframe[title="streamlit_pdf_viewer.streamlit_pdf_viewer"]').nth(0)
+    iframe_frame.locator('div[id="pdfViewer"] canvas').first.wait_for(timeout=5000, state='visible')
tests/test_zoom_numeric.py (1)

56-56: Same button order assumption as other tests.

The selector .top-buttons button.control-button.nth(1) is duplicated across multiple test files with the same brittleness concern noted in test_zoom_auto_height.py.

Consider extracting this into a helper function in conftest.py or e2e_utils.py:

def get_zoom_button(iframe_frame):
    """Get the zoom control button, accounting for optional fullscreen toggle."""
    return iframe_frame.locator('.top-buttons button.control-button').nth(1)

This centralizes the selector logic and makes it easier to update if the UI changes.

Also applies to: 78-78

streamlit_pdf_viewer/frontend/src/PdfViewer.vue (2)

476-497: Scroll position restoration could fail on undefined dataset values.

Lines 484 and 488 access scrollingContainer.dataset.savedScrollTop and savedScrollLeft, but these may be undefined before being set, potentially causing issues.

The current code uses || 0 and !== undefined checks, which should handle this safely. However, for clarity and consistency, consider using a more explicit pattern:

 const toggleMaximized = () => {
   isMaximized.value = !isMaximized.value;
 
   const scrollingContainer = document.querySelector('.scrolling-container');
   if (scrollingContainer) {
     if (isMaximized.value) {
-      scrollingContainer.dataset.savedScrollTop = scrollingContainer.scrollTop;
-      scrollingContainer.dataset.savedScrollLeft = scrollingContainer.scrollLeft;
+      scrollingContainer.dataset.savedScrollTop = String(scrollingContainer.scrollTop);
+      scrollingContainer.dataset.savedScrollLeft = String(scrollingContainer.scrollLeft);
     } else {
-      if (scrollingContainer.dataset.savedScrollTop !== undefined) {
-        scrollingContainer.scrollTop = parseFloat(scrollingContainer.dataset.savedScrollTop);
-        scrollingContainer.scrollLeft = parseFloat(scrollingContainer.dataset.savedScrollLeft);
-      }
+      scrollingContainer.scrollTop = parseFloat(scrollingContainer.dataset.savedScrollTop || '0');
+      scrollingContainer.scrollLeft = parseFloat(scrollingContainer.dataset.savedScrollLeft || '0');
     }
   }
 
   handleMaximizedResize();
 };

553-557: Add event.stopPropagation() and preventDefault() for defensive event handling.

While codebase analysis reveals no conflicting Escape key handlers from other Streamlit components (Streamlit itself doesn't provide built-in Escape handling), adding these calls follows best practice for global keyboard listeners to prevent unintended event bubbling:

const handleKeyDown = (event) => {
  if (event.key === 'Escape' && isMaximized.value) {
+   event.stopPropagation();
+   event.preventDefault();
    toggleMaximized();
  }
};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f228bf and 37833b2.

📒 Files selected for processing (6)
  • README.md (2 hunks)
  • streamlit_pdf_viewer/__init__.py (3 hunks)
  • streamlit_pdf_viewer/frontend/src/PdfViewer.vue (9 hunks)
  • tests/test_zoom_auto.py (1 hunks)
  • tests/test_zoom_auto_height.py (1 hunks)
  • tests/test_zoom_numeric.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • streamlit_pdf_viewer/init.py
🧰 Additional context used
🧬 Code graph analysis (3)
tests/test_zoom_auto_height.py (1)
tests/conftest.py (1)
  • page (46-68)
tests/test_zoom_numeric.py (1)
tests/conftest.py (1)
  • page (46-68)
tests/test_zoom_auto.py (1)
tests/conftest.py (1)
  • page (46-68)
🪛 LanguageTool
README.md

[style] ~100-~100: To form a complete sentence, be sure to include a subject or ‘there’.
Context: ... | The zoom level of the PDF viewer. Can be a float (0.1-10.0), "auto" for fit...

(MISSING_IT_THERE)


[style] ~101-~101: To form a complete sentence, be sure to include a subject.
Context: ...of the PDF viewer within its container. Can be "center" (default), "left", or `...

(MISSING_IT_THERE)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: build (ubuntu-latest, 3.11, 18)
  • GitHub Check: build (ubuntu-latest, 3.10, 20)
  • GitHub Check: build (ubuntu-latest, 3.13, 20)
  • GitHub Check: build (ubuntu-latest, 3.12, 20)
  • GitHub Check: build (ubuntu-latest, 3.11, 20)
  • GitHub Check: build (ubuntu-latest, 3.12, 18)
  • GitHub Check: build (ubuntu-latest, 3.13, 18)
  • GitHub Check: build (ubuntu-latest, 3.10, 18)
🔇 Additional comments (5)
README.md (2)

28-28: LGTM! Clear feature addition.

The new feature bullet accurately describes the maximization toggle. The phrase "within the component boundaries" effectively distinguishes this from browser-level fullscreen.


106-106: Documentation is clear and accurate.

The parameter documentation correctly describes the show_fullscreen_toggle behavior and clarifies that it provides "expanded viewing within the component boundaries rather than browser fullscreen."

streamlit_pdf_viewer/frontend/src/PdfViewer.vue (3)

85-86: LGTM! Clean state management setup.

The isMaximized reactive state and showFullscreen computed property provide clear separation between UI state and prop-driven visibility control.


110-149: Well-structured computed property with clear mode handling.

The conditional logic cleanly separates maximized and normal modes, with appropriate styling for each state. The transition property adds polish to the mode switching.


2-15: UI structure aligns well with updated test selectors.

The new .top-buttons and .control-button class structure matches the test selectors (.top-buttons button.control-button), ensuring test reliability. The conditional rendering of the fullscreen button via v-if="showFullscreen" correctly implements the feature toggle.

Comment on lines +420 to +445
// Handle maximized state changes without full re-render
const handleMaximizedResize = () => {
if (isMaximized.value) {
// When maximized, adjust container dimensions without re-rendering PDF
const scrollingContainer = document.querySelector('.scrolling-container');
if (scrollingContainer) {
scrollingContainer.style.height = 'calc(100vh - 120px)'; // Account for control buttons
}
// Adjust PDF viewer width to fit the maximized container
const pdfViewer = document.getElementById("pdfViewer");
if (pdfViewer && maxWidth.value) {
pdfViewer.style.width = '100%';
pdfViewer.style.maxWidth = 'none';
}
} else {
// Restore original dimensions when exiting maximized mode
const scrollingContainer = document.querySelector('.scrolling-container');
if (scrollingContainer) {
scrollingContainer.style.height = '';
}
// Trigger a single re-render to restore original layout
handleResize();
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Full re-render on exit may be expensive for large PDFs.

Line 443 calls handleResize() which re-renders the entire PDF when exiting maximized mode. For large documents, this could cause a noticeable delay or freeze.

Consider alternatives:

  1. Restore the original layout without re-rendering by reverting CSS changes only
  2. If re-render is necessary, show a loading indicator during the operation
  3. Cache the rendered pages to avoid full re-computation

Additionally, line 426 uses a magic number 120px for control button height. Consider extracting this to a constant or computing it dynamically:

+const CONTROL_BUTTONS_HEIGHT = 120; // Height of control buttons area
+
 const handleMaximizedResize = () => {
   if (isMaximized.value) {
     const scrollingContainer = document.querySelector('.scrolling-container');
     if (scrollingContainer) {
-      scrollingContainer.style.height = 'calc(100vh - 120px)';
+      scrollingContainer.style.height = `calc(100vh - ${CONTROL_BUTTONS_HEIGHT}px)`;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Handle maximized state changes without full re-render
const handleMaximizedResize = () => {
if (isMaximized.value) {
// When maximized, adjust container dimensions without re-rendering PDF
const scrollingContainer = document.querySelector('.scrolling-container');
if (scrollingContainer) {
scrollingContainer.style.height = 'calc(100vh - 120px)'; // Account for control buttons
}
// Adjust PDF viewer width to fit the maximized container
const pdfViewer = document.getElementById("pdfViewer");
if (pdfViewer && maxWidth.value) {
pdfViewer.style.width = '100%';
pdfViewer.style.maxWidth = 'none';
}
} else {
// Restore original dimensions when exiting maximized mode
const scrollingContainer = document.querySelector('.scrolling-container');
if (scrollingContainer) {
scrollingContainer.style.height = '';
}
// Trigger a single re-render to restore original layout
handleResize();
}
};
const CONTROL_BUTTONS_HEIGHT = 120; // Height of control buttons area
// Handle maximized state changes without full re-render
const handleMaximizedResize = () => {
if (isMaximized.value) {
// When maximized, adjust container dimensions without re-rendering PDF
const scrollingContainer = document.querySelector('.scrolling-container');
if (scrollingContainer) {
scrollingContainer.style.height = `calc(100vh - ${CONTROL_BUTTONS_HEIGHT}px)`; // Account for control buttons
}
// Adjust PDF viewer width to fit the maximized container
const pdfViewer = document.getElementById("pdfViewer");
if (pdfViewer && maxWidth.value) {
pdfViewer.style.width = '100%';
pdfViewer.style.maxWidth = 'none';
}
} else {
// Restore original dimensions when exiting maximized mode
const scrollingContainer = document.querySelector('.scrolling-container');
if (scrollingContainer) {
scrollingContainer.style.height = '';
}
// Trigger a single re-render to restore original layout
handleResize();
}
};

Comment on lines +463 to +474
watch(isMaximized, (newVal) => {
if (newVal) {
// When entering maximized mode, ensure zoom controls work properly
setTimeout(() => {
const scrollingContainer = document.querySelector('.scrolling-container');
if (scrollingContainer) {
scrollingContainer.scrollTop = parseFloat(scrollingContainer.dataset.savedScrollTop || 0);
scrollingContainer.scrollLeft = parseFloat(scrollingContainer.dataset.savedScrollLeft || 0);
}
}, 100); // Small delay to ensure DOM has updated
}
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Avoid setTimeout for DOM synchronization.

Lines 466-472 use setTimeout with a 100ms delay to wait for the DOM to update. This is fragile and could cause race conditions.

Use Vue's nextTick instead for proper DOM update synchronization:

 watch(isMaximized, (newVal) => {
   if (newVal) {
-    setTimeout(() => {
+    nextTick(() => {
       const scrollingContainer = document.querySelector('.scrolling-container');
       if (scrollingContainer) {
         scrollingContainer.scrollTop = parseFloat(scrollingContainer.dataset.savedScrollTop || 0);
         scrollingContainer.scrollLeft = parseFloat(scrollingContainer.dataset.savedScrollLeft || 0);
       }
-    }, 100);
+    });
   }
 });

Don't forget to import nextTick:

-import {onMounted, computed, ref, onUnmounted, watch} from "vue";
+import {onMounted, computed, ref, onUnmounted, watch, nextTick} from "vue";

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In streamlit_pdf_viewer/frontend/src/PdfViewer.vue around lines 463 to 474, the
watch callback uses setTimeout(…,100) to wait for DOM updates; replace this with
Vue's nextTick: import nextTick from 'vue' (or add it to the existing import),
make the watch callback async, await nextTick() instead of setTimeout, then run
the scrollingContainer.dataset restore logic immediately after the await so the
DOM synchronization is deterministic and the arbitrary delay is removed.


# Check that zoom controls are present
zoom_button = iframe_frame.locator('button.zoom-button')
zoom_button = iframe_frame.locator('.top-buttons button.control-button').nth(1)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify button order assumption with nth(1).

The selector .top-buttons button.control-button.nth(1) assumes the zoom button is always the second button (index 1). If show_fullscreen_toggle is false or the button order changes, this will break.

Run this script to verify the button order across different configurations:


🏁 Script executed:

#!/bin/bash
# Check if show_fullscreen_toggle is configurable in test apps
rg -n "show_fullscreen_toggle" tests/streamlit_apps/

# Check the button rendering order in the Vue component
ast-grep --pattern $'<div class="top-buttons">
  $$$
</div>'

Length of output: 0


I need to explore the codebase structure to understand the button rendering logic and verify the concern about button order assumptions.


🏁 Script executed:

#!/bin/bash
# Find the test file and examine its contents
find . -name "test_zoom_auto_height.py" -type f 2>/dev/null

# Find streamlit apps directory structure
find . -path "*streamlit_apps*" -type d 2>/dev/null | head -10

# Search for button.control-button selector in test files
rg -n "button\.control-button" tests/ 2>/dev/null | head -20

# Look for show_fullscreen_toggle in the entire codebase
rg -n "show_fullscreen_toggle" . 2>/dev/null | head -20

Length of output: 1782


🏁 Script executed:

#!/bin/bash
# Search for top-buttons div and button rendering in any component files
rg -n "top-buttons" . 2>/dev/null | head -30

# Look for nth(1) or similar index-based selectors in tests
rg -n "\.nth\(" tests/ 2>/dev/null | head -20

Length of output: 3167


🏁 Script executed:

#!/bin/bash
# Find Vue/JavaScript/TypeScript component files that might render buttons
fd -e vue -e jsx -e tsx -e js . 2>/dev/null | head -30

# Look for fullscreen or zoom button related code
rg -n "fullscreen\|zoom" . --type vue --type js --type jsx --type tsx 2>/dev/null | head -30

Length of output: 251


I need to examine the Vue component to understand the button rendering order and whether it depends on show_fullscreen_toggle.


🏁 Script executed:

#!/bin/bash
# Extract the top-buttons div and surrounding context from PdfViewer.vue
sed -n '7,50p' ./streamlit_pdf_viewer/frontend/src/PdfViewer.vue

# Also check for any conditional rendering of buttons
rg -A 15 "top-buttons" ./streamlit_pdf_viewer/frontend/src/PdfViewer.vue

Length of output: 3827


🏁 Script executed:

#!/bin/bash
# Look for button rendering logic and fullscreen/zoom button definitions
rg -n "control-button\|zoom\|fullscreen" ./streamlit_pdf_viewer/frontend/src/PdfViewer.vue | head -40

Length of output: 0


Now I need to verify what configurations the test apps use for show_fullscreen_toggle:


🏁 Script executed:

#!/bin/bash
# Check what test apps exist and their configurations
ls -la tests/streamlit_apps/

# Search for show_fullscreen_toggle in test app files
rg -n "show_fullscreen_toggle" tests/streamlit_apps/ -A 2 -B 2

Length of output: 1399


🏁 Script executed:

#!/bin/bash
# Check if any test explicitly sets show_fullscreen_toggle to False or passes it as a parameter
rg -n "show_fullscreen_toggle\s*=\s*False\|show_fullscreen_toggle.*false" tests/ 

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Read the test app files to see how they call pdf_viewer
cat tests/streamlit_apps/example_zoom_auto_height.py
echo "---"
cat tests/streamlit_apps/example_zoom_auto.py
echo "---"
cat tests/streamlit_apps/example_zoom_numeric.py

Length of output: 828


🏁 Script executed:

#!/bin/bash
# Check what the corresponding test files do - look at their app parameter
grep -n "st_pdf_viewer\|pdf_viewer\|example_" tests/test_zoom_auto_height.py | head -15
echo "---"
grep -n "st_pdf_viewer\|pdf_viewer\|example_" tests/test_zoom_auto.py | head -15
echo "---"  
grep -n "st_pdf_viewer\|pdf_viewer\|example_" tests/test_zoom_numeric.py | head -15

Length of output: 1790


Improve selector robustness—nth(1) depends on implicit fullscreen toggle configuration.

The selector .nth(1) assumes the zoom button is always the second button. However, the first button (fullscreen toggle) is conditionally rendered based on show_fullscreen_toggle, which defaults to True. While current test apps don't override this default, the selector is fragile and will break if:

  • A test app sets show_fullscreen_toggle=False
  • Component configuration changes in the future

Recommendation: Replace index-based selection with a more resilient selector that targets the zoom button directly, such as by its text content (contains %) or by its click handler. Alternatively, explicitly pass show_fullscreen_toggle=True in test apps and document this assumption.

🤖 Prompt for AI Agents
In tests/test_zoom_auto_height.py around line 62, the test uses .nth(1) to pick
the zoom button which is fragile because the fullscreen toggle may or may not be
present; replace the index-based locator with a resilient selector that targets
the zoom control explicitly (for example, select the button by its visible text
containing "%" or by a dedicated test attribute/aria-label), or alternatively
ensure test apps explicitly set show_fullscreen_toggle=True and document that
dependency; update the locator accordingly so the test no longer relies on
button order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants