MOBILE-4202 split-view: Scroll to current element when swipe

main
Alfonso Salces 2022-12-12 14:41:14 +01:00
parent 77605b87f4
commit c6b2ea058e
6 changed files with 55 additions and 7 deletions

View File

@ -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));

View File

@ -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.
*

View File

@ -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();
}

View File

@ -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);

View File

@ -57,6 +57,10 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy {
return this.outletRouteSubject.asObservable();
}
get nativeElement(): HTMLElement {
return this.element.nativeElement;
}
/**
* @inheritdoc
*/

View File

@ -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) *