@@ -98,6 +98,16 @@ function writeWidthInstantly(el: HTMLElement, width: number) {
9898 el . style . transition = prevTransition
9999}
100100
101+ /** Restores the preference within the current layout without changing the saved width. */
102+ function restorePanelWidth ( el : HTMLElement , preferred : number | undefined ) {
103+ if ( preferred === undefined ) {
104+ el . style . removeProperty ( 'width' )
105+ return
106+ }
107+ const width = Math . min ( preferred , measureMaxWidth ( el ) )
108+ if ( el . style . width !== `${ width } px` ) writeWidthInstantly ( el , width )
109+ }
110+
101111/** Mirrors the panel's current width and bounds onto the divider for assistive tech. */
102112function syncDividerValue ( handle : HTMLElement , el : HTMLElement , maxWidth = measureMaxWidth ( el ) ) {
103113 handle . setAttribute ( 'aria-valuemin' , String ( MOTHERSHIP_WIDTH . MIN ) )
@@ -150,13 +160,7 @@ export function useMothershipResize(
150160 const restoreWidth = ( ) => {
151161 rafId = null
152162 if ( cleanupRef . current ) return
153- const preferred = preferredWidthRef . current
154- if ( collapsed || preferred === undefined ) {
155- el . style . removeProperty ( 'width' )
156- } else {
157- const width = Math . min ( preferred , measureMaxWidth ( el ) )
158- if ( el . style . width !== `${ width } px` ) writeWidthInstantly ( el , width )
159- }
163+ restorePanelWidth ( el , collapsed ? undefined : preferredWidthRef . current )
160164 const divider = focusedDividerRef . current
161165 if ( divider && document . activeElement === divider ) syncDividerValue ( divider , el )
162166 }
@@ -195,8 +199,7 @@ export function useMothershipResize(
195199 const startRect = el . getBoundingClientRect ( )
196200 el . style . width = `${ startRect . width } px`
197201
198- // Nothing moves the panel's right edge mid-drag, and the pointer keeps the
199- // offset it grabbed at, so one measurement serves the whole gesture.
202+ // Snapshot geometry avoids layout reads on every move; release applies fresh bounds.
200203 const geometry : DragGeometry = {
201204 panelRight : startRect . right ,
202205 grabOffset : e . clientX - startRect . left ,
@@ -227,30 +230,31 @@ export function useMothershipResize(
227230 const ac = new AbortController ( )
228231 const { signal } = ac
229232
230- const cleanup = ( ) => {
233+ const finish = ( commit : boolean ) => {
231234 ac . abort ( )
232235 if ( rafId !== null ) {
233236 cancelAnimationFrame ( rafId )
234237 rafId = null
235238 }
236- // Land on the exact final pointer position before transitions come back,
237- // so a fast flick whose last move never got a frame is not lost. The
238- // flush is what stops that catch-up delta from animating: without it the
239- // width write and the transition restore land in one style change, and
240- // the panel eases into its final width over 200ms while the native view
241- // chases it.
242- if ( lastClientX !== null ) {
243- applyWidth ( lastClientX )
239+ if ( commit && lastClientX !== null ) {
244240 rememberWidth ( panelWidthAt ( lastClientX , geometry ) )
245241 }
242+ // Flush the restored width before transitions return, so the native view
243+ // does not chase a 200ms catch-up animation.
244+ restorePanelWidth ( el , preferredWidthRef . current )
246245 void el . offsetWidth
247246 el . style . transition = prevTransition
248247 document . body . style . cursor = ''
249248 document . body . style . userSelect = ''
250249 cleanupRef . current = null
250+ if ( handle . hasPointerCapture ( pointerId ) ) handle . releasePointerCapture ( pointerId )
251251 syncDividerValue ( handle , el )
252252 }
253- cleanupRef . current = cleanup
253+ const cancel = ( ) => finish ( false )
254+ const cancelPointer = ( event : PointerEvent ) => {
255+ if ( event . pointerId === pointerId ) cancel ( )
256+ }
257+ cleanupRef . current = cancel
254258
255259 handle . addEventListener (
256260 'pointermove' ,
@@ -276,18 +280,18 @@ export function useMothershipResize(
276280 'pointerup' ,
277281 ( upEvent : PointerEvent ) => {
278282 if ( upEvent . pointerId !== pointerId ) return
279- handle . releasePointerCapture ( upEvent . pointerId )
280- cleanup ( )
283+ finish ( true )
281284 } ,
282285 { signal }
283286 )
284287
285288 // Browser fires pointercancel when it reclaims the gesture (scroll, palm rejection, etc.)
286289 // Without this, body cursor/userSelect and transition would be permanently stuck
287- handle . addEventListener ( 'pointercancel' , cleanup , { signal } )
290+ handle . addEventListener ( 'pointercancel' , cancelPointer , { signal } )
291+ handle . addEventListener ( 'lostpointercapture' , cancelPointer , { signal } )
288292 // A blur mid-drag (cmd-tab, window switch) would otherwise strand the
289293 // body cursor/userSelect overrides with no pointerup coming
290- window . addEventListener ( 'blur' , cleanup , { signal } )
294+ window . addEventListener ( 'blur' , cancel , { signal } )
291295 } ,
292296 [ desktopScopeId , rememberWidth ]
293297 )
0 commit comments