MOBILE-4202 split-view: Scroll to current element when swipe
parent
77605b87f4
commit
c6b2ea058e
|
@ -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));
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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) *
|
||||||
|
|
Loading…
Reference in New Issue