From c6b2ea058e279505ec4dd4b8909b4bdaecb4739b Mon Sep 17 00:00:00 2001 From: Alfonso Salces Date: Mon, 12 Dec 2022 14:41:14 +0100 Subject: [PATCH] MOBILE-4202 split-view: Scroll to current element when swipe --- .../classes/items-management/items-manager.ts | 4 +++ .../items-management/list-items-manager.ts | 27 ++++++++++++++++++- .../items-management/routed-items-manager.ts | 5 ++++ .../swipe-navigation-items-manager.test.ts | 8 ++++++ src/core/components/split-view/split-view.ts | 4 +++ src/core/singletons/dom.ts | 14 +++++----- 6 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/core/classes/items-management/items-manager.ts b/src/core/classes/items-management/items-manager.ts index dd4bc269b..80f433fdb 100644 --- a/src/core/classes/items-management/items-manager.ts +++ b/src/core/classes/items-management/items-manager.ts @@ -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)); diff --git a/src/core/classes/items-management/list-items-manager.ts b/src/core/classes/items-management/list-items-manager.ts index 744eb25bf..c66a35fde 100644 --- a/src/core/classes/items-management/list-items-manager.ts +++ b/src/core/classes/items-management/list-items-manager.ts @@ -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 { + if (CoreScreen.isMobile) { + return; + } + + const element = this.splitView?.nativeElement ?? document; + const currentItem = element.querySelector('[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. * diff --git a/src/core/classes/items-management/routed-items-manager.ts b/src/core/classes/items-management/routed-items-manager.ts index 2d0ede1f4..a2e8bf514 100644 --- a/src/core/classes/items-management/routed-items-manager.ts +++ b/src/core/classes/items-management/routed-items-manager.ts @@ -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(); } diff --git a/src/core/classes/tests/swipe-navigation-items-manager.test.ts b/src/core/classes/tests/swipe-navigation-items-manager.test.ts index 4603fe670..07ae31810 100644 --- a/src/core/classes/tests/swipe-navigation-items-manager.test.ts +++ b/src/core/classes/tests/swipe-navigation-items-manager.test.ts @@ -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); diff --git a/src/core/components/split-view/split-view.ts b/src/core/components/split-view/split-view.ts index 060b6885c..f053a7048 100644 --- a/src/core/components/split-view/split-view.ts +++ b/src/core/components/split-view/split-view.ts @@ -57,6 +57,10 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy { return this.outletRouteSubject.asObservable(); } + get nativeElement(): HTMLElement { + return this.element.nativeElement; + } + /** * @inheritdoc */ diff --git a/src/core/singletons/dom.ts b/src/core/singletons/dom.ts index 9f74b584b..66aff6a71 100644 --- a/src/core/singletons/dom.ts +++ b/src/core/singletons/dom.ts @@ -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) *