@@ -58,9 +58,8 @@ export function setupPinchZoom({
5858 let containerRectHeight = 0 ;
5959
6060 // Layout Dimensions (Client Box from Metrics)
61- // This is the actual space the CSS uses for centering.
6261 let layoutWidth = 0 ;
63- let layoutCenterX = 0 ; // Relative to the container Rect origin
62+ let layoutCenterX = 0 ;
6463
6564 let pointerLocalY = 0 ;
6665 let pointerContainerX = 0 ;
@@ -71,34 +70,36 @@ export function setupPinchZoom({
7170
7271 const clamp = ( val : number , min : number , max : number ) => Math . min ( Math . max ( val , min ) , max ) ;
7372
73+ // --- Margin calculation (no scroll plugin needed!) ---
74+ const updateMargin = ( ) => {
75+ const metrics = viewportScope . getMetrics ( ) ;
76+ const vpGap = viewportProvides . getViewportGap ( ) || 0 ;
77+ const availableWidth = metrics . clientWidth - 2 * vpGap ;
78+
79+ // Use element's actual rendered width - no need for scroll plugin!
80+ const elementWidth = element . offsetWidth ;
81+
82+ const newMargin = elementWidth < availableWidth ? ( availableWidth - elementWidth ) / 2 : 0 ;
83+
84+ element . style . marginLeft = `${ newMargin } px` ;
85+ } ;
86+
7487 const calculateTransform = ( scale : number ) => {
7588 const finalWidth = initialElementWidth * scale ;
7689 const finalHeight = initialElementHeight * scale ;
7790
7891 let ty = pointerLocalY * ( 1 - scale ) ;
7992
80- // --- 1. Center-based Transform (The "Structural" Center) ---
81- // Instead of using containerRectWidth, we use the layoutCenterX derived from Metrics.
82- // layoutCenterX is the specific pixel where the content center should align.
83-
84- // Target X position relative to Container Rect Left:
8593 const targetX = layoutCenterX - finalWidth / 2 ;
86-
87- // Convert to translation (tx) relative to initial position:
8894 const txCenter = targetX - initialElementLeft ;
89-
90- // --- 2. Mouse-based Transform ---
9195 const txMouse = pointerContainerX - pivotLocalX * scale - initialElementLeft ;
9296
93- // --- 3. Blending ---
94- // Compare finalWidth against layoutWidth (actual available space).
9597 const overflow = Math . max ( 0 , finalWidth - layoutWidth ) ;
9698 const blendRange = layoutWidth * 0.3 ;
9799 const blend = Math . min ( 1 , overflow / blendRange ) ;
98100
99101 let tx = txCenter + ( txMouse - txCenter ) * blend ;
100102
101- // --- 4. Gap-Aware Clamping ---
102103 const safeHeight = containerRectHeight - currentGap * 2 ;
103104 if ( finalHeight > safeHeight ) {
104105 const currentTop = initialElementTop + ty ;
@@ -134,17 +135,13 @@ export function setupPinchZoom({
134135 } ;
135136
136137 const commitZoom = ( ) => {
137- const { tx, ty , finalWidth } = calculateTransform ( currentScale ) ;
138+ const { tx, finalWidth } = calculateTransform ( currentScale ) ;
138139 const delta = ( currentScale - 1 ) * initialZoom ;
139140
140141 let anchorX : number ;
141142 let anchorY : number = pointerContainerY ;
142143
143- // --- CRITICAL FIX ---
144- // If the content fits within the LAYOUT width (not just rect width),
145- // we force the anchor to be the Layout Center.
146144 if ( finalWidth <= layoutWidth ) {
147- // anchorX is relative to the Container Rect Origin (which zoomScope uses)
148145 anchorX = layoutCenterX ;
149146 } else {
150147 const scaleDiff = 1 - currentScale ;
@@ -160,8 +157,6 @@ export function setupPinchZoom({
160157 const initializeGestureState = ( clientX : number , clientY : number ) => {
161158 const contRect = viewportScope . getBoundingRect ( ) ;
162159 const innerRect = element . getBoundingClientRect ( ) ;
163-
164- // FETCH METRICS (Single Source of Truth)
165160 const metrics = viewportScope . getMetrics ( ) ;
166161
167162 currentGap = viewportProvides . getViewportGap ( ) || 0 ;
@@ -173,12 +168,7 @@ export function setupPinchZoom({
173168 containerRectWidth = contRect . size . width ;
174169 containerRectHeight = contRect . size . height ;
175170
176- // --- CLEAN LAYOUT CALCULATION ---
177- // We use the viewport metrics to determine the layout geometry.
178- // clientWidth: The width available for content (excludes scrollbars/borders)
179- // clientLeft: The width of the left border (offset from Rect origin to Content origin)
180171 const clientLeft = metrics . clientLeft ;
181-
182172 layoutWidth = metrics . clientWidth ;
183173 layoutCenterX = clientLeft + layoutWidth / 2 ;
184174
@@ -187,7 +177,6 @@ export function setupPinchZoom({
187177 pointerContainerX = clientX - contRect . origin . x ;
188178 pointerContainerY = clientY - contRect . origin . y ;
189179
190- // Pivot Calculation based on Layout Width
191180 if ( initialElementWidth < layoutWidth ) {
192181 pivotLocalX = ( pointerContainerX * initialElementWidth ) / layoutWidth ;
193182 } else {
@@ -245,6 +234,16 @@ export function setupPinchZoom({
245234 } , 150 ) ;
246235 } ;
247236
237+ // Subscribe to zoom changes to update margin
238+ const unsubZoom = zoomScope . onStateChange ( ( ) => updateMargin ( ) ) ;
239+
240+ // Use ResizeObserver to update margin when element size changes
241+ const resizeObserver = new ResizeObserver ( ( ) => updateMargin ( ) ) ;
242+ resizeObserver . observe ( element ) ;
243+
244+ // Initial margin calculation
245+ updateMargin ( ) ;
246+
248247 element . addEventListener ( 'touchstart' , handleTouchStart , { passive : false } ) ;
249248 element . addEventListener ( 'touchmove' , handleTouchMove , { passive : false } ) ;
250249 element . addEventListener ( 'touchend' , handleTouchEnd ) ;
@@ -260,6 +259,9 @@ export function setupPinchZoom({
260259 if ( wheelZoomTimeout ) {
261260 clearTimeout ( wheelZoomTimeout ) ;
262261 }
262+ unsubZoom ( ) ;
263+ resizeObserver . disconnect ( ) ;
263264 resetTransform ( ) ;
265+ element . style . marginLeft = '' ;
264266 } ;
265267}
0 commit comments