@@ -34,6 +34,31 @@ export abstract class BaseScrollStrategy {
3434 protected abstract getScrollOffset ( viewport : ViewportMetrics ) : number ;
3535 protected abstract getClientSize ( viewport : ViewportMetrics ) : number ;
3636
37+ /**
38+ * Returns the item's extent along the scroll axis. Vertical layout uses height;
39+ * horizontal layout uses width. Used for visible range and end spacing calculations.
40+ */
41+ protected abstract getItemSizeAlongScrollAxis ( item : VirtualItem ) : number ;
42+
43+ /**
44+ * Horizontal centering offset for items narrower than the content max width.
45+ * Vertical layout centers spreads within the row; horizontal layout overrides to 0
46+ * since items stay in a simple row without centering.
47+ */
48+ protected getCenteringOffsetX ( _item : VirtualItem , totalContentSize : Size | undefined ) : number {
49+ if ( ! totalContentSize || _item . width >= totalContentSize . width ) return 0 ;
50+ return ( totalContentSize . width - _item . width ) / 2 ;
51+ }
52+
53+ /**
54+ * Vertical centering offset for items shorter than the content max height.
55+ * Horizontal layout centers items within the row height; vertical layout keeps
56+ * items top-aligned and uses the default of 0.
57+ */
58+ protected getCenteringOffsetY ( _item : VirtualItem , _totalContentSize : Size | undefined ) : number {
59+ return 0 ;
60+ }
61+
3762 protected getVisibleRange (
3863 viewport : ViewportMetrics ,
3964 virtualItems : VirtualItem [ ] ,
@@ -44,10 +69,14 @@ export abstract class BaseScrollStrategy {
4469 const viewportStart = scrollOffset ;
4570 const viewportEnd = scrollOffset + clientSize ;
4671
72+ // Use extent along scroll axis (height for vertical, width for horizontal)
4773 let startIndex = 0 ;
4874 while (
4975 startIndex < virtualItems . length &&
50- ( virtualItems [ startIndex ] . offset + virtualItems [ startIndex ] . height ) * scale <= viewportStart
76+ ( virtualItems [ startIndex ] . offset +
77+ this . getItemSizeAlongScrollAxis ( virtualItems [ startIndex ] ) ) *
78+ scale <=
79+ viewportStart
5180 ) {
5281 startIndex ++ ;
5382 }
@@ -75,7 +104,7 @@ export abstract class BaseScrollStrategy {
75104 visibleItems ,
76105 viewport ,
77106 scale ,
78- totalContentSize . width ,
107+ totalContentSize ,
79108 ) ;
80109 const visiblePages = pageVisibilityMetrics . map ( ( m ) => m . pageNumber ) ;
81110 const renderedPageIndexes = virtualItems
@@ -85,11 +114,11 @@ export abstract class BaseScrollStrategy {
85114 const first = virtualItems [ range . start ] ;
86115 const last = virtualItems [ range . end ] ;
87116 const startSpacing = first ? first . offset * scale : 0 ;
117+ const lastItem = virtualItems [ virtualItems . length - 1 ] ;
118+ // End spacing = distance from last rendered item to end of content (uses extent along scroll axis)
88119 const endSpacing = last
89- ? ( virtualItems [ virtualItems . length - 1 ] . offset + // end of content
90- virtualItems [ virtualItems . length - 1 ] . height ) *
91- scale - // minus
92- ( last . offset + last . height ) * scale // end of last rendered
120+ ? ( lastItem . offset + this . getItemSizeAlongScrollAxis ( lastItem ) ) * scale -
121+ ( last . offset + this . getItemSizeAlongScrollAxis ( last ) ) * scale
93122 : 0 ;
94123
95124 return {
@@ -107,19 +136,17 @@ export abstract class BaseScrollStrategy {
107136 virtualItems : VirtualItem [ ] ,
108137 viewport : ViewportMetrics ,
109138 scale : number ,
110- contentWidth ?: number ,
139+ totalContentSize ?: Size ,
111140 ) : ScrollMetrics [ 'pageVisibilityMetrics' ] {
112141 const visibilityMetrics : ScrollMetrics [ 'pageVisibilityMetrics' ] = [ ] ;
113- // Calculate max width for centering if not provided
114- const maxWidth = contentWidth ?? Math . max ( ...virtualItems . map ( ( i ) => i . width ) ) ;
115142
116143 virtualItems . forEach ( ( item ) => {
117- // Calculate horizontal centering offset for items narrower than max width
118- const centeringOffsetX = item . width < maxWidth ? ( maxWidth - item . width ) / 2 : 0 ;
144+ const centeringOffsetX = this . getCenteringOffsetX ( item , totalContentSize ) ;
145+ const centeringOffsetY = this . getCenteringOffsetY ( item , totalContentSize ) ;
119146
120147 item . pageLayouts . forEach ( ( page ) => {
121148 const itemX = ( item . x + centeringOffsetX ) * scale ;
122- const itemY = item . y * scale ;
149+ const itemY = ( item . y + centeringOffsetY ) * scale ;
123150 const pageX = itemX + page . x * scale ;
124151 const pageY = itemY + page . y * scale ;
125152 const pageWidth = page . rotatedWidth * scale ;
@@ -194,19 +221,13 @@ export abstract class BaseScrollStrategy {
194221 const pageLayout = item . pageLayouts . find ( ( layout ) => layout . pageNumber === pageNumber ) ;
195222 if ( ! pageLayout ) return null ;
196223
197- // Calculate centering offset for items that are narrower than the maximum width
198- let centeringOffsetX = 0 ;
199- if ( totalContentSize ) {
200- const maxWidth = totalContentSize . width ;
201- if ( item . width < maxWidth ) {
202- centeringOffsetX = ( maxWidth - item . width ) / 2 ;
203- }
204- }
224+ const centeringOffsetX = this . getCenteringOffsetX ( item , totalContentSize ) ;
225+ const centeringOffsetY = this . getCenteringOffsetY ( item , totalContentSize ) ;
205226
206227 return {
207228 origin : {
208229 x : item . x + pageLayout . x + centeringOffsetX ,
209- y : item . y + pageLayout . y ,
230+ y : item . y + pageLayout . y + centeringOffsetY ,
210231 } ,
211232 size : {
212233 width : pageLayout . width ,
0 commit comments