diff --git a/src/addons/qtype/ddimageortext/classes/ddimageortext.ts b/src/addons/qtype/ddimageortext/classes/ddimageortext.ts index 8a9f75b64..5fd0cc61b 100644 --- a/src/addons/qtype/ddimageortext/classes/ddimageortext.ts +++ b/src/addons/qtype/ddimageortext/classes/ddimageortext.ts @@ -14,6 +14,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; +import { CoreEventObserver } from '@singletons/events'; import { CoreLogger } from '@singletons/logger'; import { AddonModQuizDdImageOrTextQuestionData } from '../component/ddimageortext'; @@ -28,7 +29,7 @@ export class AddonQtypeDdImageOrTextQuestion { protected afterImageLoadDone = false; protected proportion = 1; protected selected?: HTMLElement | null; // Selected element (being "dragged"). - protected resizeFunction?: (ev?: Event) => void; + protected resizeListener?: CoreEventObserver; /** * Create the this. @@ -174,9 +175,7 @@ export class AddonQtypeDdImageOrTextQuestion { destroy(): void { this.stopPolling(); - if (this.resizeFunction) { - window.removeEventListener('resize', this.resizeFunction); - } + this.resizeListener?.off(); } /** @@ -360,8 +359,9 @@ export class AddonQtypeDdImageOrTextQuestion { this.pollForImageLoad(); }); - this.resizeFunction = this.windowResized.bind(this); - window.addEventListener('resize', this.resizeFunction!); + this.resizeListener = CoreDomUtils.onWindowResize(() => { + this.windowResized(); + }); } /** diff --git a/src/addons/qtype/ddmarker/classes/ddmarker.ts b/src/addons/qtype/ddmarker/classes/ddmarker.ts index 1e747e0de..a99b4387c 100644 --- a/src/addons/qtype/ddmarker/classes/ddmarker.ts +++ b/src/addons/qtype/ddmarker/classes/ddmarker.ts @@ -14,6 +14,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; +import { CoreEventObserver } from '@singletons/events'; import { CoreLogger } from '@singletons/logger'; import { AddonQtypeDdMarkerQuestionData } from '../component/ddmarker'; import { AddonQtypeDdMarkerGraphicsApi } from './graphics_api'; @@ -41,7 +42,7 @@ export class AddonQtypeDdMarkerQuestion { protected proportion = 1; protected selected?: HTMLElement; // Selected element (being "dragged"). protected graphics: AddonQtypeDdMarkerGraphicsApi; - protected resizeFunction?: () => void; + protected resizeListener?: CoreEventObserver; doc!: AddonQtypeDdMarkerQuestionDocStructure; shapes: SVGElement[] = []; @@ -160,9 +161,7 @@ export class AddonQtypeDdMarkerQuestion { * Function to call when the instance is no longer needed. */ destroy(): void { - if (this.resizeFunction) { - window.removeEventListener('resize', this.resizeFunction); - } + this.resizeListener?.off(); } /** @@ -601,8 +600,9 @@ export class AddonQtypeDdMarkerQuestion { this.pollForImageLoad(); }); - this.resizeFunction = this.windowResized.bind(this); - window.addEventListener('resize', this.resizeFunction!); + this.resizeListener = CoreDomUtils.onWindowResize(() => { + this.windowResized(); + }); } /** diff --git a/src/addons/qtype/ddwtos/classes/ddwtos.ts b/src/addons/qtype/ddwtos/classes/ddwtos.ts index 7808cf602..9bb54147a 100644 --- a/src/addons/qtype/ddwtos/classes/ddwtos.ts +++ b/src/addons/qtype/ddwtos/classes/ddwtos.ts @@ -15,6 +15,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; +import { CoreEventObserver } from '@singletons/events'; import { CoreLogger } from '@singletons/logger'; import { AddonModQuizDdwtosQuestionData } from '../component/ddwtos'; @@ -28,13 +29,11 @@ export class AddonQtypeDdwtosQuestion { protected selectors!: AddonQtypeDdwtosQuestionCSSSelectors; // Result of cssSelectors. protected placed: {[no: number]: number} = {}; // Map that relates drag elements numbers with drop zones numbers. protected selected?: HTMLElement; // Selected element (being "dragged"). - protected resizeFunction?: () => void; + protected resizeListener?: CoreEventObserver; /** * Create the instance. * - * @param logger Logger provider. - * @param domUtils Dom Utils provider. * @param container The container HTMLElement of the question. * @param question The question instance. * @param readOnly Whether it's read only. @@ -122,9 +121,7 @@ export class AddonQtypeDdwtosQuestion { * Function to call when the instance is no longer needed. */ destroy(): void { - if (this.resizeFunction) { - window.removeEventListener('resize', this.resizeFunction); - } + this.resizeListener?.off(); } /** @@ -214,8 +211,9 @@ export class AddonQtypeDdwtosQuestion { this.positionDragItems(); - this.resizeFunction = this.windowResized.bind(this); - window.addEventListener('resize', this.resizeFunction!); + this.resizeListener = CoreDomUtils.onWindowResize(() => { + this.windowResized(); + }); } /** diff --git a/src/core/classes/tabs.ts b/src/core/classes/tabs.ts index ca644c64e..bd4cf9efe 100644 --- a/src/core/classes/tabs.ts +++ b/src/core/classes/tabs.ts @@ -32,6 +32,8 @@ import { Subscription } from 'rxjs'; import { Platform, Translate } from '@singletons'; import { CoreSettingsHelper } from '@features/settings/services/settings-helper'; import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from './aria-role-tab'; +import { CoreEventObserver } from '@singletons/events'; +import { CoreDomUtils } from '@services/utils/dom'; /** * Class to abstract some common code for tabs. @@ -75,7 +77,7 @@ export class CoreTabsBaseComponent implements OnInit, Aft protected tabsElement?: HTMLElement; // The tabs parent element. It's the element that will be "scrolled" to hide tabs. protected tabBarElement?: HTMLIonTabBarElement; // The top tab bar element. protected tabsShown = true; - protected resizeFunction: EventListenerOrEventListenerObject; + protected resizeListener?: CoreEventObserver; protected isDestroyed = false; protected isCurrentView = true; protected shouldSlideToInitial = false; // Whether we need to slide to the initial slide because it's out of view. @@ -99,7 +101,6 @@ export class CoreTabsBaseComponent implements OnInit, Aft protected element: ElementRef, ) { this.backButtonFunction = this.backButtonClicked.bind(this); - this.resizeFunction = this.windowResized.bind(this); this.tabAction = new CoreTabsRoleTab(this); } @@ -134,7 +135,9 @@ export class CoreTabsBaseComponent implements OnInit, Aft await this.initializeTabs(); } - window.addEventListener('resize', this.resizeFunction); + this.resizeListener = CoreDomUtils.onWindowResize(() => { + this.windowResized(); + }); } /** @@ -419,24 +422,24 @@ export class CoreTabsBaseComponent implements OnInit, Aft */ async slideNext(): Promise { // Stop if slides are in transition. - if (!this.showNextButton || this.isInTransition) { + if (!this.showNextButton || this.isInTransition || !this.slides) { return; } - if (await this.slides!.isBeginning()) { + if (await this.slides.isBeginning()) { // Slide to the second page. - this.slides!.slideTo(this.maxSlides); + this.slides.slideTo(this.maxSlides); } else { - const currentIndex = await this.slides!.getActiveIndex(); + const currentIndex = await this.slides.getActiveIndex(); if (currentIndex !== undefined) { const nextSlideIndex = currentIndex + this.maxSlides; this.isInTransition = true; if (nextSlideIndex < this.numTabsShown) { // Slide to the next page. - await this.slides!.slideTo(nextSlideIndex); + await this.slides.slideTo(nextSlideIndex); } else { // Slide to the latest slide. - await this.slides!.slideTo(this.numTabsShown - 1); + await this.slides.slideTo(this.numTabsShown - 1); } } @@ -448,24 +451,24 @@ export class CoreTabsBaseComponent implements OnInit, Aft */ async slidePrev(): Promise { // Stop if slides are in transition. - if (!this.showPrevButton || this.isInTransition) { + if (!this.showPrevButton || this.isInTransition || !this.slides) { return; } - if (await this.slides!.isEnd()) { - this.slides!.slideTo(this.numTabsShown - this.maxSlides * 2); + if (await this.slides.isEnd()) { + this.slides.slideTo(this.numTabsShown - this.maxSlides * 2); // Slide to the previous of the latest page. } else { - const currentIndex = await this.slides!.getActiveIndex(); + const currentIndex = await this.slides.getActiveIndex(); if (currentIndex !== undefined) { const prevSlideIndex = currentIndex - this.maxSlides; this.isInTransition = true; if (prevSlideIndex >= 0) { // Slide to the previous page. - await this.slides!.slideTo(prevSlideIndex); + await this.slides.slideTo(prevSlideIndex); } else { // Slide to the first page. - await this.slides!.slideTo(0); + await this.slides.slideTo(0); } } } @@ -646,9 +649,7 @@ export class CoreTabsBaseComponent implements OnInit, Aft ngOnDestroy(): void { this.isDestroyed = true; - if (this.resizeFunction) { - window.removeEventListener('resize', this.resizeFunction); - } + this.resizeListener?.off(); this.languageChangedSubscription?.unsubscribe(); } diff --git a/src/core/directives/collapsible-footer.ts b/src/core/directives/collapsible-footer.ts index dc1e2c55a..601a12f5a 100644 --- a/src/core/directives/collapsible-footer.ts +++ b/src/core/directives/collapsible-footer.ts @@ -21,6 +21,7 @@ import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreFormatTextDirective } from './format-text'; import { CoreEventObserver } from '@singletons/events'; import { CoreLoadingComponent } from '@components/loading/loading'; +import { CoreDomUtils } from '@services/utils/dom'; /** * Directive to make an element fixed at the bottom collapsible when scrolling. @@ -37,7 +38,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { @Input() appearOnBottom = false; protected element: HTMLElement; - protected initialHeight = 0; + protected initialHeight = 48; protected finalHeight = 0; protected initialPaddingBottom = '0px'; protected previousTop = 0; @@ -46,22 +47,38 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { protected loadingChangedListener?: CoreEventObserver; protected contentScrollListener?: EventListener; protected endContentScrollListener?: EventListener; + protected resizeListener?: CoreEventObserver; constructor(el: ElementRef, protected ionContent: IonContent) { this.element = el.nativeElement; this.element.setAttribute('slot', 'fixed'); // Just in case somebody forgets to add it. } + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + // Only if not present or explicitly falsy it will be false. + this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom); + + await CoreDomUtils.waitToBeInDOM(this.element); + await this.waitLoadingsDone(); + await this.waitFormatTextsRendered(this.element); + + await this.calculateHeight(); + + this.listenScrollEvents(); + } + /** * Calculate the height of the footer. */ protected async calculateHeight(): Promise { - await this.waitFormatTextsRendered(this.element); - + this.element.classList.remove('is-active'); await CoreUtils.nextTick(); // Set a minimum height value. - this.initialHeight = this.element.getBoundingClientRect().height || 48; + this.initialHeight = this.element.getBoundingClientRect().height || this.initialHeight; const moduleNav = this.element.querySelector('core-course-module-navigation'); if (moduleNav) { this.element.classList.add('has-module-nav'); @@ -71,6 +88,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { this.previousHeight = this.initialHeight; this.content?.style.setProperty('--core-collapsible-footer-max-height', this.initialHeight + 'px'); + this.element.classList.add('is-active'); this.setBarHeight(this.initialHeight); } @@ -131,6 +149,10 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { this.setBarHeight(newHeight); } }); + + this.resizeListener = CoreDomUtils.onWindowResize(() => { + this.calculateHeight(); + }, 50); } /** @@ -180,20 +202,6 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { this.previousHeight = height; } - /** - * @inheritdoc - */ - async ngOnInit(): Promise { - // Only if not present or explicitly falsy it will be false. - this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom); - - await this.waitLoadingsDone(); - - await this.calculateHeight(); - - this.listenScrollEvents(); - } - /** * Wait until all children inside the page. * @@ -225,6 +233,8 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { if (this.content && this.endContentScrollListener) { this.content.removeEventListener('ionScrollEnd', this.endContentScrollListener); } + + this.resizeListener?.off(); } } diff --git a/src/core/directives/collapsible-header.ts b/src/core/directives/collapsible-header.ts index 3b26e56a1..867e73c14 100644 --- a/src/core/directives/collapsible-header.ts +++ b/src/core/directives/collapsible-header.ts @@ -17,8 +17,10 @@ import { CorePromisedValue } from '@classes/promised-value'; import { CoreLoadingComponent } from '@components/loading/loading'; import { CoreTabsOutletComponent } from '@components/tabs-outlet/tabs-outlet'; import { ScrollDetail } from '@ionic/core'; +import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; import { CoreComponentsRegistry } from '@singletons/components-registry'; +import { CoreEventObserver } from '@singletons/events'; import { CoreMath } from '@singletons/math'; import { Subscription } from 'rxjs'; import { CoreFormatTextDirective } from './format-text'; @@ -66,6 +68,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest protected contentScrollListener?: EventListener; protected endContentScrollListener?: EventListener; protected pageDidEnterListener?: EventListener; + protected resizeListener?: CoreEventObserver; protected floatingTitle?: HTMLHeadingElement; protected scrollingHeight?: number; protected subscriptions: Subscription[] = []; @@ -120,6 +123,8 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest if (this.page && this.pageDidEnterListener) { this.page.removeEventListener('ionViewDidEnter', this.pageDidEnterListener); } + + this.resizeListener?.off(); } /** @@ -147,6 +152,10 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest const timeout = window.setTimeout(() => { this.enteredPromise.reject(new Error('[collapsible-header] Waiting for ionViewDidEnter timeout reached')); }, 5000); + + this.resizeListener = CoreDomUtils.onWindowResize(() => { + this.initializeFloatingTitle(); + }, 50); } /** @@ -217,16 +226,26 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest throw new Error('[collapsible-header] Couldn\'t create floating title'); } + this.page.classList.remove('is-active'); + CoreUtils.nextTick(); + // Add floating title and measure initial position. const collapsedHeaderTitle = this.collapsedHeader.querySelector('h1') as HTMLHeadingElement; - const originalTitle = this.expandedHeader.querySelector('h1') as HTMLHeadingElement; - const floatingTitleWrapper = originalTitle.parentElement as HTMLElement; - const floatingTitle = originalTitle.cloneNode(true) as HTMLHeadingElement; + const originalTitle = this.expandedHeader.querySelector('h1.collapsible-header-original-title') || + this.expandedHeader.querySelector('h1') as HTMLHeadingElement; - originalTitle.classList.add('collapsible-header-original-title'); - floatingTitle.classList.add('collapsible-header-floating-title'); - floatingTitleWrapper.classList.add('collapsible-header-floating-title-wrapper'); - floatingTitleWrapper.insertBefore(floatingTitle, originalTitle); + const floatingTitleWrapper = originalTitle.parentElement as HTMLElement; + let floatingTitle = floatingTitleWrapper.querySelector('.collapsible-header-floating-title') as HTMLHeadingElement; + if (!floatingTitle) { + // First time, create it. + floatingTitle = originalTitle.cloneNode(true) as HTMLHeadingElement; + floatingTitle.classList.add('collapsible-header-floating-title'); + + floatingTitleWrapper.classList.add('collapsible-header-floating-title-wrapper'); + floatingTitleWrapper.insertBefore(floatingTitle, originalTitle); + + originalTitle.classList.add('collapsible-header-original-title'); + } const floatingTitleBoundingBox = floatingTitle.getBoundingClientRect(); diff --git a/src/core/directives/collapsible-item.ts b/src/core/directives/collapsible-item.ts index 08cc6da18..fc1730cce 100644 --- a/src/core/directives/collapsible-item.ts +++ b/src/core/directives/collapsible-item.ts @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Directive, ElementRef, Input, OnInit } from '@angular/core'; +import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; import { CoreLoadingComponent } from '@components/loading/loading'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; import { CoreComponentsRegistry } from '@singletons/components-registry'; +import { CoreEventObserver } from '@singletons/events'; import { CoreFormatTextDirective } from './format-text'; const defaultMaxHeight = 80; @@ -33,7 +34,7 @@ const minMaxHeight = 56; @Directive({ selector: '[collapsible-item]', }) -export class CoreCollapsibleItemDirective implements OnInit { +export class CoreCollapsibleItemDirective implements OnInit, OnDestroy { /** * Max height in pixels to render the content box. It should be 56 at least to make sense. @@ -47,6 +48,7 @@ export class CoreCollapsibleItemDirective implements OnInit { protected expanded = false; protected maxHeight = defaultMaxHeight; protected expandedHeight = 0; + protected resizeListener?: CoreEventObserver; constructor(el: ElementRef) { this.element = el.nativeElement; @@ -81,6 +83,10 @@ export class CoreCollapsibleItemDirective implements OnInit { await this.waitLoadingsDone(); await this.calculateHeight(); + + this.resizeListener = CoreDomUtils.onWindowResize(() => { + this.calculateHeight(); + }, 50); } /** @@ -126,7 +132,7 @@ export class CoreCollapsibleItemDirective implements OnInit { await this.waitFormatTextsRendered(this.element); - this.expandedHeight = CoreDomUtils.getElementHeight(this.element) || 0; + this.expandedHeight = this.element.getBoundingClientRect().height; // Restore the max height now. this.element.classList.remove('collapsible-loading-height'); @@ -229,4 +235,11 @@ export class CoreCollapsibleItemDirective implements OnInit { this.toggleExpand(); } + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.resizeListener?.off(); + } + } diff --git a/src/core/features/mainmenu/pages/menu/menu.ts b/src/core/features/mainmenu/pages/menu/menu.ts index bda5d03dc..47275c179 100644 --- a/src/core/features/mainmenu/pages/menu/menu.ts +++ b/src/core/features/mainmenu/pages/menu/menu.ts @@ -29,6 +29,7 @@ import { filter } from 'rxjs/operators'; import { NavigationEnd } from '@angular/router'; import { trigger, state, style, transition, animate } from '@angular/animations'; import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; /** * Page that displays the main menu of the app. @@ -73,7 +74,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { protected navSubscription?: Subscription; protected keyboardObserver?: CoreEventObserver; protected badgeUpdateObserver?: CoreEventObserver; - protected resizeFunction: () => void; + protected resizeListener?: CoreEventObserver; protected backButtonFunction: (event: BackButtonEvent) => void; protected selectHistory: string[] = []; protected firstSelectedTab?: string; @@ -86,7 +87,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { tabAction: CoreMainMenuRoleTab; constructor() { - this.resizeFunction = this.initHandlers.bind(this); this.backButtonFunction = this.backButtonClicked.bind(this); this.tabAction = new CoreMainMenuRoleTab(this); @@ -122,7 +122,9 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { } }); - window.addEventListener('resize', this.resizeFunction); + this.resizeListener = CoreDomUtils.onWindowResize(() => { + this.initHandlers(); + }); document.addEventListener('ionBackButton', this.backButtonFunction); if (CoreApp.isIOS()) { @@ -221,10 +223,10 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { ngOnDestroy(): void { this.subscription?.unsubscribe(); this.navSubscription?.unsubscribe(); - window.removeEventListener('resize', this.resizeFunction); document.removeEventListener('ionBackButton', this.backButtonFunction); this.keyboardObserver?.off(); this.badgeUpdateObserver?.off(); + this.resizeListener?.off(); } /** diff --git a/src/core/features/mainmenu/pages/more/more.ts b/src/core/features/mainmenu/pages/more/more.ts index 5a4f6d7b5..d3e91ebfa 100644 --- a/src/core/features/mainmenu/pages/more/more.ts +++ b/src/core/features/mainmenu/pages/more/more.ts @@ -26,6 +26,7 @@ import { CoreContentLinksHelper } from '@features/contentlinks/services/contentl import { CoreTextUtils } from '@services/utils/text'; import { Translate } from '@singletons'; import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; +import { CoreDomUtils } from '@services/utils/dom'; /** * Page that displays the more page of the app. @@ -46,6 +47,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { protected subscription!: Subscription; protected langObserver: CoreEventObserver; protected updateSiteObserver: CoreEventObserver; + protected resizeListener?: CoreEventObserver; constructor() { this.langObserver = CoreEvents.on(CoreEvents.LANGUAGE_CHANGED, this.loadCustomMenuItems.bind(this)); @@ -71,7 +73,9 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { this.initHandlers(); }); - window.addEventListener('resize', this.initHandlers.bind(this)); + this.resizeListener = CoreDomUtils.onWindowResize(() => { + this.initHandlers(); + }); const deepLinkManager = new CoreMainMenuDeepLinkManager(); deepLinkManager.treatLink(); @@ -81,10 +85,10 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { * @inheritdoc */ ngOnDestroy(): void { - window.removeEventListener('resize', this.initHandlers.bind(this)); this.langObserver?.off(); this.updateSiteObserver?.off(); this.subscription?.unsubscribe(); + this.resizeListener?.off(); } /** diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index 390fe0c53..c9c2aa227 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -53,6 +53,7 @@ import { NavigationStart } from '@angular/router'; import { filter } from 'rxjs/operators'; import { Subscription } from 'rxjs'; import { CoreComponentsRegistry } from '@singletons/components-registry'; +import { CoreEventObserver } from '@singletons/events'; /* * "Utils" service with helper functions for UI, DOM elements and HTML code. @@ -127,6 +128,28 @@ export class CoreDomUtilsProvider { }); } + /** + * Window resize is widely checked and may have many performance issues, debouce usage is needed to avoid calling it too much. + * This function helps setting up the debounce feature and remove listener easily. + * + * @param resizeFunction Function to execute on resize. + * @param debounceDelay Debounce time in ms. + * @return Event observer to call off when finished. + */ + onWindowResize(resizeFunction: (ev?: Event) => void, debounceDelay = 20): CoreEventObserver { + const resizeListener = CoreUtils.debounce((ev?: Event) => { + resizeFunction(ev); + }, debounceDelay); + + window.addEventListener('resize', resizeListener); + + return { + off: (): void => { + window.removeEventListener('resize', resizeListener); + }, + }; + } + /** * Equivalent to element.closest(). If the browser doesn't support element.closest, it will * traverse the parents to achieve the same functionality. diff --git a/src/theme/components/collapsible-header.scss b/src/theme/components/collapsible-header.scss index c0650635a..a5bfd6a22 100644 --- a/src/theme/components/collapsible-header.scss +++ b/src/theme/components/collapsible-header.scss @@ -8,6 +8,7 @@ --collapsible-header-floating-title-width: 0px; --collapsible-header-floating-title-x-delta: 0px; --collapsible-header-floating-title-width-delta: 0px; + ion-header.core-header-shadow { --core-header-shadow: none; } @@ -19,9 +20,8 @@ .collapsible-header-floating-title { position: absolute; - top: var(--collapsible-header-floating-title-top); - left: var(--collapsible-header-floating-title-left); - transform: translateX(calc(var(--collapsible-header-floating-title-x-delta) * var(--collapsible-header-progress))); + top: 0; + left: 0; opacity: 0; } @@ -52,6 +52,9 @@ .collapsible-header-floating-title { opacity: 1; + top: var(--collapsible-header-floating-title-top); + left: var(--collapsible-header-floating-title-left); + transform: translateX(calc(var(--collapsible-header-floating-title-x-delta) * var(--collapsible-header-progress))); width: calc(var(--collapsible-header-floating-title-width) + var(--collapsible-header-progress) * var(--collapsible-header-floating-title-width-delta)); @include core-transition(width transform, 200ms, linear); diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 1396e9a71..250f07ba4 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -1421,37 +1421,42 @@ ion-grid.core-no-grid > ion-row { } [collapsible-footer] { - &.footer-collapsed { - --core-collapsible-footer-height: 0; - opacity: 0; - } - &.has-module-nav.footer-collapsed { - --core-collapsible-footer-height: auto; - opacity: 1; - core-course-module-navigation { - height: 0; - opacity: 0; - @include core-transition(all, 200ms); - } - - } - &.footer-expanded { - --core-collapsible-footer-height: auto; - } + box-shadow: var(--drop-shadow-top, none); + width: 100%; + bottom: 0; + z-index: 3; + display: block; + background-color: var(--core-collapsible-footer-background); .ion-margin { margin-top: 8px; margin-bottom: 8px; } - box-shadow: var(--drop-shadow-top, none); - width: 100%; - bottom: 0; - z-index: 3; - height: var(--core-collapsible-footer-height, auto); - background-color: var(--core-collapsible-footer-background); - display: block; - @include core-transition(all, 200ms); + &.is-active { + height: var(--core-collapsible-footer-height, auto); + @include core-transition(all, 200ms); + + &.footer-collapsed { + --core-collapsible-footer-height: 0; + opacity: 0; + } + &.has-module-nav.footer-collapsed { + --core-collapsible-footer-height: auto; + opacity: 1; + core-course-module-navigation { + height: 0; + opacity: 0; + @include core-transition(all, 200ms); + } + + } + &.footer-expanded { + --core-collapsible-footer-height: auto; + } + + + } } .core-iframe-fullscreen [collapsible-footer] { diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss index c19cac3bf..d736a833c 100644 --- a/src/theme/theme.light.scss +++ b/src/theme/theme.light.scss @@ -329,7 +329,6 @@ --core-courseimage-on-course-size: 72px; --core-courseimage-radius: var(--medium-radius); - --core-collapsible-footer-height: 48px; --core-navigation-background: var(--contrast-background); --core-collapsible-footer-background: var(--contrast-background);