Merge pull request #3487 from alfonso-salces/MOBILE-4202

[4.2] MOBILE-4202 split-view: Scroll to current element when swipe
main
Noel De Martin 2023-04-25 15:35:52 +02:00 committed by GitHub
commit 998eddb74c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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. * @param item Item, null if none.
*/ */
setSelectedItem(item: Item | null): void { setSelectedItem(item: Item | null): void {
if (item === this.selectedItem) {
return;
}
this.selectedItem = item; this.selectedItem = item;
this.listeners.forEach(listener => listener.onSelectedItemUpdated?.call(listener, 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 { CoreRoutedItemsManagerSource } from './routed-items-manager-source';
import { CoreRoutedItemsManager } from './routed-items-manager'; 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. * 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() }); await this.navigateToItem(item, { reset: this.resetNavigation() });
setTimeout(async () => await this.scrollToCurrentElement(), 100);
} }
/** /**
@ -196,10 +198,33 @@ export class CoreListItemsManager<
super.updateSelectedItem(route); super.updateSelectedItem(route);
const selectDefault = CoreScreen.isTablet && this.selectedItem === null && this.splitView && !this.splitView.isNested; const selectDefault = CoreScreen.isTablet && this.selectedItem === null && this.splitView && !this.splitView.isNested;
this.select(selectDefault ? this.getDefaultItem() : this.selectedItem); 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. * Get the item that should be selected by default.
* *

View File

@ -152,6 +152,11 @@ export abstract class CoreRoutedItemsManager<
*/ */
protected onSourceItemsUpdated(items: Item[]): void { protected onSourceItemsUpdated(items: Item[]): void {
super.onSourceItemsUpdated(items); super.onSourceItemsUpdated(items);
const selectedItem = this.selectedItem;
if (selectedItem !== null && items.some(item => item === selectedItem)) {
return;
}
this.updateSelectedItem(); this.updateSelectedItem();
} }

View File

@ -81,6 +81,8 @@ describe('CoreSwipeNavigationItemsManager', () => {
await source.load(); await source.load();
instance.setSelectedItem(items[0]);
// Act. // Act.
await instance.navigateToNextItem(); await instance.navigateToNextItem();
@ -96,6 +98,8 @@ describe('CoreSwipeNavigationItemsManager', () => {
await source.load(); await source.load();
instance.setSelectedItem(items[1]);
// Act. // Act.
await instance.navigateToPreviousItem(); await instance.navigateToPreviousItem();
@ -112,6 +116,8 @@ describe('CoreSwipeNavigationItemsManager', () => {
await source.load(); await source.load();
instance.setSelectedItem(items[0]);
// Act. // Act.
await instance.navigateToNextItem(); await instance.navigateToNextItem();
@ -127,6 +133,8 @@ describe('CoreSwipeNavigationItemsManager', () => {
await source.load(); await source.load();
instance.setSelectedItem(items[0]);
// Assert. // Assert.
await expect(instance.hasNextItem()).resolves.toBe(true); await expect(instance.hasNextItem()).resolves.toBe(true);
await expect(instance.hasPreviousItem()).resolves.toBe(false); await expect(instance.hasPreviousItem()).resolves.toBe(false);

View File

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

View File

@ -88,21 +88,23 @@ export class CoreDom {
* *
* @param element Element to check. * @param element Element to check.
* @param intersectionRatio Intersection ratio (From 0 to 1). * @param intersectionRatio Intersection ratio (From 0 to 1).
* @param container Container where element is located
* @returns True if in viewport. * @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 elementRectangle = element.getBoundingClientRect();
const containerRectangle = container?.getBoundingClientRect();
const elementArea = elementRectangle.width * elementRectangle.height; const elementArea = elementRectangle.width * elementRectangle.height;
if (elementArea == 0) { if (elementArea == 0) {
return false; return false;
} }
const intersectionRectangle = { const intersectionRectangle = {
top: Math.max(0, elementRectangle.top), top: Math.max(containerRectangle?.top ?? 0, elementRectangle.top),
left: Math.max(0, elementRectangle.left), left: Math.max(containerRectangle?.left ?? 0, elementRectangle.left),
bottom: Math.min(window.innerHeight, elementRectangle.bottom), bottom: Math.min(containerRectangle?.bottom ?? window.innerHeight, elementRectangle.bottom),
right: Math.min(window.innerWidth, elementRectangle.right), right: Math.min(containerRectangle?.right ?? window.innerWidth, elementRectangle.right),
}; };
const intersectionArea = (intersectionRectangle.right - intersectionRectangle.left) * const intersectionArea = (intersectionRectangle.right - intersectionRectangle.left) *