@@ -103,6 +103,9 @@ async function EditorManager($header, $body) {
103103 let lastScrollTop = 0 ;
104104 let lastScrollLeft = 0 ;
105105 let suppressCursorRevealUntil = 0 ;
106+ let scrollbarScrollLockUntil = 0 ;
107+ let scrollbarScrollLockTop = null ;
108+ let scrollbarScrollLockLeft = null ;
106109
107110 // Debounce timers for CodeMirror change handling
108111 let checkTimeout = null ;
@@ -185,7 +188,7 @@ async function EditorManager($header, $body) {
185188
186189 const pointerCursorVisibilityExtension = EditorView . updateListener . of (
187190 ( update ) => {
188- if ( ! update . selectionSet ) return ;
191+ if ( ! update . transactions . length ) return ;
189192 const pointerTriggered = update . transactions . some (
190193 ( tr ) =>
191194 tr . isUserEvent ( "pointer" ) ||
@@ -194,14 +197,17 @@ async function EditorManager($header, $body) {
194197 tr . isUserEvent ( "touch" ) ||
195198 tr . isUserEvent ( "select.touch" ) ,
196199 ) ;
197- if ( ! pointerTriggered ) return ;
200+ if ( ! pointerTriggered ) {
201+ clearScrollbarScrollLock ( ) ;
202+ return ;
203+ }
204+ if ( ! update . selectionSet ) return ;
198205 requestAnimationFrame ( ( ) => {
199206 if ( isCursorRevealSuppressed ( ) ) return ;
200207 if ( ! isCursorVisible ( ) ) scrollCursorIntoView ( { behavior : "instant" } ) ;
201208 } ) ;
202209 } ,
203210 ) ;
204-
205211 const isShiftSelectionActive = ( event ) => {
206212 if ( ! appSettings . value . shiftClickSelection ) return false ;
207213 return ! ! event ?. shiftKey || quickTools ?. $footer ?. dataset ?. shift != null ;
@@ -1155,6 +1161,7 @@ async function EditorManager($header, $body) {
11551161 if ( ! scroller ) return false ;
11561162
11571163 if ( row === Number . POSITIVE_INFINITY ) {
1164+ clearScrollbarScrollLock ( ) ;
11581165 scroller . scrollTop = Math . max (
11591166 scroller . scrollHeight - scroller . clientHeight ,
11601167 0 ,
@@ -2196,12 +2203,15 @@ async function EditorManager($header, $body) {
21962203
21972204 function syncScrollUi ( ) {
21982205 scrollSyncRaf = 0 ;
2199- onscrolltop ( ) ;
2200- onscrollleft ( ) ;
2206+ editor . requestMeasure ( {
2207+ read : ( ) => readScrollMetrics ( ) ,
2208+ write : updateScrollbarsFromMetrics ,
2209+ } ) ;
22012210 }
22022211
22032212 function handleEditorScroll ( ) {
22042213 if ( ! scroller ) return ;
2214+ if ( restoreScrollbarScrollLock ( ) ) return ;
22052215 if ( ! isScrolling ) {
22062216 isScrolling = true ;
22072217 if ( hasHoverTooltips ( editor . state ) ) {
@@ -2220,6 +2230,15 @@ async function EditorManager($header, $body) {
22202230 }
22212231
22222232 scroller ?. addEventListener ( "scroll" , handleEditorScroll , { passive : true } ) ;
2233+ scroller ?. addEventListener ( "pointerdown" , clearScrollbarScrollLock , {
2234+ passive : true ,
2235+ } ) ;
2236+ scroller ?. addEventListener ( "touchstart" , clearScrollbarScrollLock , {
2237+ passive : true ,
2238+ } ) ;
2239+ scroller ?. addEventListener ( "wheel" , clearScrollbarScrollLock , {
2240+ passive : true ,
2241+ } ) ;
22232242 syncScrollUi ( ) ;
22242243
22252244 keyboardHandler . on ( "keyboardShowStart" , ( ) => {
@@ -2328,6 +2347,49 @@ async function EditorManager($header, $body) {
23282347 return Date . now ( ) < suppressCursorRevealUntil ;
23292348 }
23302349
2350+ function lockScrollbarScrollPosition ( { top, left } , duration = 1200 ) {
2351+ const scroller = editor ?. scrollDOM ;
2352+ if ( ! scroller ) return ;
2353+ scrollbarScrollLockUntil = Date . now ( ) + duration ;
2354+ if ( typeof top === "number" ) scrollbarScrollLockTop = top ;
2355+ if ( typeof left === "number" ) scrollbarScrollLockLeft = left ;
2356+ }
2357+
2358+ function clearScrollbarScrollLock ( ) {
2359+ scrollbarScrollLockUntil = 0 ;
2360+ scrollbarScrollLockTop = null ;
2361+ scrollbarScrollLockLeft = null ;
2362+ }
2363+
2364+ function restoreScrollbarScrollLock ( ) {
2365+ if ( Date . now ( ) >= scrollbarScrollLockUntil ) {
2366+ clearScrollbarScrollLock ( ) ;
2367+ return false ;
2368+ }
2369+
2370+ const scroller = editor ?. scrollDOM ;
2371+ if ( ! scroller ) return false ;
2372+
2373+ let restored = false ;
2374+ if (
2375+ typeof scrollbarScrollLockTop === "number" &&
2376+ Math . abs ( scroller . scrollTop - scrollbarScrollLockTop ) > 1
2377+ ) {
2378+ scroller . scrollTop = scrollbarScrollLockTop ;
2379+ lastScrollTop = scroller . scrollTop ;
2380+ restored = true ;
2381+ }
2382+ if (
2383+ typeof scrollbarScrollLockLeft === "number" &&
2384+ Math . abs ( scroller . scrollLeft - scrollbarScrollLockLeft ) > 1
2385+ ) {
2386+ scroller . scrollLeft = scrollbarScrollLockLeft ;
2387+ lastScrollLeft = scroller . scrollLeft ;
2388+ restored = true ;
2389+ }
2390+ return restored ;
2391+ }
2392+
23312393 /**
23322394 * Checks if the cursor is visible within the CodeMirror viewport.
23332395 * @returns {boolean } - True if the cursor is visible, false otherwise.
@@ -2369,13 +2431,15 @@ async function EditorManager($header, $body) {
23692431 preventScrollbarV = true ;
23702432 scroller . scrollTop = normalized * maxScroll ;
23712433 lastScrollTop = scroller . scrollTop ;
2434+ lockScrollbarScrollPosition ( { top : lastScrollTop } ) ;
23722435 }
23732436
23742437 /**
23752438 * Handles the onscroll event for the vend element.
23762439 */
23772440 function onscrollVend ( ) {
2378- suppressCursorReveal ( ) ;
2441+ suppressCursorReveal ( 1200 ) ;
2442+ lockScrollbarScrollPosition ( { top : editor ?. scrollDOM ?. scrollTop } , 1200 ) ;
23792443 preventScrollbarV = false ;
23802444 setVScrollValue ( ) ;
23812445 }
@@ -2394,13 +2458,15 @@ async function EditorManager($header, $body) {
23942458 preventScrollbarH = true ;
23952459 scroller . scrollLeft = normalized * maxScroll ;
23962460 lastScrollLeft = scroller . scrollLeft ;
2461+ lockScrollbarScrollPosition ( { left : lastScrollLeft } ) ;
23972462 }
23982463
23992464 /**
24002465 * Handles the event when the horizontal scrollbar reaches the end.
24012466 */
24022467 function onscrollHEnd ( ) {
2403- suppressCursorReveal ( ) ;
2468+ suppressCursorReveal ( 1200 ) ;
2469+ lockScrollbarScrollPosition ( { left : editor ?. scrollDOM ?. scrollLeft } , 1200 ) ;
24042470 preventScrollbarH = false ;
24052471 setHScrollValue ( ) ;
24062472 }
@@ -2447,6 +2513,61 @@ async function EditorManager($header, $body) {
24472513 $hScrollbar . render ( ) ;
24482514 }
24492515
2516+ function readScrollMetrics ( ) {
2517+ const scroller = editor ?. scrollDOM ;
2518+ if ( ! scroller ) return null ;
2519+ return {
2520+ scrollTop : scroller . scrollTop ,
2521+ scrollLeft : scroller . scrollLeft ,
2522+ scrollHeight : scroller . scrollHeight ,
2523+ scrollWidth : scroller . scrollWidth ,
2524+ clientHeight : scroller . clientHeight ,
2525+ clientWidth : scroller . clientWidth ,
2526+ } ;
2527+ }
2528+
2529+ function updateScrollbarsFromMetrics ( metrics ) {
2530+ if ( ! metrics ) return ;
2531+
2532+ const maxScrollTop = Math . max (
2533+ metrics . scrollHeight - metrics . clientHeight ,
2534+ 0 ,
2535+ ) ;
2536+ if ( maxScrollTop <= 0 ) {
2537+ $vScrollbar . hide ( ) ;
2538+ lastScrollTop = 0 ;
2539+ $vScrollbar . value = 0 ;
2540+ } else {
2541+ if ( ! preventScrollbarV && metrics . scrollTop !== lastScrollTop ) {
2542+ lastScrollTop = metrics . scrollTop ;
2543+ $vScrollbar . value = clamp01 ( metrics . scrollTop / maxScrollTop ) ;
2544+ }
2545+ $vScrollbar . render ( ) ;
2546+ }
2547+
2548+ if ( appSettings . value . textWrap ) {
2549+ $hScrollbar . hide ( ) ;
2550+ return ;
2551+ }
2552+
2553+ const maxScrollLeft = Math . max (
2554+ metrics . scrollWidth - metrics . clientWidth ,
2555+ 0 ,
2556+ ) ;
2557+ if ( maxScrollLeft <= 0 ) {
2558+ $hScrollbar . hide ( ) ;
2559+ lastScrollLeft = 0 ;
2560+ $hScrollbar . value = 0 ;
2561+ return ;
2562+ }
2563+
2564+ if ( ! preventScrollbarH && metrics . scrollLeft !== lastScrollLeft ) {
2565+ lastScrollLeft = metrics . scrollLeft ;
2566+ $hScrollbar . value = clamp01 ( metrics . scrollLeft / maxScrollLeft ) ;
2567+ }
2568+ $hScrollbar . render ( ) ;
2569+ }
2570+
24502571 /**
24512572 * Sets scrollbars value based on the editor's scroll position.
24522573 */
0 commit comments