forked from EVOgeek/Vmeda.Online
		
	MOBILE-3814 format-text: Fix expandable items height
This commit is contained in:
		
							parent
							
								
									a91b19aedb
								
							
						
					
					
						commit
						389a1c8964
					
				| @ -17,6 +17,10 @@ import { ScrollDetail } from '@ionic/core'; | ||||
| import { IonContent } from '@ionic/angular'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreMath } from '@singletons/math'; | ||||
| import { CoreComponentsRegistry } from '@singletons/components-registry'; | ||||
| import { CoreFormatTextDirective } from './format-text'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||
| 
 | ||||
| /** | ||||
|  * Directive to make an element fixed at the bottom collapsible when scrolling. | ||||
| @ -32,11 +36,12 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { | ||||
| 
 | ||||
|     protected element: HTMLElement; | ||||
|     protected initialHeight = 0; | ||||
|     protected initialPaddingBottom = 0; | ||||
|     protected initialPaddingBottom = '0px'; | ||||
|     protected previousTop = 0; | ||||
|     protected previousHeight = 0; | ||||
|     protected stickTimeout?: number; | ||||
|     protected content?: HTMLIonContentElement | null; | ||||
|     protected loadingChangedListener?: CoreEventObserver; | ||||
| 
 | ||||
|     constructor(el: ElementRef, protected ionContent: IonContent) { | ||||
|         this.element = el.nativeElement; | ||||
| @ -44,30 +49,28 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Setup scroll event listener. | ||||
|      * | ||||
|      * @param retries Number of retries left. | ||||
|      * Calculate the height of the footer. | ||||
|      */ | ||||
|     protected async listenScrollEvents(retries = 5): Promise<void> { | ||||
|         // Already initialized.
 | ||||
|         if (this.initialHeight > 0) { | ||||
|             return; | ||||
|         } | ||||
|     protected async calculateHeight(): Promise<void> { | ||||
|         await this.waitFormatTextsRendered(this.element); | ||||
| 
 | ||||
|         this.initialHeight = this.element.getBoundingClientRect().height; | ||||
| 
 | ||||
|         if (this.initialHeight == 0 && retries > 0) { | ||||
|             await CoreUtils.nextTicks(50); | ||||
| 
 | ||||
|             this.listenScrollEvents(retries - 1); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         await CoreUtils.nextTick(); | ||||
| 
 | ||||
|         // Set a minimum height value.
 | ||||
|         this.initialHeight = this.initialHeight || 48; | ||||
|         this.initialHeight = this.element.getBoundingClientRect().height || 48; | ||||
|         this.previousHeight = this.initialHeight; | ||||
| 
 | ||||
|         this.setBarHeight(this.initialHeight); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Setup scroll event listener. | ||||
|      */ | ||||
|     protected async listenScrollEvents(): Promise<void> { | ||||
|         if (this.content) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.content = this.element.closest('ion-content'); | ||||
| 
 | ||||
|         if (!this.content) { | ||||
| @ -82,12 +85,15 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { | ||||
|         } | ||||
| 
 | ||||
|         // Set a padding to not overlap elements.
 | ||||
|         this.initialPaddingBottom = parseFloat(this.content.style.getPropertyValue('--padding-bottom') || '0'); | ||||
|         this.content.style.setProperty('--padding-bottom', this.initialPaddingBottom + this.initialHeight + 'px'); | ||||
|         this.initialPaddingBottom = this.content.style.getPropertyValue('--padding-bottom') || this.initialPaddingBottom; | ||||
|         this.content.style.setProperty( | ||||
|             '--padding-bottom', | ||||
|             `calc(${this.initialPaddingBottom} + var(--core-collapsible-footer-height, 0px))`, | ||||
|         ); | ||||
| 
 | ||||
|         const scroll = await this.content.getScrollElement(); | ||||
|         this.content.scrollEvents = true; | ||||
| 
 | ||||
|         this.setBarHeight(this.initialHeight); | ||||
|         this.content.addEventListener('ionScroll', (e: CustomEvent<ScrollDetail>): void => { | ||||
|             if (!this.content) { | ||||
|                 return; | ||||
| @ -98,6 +104,19 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Wait until all <core-format-text> children inside the element are done rendering. | ||||
|      * | ||||
|      * @param element Element. | ||||
|      */ | ||||
|     protected async waitFormatTextsRendered(element: Element): Promise<void> { | ||||
|         const formatTexts = Array | ||||
|             .from(element.querySelectorAll('core-format-text')) | ||||
|             .map(element => CoreComponentsRegistry.resolve(element, CoreFormatTextDirective)); | ||||
| 
 | ||||
|         await Promise.all(formatTexts.map(formatText => formatText?.rendered())); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * On scroll function. | ||||
|      * | ||||
| @ -144,15 +163,29 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|     async ngOnInit(): Promise<void> { | ||||
|         // Calculate the height now.
 | ||||
|         await this.calculateHeight(); | ||||
|         setTimeout(() => this.calculateHeight(), 200); // Try again, sometimes the first calculation is wrong.
 | ||||
| 
 | ||||
|         this.listenScrollEvents(); | ||||
| 
 | ||||
|         // Recalculate the height if a parent core-loading displays the content.
 | ||||
|         this.loadingChangedListener = | ||||
|             CoreEvents.on(CoreEvents.CORE_LOADING_CHANGED, async (data: CoreEventLoadingChangedData) => { | ||||
|                 if (data.loaded && CoreDomUtils.closest(this.element.parentElement, '#' + data.uniqueId)) { | ||||
|                     // The format-text is inside the loading, re-calculate the height.
 | ||||
|                     await this.calculateHeight(); | ||||
|                     setTimeout(() => this.calculateHeight(), 200); | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async ngOnDestroy(): Promise<void> { | ||||
|         this.content?.style.setProperty('--padding-bottom', this.initialPaddingBottom + 'px'); | ||||
|         this.content?.style.setProperty('--padding-bottom', this.initialPaddingBottom); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -14,8 +14,11 @@ | ||||
| 
 | ||||
| import { Directive, ElementRef, Input, OnInit } from '@angular/core'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { Translate } from '@singletons'; | ||||
| import { CoreComponentsRegistry } from '@singletons/components-registry'; | ||||
| import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||
| import { CoreFormatTextDirective } from './format-text'; | ||||
| 
 | ||||
| const defaultMaxHeight = 56; | ||||
| const buttonHeight = 44; | ||||
| @ -54,7 +57,7 @@ export class CoreCollapsibleItemDirective implements OnInit { | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|     async ngOnInit(): Promise<void> { | ||||
|         if (typeof this.height === 'string') { | ||||
|             this.maxHeight = this.height === '' | ||||
|                 ? defaultMaxHeight | ||||
| @ -70,31 +73,44 @@ export class CoreCollapsibleItemDirective implements OnInit { | ||||
|         } | ||||
| 
 | ||||
|         // Calculate the height now.
 | ||||
|         this.calculateHeight(); | ||||
|         await this.calculateHeight(); | ||||
|         setTimeout(() => this.calculateHeight(), 200); // Try again, sometimes the first calculation is wrong.
 | ||||
| 
 | ||||
|         this.setExpandButtonEnabled(false); | ||||
| 
 | ||||
|         // Recalculate the height if a parent core-loading displays the content.
 | ||||
|         this.loadingChangedListener = | ||||
|             CoreEvents.on(CoreEvents.CORE_LOADING_CHANGED, (data: CoreEventLoadingChangedData) => { | ||||
|             CoreEvents.on(CoreEvents.CORE_LOADING_CHANGED, async (data: CoreEventLoadingChangedData) => { | ||||
|                 if (data.loaded && CoreDomUtils.closest(this.element.parentElement, '#' + data.uniqueId)) { | ||||
|                     // The format-text is inside the loading, re-calculate the height.
 | ||||
|                     this.calculateHeight(); | ||||
|                     // The element is inside the loading, re-calculate the height.
 | ||||
|                     await this.calculateHeight(); | ||||
|                     setTimeout(() => this.calculateHeight(), 200); | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Wait until all <core-format-text> children inside the element are done rendering. | ||||
|      * | ||||
|      * @param element Element. | ||||
|      */ | ||||
|     protected async waitFormatTextsRendered(element: Element): Promise<void> { | ||||
|         const formatTexts = Array | ||||
|             .from(element.querySelectorAll('core-format-text')) | ||||
|             .map(element => CoreComponentsRegistry.resolve(element, CoreFormatTextDirective)); | ||||
| 
 | ||||
|         await Promise.all(formatTexts.map(formatText => formatText?.rendered())); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Calculate the height and check if we need to display show more or not. | ||||
|      */ | ||||
|     protected calculateHeight(): void { | ||||
|         // @todo: Work on calculate this height better.
 | ||||
|     protected async calculateHeight(): Promise<void> { | ||||
|         await this.waitFormatTextsRendered(this.element); | ||||
| 
 | ||||
|         // Remove max-height (if any) to calculate the real height.
 | ||||
|         const initialMaxHeight = this.element.style.maxHeight; | ||||
|         this.element.style.maxHeight = ''; | ||||
|         this.element.style.maxHeight = 'none'; | ||||
| 
 | ||||
|         await CoreUtils.nextTick(); | ||||
| 
 | ||||
|         const height = CoreDomUtils.getElementHeight(this.element) || 0; | ||||
| 
 | ||||
| @ -102,7 +118,7 @@ export class CoreCollapsibleItemDirective implements OnInit { | ||||
|         this.element.style.maxHeight = initialMaxHeight; | ||||
| 
 | ||||
|         // If cannot calculate height, shorten always.
 | ||||
|         this.setExpandButtonEnabled(!height || height > this.maxHeight); | ||||
|         this.setExpandButtonEnabled(!height || height >= this.maxHeight); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -115,9 +131,7 @@ export class CoreCollapsibleItemDirective implements OnInit { | ||||
|         this.element.classList.toggle('collapsible-enabled', enable); | ||||
| 
 | ||||
|         if (!enable || this.element.querySelector('ion-button.collapsible-toggle')) { | ||||
|             this.element.style.maxHeight = !enable || this.expanded | ||||
|                 ? '' | ||||
|                 : this.maxHeight + 'px'; | ||||
|             this.setMaxHeight(!enable || this.expanded? undefined : this.maxHeight); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| @ -141,6 +155,19 @@ export class CoreCollapsibleItemDirective implements OnInit { | ||||
|         this.toggleExpand(this.expanded); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set max height to element. | ||||
|      * | ||||
|      * @param maxHeight Max height if collapsed or undefined if expanded. | ||||
|      */ | ||||
|     protected setMaxHeight(maxHeight?: number): void { | ||||
|         if (maxHeight) { | ||||
|             this.element.style.setProperty('--max-height', maxHeight + buttonHeight + 'px'); | ||||
|         } else { | ||||
|             this.element.style.removeProperty('--max-height'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Expand or collapse text. | ||||
|      * | ||||
| @ -151,13 +178,8 @@ export class CoreCollapsibleItemDirective implements OnInit { | ||||
|             expand = !this.expanded; | ||||
|         } | ||||
|         this.expanded = expand; | ||||
|         this.element.classList.toggle('collapsible-expanded', expand); | ||||
|         this.element.classList.toggle('collapsible-collapsed', !expand); | ||||
|         if (expand) { | ||||
|             this.element.style.setProperty('--max-height', this.maxHeight + buttonHeight + 'px'); | ||||
|         } else { | ||||
|             this.element.style.removeProperty('--max-height'); | ||||
|         } | ||||
|         this.setMaxHeight(!expand? this.maxHeight: undefined); | ||||
| 
 | ||||
|         const toggleButton = this.element.querySelector('ion-button.collapsible-toggle'); | ||||
|         const toggleText = toggleButton?.querySelector('.collapsible-toggle-text'); | ||||
|  | ||||
| @ -272,15 +272,19 @@ export class CoreFormatTextDirective implements OnChanges { | ||||
|     /** | ||||
|      * Calculate the height and check if we need to display show more or not. | ||||
|      */ | ||||
|     protected calculateHeight(): void { | ||||
|     protected async calculateHeight(): Promise<void> { | ||||
|         // @todo: Work on calculate this height better.
 | ||||
|         if (!this.maxHeight) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await this.rendered(); | ||||
| 
 | ||||
|         // Remove max-height (if any) to calculate the real height.
 | ||||
|         const initialMaxHeight = this.element.style.maxHeight; | ||||
|         this.element.style.maxHeight = ''; | ||||
|         this.element.style.maxHeight = 'none'; | ||||
| 
 | ||||
|         await CoreUtils.nextTick(); | ||||
| 
 | ||||
|         const height = this.getElementHeight(this.element); | ||||
| 
 | ||||
| @ -288,7 +292,20 @@ export class CoreFormatTextDirective implements OnChanges { | ||||
|         this.element.style.maxHeight = initialMaxHeight; | ||||
| 
 | ||||
|         // If cannot calculate height, shorten always.
 | ||||
|         this.setExpandButtonEnabled(!height || height > this.maxHeight); | ||||
|         this.setExpandButtonEnabled(!height || height >= this.maxHeight); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set max height to element. | ||||
|      * | ||||
|      * @param maxHeight Max height if collapsed or undefined if expanded. | ||||
|      */ | ||||
|     protected setMaxHeight(maxHeight?: number): void { | ||||
|         if (maxHeight) { | ||||
|             this.element.style.setProperty('--max-height', maxHeight + 'px'); | ||||
|         } else { | ||||
|             this.element.style.removeProperty('--max-height'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -301,9 +318,7 @@ export class CoreFormatTextDirective implements OnChanges { | ||||
|         this.element.classList.toggle('collapsible-enabled', enable); | ||||
| 
 | ||||
|         if (!enable || this.element.querySelector('ion-button.collapsible-toggle'))  { | ||||
|             this.element.style.maxHeight = !enable || this.expanded | ||||
|                 ? '' | ||||
|                 : this.maxHeight + 'px'; | ||||
|             this.setMaxHeight(!enable || this.expanded? undefined : this.maxHeight); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| @ -337,13 +352,9 @@ export class CoreFormatTextDirective implements OnChanges { | ||||
|             expand = !this.expanded; | ||||
|         } | ||||
|         this.expanded = expand; | ||||
|         this.element.classList.toggle('collapsible-expanded', expand); | ||||
|         this.element.classList.toggle('collapsible-collapsed', !expand); | ||||
|         if (expand) { | ||||
|             this.element.style.setProperty('--max-height', this.maxHeight + 'px'); | ||||
|         } else { | ||||
|             this.element.style.removeProperty('--max-height'); | ||||
|         } | ||||
|         this.setMaxHeight(!expand? this.maxHeight: undefined); | ||||
| 
 | ||||
|         const toggleButton = this.element.querySelector('ion-button.collapsible-toggle'); | ||||
|         const toggleText = toggleButton?.querySelector('.collapsible-toggle-text'); | ||||
|         if (!toggleButton || !toggleText) { | ||||
|  | ||||
| @ -235,6 +235,7 @@ | ||||
|     @include media-breakpoint-down(sm) { | ||||
|         &.collapsible-enabled { | ||||
|             position:relative; | ||||
|             padding-bottom: var(--collapsible-min-button-height); // So the Show less button can fit. | ||||
|             --display-toggle: block; | ||||
| 
 | ||||
|             .collapsible-toggle { | ||||
| @ -262,6 +263,8 @@ | ||||
|                     background-position: center; | ||||
|                     background-repeat: no-repeat; | ||||
|                     background-size: 14px 14px; | ||||
|                     transform: rotate(-90deg); | ||||
| 
 | ||||
|                     @include core-transition(transform, 500ms); | ||||
| 
 | ||||
|                     @include push-arrow-color(626262, true); | ||||
| @ -275,7 +278,7 @@ | ||||
|             &.collapsible-collapsed { | ||||
|                 overflow: hidden; | ||||
|                 min-height: calc(var(--collapsible-min-button-height) + 12px); | ||||
|                 max-height: calc(var(--max-height), auto); | ||||
|                 max-height: calc(var(--max-height, auto)); | ||||
| 
 | ||||
|                 .collapsible-toggle-arrow { | ||||
|                     transform: rotate(90deg); | ||||
| @ -291,15 +294,6 @@ | ||||
|                     z-index: 6; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             &.collapsible-expanded { | ||||
|                 max-height: none !important; | ||||
|                 padding-bottom: var(--collapsible-min-button-height); // So the Show less button can fit. | ||||
| 
 | ||||
|                 .collapsible-toggle-arrow { | ||||
|                     transform: rotate(-90deg); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user