1313 * limitations under the License.
1414 */
1515
16- import { MathClamp , noContextMenu , stopEvent } from "pdfjs-lib" ;
16+ import { noContextMenu , stopEvent } from "pdfjs-lib" ;
17+
18+ // Timeout before ending resize operation.
19+ const RESIZE_TIMEOUT = 400 ; // ms
1720
1821/**
1922 * Viewer control to display a sidebar with resizer functionality.
2023 */
2124class Sidebar {
22- #minWidth = 0 ;
23-
24- #maxWidth = 0 ;
25-
2625 #initialWidth = 0 ;
2726
2827 #width = 0 ;
2928
3029 #coefficient;
3130
32- #visible = false ;
31+ #resizeTimeout = null ;
32+
33+ #resizer;
34+
35+ #isResizerOnTheLeft;
36+
37+ #isKeyboardResizing = false ;
38+
39+ #resizeObserver = null ;
3340
3441 /**
3542 * @typedef {Object } SidebarElements
@@ -48,46 +55,67 @@ class Sidebar {
4855 constructor ( { sidebar, resizer, toggleButton } , ltr , isResizerOnTheLeft ) {
4956 this . _sidebar = sidebar ;
5057 this . #coefficient = ltr === isResizerOnTheLeft ? - 1 : 1 ;
58+ this . #resizer = resizer ;
59+ this . #isResizerOnTheLeft = isResizerOnTheLeft ;
5160
5261 const style = window . getComputedStyle ( sidebar ) ;
53- this . #minWidth = parseFloat ( style . getPropertyValue ( "--sidebar-min-width" ) ) ;
54- this . #maxWidth = parseFloat ( style . getPropertyValue ( "--sidebar-max-width" ) ) ;
5562 this . #initialWidth = this . #width = parseFloat (
5663 style . getPropertyValue ( "--sidebar-width" )
5764 ) ;
65+ resizer . ariaValueMin = parseFloat (
66+ style . getPropertyValue ( "--sidebar-min-width" )
67+ ) ;
68+ resizer . ariaValueMax = parseFloat (
69+ style . getPropertyValue ( "--sidebar-max-width" )
70+ ) ;
71+ resizer . ariaValueNow = this . #width;
5872
59- this . #makeSidebarResizable( resizer , isResizerOnTheLeft ) ;
73+ this . #makeSidebarResizable( ) ;
6074 toggleButton . addEventListener ( "click" , this . toggle . bind ( this ) ) ;
75+ this . _isOpen = false ;
6176 sidebar . hidden = true ;
6277 }
6378
64- #makeSidebarResizable( resizer , isResizerOnTheLeft ) {
65- resizer . ariaValueMin = this . #minWidth;
66- resizer . ariaValueMax = this . #maxWidth;
67- resizer . ariaValueNow = this . #width;
68-
79+ #makeSidebarResizable( ) {
80+ const sidebarStyle = this . _sidebar . style ;
6981 let pointerMoveAC ;
7082 const cancelResize = ( ) => {
71- this . #width = MathClamp ( this . #width , this . #minWidth , this . #maxWidth ) ;
83+ this . #resizeTimeout = null ;
7284 this . _sidebar . classList . remove ( "resizing" ) ;
7385 pointerMoveAC ?. abort ( ) ;
7486 pointerMoveAC = null ;
87+ this . #resizeObserver?. disconnect ( ) ;
88+ this . #resizeObserver = null ;
89+ this . #isKeyboardResizing = false ;
90+ this . onStopResizing ( ) ;
7591 } ;
76- resizer . addEventListener ( "pointerdown" , e => {
92+ this . # resizer. addEventListener ( "pointerdown" , e => {
7793 if ( pointerMoveAC ) {
7894 cancelResize ( ) ;
7995 return ;
8096 }
97+ this . onStartResizing ( ) ;
8198 const { clientX } = e ;
8299 stopEvent ( e ) ;
83100 let prevX = clientX ;
84101 pointerMoveAC = new AbortController ( ) ;
85102 const { signal } = pointerMoveAC ;
86103 const sidebar = this . _sidebar ;
87- const sidebarStyle = sidebar . style ;
88104 sidebar . classList . add ( "resizing" ) ;
89105 const parentStyle = sidebar . parentElement . style ;
90106 parentStyle . minWidth = 0 ;
107+ this . #resizeObserver?. disconnect ( ) ;
108+ this . #resizeObserver = new ResizeObserver (
109+ ( [
110+ {
111+ borderBoxSize : [ { inlineSize } ] ,
112+ } ,
113+ ] ) => {
114+ prevX += this . #width - inlineSize ;
115+ this . #setWidth( inlineSize ) ;
116+ }
117+ ) ;
118+ this . #resizeObserver. observe ( sidebar ) ;
91119 window . addEventListener ( "contextmenu" , noContextMenu , { signal } ) ;
92120 window . addEventListener (
93121 "pointermove" ,
@@ -96,16 +124,7 @@ class Sidebar {
96124 return ;
97125 }
98126 stopEvent ( ev ) ;
99- const { clientX : x } = ev ;
100- this . #setNewWidth(
101- x - prevX ,
102- parentStyle ,
103- resizer ,
104- sidebarStyle ,
105- isResizerOnTheLeft ,
106- /* isFromKeyboard */ false
107- ) ;
108- prevX = x ;
127+ sidebarStyle . width = `${ Math . round ( this . #width + this . #coefficient * ( ev . clientX - prevX ) ) } px` ;
109128 } ,
110129 { signal, capture : true }
111130 ) ;
@@ -121,59 +140,101 @@ class Sidebar {
121140 { signal }
122141 ) ;
123142 } ) ;
124- resizer . addEventListener ( "keydown" , e => {
143+ this . # resizer. addEventListener ( "keydown" , e => {
125144 const { key } = e ;
126145 const isArrowLeft = key === "ArrowLeft" ;
127146 if ( isArrowLeft || key === "ArrowRight" ) {
147+ if ( ! this . #isKeyboardResizing) {
148+ this . _sidebar . classList . add ( "resizing" ) ;
149+ this . #isKeyboardResizing = true ;
150+ this . #resizeObserver?. disconnect ( ) ;
151+ this . #resizeObserver = new ResizeObserver (
152+ ( [
153+ {
154+ borderBoxSize : [ { inlineSize } ] ,
155+ } ,
156+ ] ) => {
157+ this . #setWidth( inlineSize ) ;
158+ }
159+ ) ;
160+ this . #resizeObserver. observe ( this . _sidebar ) ;
161+ this . onStartResizing ( ) ;
162+ }
163+
128164 const base = e . ctrlKey || e . metaKey ? 10 : 1 ;
129165 const dx = base * ( isArrowLeft ? - 1 : 1 ) ;
130- this . #setNewWidth(
131- dx ,
132- this . _sidebar . parentElement . style ,
133- resizer ,
134- this . _sidebar . style ,
135- isResizerOnTheLeft ,
136- /* isFromKeyboard */ true
137- ) ;
166+ clearTimeout ( this . #resizeTimeout) ;
167+ this . #resizeTimeout = setTimeout ( cancelResize , RESIZE_TIMEOUT ) ;
168+ sidebarStyle . width = `${ Math . round ( this . #width + this . #coefficient * dx ) } px` ;
138169 stopEvent ( e ) ;
139170 }
140171 } ) ;
141172 }
142173
143- #setNewWidth(
144- dx ,
145- parentStyle ,
146- resizer ,
147- sidebarStyle ,
148- isResizerOnTheLeft ,
149- isFromKeyboard
150- ) {
151- let newWidth = this . #width + this . #coefficient * dx ;
152- if ( ! isFromKeyboard ) {
153- this . #width = newWidth ;
154- }
155- if (
156- ( newWidth > this . #maxWidth || newWidth < this . #minWidth) &&
157- ( this . #width === this . #maxWidth || this . #width === this . #minWidth)
158- ) {
159- return ;
160- }
161- newWidth = MathClamp ( newWidth , this . #minWidth, this . #maxWidth) ;
162- if ( isFromKeyboard ) {
163- this . #width = newWidth ;
174+ #setWidth( newWidth ) {
175+ this . #width = newWidth ;
176+ this . #resizer. ariaValueNow = Math . round ( newWidth ) ;
177+ if ( this . #isResizerOnTheLeft) {
178+ this . _sidebar . parentElement . style . insetInlineStart = `${ ( this . #initialWidth - newWidth ) . toFixed ( 3 ) } px` ;
164179 }
165- resizer . ariaValueNow = Math . round ( newWidth ) ;
166- sidebarStyle . width = `${ newWidth . toFixed ( 3 ) } px` ;
167- if ( isResizerOnTheLeft ) {
168- parentStyle . insetInlineStart = `${ ( this . #initialWidth - newWidth ) . toFixed ( 3 ) } px` ;
180+ this . onResizing ( newWidth ) ;
181+ }
182+
183+ /**
184+ * Get the current width of the sidebar in pixels.
185+ * @returns {number }
186+ */
187+ get width ( ) {
188+ return this . #width;
189+ }
190+
191+ /**
192+ * Set the width of the sidebar in pixels.
193+ * @param {number } newWidth
194+ */
195+ set width ( newWidth ) {
196+ if ( ! this . #resizeObserver) {
197+ this . #resizeObserver = new ResizeObserver (
198+ ( [
199+ {
200+ borderBoxSize : [ { inlineSize } ] ,
201+ } ,
202+ ] ) => {
203+ this . #setWidth( inlineSize ) ;
204+ }
205+ ) ;
206+ this . #resizeObserver. observe ( this . _sidebar ) ;
169207 }
208+ this . _sidebar . style . width = `${ newWidth } px` ;
209+ clearTimeout ( this . #resizeTimeout) ;
210+ this . #resizeTimeout = setTimeout ( ( ) => {
211+ this . #resizeObserver. disconnect ( ) ;
212+ this . #resizeObserver = null ;
213+ } , RESIZE_TIMEOUT ) ;
170214 }
171215
216+ /**
217+ * Callback to be executed when the user starts resizing the sidebar.
218+ */
219+ onStartResizing ( ) { }
220+
221+ /**
222+ * Callback to be executed when the user stops resizing the sidebar.
223+ */
224+ onStopResizing ( ) { }
225+
226+ /**
227+ * Callback to be executed when the sidebar is being resized.
228+ * @param {number } newWidth - The new width of the sidebar in pixels.
229+ */
230+ onResizing ( _newWidth ) { }
231+
172232 /**
173233 * Toggle the sidebar's visibility.
234+ * @param {boolean } [visibility] - The visibility state to set.
174235 */
175- toggle ( ) {
176- this . _sidebar . hidden = ! ( this . #visible = ! this . #visible ) ;
236+ toggle ( visibility = ! this . _isOpen ) {
237+ this . _sidebar . hidden = ! ( this . _isOpen = visibility ) ;
177238 }
178239}
179240
0 commit comments