@@ -529,6 +529,7 @@ export class Adhesive {
529529 // Request Animation Frame optimization for smooth updates
530530 #rafId: number | null = null ;
531531 #pendingUpdate = false ;
532+ #pendingResizeUpdate = false ;
532533
533534 /**
534535 * Static factory method for convenient instance creation and initialization
@@ -710,8 +711,14 @@ export class Adhesive {
710711 const innerRect = this . #innerWrapper. getBoundingClientRect ( ) ;
711712
712713 // Calculate dimensions with fallbacks for browser compatibility
713- const width = outerRect . width || outerRect . right - outerRect . left ;
714- const height = innerRect . height || innerRect . bottom - innerRect . top ;
714+ const width =
715+ outerRect . width ||
716+ outerRect . right - outerRect . left ||
717+ this . #outerWrapper. offsetWidth ;
718+ const height =
719+ innerRect . height ||
720+ innerRect . bottom - innerRect . top ||
721+ this . #innerWrapper. offsetHeight ;
715722 const outerY = outerRect . top + this . #scrollTop;
716723
717724 // Batch update state for better performance
@@ -725,6 +732,72 @@ export class Adhesive {
725732 } ) ;
726733 }
727734
735+ /**
736+ * Updates only the width dimensions for resize scenarios
737+ * This method is optimized for responsive width changes without full recalculation
738+ * @internal
739+ */
740+ #updateWidthDimensions( ) : void {
741+ if ( ! this . #outerWrapper || ! this . #innerWrapper) return ;
742+
743+ // For width updates, we need to temporarily reset positioning to get accurate measurements
744+ const wasFixed = this . #state. status === ADHESIVE_STATUS . FIXED ;
745+ const wasRelative = this . #state. status === ADHESIVE_STATUS . RELATIVE ;
746+
747+ if ( wasFixed || wasRelative ) {
748+ // Temporarily clear positioning styles to get natural width
749+ const innerStyle = this . #innerWrapper. style ;
750+ const originalPosition = innerStyle . position ;
751+ const originalTransform = innerStyle . transform ;
752+ const originalTop = innerStyle . top ;
753+ const originalBottom = innerStyle . bottom ;
754+
755+ innerStyle . position = "static" ;
756+ innerStyle . transform = "" ;
757+ innerStyle . top = "" ;
758+ innerStyle . bottom = "" ;
759+
760+ // Force reflow to get accurate measurements
761+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
762+ this . #outerWrapper. offsetHeight ;
763+
764+ const outerRect = this . #outerWrapper. getBoundingClientRect ( ) ;
765+ const newWidth =
766+ outerRect . width ||
767+ outerRect . right - outerRect . left ||
768+ this . #outerWrapper. offsetWidth ;
769+
770+ // Restore positioning styles
771+ innerStyle . position = originalPosition ;
772+ innerStyle . transform = originalTransform ;
773+ innerStyle . top = originalTop ;
774+ innerStyle . bottom = originalBottom ;
775+
776+ // Update state with new width
777+ this . #state. width = newWidth ;
778+ this . #state. x = outerRect . left ;
779+ } else {
780+ // For initial state, simple measurement is sufficient
781+ const outerRect = this . #outerWrapper. getBoundingClientRect ( ) ;
782+ const newWidth =
783+ outerRect . width ||
784+ outerRect . right - outerRect . left ||
785+ this . #outerWrapper. offsetWidth ;
786+ this . #state. width = newWidth ;
787+ this . #state. x = outerRect . left ;
788+ }
789+ }
790+
791+ /**
792+ * Forces an immediate width update and style refresh
793+ * Useful for responsive design scenarios where width changes need immediate application
794+ * @internal
795+ */
796+ #forceWidthUpdate( ) : void {
797+ this . #updateWidthDimensions( ) ;
798+ this . #updateStyles( ) ;
799+ }
800+
728801 // =============================================================================
729802 // State Management Methods
730803 // =============================================================================
@@ -787,7 +860,6 @@ export class Adhesive {
787860 } ;
788861
789862 const isFixed = this . #state. status === ADHESIVE_STATUS . FIXED ;
790- const isNotInitial = this . #state. status !== ADHESIVE_STATUS . INITIAL ;
791863 const { position } = this . #options;
792864
793865 // Clear all positioning styles first
@@ -798,8 +870,8 @@ export class Adhesive {
798870
799871 // Set common styles
800872 innerWrapper . style . zIndex = String ( this . #options. zIndex ) ;
801- innerWrapper . style . width = isNotInitial ? `${ this . #state. width } px` : "" ;
802- outerWrapper . style . height = isNotInitial ? `${ this . #state. height } px` : "" ;
873+ innerWrapper . style . width = isFixed ? `${ this . #state. width } px` : "" ;
874+ outerWrapper . style . height = isFixed ? `${ this . #state. height } px` : "" ;
803875
804876 // Apply positioning based on state
805877 if ( isFixed ) {
@@ -1069,10 +1141,11 @@ export class Adhesive {
10691141 } ;
10701142
10711143 /**
1072- * Optimized resize handler with RAF throttling
1144+ * Optimized window resize handler with RAF throttling
1145+ * Handles viewport dimension changes
10731146 * @internal
10741147 */
1075- readonly #onResize = ( ) : void => {
1148+ readonly #onWindowResize = ( ) : void => {
10761149 if ( ! this . #isEnabled || this . #pendingUpdate) return ;
10771150
10781151 this . #pendingUpdate = true ;
@@ -1084,6 +1157,47 @@ export class Adhesive {
10841157 } ) ;
10851158 } ;
10861159
1160+ /**
1161+ * ResizeObserver callback for element dimension changes
1162+ * Handles width updates with immediate style application and debouncing for performance
1163+ * @internal
1164+ */
1165+ readonly #onElementResize = ( entries : ResizeObserverEntry [ ] ) : void => {
1166+ if ( ! this . #isEnabled) return ;
1167+
1168+ if ( this . #pendingResizeUpdate) return ;
1169+
1170+ this . #pendingResizeUpdate = true ;
1171+ this . #rafId = requestAnimationFrame ( ( ) => {
1172+ this . #pendingResizeUpdate = false ;
1173+
1174+ // Check if this is a width-affecting resize
1175+ let needsWidthUpdate = false ;
1176+ let needsFullUpdate = false ;
1177+
1178+ for ( const entry of entries ) {
1179+ if ( entry . target === this . #outerWrapper) {
1180+ // Outer wrapper resize affects width
1181+ needsWidthUpdate = true ;
1182+ } else if ( entry . target === this . #boundingEl) {
1183+ // Bounding element resize affects boundaries
1184+ needsFullUpdate = true ;
1185+ } else if ( entry . target === this . #targetEl) {
1186+ // Target element content changes might affect height
1187+ needsFullUpdate = true ;
1188+ }
1189+ }
1190+
1191+ if ( needsFullUpdate ) {
1192+ this . #updateInitialDimensions( ) ;
1193+ } else if ( needsWidthUpdate ) {
1194+ this . #updateWidthDimensions( ) ;
1195+ }
1196+
1197+ this . #updateStyles( ) ;
1198+ this . #update( ) ;
1199+ } ) ;
1200+ } ;
10871201 // =============================================================================
10881202 // Public API Methods
10891203 // =============================================================================
@@ -1116,15 +1230,17 @@ export class Adhesive {
11161230
11171231 // Add event listeners with optimal performance settings
11181232 window . addEventListener ( "scroll" , this . #onScroll, { passive : true } ) ;
1119- window . addEventListener ( "resize" , this . #onResize , { passive : true } ) ;
1233+ window . addEventListener ( "resize" , this . #onWindowResize , { passive : true } ) ;
11201234
11211235 // Modern ResizeObserver for better performance
11221236 if ( "ResizeObserver" in window ) {
1123- this . #observer = new ResizeObserver ( this . #onResize ) ;
1237+ this . #observer = new ResizeObserver ( this . #onElementResize ) ;
11241238 this . #observer. observe ( this . #boundingEl) ;
11251239 if ( this . #outerWrapper) {
11261240 this . #observer. observe ( this . #outerWrapper) ;
11271241 }
1242+ // Also observe the target element for content changes
1243+ this . #observer. observe ( this . #targetEl) ;
11281244 } else {
11291245 const error = ERROR_REGISTRY . RESIZE_OBSERVER_NOT_SUPPORTED ;
11301246 console . warn ( `@adhesivejs/core: ${ error . message } ` ) ;
@@ -1222,6 +1338,25 @@ export class Adhesive {
12221338 return { ...this . #state } ;
12231339 }
12241340
1341+ /**
1342+ * Manually triggers a width update for the sticky element.
1343+ * This is useful when the element's container width changes due to external factors
1344+ * that might not be detected by the ResizeObserver (e.g., CSS changes via JavaScript).
1345+ *
1346+ * @returns The Adhesive instance for method chaining
1347+ *
1348+ * @example
1349+ * ```ts
1350+ * // After programmatically changing container width
1351+ * adhesive.refreshWidth();
1352+ * ```
1353+ */
1354+ refreshWidth ( ) : this {
1355+ if ( ! this . #isEnabled) return this ;
1356+ this . #forceWidthUpdate( ) ;
1357+ return this ;
1358+ }
1359+
12251360 /**
12261361 * Cleans up the Adhesive instance by removing event listeners, disconnecting observers,
12271362 * canceling pending animations, and restoring the original DOM structure.
@@ -1236,16 +1371,18 @@ export class Adhesive {
12361371 * ```
12371372 */
12381373 cleanup ( ) : void {
1239- // Cancel any pending RAF operations
1374+ // Cancel any pending RAF operations and timeouts
12401375 if ( this . #rafId !== null ) {
12411376 cancelAnimationFrame ( this . #rafId) ;
12421377 this . #rafId = null ;
12431378 }
1379+
12441380 this . #pendingUpdate = false ;
1381+ this . #pendingResizeUpdate = false ;
12451382
12461383 // Remove event listeners
12471384 window . removeEventListener ( "scroll" , this . #onScroll) ;
1248- window . removeEventListener ( "resize" , this . #onResize ) ;
1385+ window . removeEventListener ( "resize" , this . #onWindowResize ) ;
12491386
12501387 // Disconnect observers
12511388 this . #observer?. disconnect ( ) ;
0 commit comments