6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
+ import { Directionality } from '@angular/cdk/bidi' ;
9
10
import { ListRange } from '@angular/cdk/collections' ;
10
11
import { supportsScrollBehavior } from '@angular/cdk/platform' ;
11
12
import {
@@ -18,12 +19,13 @@ import {
18
19
NgZone ,
19
20
OnDestroy ,
20
21
OnInit ,
22
+ Optional ,
21
23
Output ,
22
24
ViewChild ,
23
25
ViewEncapsulation ,
24
26
} from '@angular/core' ;
25
27
import { animationFrameScheduler , fromEvent , Observable , Subject } from 'rxjs' ;
26
- import { sampleTime , takeUntil } from 'rxjs/operators' ;
28
+ import { sampleTime , startWith , takeUntil } from 'rxjs/operators' ;
27
29
import { CdkVirtualForOf } from './virtual-for-of' ;
28
30
import { VIRTUAL_SCROLL_STRATEGY , VirtualScrollStrategy } from './virtual-scroll-strategy' ;
29
31
@@ -99,9 +101,6 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
99
101
/** The size of the viewport (in pixels). */
100
102
private _viewportSize = 0 ;
101
103
102
- /** The pending scroll offset to be applied during the next change detection cycle. */
103
- private _pendingScrollOffset : number | null ;
104
-
105
104
/** the currently attached CdkVirtualForOf. */
106
105
private _forOf : CdkVirtualForOf < any > | null ;
107
106
@@ -126,7 +125,8 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
126
125
constructor ( public elementRef : ElementRef < HTMLElement > ,
127
126
private _changeDetectorRef : ChangeDetectorRef ,
128
127
private _ngZone : NgZone ,
129
- @Inject ( VIRTUAL_SCROLL_STRATEGY ) private _scrollStrategy : VirtualScrollStrategy ) { }
128
+ @Inject ( VIRTUAL_SCROLL_STRATEGY ) private _scrollStrategy : VirtualScrollStrategy ,
129
+ @Optional ( ) private _dir : Directionality ) { }
130
130
131
131
ngOnInit ( ) {
132
132
// It's still too early to measure the viewport at this point. Deferring with a promise allows
@@ -138,9 +138,13 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
138
138
this . _scrollStrategy . attach ( this ) ;
139
139
140
140
fromEvent ( this . elementRef . nativeElement , 'scroll' )
141
- // Sample the scroll stream at every animation frame. This way if there are multiple
142
- // scroll events in the same frame we only need to recheck our layout once.
143
- . pipe ( sampleTime ( 0 , animationFrameScheduler ) , takeUntil ( this . _destroyed ) )
141
+ . pipe (
142
+ // Start off with a fake scroll event so we properly detect our initial position.
143
+ startWith ( null ! ) ,
144
+ // Sample the scroll stream at every animation frame. This way if there are multiple
145
+ // scroll events in the same frame we only need to recheck our layout once.
146
+ sampleTime ( 0 , animationFrameScheduler ) ,
147
+ takeUntil ( this . _destroyed ) )
144
148
. subscribe ( ( ) => this . _scrollStrategy . onContentScrolled ( ) ) ;
145
149
146
150
this . _markChangeDetectionNeeded ( ) ;
@@ -238,8 +242,12 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
238
242
* (in pixels).
239
243
*/
240
244
setRenderedContentOffset ( offset : number , to : 'to-start' | 'to-end' = 'to-start' ) {
245
+ // For a horizontal viewport in a right-to-left language we need to translate along the x-axis
246
+ // in the negative direction.
247
+ const axisDirection = this . orientation == 'horizontal' && this . _dir && this . _dir . value == 'rtl'
248
+ ? - 1 : 1 ;
241
249
const axis = this . orientation === 'horizontal' ? 'X' : 'Y' ;
242
- let transform = `translate${ axis } (${ Number ( offset ) } px)` ;
250
+ let transform = `translate${ axis } (${ Number ( axisDirection * offset ) } px)` ;
243
251
this . _renderedContentOffset = offset ;
244
252
if ( to === 'to-end' ) {
245
253
transform += ` translate${ axis } (-100%)` ;
@@ -265,13 +273,20 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
265
273
}
266
274
267
275
/**
268
- * Scrolls to the offset on the viewport.
276
+ * Scrolls to the given offset from the start of the viewport. Please note that this is not always
277
+ * the same as setting `scrollTop` or `scrollLeft`. In a horizontal viewport with right-to-left
278
+ * direction, this would be the equivalent of setting a fictional `scrollRight` property.
269
279
* @param offset The offset to scroll to.
270
280
* @param behavior The ScrollBehavior to use when scrolling. Default is behavior is `auto`.
271
281
*/
272
282
scrollToOffset ( offset : number , behavior : ScrollBehavior = 'auto' ) {
273
283
const viewportElement = this . elementRef . nativeElement ;
274
284
285
+ // For a horizontal viewport in a right-to-left language we need to calculate what `scrollRight`
286
+ // would be.
287
+ offset = this . orientation == 'horizontal' && this . _dir && this . _dir . value == 'rtl' ?
288
+ Math . max ( 0 , this . _totalContentSize - this . _viewportSize - offset ) : offset ;
289
+
275
290
if ( supportsScrollBehavior ( ) ) {
276
291
const offsetDirection = this . orientation === 'horizontal' ? 'left' : 'top' ;
277
292
viewportElement . scrollTo ( { [ offsetDirection ] : offset , behavior} ) ;
@@ -293,18 +308,16 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
293
308
this . _scrollStrategy . scrollToIndex ( index , behavior ) ;
294
309
}
295
310
296
- /** @docs -private Internal method to set the scroll offset on the viewport. */
297
- setScrollOffset ( offset : number ) {
298
- // Rather than setting the offset immediately, we batch it up to be applied along with other DOM
299
- // writes during the next change detection cycle.
300
- this . _pendingScrollOffset = offset ;
301
- this . _markChangeDetectionNeeded ( ) ;
302
- }
303
-
304
- /** Gets the current scroll offset of the viewport (in pixels). */
311
+ /** Gets the current scroll offset from the start of the viewport (in pixels). */
305
312
measureScrollOffset ( ) : number {
306
- return this . orientation === 'horizontal' ?
307
- this . elementRef . nativeElement . scrollLeft : this . elementRef . nativeElement . scrollTop ;
313
+ if ( this . orientation == 'horizontal' ) {
314
+ const offset = this . elementRef . nativeElement . scrollLeft ;
315
+ // For a horizontal viewport in a right-to-left language we need to calculate what
316
+ // `scrollRight` would be.
317
+ return this . _dir && this . _dir . value == 'rtl' ?
318
+ Math . max ( 0 , this . _totalContentSize - this . _viewportSize - offset ) : offset ;
319
+ }
320
+ return this . elementRef . nativeElement . scrollTop ;
308
321
}
309
322
310
323
/** Measure the combined size of all of the rendered items. */
@@ -367,14 +380,6 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
367
380
// string literals, a variable that can only be 'X' or 'Y', and user input that is run through
368
381
// the `Number` function first to coerce it to a numeric value.
369
382
this . _contentWrapper . nativeElement . style . transform = this . _renderedContentTransform ;
370
- // Apply the pending scroll offset separately, since it can't be set up as an Angular binding.
371
- if ( this . _pendingScrollOffset != null ) {
372
- if ( this . orientation === 'horizontal' ) {
373
- this . elementRef . nativeElement . scrollLeft = this . _pendingScrollOffset ;
374
- } else {
375
- this . elementRef . nativeElement . scrollTop = this . _pendingScrollOffset ;
376
- }
377
- }
378
383
379
384
const runAfterChangeDetection = this . _runAfterChangeDetection ;
380
385
this . _runAfterChangeDetection = [ ] ;
0 commit comments