@@ -35,18 +35,73 @@ const PDFViewer: DocRendererElement = (props: DocumentRendererProps) => {
3535 const [ numberOfPages , setNumberOfPages ] = useState < number > ( 1 ) ;
3636 const { zoomLevel, zoomIn, zoomOut, reset } = useZoomScale ( ) ;
3737 const [ currentPage , setCurrentPage ] = useState < number > ( 1 ) ;
38+ const [ pageInputValue , setPageInputValue ] = useState < string > ( "1" ) ;
3839 const [ pdfUrl , setPdfUrl ] = useState < string | null > ( null ) ;
3940
4041 const onDownloadClick = useCallback ( ( ) => {
4142 downloadFile ( file . value ?. uri ) ;
4243 } , [ file ] ) ;
4344
45+ const handlePageInputChange = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
46+ const value = event . target . value ;
47+ // Allow only numbers and empty string
48+ if ( value === "" || / ^ \d + $ / . test ( value ) ) {
49+ setPageInputValue ( value ) ;
50+ }
51+ } , [ ] ) ;
52+
53+ const validateAndSetPage = useCallback ( ( ) => {
54+ const pageNumber = parseInt ( pageInputValue , 10 ) ;
55+ if ( ! isNaN ( pageNumber ) && pageNumber >= 1 && pageNumber <= numberOfPages ) {
56+ setCurrentPage ( pageNumber ) ;
57+ } else {
58+ // Reset to current page if invalid input
59+ setPageInputValue ( currentPage . toString ( ) ) ;
60+ }
61+ } , [ pageInputValue , numberOfPages , currentPage ] ) ;
62+
63+ const handlePageInputSubmit = useCallback (
64+ ( event : React . FormEvent ) => {
65+ event . preventDefault ( ) ;
66+ validateAndSetPage ( ) ;
67+ } ,
68+ [ validateAndSetPage ]
69+ ) ;
70+
71+ const handlePageInputBlur = useCallback ( ( ) => {
72+ validateAndSetPage ( ) ;
73+ } , [ validateAndSetPage ] ) ;
74+
75+ const handlePageInputKeyDown = useCallback (
76+ ( event : React . KeyboardEvent < HTMLInputElement > ) => {
77+ if ( event . key === "Enter" ) {
78+ event . preventDefault ( ) ;
79+ validateAndSetPage ( ) ;
80+ }
81+ // Prevent non-numeric characters except backspace, delete, arrow keys, etc.
82+ if (
83+ ! / [ \d ] / . test ( event . key ) &&
84+ ! [ "Backspace" , "Delete" , "ArrowLeft" , "ArrowRight" , "Home" , "End" , "Tab" ] . includes ( event . key ) &&
85+ ! event . ctrlKey &&
86+ ! event . metaKey
87+ ) {
88+ event . preventDefault ( ) ;
89+ }
90+ } ,
91+ [ validateAndSetPage ]
92+ ) ;
93+
4494 useEffect ( ( ) => {
4595 if ( file . status === "available" && file . value . uri ) {
4696 setPdfUrl ( file . value . uri ) ;
4797 }
4898 } , [ file , file . status , file . value ?. uri ] ) ;
4999
100+ // Sync page input value with current page
101+ useEffect ( ( ) => {
102+ setPageInputValue ( currentPage . toString ( ) ) ;
103+ } , [ currentPage ] ) ;
104+
50105 function onDocumentLoadSuccess ( { numPages } : { numPages : number } ) : void {
51106 setNumberOfPages ( numPages ) ;
52107 }
@@ -69,11 +124,27 @@ const PDFViewer: DocRendererElement = (props: DocumentRendererProps) => {
69124 aria-label = { "Go to previous page" }
70125 title = { "Go to previous page" }
71126 > </ button >
72- < span >
73- { currentPage } / { numberOfPages }
74- </ span >
127+ < div className = "widget-document-viewer-page-input" >
128+ < form onSubmit = { handlePageInputSubmit } >
129+ < input
130+ type = "text"
131+ inputMode = "numeric"
132+ pattern = "[0-9]*"
133+ value = { pageInputValue }
134+ onChange = { handlePageInputChange }
135+ onKeyDown = { handlePageInputKeyDown }
136+ onBlur = { handlePageInputBlur }
137+ className = "form-control widget-document-viewer-page-number-input"
138+ aria-label = "Page number"
139+ title = { `Go to page (1-${ numberOfPages } )` }
140+ placeholder = { currentPage . toString ( ) }
141+ />
142+ </ form >
143+ < span className = "widget-document-viewer-total-pages" > / { numberOfPages } </ span >
144+ </ div >
75145 < button
76146 onClick = { ( ) => setCurrentPage ( prev => Math . min ( prev + 1 , numberOfPages ) ) }
147+ disabled = { currentPage >= numberOfPages }
77148 className = "icons icon-Right btn btn-icon-only"
78149 aria-label = { "Go to next page" }
79150 title = { "Go to next page" }
0 commit comments