MOBILE-3833 collapsible: Change collapsible visible strategy

main
Pau Ferrer Ocaña 2022-04-01 09:58:11 +02:00
parent e3e54ec194
commit 50c3985822
4 changed files with 60 additions and 73 deletions

View File

@ -683,7 +683,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
* Go to search courses. * Go to search courses.
*/ */
async openSearch(): Promise<void> { async openSearch(): Promise<void> {
CoreNavigator.navigateToSitePath('/list', { params : { mode: 'search' } }); CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'search' } });
} }
/** /**

View File

@ -50,7 +50,8 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
protected endContentScrollListener?: EventListener; protected endContentScrollListener?: EventListener;
protected resizeListener?: CoreEventObserver; protected resizeListener?: CoreEventObserver;
protected slotPromise?: CoreCancellablePromise<void>; protected slotPromise?: CoreCancellablePromise<void>;
protected calcPending = false; protected viewportPromise?: CoreCancellablePromise<void>;
protected loadingHeight = false;
protected pageDidEnterListener?: EventListener; protected pageDidEnterListener?: EventListener;
protected page?: HTMLElement; protected page?: HTMLElement;
@ -85,13 +86,14 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
* Calculate the height of the footer. * Calculate the height of the footer.
*/ */
protected async calculateHeight(): Promise<void> { protected async calculateHeight(): Promise<void> {
if (!CoreDom.isElementVisible(this.element)) { if (this.loadingHeight) {
this.calcPending = true; // Already calculating, return.
return; return;
} }
this.loadingHeight = true;
this.calcPending = false; this.viewportPromise = CoreDom.waitToBeInViewport(this.element);
await this.viewportPromise;
this.element.classList.remove('is-active'); this.element.classList.remove('is-active');
await CoreUtils.nextTick(); await CoreUtils.nextTick();
@ -110,6 +112,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
this.element.classList.add('is-active'); this.element.classList.add('is-active');
this.setBarHeight(this.initialHeight); this.setBarHeight(this.initialHeight);
this.loadingHeight = false;
} }
/** /**
@ -175,9 +178,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
this.page?.addEventListener( this.page?.addEventListener(
'ionViewDidEnter', 'ionViewDidEnter',
this.pageDidEnterListener = () => { this.pageDidEnterListener = () => {
if (this.calcPending) { this.calculateHeight();
this.calculateHeight();
}
}, },
); );
} }
@ -255,6 +256,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
this.resizeListener?.off(); this.resizeListener?.off();
this.slotPromise?.cancel(); this.slotPromise?.cancel();
this.viewportPromise?.cancel();
} }
} }

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import { Directive, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChange } from '@angular/core'; import { Directive, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChange } from '@angular/core';
import { CorePromisedValue } from '@classes/promised-value'; import { CoreCancellablePromise } from '@classes/cancellable-promise';
import { CoreLoadingComponent } from '@components/loading/loading'; import { CoreLoadingComponent } from '@components/loading/loading';
import { CoreTabsOutletComponent } from '@components/tabs-outlet/tabs-outlet'; import { CoreTabsOutletComponent } from '@components/tabs-outlet/tabs-outlet';
import { CoreTabsComponent } from '@components/tabs/tabs'; import { CoreTabsComponent } from '@components/tabs/tabs';
@ -69,17 +69,15 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
protected content?: HTMLIonContentElement; protected content?: HTMLIonContentElement;
protected contentScrollListener?: EventListener; protected contentScrollListener?: EventListener;
protected endContentScrollListener?: EventListener; protected endContentScrollListener?: EventListener;
protected pageDidEnterListener?: EventListener;
protected resizeListener?: CoreEventObserver; protected resizeListener?: CoreEventObserver;
protected floatingTitle?: HTMLHeadingElement; protected floatingTitle?: HTMLHeadingElement;
protected scrollingHeight?: number; protected scrollingHeight?: number;
protected subscriptions: Subscription[] = []; protected subscriptions: Subscription[] = [];
protected enabled = true; protected enabled = true;
protected isWithinContent = false; protected isWithinContent = false;
protected enteredPromise = new CorePromisedValue<void>();
protected mutationObserver?: MutationObserver; protected mutationObserver?: MutationObserver;
protected firstEnter = true; protected loadingFloatingTitle = false;
protected initPending = false; protected visiblePromise?: CoreCancellablePromise<void>;
constructor(el: ElementRef) { constructor(el: ElementRef) {
this.collapsedHeader = el.nativeElement; this.collapsedHeader = el.nativeElement;
@ -106,10 +104,9 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
await Promise.all([ await Promise.all([
this.initializeCollapsedHeader(), this.initializeCollapsedHeader(),
this.initializeExpandedHeader(), this.initializeExpandedHeader(),
await this.enteredPromise,
]); ]);
this.initializeFloatingTitle(); await this.initializeFloatingTitle();
this.initializeContent(); this.initializeContent();
} }
@ -117,7 +114,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
* @inheritdoc * @inheritdoc
*/ */
async ngOnChanges(changes: {[name: string]: SimpleChange}): Promise<void> { async ngOnChanges(changes: {[name: string]: SimpleChange}): Promise<void> {
if (changes.collapsible) { if (changes.collapsible && !changes.collapsible.firstChange) {
this.collapsible = !CoreUtils.isFalseOrZero(changes.collapsible.currentValue); this.collapsible = !CoreUtils.isFalseOrZero(changes.collapsible.currentValue);
this.enabled = this.collapsible; this.enabled = this.collapsible;
@ -141,47 +138,16 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
if (this.content && this.endContentScrollListener) { if (this.content && this.endContentScrollListener) {
this.content.removeEventListener('ionScrollEnd', this.endContentScrollListener); this.content.removeEventListener('ionScrollEnd', this.endContentScrollListener);
} }
if (this.page && this.pageDidEnterListener) {
this.page.removeEventListener('ionViewDidEnter', this.pageDidEnterListener);
}
this.resizeListener?.off(); this.resizeListener?.off();
this.mutationObserver?.disconnect(); this.mutationObserver?.disconnect();
this.visiblePromise?.cancel();
} }
/** /**
* Search the page element, initialize it, and wait until it's ready for the transition to trigger on scroll. * Listen to changing events.
*/ */
protected initializePage(): void { protected listenEvents(): void {
if (!this.collapsedHeader.parentElement) {
throw new Error('[collapsible-header] Couldn\'t get page');
}
// Find element and prepare classes.
this.page = this.collapsedHeader.parentElement;
this.page.classList.add('collapsible-header-page');
this.page.addEventListener(
'ionViewDidEnter',
this.pageDidEnterListener = () => {
if (this.firstEnter) {
this.firstEnter = false;
clearTimeout(timeout);
this.enteredPromise.resolve();
} else if (this.initPending) {
this.initializeFloatingTitle();
}
},
);
// Timeout in case event is never fired.
const timeout = window.setTimeout(() => {
if (this.firstEnter) {
this.firstEnter = false;
this.enteredPromise.reject(new Error('[collapsible-header] Waiting for ionViewDidEnter timeout reached'));
}
}, 5000);
this.resizeListener = CoreDom.onWindowResize(() => { this.resizeListener = CoreDom.onWindowResize(() => {
this.initializeFloatingTitle(); this.initializeFloatingTitle();
}, 50); }, 50);
@ -216,6 +182,19 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
}); });
} }
/**
* Search the page element, initialize it, and wait until it's ready for the transition to trigger on scroll.
*/
protected initializePage(): void {
if (!this.collapsedHeader.parentElement) {
throw new Error('[collapsible-header] Couldn\'t get page');
}
// Find element and prepare classes.
this.page = this.collapsedHeader.parentElement;
this.page.classList.add('collapsible-header-page');
}
/** /**
* Search the collapsed header element, initialize it, and wait until it's ready for the transition to trigger on scroll. * Search the collapsed header element, initialize it, and wait until it's ready for the transition to trigger on scroll.
*/ */
@ -253,6 +232,8 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
return; return;
} }
this.listenEvents();
// Initialize from tabs. // Initialize from tabs.
const tabs = CoreComponentsRegistry.resolve(this.page.querySelector('core-tabs-outlet'), CoreTabsOutletComponent); const tabs = CoreComponentsRegistry.resolve(this.page.querySelector('core-tabs-outlet'), CoreTabsOutletComponent);
@ -284,21 +265,22 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
/** /**
* Initialize a floating title to mimic transitioning the title from one state to the other. * Initialize a floating title to mimic transitioning the title from one state to the other.
*/ */
protected initializeFloatingTitle(): void { protected async initializeFloatingTitle(): Promise<void> {
if (!this.page || !this.expandedHeader) { if (!this.page || !this.expandedHeader) {
throw new Error('[collapsible-header] Couldn\'t create floating title');
}
if (!CoreDom.isElementVisible(this.expandedHeader)) {
this.initPending = true;
return; return;
} }
this.initPending = false; if (this.loadingFloatingTitle) {
// Already calculating, return.
return;
}
this.loadingFloatingTitle = true;
this.visiblePromise = CoreDom.waitToBeVisible(this.expandedHeader);
await this.visiblePromise;
this.page.classList.remove('collapsible-header-page-is-active'); this.page.classList.remove('collapsible-header-page-is-active');
CoreUtils.nextTick(); await CoreUtils.nextTick();
// Add floating title and measure initial position. // Add floating title and measure initial position.
const collapsedHeaderTitle = this.collapsedHeader.querySelector('h1') as HTMLHeadingElement; const collapsedHeaderTitle = this.collapsedHeader.querySelector('h1') as HTMLHeadingElement;
@ -370,6 +352,8 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
this.collapsedFontStyles = collapsedFontStyles; this.collapsedFontStyles = collapsedFontStyles;
this.expandedFontStyles = expandedFontStyles; this.expandedFontStyles = expandedFontStyles;
this.expandedHeaderHeight = expandedHeaderHeight; this.expandedHeaderHeight = expandedHeaderHeight;
this.loadingFloatingTitle = false;
} }
/** /**

View File

@ -55,8 +55,9 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
protected resizeListener?: CoreEventObserver; protected resizeListener?: CoreEventObserver;
protected darkModeListener?: Subscription; protected darkModeListener?: Subscription;
protected domPromise?: CoreCancellablePromise<void>; protected domPromise?: CoreCancellablePromise<void>;
protected visiblePromise?: CoreCancellablePromise<void>;
protected uniqueId: string; protected uniqueId: string;
protected calcPending = false; protected loadingHeight = false;
protected pageDidEnterListener?: EventListener; protected pageDidEnterListener?: EventListener;
protected page?: HTMLElement; protected page?: HTMLElement;
@ -99,9 +100,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
this.page?.addEventListener( this.page?.addEventListener(
'ionViewDidEnter', 'ionViewDidEnter',
this.pageDidEnterListener = () => { this.pageDidEnterListener = () => {
if (this.calcPending) { this.calculateHeight();
this.calculateHeight();
}
}, },
); );
@ -143,20 +142,20 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
* Calculate the height and check if we need to display show more or not. * Calculate the height and check if we need to display show more or not.
*/ */
protected async calculateHeight(): Promise<void> { protected async calculateHeight(): Promise<void> {
if (this.loadingHeight) {
// Already calculating, return.
return;
}
this.loadingHeight = true;
this.visiblePromise = CoreDom.waitToBeVisible(this.element);
await this.visiblePromise;
// Remove max-height (if any) to calculate the real height. // Remove max-height (if any) to calculate the real height.
this.element.classList.add('collapsible-loading-height'); this.element.classList.add('collapsible-loading-height');
await this.waitFormatTextsRendered(); await this.waitFormatTextsRendered();
if (!this.element.clientHeight) {
this.calcPending = true;
this.element.classList.remove('collapsible-loading-height');
return;
}
this.calcPending = false;
this.expandedHeight = this.element.getBoundingClientRect().height; this.expandedHeight = this.element.getBoundingClientRect().height;
// Restore the max height now. // Restore the max height now.
@ -167,6 +166,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
this.setExpandButtonEnabled(enable); this.setExpandButtonEnabled(enable);
this.setGradientColor(); this.setGradientColor();
this.loadingHeight = false;
} }
/** /**
@ -298,6 +298,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
this.resizeListener?.off(); this.resizeListener?.off();
this.darkModeListener?.unsubscribe(); this.darkModeListener?.unsubscribe();
this.domPromise?.cancel(); this.domPromise?.cancel();
this.visiblePromise?.cancel();
if (this.page && this.pageDidEnterListener) { if (this.page && this.pageDidEnterListener) {
this.page.removeEventListener('ionViewDidEnter', this.pageDidEnterListener); this.page.removeEventListener('ionViewDidEnter', this.pageDidEnterListener);