diff --git a/packages/pluggableWidgets/document-viewer-web/components/PDFViewer.tsx b/packages/pluggableWidgets/document-viewer-web/components/PDFViewer.tsx index 591c378b60..7a375a22cb 100644 --- a/packages/pluggableWidgets/document-viewer-web/components/PDFViewer.tsx +++ b/packages/pluggableWidgets/document-viewer-web/components/PDFViewer.tsx @@ -35,18 +35,73 @@ const PDFViewer: DocRendererElement = (props: DocumentRendererProps) => { const [numberOfPages, setNumberOfPages] = useState(1); const { zoomLevel, zoomIn, zoomOut, reset } = useZoomScale(); const [currentPage, setCurrentPage] = useState(1); + const [pageInputValue, setPageInputValue] = useState("1"); const [pdfUrl, setPdfUrl] = useState(null); const onDownloadClick = useCallback(() => { downloadFile(file.value?.uri); }, [file]); + const handlePageInputChange = useCallback((event: React.ChangeEvent) => { + const value = event.target.value; + // Allow only numbers and empty string + if (value === "" || /^\d+$/.test(value)) { + setPageInputValue(value); + } + }, []); + + const validateAndSetPage = useCallback(() => { + const pageNumber = parseInt(pageInputValue, 10); + if (!isNaN(pageNumber) && pageNumber >= 1 && pageNumber <= numberOfPages) { + setCurrentPage(pageNumber); + } else { + // Reset to current page if invalid input + setPageInputValue(currentPage.toString()); + } + }, [pageInputValue, numberOfPages, currentPage]); + + const handlePageInputSubmit = useCallback( + (event: React.FormEvent) => { + event.preventDefault(); + validateAndSetPage(); + }, + [validateAndSetPage] + ); + + const handlePageInputBlur = useCallback(() => { + validateAndSetPage(); + }, [validateAndSetPage]); + + const handlePageInputKeyDown = useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault(); + validateAndSetPage(); + } + // Prevent non-numeric characters except backspace, delete, arrow keys, etc. + if ( + !/[\d]/.test(event.key) && + !["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Home", "End", "Tab"].includes(event.key) && + !event.ctrlKey && + !event.metaKey + ) { + event.preventDefault(); + } + }, + [validateAndSetPage] + ); + useEffect(() => { if (file.status === "available" && file.value.uri) { setPdfUrl(file.value.uri); } }, [file, file.status, file.value?.uri]); + // Sync page input value with current page + useEffect(() => { + setPageInputValue(currentPage.toString()); + }, [currentPage]); + function onDocumentLoadSuccess({ numPages }: { numPages: number }): void { setNumberOfPages(numPages); } @@ -69,11 +124,27 @@ const PDFViewer: DocRendererElement = (props: DocumentRendererProps) => { aria-label={"Go to previous page"} title={"Go to previous page"} > - - {currentPage} / {numberOfPages} - +
+
+ +
+ / {numberOfPages} +