MOBILE-4202 split-view: Scroll to current element when swipe
This commit is contained in:
		
							parent
							
								
									77605b87f4
								
							
						
					
					
						commit
						c6b2ea058e
					
				| @ -103,6 +103,10 @@ export abstract class CoreItemsManager< | ||||
|      * @param item Item, null if none. | ||||
|      */ | ||||
|     setSelectedItem(item: Item | null): void { | ||||
|         if (item === this.selectedItem) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.selectedItem = item; | ||||
| 
 | ||||
|         this.listeners.forEach(listener => listener.onSelectedItemUpdated?.call(listener, item)); | ||||
|  | ||||
| @ -22,6 +22,7 @@ import { CoreUtils } from '@services/utils/utils'; | ||||
| 
 | ||||
| import { CoreRoutedItemsManagerSource } from './routed-items-manager-source'; | ||||
| import { CoreRoutedItemsManager } from './routed-items-manager'; | ||||
| import { CoreDom } from '@singletons/dom'; | ||||
| 
 | ||||
| /** | ||||
|  * Helper class to manage the state and routing of a list of items in a page. | ||||
| @ -129,6 +130,7 @@ export class CoreListItemsManager< | ||||
|         } | ||||
| 
 | ||||
|         await this.navigateToItem(item, { reset: this.resetNavigation() }); | ||||
|         setTimeout(async () => await this.scrollToCurrentElement(), 100); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -196,10 +198,33 @@ export class CoreListItemsManager< | ||||
|         super.updateSelectedItem(route); | ||||
| 
 | ||||
|         const selectDefault = CoreScreen.isTablet && this.selectedItem === null && this.splitView && !this.splitView.isNested; | ||||
| 
 | ||||
|         this.select(selectDefault ? this.getDefaultItem() : this.selectedItem); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Scroll to current element in split-view list. | ||||
|      */ | ||||
|     protected async scrollToCurrentElement(): Promise<void> { | ||||
|         if (CoreScreen.isMobile) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const element = this.splitView?.nativeElement ?? document; | ||||
|         const currentItem = element.querySelector<HTMLElement>('[aria-current="page"]'); | ||||
| 
 | ||||
|         if (!currentItem) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const isElementInViewport = CoreDom.isElementInViewport(currentItem, 1, this.splitView?.nativeElement); | ||||
| 
 | ||||
|         if (isElementInViewport) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         currentItem.scrollIntoView({ behavior: 'smooth' }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the item that should be selected by default. | ||||
|      * | ||||
|  | ||||
| @ -152,6 +152,11 @@ export abstract class CoreRoutedItemsManager< | ||||
|      */ | ||||
|     protected onSourceItemsUpdated(items: Item[]): void { | ||||
|         super.onSourceItemsUpdated(items); | ||||
|         const selectedItem = this.selectedItem; | ||||
| 
 | ||||
|         if (selectedItem !== null && items.some(item => item === selectedItem)) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.updateSelectedItem(); | ||||
|     } | ||||
|  | ||||
| @ -81,6 +81,8 @@ describe('CoreSwipeNavigationItemsManager', () => { | ||||
| 
 | ||||
|         await source.load(); | ||||
| 
 | ||||
|         instance.setSelectedItem(items[0]); | ||||
| 
 | ||||
|         // Act.
 | ||||
|         await instance.navigateToNextItem(); | ||||
| 
 | ||||
| @ -96,6 +98,8 @@ describe('CoreSwipeNavigationItemsManager', () => { | ||||
| 
 | ||||
|         await source.load(); | ||||
| 
 | ||||
|         instance.setSelectedItem(items[1]); | ||||
| 
 | ||||
|         // Act.
 | ||||
|         await instance.navigateToPreviousItem(); | ||||
| 
 | ||||
| @ -112,6 +116,8 @@ describe('CoreSwipeNavigationItemsManager', () => { | ||||
| 
 | ||||
|         await source.load(); | ||||
| 
 | ||||
|         instance.setSelectedItem(items[0]); | ||||
| 
 | ||||
|         // Act.
 | ||||
|         await instance.navigateToNextItem(); | ||||
| 
 | ||||
| @ -127,6 +133,8 @@ describe('CoreSwipeNavigationItemsManager', () => { | ||||
| 
 | ||||
|         await source.load(); | ||||
| 
 | ||||
|         instance.setSelectedItem(items[0]); | ||||
| 
 | ||||
|         // Assert.
 | ||||
|         await expect(instance.hasNextItem()).resolves.toBe(true); | ||||
|         await expect(instance.hasPreviousItem()).resolves.toBe(false); | ||||
|  | ||||
| @ -57,6 +57,10 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy { | ||||
|         return this.outletRouteSubject.asObservable(); | ||||
|     } | ||||
| 
 | ||||
|     get nativeElement(): HTMLElement { | ||||
|         return this.element.nativeElement; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|  | ||||
| @ -88,21 +88,23 @@ export class CoreDom { | ||||
|      * | ||||
|      * @param element Element to check. | ||||
|      * @param intersectionRatio Intersection ratio (From 0 to 1). | ||||
|      * @param container Container where element is located | ||||
|      * @returns True if in viewport. | ||||
|      */ | ||||
|     static isElementInViewport(element: HTMLElement, intersectionRatio = 1): boolean { | ||||
|     static isElementInViewport(element: HTMLElement, intersectionRatio = 1, container: HTMLElement | null = null): boolean { | ||||
|         const elementRectangle = element.getBoundingClientRect(); | ||||
| 
 | ||||
|         const containerRectangle = container?.getBoundingClientRect(); | ||||
|         const elementArea = elementRectangle.width * elementRectangle.height; | ||||
| 
 | ||||
|         if (elementArea == 0) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         const intersectionRectangle = { | ||||
|             top: Math.max(0, elementRectangle.top), | ||||
|             left: Math.max(0, elementRectangle.left), | ||||
|             bottom: Math.min(window.innerHeight, elementRectangle.bottom), | ||||
|             right: Math.min(window.innerWidth, elementRectangle.right), | ||||
|             top: Math.max(containerRectangle?.top ?? 0, elementRectangle.top), | ||||
|             left: Math.max(containerRectangle?.left ?? 0, elementRectangle.left), | ||||
|             bottom: Math.min(containerRectangle?.bottom ?? window.innerHeight, elementRectangle.bottom), | ||||
|             right: Math.min(containerRectangle?.right ?? window.innerWidth, elementRectangle.right), | ||||
|         }; | ||||
| 
 | ||||
|         const intersectionArea = (intersectionRectangle.right - intersectionRectangle.left) * | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user