commit
b364f6382b
|
@ -33,7 +33,7 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp
|
||||||
|
|
||||||
component = AddonModPageProvider.COMPONENT;
|
component = AddonModPageProvider.COMPONENT;
|
||||||
contents?: string;
|
contents?: string;
|
||||||
displayDescription = true;
|
displayDescription = false;
|
||||||
displayTimemodified = true;
|
displayTimemodified = true;
|
||||||
timemodified?: number;
|
timemodified?: number;
|
||||||
page?: AddonModPagePage;
|
page?: AddonModPagePage;
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreEventObserver } from '@singletons/events';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { AddonModQuizDdImageOrTextQuestionData } from '../component/ddimageortext';
|
import { AddonModQuizDdImageOrTextQuestionData } from '../component/ddimageortext';
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ export class AddonQtypeDdImageOrTextQuestion {
|
||||||
protected afterImageLoadDone = false;
|
protected afterImageLoadDone = false;
|
||||||
protected proportion = 1;
|
protected proportion = 1;
|
||||||
protected selected?: HTMLElement | null; // Selected element (being "dragged").
|
protected selected?: HTMLElement | null; // Selected element (being "dragged").
|
||||||
protected resizeFunction?: (ev?: Event) => void;
|
protected resizeListener?: CoreEventObserver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the this.
|
* Create the this.
|
||||||
|
@ -174,9 +175,7 @@ export class AddonQtypeDdImageOrTextQuestion {
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.stopPolling();
|
this.stopPolling();
|
||||||
|
|
||||||
if (this.resizeFunction) {
|
this.resizeListener?.off();
|
||||||
window.removeEventListener('resize', this.resizeFunction);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -360,8 +359,9 @@ export class AddonQtypeDdImageOrTextQuestion {
|
||||||
this.pollForImageLoad();
|
this.pollForImageLoad();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.resizeFunction = this.windowResized.bind(this);
|
this.resizeListener = CoreDomUtils.onWindowResize(() => {
|
||||||
window.addEventListener('resize', this.resizeFunction!);
|
this.windowResized();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
import { CoreEventObserver } from '@singletons/events';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { AddonQtypeDdMarkerQuestionData } from '../component/ddmarker';
|
import { AddonQtypeDdMarkerQuestionData } from '../component/ddmarker';
|
||||||
import { AddonQtypeDdMarkerGraphicsApi } from './graphics_api';
|
import { AddonQtypeDdMarkerGraphicsApi } from './graphics_api';
|
||||||
|
@ -41,7 +42,7 @@ export class AddonQtypeDdMarkerQuestion {
|
||||||
protected proportion = 1;
|
protected proportion = 1;
|
||||||
protected selected?: HTMLElement; // Selected element (being "dragged").
|
protected selected?: HTMLElement; // Selected element (being "dragged").
|
||||||
protected graphics: AddonQtypeDdMarkerGraphicsApi;
|
protected graphics: AddonQtypeDdMarkerGraphicsApi;
|
||||||
protected resizeFunction?: () => void;
|
protected resizeListener?: CoreEventObserver;
|
||||||
|
|
||||||
doc!: AddonQtypeDdMarkerQuestionDocStructure;
|
doc!: AddonQtypeDdMarkerQuestionDocStructure;
|
||||||
shapes: SVGElement[] = [];
|
shapes: SVGElement[] = [];
|
||||||
|
@ -160,9 +161,7 @@ export class AddonQtypeDdMarkerQuestion {
|
||||||
* Function to call when the instance is no longer needed.
|
* Function to call when the instance is no longer needed.
|
||||||
*/
|
*/
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
if (this.resizeFunction) {
|
this.resizeListener?.off();
|
||||||
window.removeEventListener('resize', this.resizeFunction);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -273,8 +272,18 @@ export class AddonQtypeDdMarkerQuestion {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const width = CoreDomUtils.getElementMeasure(markerSpan, true, true, false, true);
|
const computedStyle = getComputedStyle(markerSpan);
|
||||||
const height = CoreDomUtils.getElementMeasure(markerSpan, false, true, false, true);
|
const width = markerSpan.getBoundingClientRect().width +
|
||||||
|
CoreDomUtils.getComputedStyleMeasure(computedStyle, 'borderLeftWidth') +
|
||||||
|
CoreDomUtils.getComputedStyleMeasure(computedStyle, 'borderRightWidth') +
|
||||||
|
CoreDomUtils.getComputedStyleMeasure(computedStyle, 'paddingLeft') +
|
||||||
|
CoreDomUtils.getComputedStyleMeasure(computedStyle, 'paddingRight');
|
||||||
|
|
||||||
|
const height = markerSpan.getBoundingClientRect().height +
|
||||||
|
CoreDomUtils.getComputedStyleMeasure(computedStyle, 'borderTopWidth') +
|
||||||
|
CoreDomUtils.getComputedStyleMeasure(computedStyle, 'borderBottomWidth') +
|
||||||
|
CoreDomUtils.getComputedStyleMeasure(computedStyle, 'paddingTop') +
|
||||||
|
CoreDomUtils.getComputedStyleMeasure(computedStyle, 'paddingBottom');
|
||||||
markerSpan.style.opacity = '0.6';
|
markerSpan.style.opacity = '0.6';
|
||||||
markerSpan.style.left = (xyForText.x - (width / 2)) + 'px';
|
markerSpan.style.left = (xyForText.x - (width / 2)) + 'px';
|
||||||
markerSpan.style.top = (xyForText.y - (height / 2)) + 'px';
|
markerSpan.style.top = (xyForText.y - (height / 2)) + 'px';
|
||||||
|
@ -601,8 +610,9 @@ export class AddonQtypeDdMarkerQuestion {
|
||||||
this.pollForImageLoad();
|
this.pollForImageLoad();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.resizeFunction = this.windowResized.bind(this);
|
this.resizeListener = CoreDomUtils.onWindowResize(() => {
|
||||||
window.addEventListener('resize', this.resizeFunction!);
|
this.windowResized();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreEventObserver } from '@singletons/events';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { AddonModQuizDdwtosQuestionData } from '../component/ddwtos';
|
import { AddonModQuizDdwtosQuestionData } from '../component/ddwtos';
|
||||||
|
|
||||||
|
@ -28,13 +29,11 @@ export class AddonQtypeDdwtosQuestion {
|
||||||
protected selectors!: AddonQtypeDdwtosQuestionCSSSelectors; // Result of cssSelectors.
|
protected selectors!: AddonQtypeDdwtosQuestionCSSSelectors; // Result of cssSelectors.
|
||||||
protected placed: {[no: number]: number} = {}; // Map that relates drag elements numbers with drop zones numbers.
|
protected placed: {[no: number]: number} = {}; // Map that relates drag elements numbers with drop zones numbers.
|
||||||
protected selected?: HTMLElement; // Selected element (being "dragged").
|
protected selected?: HTMLElement; // Selected element (being "dragged").
|
||||||
protected resizeFunction?: () => void;
|
protected resizeListener?: CoreEventObserver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the instance.
|
* Create the instance.
|
||||||
*
|
*
|
||||||
* @param logger Logger provider.
|
|
||||||
* @param domUtils Dom Utils provider.
|
|
||||||
* @param container The container HTMLElement of the question.
|
* @param container The container HTMLElement of the question.
|
||||||
* @param question The question instance.
|
* @param question The question instance.
|
||||||
* @param readOnly Whether it's read only.
|
* @param readOnly Whether it's read only.
|
||||||
|
@ -122,9 +121,7 @@ export class AddonQtypeDdwtosQuestion {
|
||||||
* Function to call when the instance is no longer needed.
|
* Function to call when the instance is no longer needed.
|
||||||
*/
|
*/
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
if (this.resizeFunction) {
|
this.resizeListener?.off();
|
||||||
window.removeEventListener('resize', this.resizeFunction);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -214,8 +211,9 @@ export class AddonQtypeDdwtosQuestion {
|
||||||
|
|
||||||
this.positionDragItems();
|
this.positionDragItems();
|
||||||
|
|
||||||
this.resizeFunction = this.windowResized.bind(this);
|
this.resizeListener = CoreDomUtils.onWindowResize(() => {
|
||||||
window.addEventListener('resize', this.resizeFunction!);
|
this.windowResized();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,6 +32,8 @@ import { Subscription } from 'rxjs';
|
||||||
import { Platform, Translate } from '@singletons';
|
import { Platform, Translate } from '@singletons';
|
||||||
import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
|
import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
|
||||||
import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from './aria-role-tab';
|
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.
|
* Class to abstract some common code for tabs.
|
||||||
|
@ -75,7 +77,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
|
||||||
protected tabsElement?: HTMLElement; // The tabs parent element. It's the element that will be "scrolled" to hide tabs.
|
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 tabBarElement?: HTMLIonTabBarElement; // The top tab bar element.
|
||||||
protected tabsShown = true;
|
protected tabsShown = true;
|
||||||
protected resizeFunction: EventListenerOrEventListenerObject;
|
protected resizeListener?: CoreEventObserver;
|
||||||
protected isDestroyed = false;
|
protected isDestroyed = false;
|
||||||
protected isCurrentView = true;
|
protected isCurrentView = true;
|
||||||
protected shouldSlideToInitial = false; // Whether we need to slide to the initial slide because it's out of view.
|
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<T extends CoreTabBase> implements OnInit, Aft
|
||||||
protected element: ElementRef,
|
protected element: ElementRef,
|
||||||
) {
|
) {
|
||||||
this.backButtonFunction = this.backButtonClicked.bind(this);
|
this.backButtonFunction = this.backButtonClicked.bind(this);
|
||||||
this.resizeFunction = this.windowResized.bind(this);
|
|
||||||
|
|
||||||
this.tabAction = new CoreTabsRoleTab(this);
|
this.tabAction = new CoreTabsRoleTab(this);
|
||||||
}
|
}
|
||||||
|
@ -134,7 +135,9 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
|
||||||
await this.initializeTabs();
|
await this.initializeTabs();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', this.resizeFunction);
|
this.resizeListener = CoreDomUtils.onWindowResize(() => {
|
||||||
|
this.windowResized();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -419,24 +422,24 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
|
||||||
*/
|
*/
|
||||||
async slideNext(): Promise<void> {
|
async slideNext(): Promise<void> {
|
||||||
// Stop if slides are in transition.
|
// Stop if slides are in transition.
|
||||||
if (!this.showNextButton || this.isInTransition) {
|
if (!this.showNextButton || this.isInTransition || !this.slides) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await this.slides!.isBeginning()) {
|
if (await this.slides.isBeginning()) {
|
||||||
// Slide to the second page.
|
// Slide to the second page.
|
||||||
this.slides!.slideTo(this.maxSlides);
|
this.slides.slideTo(this.maxSlides);
|
||||||
} else {
|
} else {
|
||||||
const currentIndex = await this.slides!.getActiveIndex();
|
const currentIndex = await this.slides.getActiveIndex();
|
||||||
if (currentIndex !== undefined) {
|
if (currentIndex !== undefined) {
|
||||||
const nextSlideIndex = currentIndex + this.maxSlides;
|
const nextSlideIndex = currentIndex + this.maxSlides;
|
||||||
this.isInTransition = true;
|
this.isInTransition = true;
|
||||||
if (nextSlideIndex < this.numTabsShown) {
|
if (nextSlideIndex < this.numTabsShown) {
|
||||||
// Slide to the next page.
|
// Slide to the next page.
|
||||||
await this.slides!.slideTo(nextSlideIndex);
|
await this.slides.slideTo(nextSlideIndex);
|
||||||
} else {
|
} else {
|
||||||
// Slide to the latest slide.
|
// 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<T extends CoreTabBase> implements OnInit, Aft
|
||||||
*/
|
*/
|
||||||
async slidePrev(): Promise<void> {
|
async slidePrev(): Promise<void> {
|
||||||
// Stop if slides are in transition.
|
// Stop if slides are in transition.
|
||||||
if (!this.showPrevButton || this.isInTransition) {
|
if (!this.showPrevButton || this.isInTransition || !this.slides) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await this.slides!.isEnd()) {
|
if (await this.slides.isEnd()) {
|
||||||
this.slides!.slideTo(this.numTabsShown - this.maxSlides * 2);
|
this.slides.slideTo(this.numTabsShown - this.maxSlides * 2);
|
||||||
// Slide to the previous of the latest page.
|
// Slide to the previous of the latest page.
|
||||||
} else {
|
} else {
|
||||||
const currentIndex = await this.slides!.getActiveIndex();
|
const currentIndex = await this.slides.getActiveIndex();
|
||||||
if (currentIndex !== undefined) {
|
if (currentIndex !== undefined) {
|
||||||
const prevSlideIndex = currentIndex - this.maxSlides;
|
const prevSlideIndex = currentIndex - this.maxSlides;
|
||||||
this.isInTransition = true;
|
this.isInTransition = true;
|
||||||
if (prevSlideIndex >= 0) {
|
if (prevSlideIndex >= 0) {
|
||||||
// Slide to the previous page.
|
// Slide to the previous page.
|
||||||
await this.slides!.slideTo(prevSlideIndex);
|
await this.slides.slideTo(prevSlideIndex);
|
||||||
} else {
|
} else {
|
||||||
// Slide to the first page.
|
// Slide to the first page.
|
||||||
await this.slides!.slideTo(0);
|
await this.slides.slideTo(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -646,9 +649,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.isDestroyed = true;
|
this.isDestroyed = true;
|
||||||
|
|
||||||
if (this.resizeFunction) {
|
this.resizeListener?.off();
|
||||||
window.removeEventListener('resize', this.resizeFunction);
|
|
||||||
}
|
|
||||||
this.languageChangedSubscription?.unsubscribe();
|
this.languageChangedSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange, ViewChild, ElementRef } from '@angular/core';
|
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange, ViewChild, ElementRef } from '@angular/core';
|
||||||
import { IonInfiniteScroll } from '@ionic/angular';
|
import { IonInfiniteScroll } from '@ionic/angular';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
|
||||||
const THRESHOLD = .15; // % of the scroll element height that must be close to the edge to consider loading more items necessary.
|
const THRESHOLD = .15; // % of the scroll element height that must be close to the edge to consider loading more items necessary.
|
||||||
|
@ -36,15 +35,12 @@ export class CoreInfiniteLoadingComponent implements OnChanges {
|
||||||
@Input() position: 'top' | 'bottom' = 'bottom';
|
@Input() position: 'top' | 'bottom' = 'bottom';
|
||||||
@Output() action: EventEmitter<() => void>; // Will emit an event when triggered.
|
@Output() action: EventEmitter<() => void>; // Will emit an event when triggered.
|
||||||
|
|
||||||
@ViewChild('topbutton') topButton?: ElementRef;
|
|
||||||
@ViewChild('bottombutton') bottomButton?: ElementRef;
|
|
||||||
@ViewChild('spinnercontainer') spinnerContainer?: ElementRef;
|
|
||||||
@ViewChild(IonInfiniteScroll) infiniteScroll?: IonInfiniteScroll;
|
@ViewChild(IonInfiniteScroll) infiniteScroll?: IonInfiniteScroll;
|
||||||
|
|
||||||
loadingMore = false; // Hide button and avoid loading more.
|
loadingMore = false; // Hide button and avoid loading more.
|
||||||
hostElement: HTMLElement;
|
hostElement: HTMLElement;
|
||||||
|
|
||||||
constructor(protected element: ElementRef<HTMLElement>) {
|
constructor(element: ElementRef<HTMLElement>) {
|
||||||
this.action = new EventEmitter();
|
this.action = new EventEmitter();
|
||||||
this.hostElement = element.nativeElement;
|
this.hostElement = element.nativeElement;
|
||||||
}
|
}
|
||||||
|
@ -142,11 +138,7 @@ export class CoreInfiniteLoadingComponent implements OnChanges {
|
||||||
* @deprecated since 3.9.5
|
* @deprecated since 3.9.5
|
||||||
*/
|
*/
|
||||||
getHeight(): number {
|
getHeight(): number {
|
||||||
return (this.position == 'top' ?
|
return this.hostElement.getBoundingClientRect().height;
|
||||||
this.getElementHeight(this.topButton?.nativeElement) :
|
|
||||||
this.getElementHeight(this.bottomButton?.nativeElement)) +
|
|
||||||
this.getElementHeight(this.infiniteScrollElement) +
|
|
||||||
this.getElementHeight(this.spinnerContainer?.nativeElement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -158,18 +150,4 @@ export class CoreInfiniteLoadingComponent implements OnChanges {
|
||||||
return this.hostElement.querySelector('ion-infinite-scroll');
|
return this.hostElement.querySelector('ion-infinite-scroll');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the height of an element.
|
|
||||||
*
|
|
||||||
* @param element Element ref.
|
|
||||||
* @return Height.
|
|
||||||
*/
|
|
||||||
protected getElementHeight(element?: HTMLElement | null): number {
|
|
||||||
if (element) {
|
|
||||||
return CoreDomUtils.getElementHeight(element, true, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,9 @@ import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreMath } from '@singletons/math';
|
import { CoreMath } from '@singletons/math';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
import { CoreFormatTextDirective } from './format-text';
|
import { CoreFormatTextDirective } from './format-text';
|
||||||
import { CoreEventObserver } from '@singletons/events';
|
import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events';
|
||||||
import { CoreLoadingComponent } from '@components/loading/loading';
|
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive to make an element fixed at the bottom collapsible when scrolling.
|
* 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;
|
@Input() appearOnBottom = false;
|
||||||
|
|
||||||
protected element: HTMLElement;
|
protected element: HTMLElement;
|
||||||
protected initialHeight = 0;
|
protected initialHeight = 48;
|
||||||
protected finalHeight = 0;
|
protected finalHeight = 0;
|
||||||
protected initialPaddingBottom = '0px';
|
protected initialPaddingBottom = '0px';
|
||||||
protected previousTop = 0;
|
protected previousTop = 0;
|
||||||
|
@ -46,22 +47,43 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
||||||
protected loadingChangedListener?: CoreEventObserver;
|
protected loadingChangedListener?: CoreEventObserver;
|
||||||
protected contentScrollListener?: EventListener;
|
protected contentScrollListener?: EventListener;
|
||||||
protected endContentScrollListener?: EventListener;
|
protected endContentScrollListener?: EventListener;
|
||||||
|
protected resizeListener?: CoreEventObserver;
|
||||||
|
protected domListener?: CoreSingleTimeEventObserver;
|
||||||
|
|
||||||
constructor(el: ElementRef, protected ionContent: IonContent) {
|
constructor(el: ElementRef, protected ionContent: IonContent) {
|
||||||
this.element = el.nativeElement;
|
this.element = el.nativeElement;
|
||||||
this.element.setAttribute('slot', 'fixed'); // Just in case somebody forgets to add it.
|
this.element.setAttribute('slot', 'fixed'); // Just in case somebody forgets to add it.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
// Only if not present or explicitly falsy it will be false.
|
||||||
|
this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom);
|
||||||
|
|
||||||
|
this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
|
||||||
|
await this.domListener.promise;
|
||||||
|
|
||||||
|
await this.waitLoadingsDone();
|
||||||
|
await this.waitFormatTextsRendered(this.element);
|
||||||
|
|
||||||
|
this.content = this.element.closest('ion-content');
|
||||||
|
|
||||||
|
await this.calculateHeight();
|
||||||
|
|
||||||
|
this.listenScrollEvents();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the height of the footer.
|
* Calculate the height of the footer.
|
||||||
*/
|
*/
|
||||||
protected async calculateHeight(): Promise<void> {
|
protected async calculateHeight(): Promise<void> {
|
||||||
await this.waitFormatTextsRendered(this.element);
|
this.element.classList.remove('is-active');
|
||||||
|
|
||||||
await CoreUtils.nextTick();
|
await CoreUtils.nextTick();
|
||||||
|
|
||||||
// Set a minimum height value.
|
// 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');
|
const moduleNav = this.element.querySelector('core-course-module-navigation');
|
||||||
if (moduleNav) {
|
if (moduleNav) {
|
||||||
this.element.classList.add('has-module-nav');
|
this.element.classList.add('has-module-nav');
|
||||||
|
@ -71,6 +93,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
||||||
this.previousHeight = this.initialHeight;
|
this.previousHeight = this.initialHeight;
|
||||||
|
|
||||||
this.content?.style.setProperty('--core-collapsible-footer-max-height', this.initialHeight + 'px');
|
this.content?.style.setProperty('--core-collapsible-footer-max-height', this.initialHeight + 'px');
|
||||||
|
this.element.classList.add('is-active');
|
||||||
|
|
||||||
this.setBarHeight(this.initialHeight);
|
this.setBarHeight(this.initialHeight);
|
||||||
}
|
}
|
||||||
|
@ -79,13 +102,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
||||||
* Setup scroll event listener.
|
* Setup scroll event listener.
|
||||||
*/
|
*/
|
||||||
protected async listenScrollEvents(): Promise<void> {
|
protected async listenScrollEvents(): Promise<void> {
|
||||||
if (this.content) {
|
if (!this.content || this.content?.classList.contains('has-collapsible-footer')) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.content = this.element.closest('ion-content');
|
|
||||||
|
|
||||||
if (!this.content) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +148,10 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.setBarHeight(newHeight); }
|
this.setBarHeight(newHeight); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.resizeListener = CoreDomUtils.onWindowResize(() => {
|
||||||
|
this.calculateHeight();
|
||||||
|
}, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,20 +201,6 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
||||||
this.previousHeight = height;
|
this.previousHeight = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
async ngOnInit(): Promise<void> {
|
|
||||||
// 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 <core-loading> children inside the page.
|
* Wait until all <core-loading> children inside the page.
|
||||||
*
|
*
|
||||||
|
@ -225,6 +232,9 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
||||||
if (this.content && this.endContentScrollListener) {
|
if (this.content && this.endContentScrollListener) {
|
||||||
this.content.removeEventListener('ionScrollEnd', this.endContentScrollListener);
|
this.content.removeEventListener('ionScrollEnd', this.endContentScrollListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.resizeListener?.off();
|
||||||
|
this.domListener?.off();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,12 @@ import { Directive, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChang
|
||||||
import { CorePromisedValue } from '@classes/promised-value';
|
import { CorePromisedValue } from '@classes/promised-value';
|
||||||
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 { CoreSettingsHelper } from '@features/settings/services/settings-helper';
|
||||||
import { ScrollDetail } from '@ionic/core';
|
import { ScrollDetail } from '@ionic/core';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
|
import { CoreEventObserver } from '@singletons/events';
|
||||||
import { CoreMath } from '@singletons/math';
|
import { CoreMath } from '@singletons/math';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { CoreFormatTextDirective } from './format-text';
|
import { CoreFormatTextDirective } from './format-text';
|
||||||
|
@ -66,6 +69,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
protected contentScrollListener?: EventListener;
|
protected contentScrollListener?: EventListener;
|
||||||
protected endContentScrollListener?: EventListener;
|
protected endContentScrollListener?: EventListener;
|
||||||
protected pageDidEnterListener?: EventListener;
|
protected pageDidEnterListener?: EventListener;
|
||||||
|
protected resizeListener?: CoreEventObserver;
|
||||||
protected floatingTitle?: HTMLHeadingElement;
|
protected floatingTitle?: HTMLHeadingElement;
|
||||||
protected scrollingHeight?: number;
|
protected scrollingHeight?: number;
|
||||||
protected subscriptions: Subscription[] = [];
|
protected subscriptions: Subscription[] = [];
|
||||||
|
@ -120,6 +124,8 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
if (this.page && this.pageDidEnterListener) {
|
if (this.page && this.pageDidEnterListener) {
|
||||||
this.page.removeEventListener('ionViewDidEnter', this.pageDidEnterListener);
|
this.page.removeEventListener('ionViewDidEnter', this.pageDidEnterListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.resizeListener?.off();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,6 +153,14 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
const timeout = window.setTimeout(() => {
|
const timeout = window.setTimeout(() => {
|
||||||
this.enteredPromise.reject(new Error('[collapsible-header] Waiting for ionViewDidEnter timeout reached'));
|
this.enteredPromise.reject(new Error('[collapsible-header] Waiting for ionViewDidEnter timeout reached'));
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
|
this.resizeListener = CoreDomUtils.onWindowResize(() => {
|
||||||
|
this.initializeFloatingTitle();
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
this.subscriptions.push(CoreSettingsHelper.onDarkModeChange().subscribe(() => {
|
||||||
|
this.initializeFloatingTitle();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,9 +204,6 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
};
|
};
|
||||||
|
|
||||||
this.subscriptions.push(outlet.activateEvents.subscribe(onOutletUpdated));
|
this.subscriptions.push(outlet.activateEvents.subscribe(onOutletUpdated));
|
||||||
this.subscriptions.push(outlet.activateEvents.subscribe(onOutletUpdated));
|
|
||||||
|
|
||||||
onOutletUpdated();
|
|
||||||
|
|
||||||
onOutletUpdated();
|
onOutletUpdated();
|
||||||
|
|
||||||
|
@ -217,17 +228,27 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
throw new Error('[collapsible-header] Couldn\'t create floating title');
|
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.
|
// Add floating title and measure initial position.
|
||||||
const collapsedHeaderTitle = this.collapsedHeader.querySelector('h1') as HTMLHeadingElement;
|
const collapsedHeaderTitle = this.collapsedHeader.querySelector('h1') as HTMLHeadingElement;
|
||||||
const originalTitle = this.expandedHeader.querySelector('h1') as HTMLHeadingElement;
|
const originalTitle = this.expandedHeader.querySelector('h1.collapsible-header-original-title') ||
|
||||||
const floatingTitleWrapper = originalTitle.parentElement as HTMLElement;
|
this.expandedHeader.querySelector('h1') as HTMLHeadingElement;
|
||||||
const floatingTitle = originalTitle.cloneNode(true) as HTMLHeadingElement;
|
|
||||||
|
|
||||||
originalTitle.classList.add('collapsible-header-original-title');
|
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');
|
floatingTitle.classList.add('collapsible-header-floating-title');
|
||||||
|
|
||||||
floatingTitleWrapper.classList.add('collapsible-header-floating-title-wrapper');
|
floatingTitleWrapper.classList.add('collapsible-header-floating-title-wrapper');
|
||||||
floatingTitleWrapper.insertBefore(floatingTitle, originalTitle);
|
floatingTitleWrapper.insertBefore(floatingTitle, originalTitle);
|
||||||
|
|
||||||
|
originalTitle.classList.add('collapsible-header-original-title');
|
||||||
|
}
|
||||||
|
|
||||||
const floatingTitleBoundingBox = floatingTitle.getBoundingClientRect();
|
const floatingTitleBoundingBox = floatingTitle.getBoundingClientRect();
|
||||||
|
|
||||||
// Prepare styles variables.
|
// Prepare styles variables.
|
||||||
|
|
|
@ -12,12 +12,13 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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 { CoreLoadingComponent } from '@components/loading/loading';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
|
import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events';
|
||||||
import { CoreFormatTextDirective } from './format-text';
|
import { CoreFormatTextDirective } from './format-text';
|
||||||
|
|
||||||
const defaultMaxHeight = 80;
|
const defaultMaxHeight = 80;
|
||||||
|
@ -33,7 +34,7 @@ const minMaxHeight = 56;
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[collapsible-item]',
|
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.
|
* Max height in pixels to render the content box. It should be 56 at least to make sense.
|
||||||
|
@ -47,6 +48,8 @@ export class CoreCollapsibleItemDirective implements OnInit {
|
||||||
protected expanded = false;
|
protected expanded = false;
|
||||||
protected maxHeight = defaultMaxHeight;
|
protected maxHeight = defaultMaxHeight;
|
||||||
protected expandedHeight = 0;
|
protected expandedHeight = 0;
|
||||||
|
protected resizeListener?: CoreEventObserver;
|
||||||
|
protected domListener?: CoreSingleTimeEventObserver;
|
||||||
|
|
||||||
constructor(el: ElementRef<HTMLElement>) {
|
constructor(el: ElementRef<HTMLElement>) {
|
||||||
this.element = el.nativeElement;
|
this.element = el.nativeElement;
|
||||||
|
@ -81,6 +84,10 @@ export class CoreCollapsibleItemDirective implements OnInit {
|
||||||
await this.waitLoadingsDone();
|
await this.waitLoadingsDone();
|
||||||
|
|
||||||
await this.calculateHeight();
|
await this.calculateHeight();
|
||||||
|
|
||||||
|
this.resizeListener = CoreDomUtils.onWindowResize(() => {
|
||||||
|
this.calculateHeight();
|
||||||
|
}, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,7 +96,8 @@ export class CoreCollapsibleItemDirective implements OnInit {
|
||||||
* @return Promise resolved when loadings are done.
|
* @return Promise resolved when loadings are done.
|
||||||
*/
|
*/
|
||||||
protected async waitLoadingsDone(): Promise<void> {
|
protected async waitLoadingsDone(): Promise<void> {
|
||||||
await CoreDomUtils.waitToBeInDOM(this.element);
|
this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
|
||||||
|
await this.domListener.promise;
|
||||||
|
|
||||||
const page = this.element.closest('.ion-page');
|
const page = this.element.closest('.ion-page');
|
||||||
|
|
||||||
|
@ -126,7 +134,7 @@ export class CoreCollapsibleItemDirective implements OnInit {
|
||||||
|
|
||||||
await this.waitFormatTextsRendered(this.element);
|
await this.waitFormatTextsRendered(this.element);
|
||||||
|
|
||||||
this.expandedHeight = CoreDomUtils.getElementHeight(this.element) || 0;
|
this.expandedHeight = this.element.getBoundingClientRect().height;
|
||||||
|
|
||||||
// Restore the max height now.
|
// Restore the max height now.
|
||||||
this.element.classList.remove('collapsible-loading-height');
|
this.element.classList.remove('collapsible-loading-height');
|
||||||
|
@ -229,4 +237,12 @@ export class CoreCollapsibleItemDirective implements OnInit {
|
||||||
this.toggleExpand();
|
this.toggleExpand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.resizeListener?.off();
|
||||||
|
this.domListener?.off();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
Optional,
|
Optional,
|
||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
|
OnDestroy,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { IonContent } from '@ionic/angular';
|
import { IonContent } from '@ionic/angular';
|
||||||
|
|
||||||
|
@ -41,6 +42,7 @@ import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
||||||
import { CoreSubscriptions } from '@singletons/subscriptions';
|
import { CoreSubscriptions } from '@singletons/subscriptions';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
import { CoreCollapsibleItemDirective } from './collapsible-item';
|
import { CoreCollapsibleItemDirective } from './collapsible-item';
|
||||||
|
import { CoreSingleTimeEventObserver } from '@singletons/events';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
|
* Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
|
||||||
|
@ -54,7 +56,7 @@ import { CoreCollapsibleItemDirective } from './collapsible-item';
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: 'core-format-text',
|
selector: 'core-format-text',
|
||||||
})
|
})
|
||||||
export class CoreFormatTextDirective implements OnChanges {
|
export class CoreFormatTextDirective implements OnChanges, OnDestroy {
|
||||||
|
|
||||||
@ViewChild(CoreCollapsibleItemDirective) collapsible?: CoreCollapsibleItemDirective;
|
@ViewChild(CoreCollapsibleItemDirective) collapsible?: CoreCollapsibleItemDirective;
|
||||||
|
|
||||||
|
@ -88,6 +90,7 @@ export class CoreFormatTextDirective implements OnChanges {
|
||||||
protected element: HTMLElement;
|
protected element: HTMLElement;
|
||||||
protected emptyText = '';
|
protected emptyText = '';
|
||||||
protected contentSpan: HTMLElement;
|
protected contentSpan: HTMLElement;
|
||||||
|
protected domListener?: CoreSingleTimeEventObserver;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
element: ElementRef,
|
element: ElementRef,
|
||||||
|
@ -126,6 +129,13 @@ export class CoreFormatTextDirective implements OnChanges {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.domListener?.off();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait until the text is fully rendered.
|
* Wait until the text is fully rendered.
|
||||||
*/
|
*/
|
||||||
|
@ -217,14 +227,14 @@ export class CoreFormatTextDirective implements OnChanges {
|
||||||
/**
|
/**
|
||||||
* Add magnifying glass icons to view adapted images at full size.
|
* Add magnifying glass icons to view adapted images at full size.
|
||||||
*/
|
*/
|
||||||
addMagnifyingGlasses(): void {
|
async addMagnifyingGlasses(): Promise<void> {
|
||||||
const imgs = Array.from(this.contentSpan.querySelectorAll('.core-adapted-img-container > img'));
|
const imgs = Array.from(this.contentSpan.querySelectorAll('.core-adapted-img-container > img'));
|
||||||
if (!imgs.length) {
|
if (!imgs.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If cannot calculate element's width, use viewport width to avoid false adapt image icons appearing.
|
// If cannot calculate element's width, use viewport width to avoid false adapt image icons appearing.
|
||||||
const elWidth = this.getElementWidth(this.element) || window.innerWidth;
|
const elWidth = await this.getElementWidth();
|
||||||
|
|
||||||
imgs.forEach((img: HTMLImageElement) => {
|
imgs.forEach((img: HTMLImageElement) => {
|
||||||
// Skip image if it's inside a link.
|
// Skip image if it's inside a link.
|
||||||
|
@ -543,41 +553,41 @@ export class CoreFormatTextDirective implements OnChanges {
|
||||||
/**
|
/**
|
||||||
* Returns the element width in pixels.
|
* Returns the element width in pixels.
|
||||||
*
|
*
|
||||||
* @param element Element to get width from.
|
* @return The width of the element in pixels.
|
||||||
* @return The width of the element in pixels. When 0 is returned it means the element is not visible.
|
|
||||||
*/
|
*/
|
||||||
protected getElementWidth(element: HTMLElement): number {
|
protected async getElementWidth(): Promise<number> {
|
||||||
let width = CoreDomUtils.getElementWidth(element);
|
this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
|
||||||
|
await this.domListener.promise;
|
||||||
|
|
||||||
|
let width = this.element.getBoundingClientRect().width;
|
||||||
if (!width) {
|
if (!width) {
|
||||||
// All elements inside are floating or inline. Change display mode to allow calculate the width.
|
// All elements inside are floating or inline. Change display mode to allow calculate the width.
|
||||||
const parentWidth = element.parentElement ?
|
const previousDisplay = getComputedStyle(this.element).display;
|
||||||
CoreDomUtils.getElementWidth(element.parentElement, true, false, false, true) : 0;
|
|
||||||
const previousDisplay = getComputedStyle(element, null).display;
|
|
||||||
|
|
||||||
element.style.display = 'inline-block';
|
this.element.style.display = 'inline-block';
|
||||||
|
await CoreUtils.nextTick();
|
||||||
|
|
||||||
width = CoreDomUtils.getElementWidth(element);
|
width = this.element.getBoundingClientRect().width;
|
||||||
|
|
||||||
// If width is incorrectly calculated use parent width instead.
|
this.element.style.display = previousDisplay;
|
||||||
if (parentWidth > 0 && (!width || width > parentWidth)) {
|
|
||||||
width = parentWidth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
element.style.display = previousDisplay;
|
// Aproximate using parent elements.
|
||||||
|
let element = this.element;
|
||||||
|
while (!width && element.parentElement) {
|
||||||
|
element = element.parentElement;
|
||||||
|
const computedStyle = getComputedStyle(element);
|
||||||
|
|
||||||
|
const padding = CoreDomUtils.getComputedStyleMeasure(computedStyle, 'paddingLeft') +
|
||||||
|
CoreDomUtils.getComputedStyleMeasure(computedStyle, 'paddingRight');
|
||||||
|
|
||||||
|
// Use parent width as an aproximation.
|
||||||
|
width = element.getBoundingClientRect().width - padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
return width;
|
return width > 0 && width < window.innerWidth
|
||||||
}
|
? width
|
||||||
|
: window.innerWidth;
|
||||||
/**
|
|
||||||
* Returns the element height in pixels.
|
|
||||||
*
|
|
||||||
* @param elementAng Element to get height from.
|
|
||||||
* @return The height of the element in pixels. When 0 is returned it means the element is not visible.
|
|
||||||
*/
|
|
||||||
protected getElementHeight(element: HTMLElement): number {
|
|
||||||
return CoreDomUtils.getElementHeight(element) || 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -701,10 +711,12 @@ export class CoreFormatTextDirective implements OnChanges {
|
||||||
let width: string | number;
|
let width: string | number;
|
||||||
let height: string | number;
|
let height: string | number;
|
||||||
|
|
||||||
|
await CoreDomUtils.waitToBeInDOM(iframe, 5000).promise;
|
||||||
|
|
||||||
if (iframe.width) {
|
if (iframe.width) {
|
||||||
width = iframe.width;
|
width = iframe.width;
|
||||||
} else {
|
} else {
|
||||||
width = this.getElementWidth(iframe);
|
width = iframe.getBoundingClientRect().width;
|
||||||
if (!width) {
|
if (!width) {
|
||||||
width = window.innerWidth;
|
width = window.innerWidth;
|
||||||
}
|
}
|
||||||
|
@ -713,7 +725,7 @@ export class CoreFormatTextDirective implements OnChanges {
|
||||||
if (iframe.height) {
|
if (iframe.height) {
|
||||||
height = iframe.height;
|
height = iframe.height;
|
||||||
} else {
|
} else {
|
||||||
height = this.getElementHeight(iframe);
|
height = iframe.getBoundingClientRect().height;
|
||||||
if (!height) {
|
if (!height) {
|
||||||
height = width;
|
height = width;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
|
import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreSingleTimeEventObserver } from '@singletons/events';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive to listen when an element becomes visible.
|
* Directive to listen when an element becomes visible.
|
||||||
|
@ -26,7 +27,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy {
|
||||||
@Output() onAppear = new EventEmitter();
|
@Output() onAppear = new EventEmitter();
|
||||||
|
|
||||||
private element: HTMLElement;
|
private element: HTMLElement;
|
||||||
private interval?: number;
|
protected domListener?: CoreSingleTimeEventObserver;
|
||||||
|
|
||||||
constructor(element: ElementRef) {
|
constructor(element: ElementRef) {
|
||||||
this.element = element.nativeElement;
|
this.element = element.nativeElement;
|
||||||
|
@ -35,24 +36,18 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy {
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
async ngOnInit(): Promise<void> {
|
||||||
this.interval = window.setInterval(() => {
|
this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
|
||||||
if (!CoreDomUtils.isElementVisible(this.element)) {
|
await this.domListener.promise;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onAppear.emit();
|
this.onAppear.emit();
|
||||||
window.clearInterval(this.interval);
|
|
||||||
|
|
||||||
delete this.interval;
|
|
||||||
}, 50);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.interval && window.clearInterval(this.interval);
|
this.domListener?.off();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
height: 40vh;
|
height: var(--core-rte-height, auto);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 200px; /* Just in case vh is not supported */
|
min-height: 200px; /* Just in case vh is not supported */
|
||||||
min-height: 40vh;
|
min-height: 40vh;
|
||||||
|
|
|
@ -36,8 +36,10 @@ import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUrlUtils } from '@services/utils/url';
|
import { CoreUrlUtils } from '@services/utils/url';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Platform, Translate } from '@singletons';
|
import { Platform, Translate } from '@singletons';
|
||||||
import { CoreEventFormActionData, CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventFormActionData, CoreEventObserver, CoreEvents, CoreSingleTimeEventObserver } from '@singletons/events';
|
||||||
import { CoreEditorOffline } from '../../services/editor-offline';
|
import { CoreEditorOffline } from '../../services/editor-offline';
|
||||||
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
|
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display a rich text editor if enabled.
|
* Component to display a rich text editor if enabled.
|
||||||
|
@ -101,7 +103,8 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
protected resizeFunction?: () => Promise<number>;
|
protected resizeFunction?: () => Promise<number>;
|
||||||
protected selectionChangeFunction?: () => void;
|
protected selectionChangeFunction?: () => void;
|
||||||
protected languageChangedSubscription?: Subscription;
|
protected languageChangedSubscription?: Subscription;
|
||||||
protected resizeObserver?: IntersectionObserver;
|
protected resizeListener?: CoreEventObserver;
|
||||||
|
protected domListener?: CoreSingleTimeEventObserver;
|
||||||
|
|
||||||
rteEnabled = false;
|
rteEnabled = false;
|
||||||
isPhone = false;
|
isPhone = false;
|
||||||
|
@ -140,14 +143,6 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
this.contentChanged = new EventEmitter<string>();
|
this.contentChanged = new EventEmitter<string>();
|
||||||
this.element = elementRef.nativeElement as HTMLDivElement;
|
this.element = elementRef.nativeElement as HTMLDivElement;
|
||||||
this.pageInstance = 'app_' + Date.now(); // Generate a "unique" ID based on timestamp.
|
this.pageInstance = 'app_' + Date.now(); // Generate a "unique" ID based on timestamp.
|
||||||
|
|
||||||
if ('IntersectionObserver' in window) {
|
|
||||||
this.resizeObserver = new IntersectionObserver((observerEntry: IntersectionObserverEntry[]) => {
|
|
||||||
if (observerEntry[0].boundingClientRect.width > 0) {
|
|
||||||
this.updateToolbarButtons();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,14 +175,9 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
// Use paragraph on enter.
|
// Use paragraph on enter.
|
||||||
document.execCommand('DefaultParagraphSeparator', false, 'p');
|
document.execCommand('DefaultParagraphSeparator', false, 'p');
|
||||||
|
|
||||||
let i = 0;
|
await this.waitLoadingsDone();
|
||||||
this.initHeightInterval = window.setInterval(async () => {
|
|
||||||
const height = await this.maximizeEditorSize();
|
this.maximizeEditorSize();
|
||||||
if (i >= 5 || height != 0) {
|
|
||||||
clearInterval(this.initHeightInterval);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}, 750);
|
|
||||||
|
|
||||||
this.setListeners();
|
this.setListeners();
|
||||||
this.updateToolbarButtons();
|
this.updateToolbarButtons();
|
||||||
|
@ -256,14 +246,11 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.resizeFunction = this.windowResized.bind(this);
|
this.resizeListener = CoreDomUtils.onWindowResize(() => {
|
||||||
window.addEventListener('resize', this.resizeFunction!);
|
this.windowResized();
|
||||||
|
}, 50);
|
||||||
|
|
||||||
// Start observing the target node for configured mutations
|
document.addEventListener('selectionchange', this.selectionChangeFunction = this.updateToolbarStyles.bind(this));
|
||||||
this.resizeObserver?.observe(this.element);
|
|
||||||
|
|
||||||
this.selectionChangeFunction = this.updateToolbarStyles.bind(this);
|
|
||||||
document.addEventListener('selectionchange', this.selectionChangeFunction!);
|
|
||||||
|
|
||||||
this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, () => {
|
this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, () => {
|
||||||
// Opening or closing the keyboard also calls the resize function, but sometimes the resize is called too soon.
|
// Opening or closing the keyboard also calls the resize function, but sometimes the resize is called too soon.
|
||||||
|
@ -281,65 +268,58 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resize editor to maximize the space occupied.
|
* Resize editor to maximize the space occupied.
|
||||||
*
|
|
||||||
* @return Resolved with calculated editor size.
|
|
||||||
*/
|
*/
|
||||||
protected async maximizeEditorSize(): Promise<number> {
|
protected async maximizeEditorSize(): Promise<void> {
|
||||||
const contentVisibleHeight = await CoreDomUtils.getContentHeight(this.content);
|
|
||||||
|
|
||||||
if (contentVisibleHeight <= 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
await CoreUtils.wait(100);
|
|
||||||
|
|
||||||
// Editor is ready, adjust Height if needed.
|
// Editor is ready, adjust Height if needed.
|
||||||
const contentHeight = await CoreDomUtils.getContentHeight(this.content);
|
const blankHeight = await this.getBlankHeightInContent();
|
||||||
const height = contentHeight - this.getSurroundingHeight(this.element);
|
const newHeight = blankHeight + this.element.getBoundingClientRect().height;
|
||||||
|
|
||||||
if (height > this.minHeight) {
|
if (newHeight > this.minHeight) {
|
||||||
this.element.style.height = CoreDomUtils.formatPixelsSize(height - 1);
|
this.element.style.setProperty('--core-rte-height', (newHeight - 1) + 'px');
|
||||||
} else {
|
} else {
|
||||||
this.element.style.height = '';
|
this.element.style.removeProperty('--core-rte-height');
|
||||||
}
|
}
|
||||||
|
|
||||||
return height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the height of the surrounding elements from the current to the top element.
|
* Wait until all <core-loading> children inside the page.
|
||||||
*
|
*
|
||||||
* @param element Directive DOM element to get surroundings elements from.
|
* @return Promise resolved when loadings are done.
|
||||||
* @return Surrounding height in px.
|
|
||||||
*/
|
*/
|
||||||
protected getSurroundingHeight(element: HTMLElement): number {
|
protected async waitLoadingsDone(): Promise<void> {
|
||||||
let height = 0;
|
this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
|
||||||
|
await this.domListener.promise;
|
||||||
|
|
||||||
while (element.parentElement?.tagName != 'ION-CONTENT') {
|
const page = this.element.closest('.ion-page');
|
||||||
const parent = element.parentElement!;
|
|
||||||
if (element.tagName && element.tagName != 'CORE-LOADING') {
|
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreLoadingComponent>(page, 'core-loading', 'whenLoaded');
|
||||||
for (let x = 0; x < parent.children.length; x++) {
|
|
||||||
const child = <HTMLElement> parent.children[x];
|
|
||||||
if (child.tagName && child != element) {
|
|
||||||
height += CoreDomUtils.getElementHeight(child, false, true, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
element = parent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const computedStyle = getComputedStyle(element);
|
/**
|
||||||
height += CoreDomUtils.getComputedStyleMeasure(computedStyle, 'paddingTop') +
|
* Get the height of the space in blank at the end of the page.
|
||||||
CoreDomUtils.getComputedStyleMeasure(computedStyle, 'paddingBottom');
|
*
|
||||||
|
* @return Blank height in px. Will be negative if no blank space.
|
||||||
|
*/
|
||||||
|
protected async getBlankHeightInContent(): Promise<number> {
|
||||||
|
await CoreUtils.nextTicks(5); // Ensure content is completely loaded in the DOM.
|
||||||
|
|
||||||
if (element.parentElement?.tagName == 'ION-CONTENT') {
|
let content: Element | null = this.element.closest('ion-content');
|
||||||
const cs2 = getComputedStyle(element);
|
const contentHeight = await CoreDomUtils.getContentHeight(this.content);
|
||||||
|
|
||||||
height -= CoreDomUtils.getComputedStyleMeasure(cs2, 'paddingTop') +
|
// Get first children with content, not fixed.
|
||||||
CoreDomUtils.getComputedStyleMeasure(cs2, 'paddingBottom');
|
let scrollContentHeight = 0;
|
||||||
|
while (scrollContentHeight == 0 && content?.children) {
|
||||||
|
const children = Array.from(content.children)
|
||||||
|
.filter((element) => element.slot !== 'fixed' && !element.classList.contains('core-loading-container'));
|
||||||
|
|
||||||
|
scrollContentHeight = children
|
||||||
|
.map((element) => element.getBoundingClientRect().height)
|
||||||
|
.reduce((a,b) => a + b, 0);
|
||||||
|
|
||||||
|
content = children[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return height;
|
return contentHeight - scrollContentHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -625,7 +605,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
this.editorElement.innerHTML = '<p></p>';
|
this.editorElement.innerHTML = '<p></p>';
|
||||||
this.textarea.value = '';
|
this.textarea.value = '';
|
||||||
} else {
|
} else {
|
||||||
this.editorElement.innerHTML = value!;
|
this.editorElement.innerHTML = value || '';
|
||||||
this.textarea.value = value;
|
this.textarea.value = value;
|
||||||
this.treatExternalContent();
|
this.treatExternalContent();
|
||||||
}
|
}
|
||||||
|
@ -759,10 +739,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
* Show the toolbar.
|
* Show the toolbar.
|
||||||
*/
|
*/
|
||||||
showToolbar(event: Event): void {
|
showToolbar(event: Event): void {
|
||||||
if (!('IntersectionObserver' in window)) {
|
|
||||||
// Fallback if IntersectionObserver is not supported.
|
|
||||||
this.updateToolbarButtons();
|
this.updateToolbarButtons();
|
||||||
}
|
|
||||||
|
|
||||||
this.element.classList.add('ion-touched');
|
this.element.classList.add('ion-touched');
|
||||||
this.element.classList.remove('ion-untouched');
|
this.element.classList.remove('ion-untouched');
|
||||||
|
@ -851,14 +828,9 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
|
|
||||||
const length = await this.toolbarSlides.length();
|
const length = await this.toolbarSlides.length();
|
||||||
|
|
||||||
const width = CoreDomUtils.getElementWidth(this.toolbar.nativeElement);
|
await CoreDomUtils.waitToBeInDOM(this.toolbar.nativeElement, 5000).promise;
|
||||||
|
|
||||||
if (!width) {
|
const width = this.toolbar.nativeElement.getBoundingClientRect().width;
|
||||||
// Width is not available yet, try later.
|
|
||||||
setTimeout(this.updateToolbarButtons.bind(this), 100);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length > 0 && width > length * this.toolbarButtonWidth) {
|
if (length > 0 && width > length * this.toolbarButtonWidth) {
|
||||||
this.slidesOpts = { ...this.slidesOpts, slidesPerView: length };
|
this.slidesOpts = { ...this.slidesOpts, slidesPerView: length };
|
||||||
|
@ -1102,20 +1074,20 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being destroyed.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.valueChangeSubscription?.unsubscribe();
|
this.valueChangeSubscription?.unsubscribe();
|
||||||
this.languageChangedSubscription?.unsubscribe();
|
this.languageChangedSubscription?.unsubscribe();
|
||||||
window.removeEventListener('resize', this.resizeFunction!);
|
this.selectionChangeFunction && document.removeEventListener('selectionchange', this.selectionChangeFunction);
|
||||||
document.removeEventListener('selectionchange', this.selectionChangeFunction!);
|
|
||||||
clearInterval(this.initHeightInterval);
|
clearInterval(this.initHeightInterval);
|
||||||
clearInterval(this.autoSaveInterval);
|
clearInterval(this.autoSaveInterval);
|
||||||
clearTimeout(this.hideMessageTimeout);
|
clearTimeout(this.hideMessageTimeout);
|
||||||
this.resizeObserver?.disconnect();
|
|
||||||
this.resetObserver?.off();
|
this.resetObserver?.off();
|
||||||
this.keyboardObserver?.off();
|
this.keyboardObserver?.off();
|
||||||
this.labelObserver?.disconnect();
|
this.labelObserver?.disconnect();
|
||||||
|
this.resizeListener?.off();
|
||||||
|
this.domListener?.off();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,9 @@ export class NetworkMock extends Network {
|
||||||
|
|
||||||
type!: string;
|
type!: string;
|
||||||
|
|
||||||
|
protected connectObservable = new Subject<'connected'>();
|
||||||
|
protected disconnectObservable = new Subject<'disconnected'>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -38,6 +41,14 @@ export class NetworkMock extends Network {
|
||||||
CELL: 'cellular', // eslint-disable-line @typescript-eslint/naming-convention
|
CELL: 'cellular', // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
NONE: 'none', // eslint-disable-line @typescript-eslint/naming-convention
|
NONE: 'none', // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
this.connectObservable.next('connected');
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
window.addEventListener('offline', () => {
|
||||||
|
this.disconnectObservable.next('disconnected');
|
||||||
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,8 +56,8 @@ export class NetworkMock extends Network {
|
||||||
*
|
*
|
||||||
* @return Observable.
|
* @return Observable.
|
||||||
*/
|
*/
|
||||||
onchange(): Observable<unknown> {
|
onChange(): Observable<'connected' | 'disconnected'> {
|
||||||
return merge(this.onConnect(), this.onDisconnect());
|
return merge(this.connectObservable, this.disconnectObservable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,14 +65,8 @@ export class NetworkMock extends Network {
|
||||||
*
|
*
|
||||||
* @return Observable.
|
* @return Observable.
|
||||||
*/
|
*/
|
||||||
onConnect(): Observable<unknown> {
|
onConnect(): Observable<'connected'> {
|
||||||
const observable = new Subject<unknown>();
|
return this.connectObservable;
|
||||||
|
|
||||||
window.addEventListener('online', (ev) => {
|
|
||||||
observable.next(ev);
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
return observable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,14 +74,8 @@ export class NetworkMock extends Network {
|
||||||
*
|
*
|
||||||
* @return Observable.
|
* @return Observable.
|
||||||
*/
|
*/
|
||||||
onDisconnect(): Observable<unknown> {
|
onDisconnect(): Observable<'disconnected'> {
|
||||||
const observable = new Subject<unknown>();
|
return this.disconnectObservable;
|
||||||
|
|
||||||
window.addEventListener('offline', (ev) => {
|
|
||||||
observable.next(ev);
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
return observable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,11 +168,11 @@ ion-tabs.placement-side {
|
||||||
opacity: .8;
|
opacity: .8;
|
||||||
z-index: 12;
|
z-index: 12;
|
||||||
|
|
||||||
.core-online {
|
.core-online-message {
|
||||||
display: var(--network-message-online);
|
display: var(--network-message-online);
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-offline {
|
.core-offline-message {
|
||||||
display: var(--network-message-offline);
|
display: var(--network-message-offline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { filter } from 'rxjs/operators';
|
||||||
import { NavigationEnd } from '@angular/router';
|
import { NavigationEnd } from '@angular/router';
|
||||||
import { trigger, state, style, transition, animate } from '@angular/animations';
|
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the main menu of the app.
|
* Page that displays the main menu of the app.
|
||||||
|
@ -73,7 +74,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
||||||
protected navSubscription?: Subscription;
|
protected navSubscription?: Subscription;
|
||||||
protected keyboardObserver?: CoreEventObserver;
|
protected keyboardObserver?: CoreEventObserver;
|
||||||
protected badgeUpdateObserver?: CoreEventObserver;
|
protected badgeUpdateObserver?: CoreEventObserver;
|
||||||
protected resizeFunction: () => void;
|
protected resizeListener?: CoreEventObserver;
|
||||||
protected backButtonFunction: (event: BackButtonEvent) => void;
|
protected backButtonFunction: (event: BackButtonEvent) => void;
|
||||||
protected selectHistory: string[] = [];
|
protected selectHistory: string[] = [];
|
||||||
protected firstSelectedTab?: string;
|
protected firstSelectedTab?: string;
|
||||||
|
@ -86,7 +87,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
||||||
tabAction: CoreMainMenuRoleTab;
|
tabAction: CoreMainMenuRoleTab;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.resizeFunction = this.initHandlers.bind(this);
|
|
||||||
this.backButtonFunction = this.backButtonClicked.bind(this);
|
this.backButtonFunction = this.backButtonClicked.bind(this);
|
||||||
this.tabAction = new CoreMainMenuRoleTab(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);
|
document.addEventListener('ionBackButton', this.backButtonFunction);
|
||||||
|
|
||||||
if (CoreApp.isIOS()) {
|
if (CoreApp.isIOS()) {
|
||||||
|
@ -221,10 +223,10 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.subscription?.unsubscribe();
|
this.subscription?.unsubscribe();
|
||||||
this.navSubscription?.unsubscribe();
|
this.navSubscription?.unsubscribe();
|
||||||
window.removeEventListener('resize', this.resizeFunction);
|
|
||||||
document.removeEventListener('ionBackButton', this.backButtonFunction);
|
document.removeEventListener('ionBackButton', this.backButtonFunction);
|
||||||
this.keyboardObserver?.off();
|
this.keyboardObserver?.off();
|
||||||
this.badgeUpdateObserver?.off();
|
this.badgeUpdateObserver?.off();
|
||||||
|
this.resizeListener?.off();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { CoreContentLinksHelper } from '@features/contentlinks/services/contentl
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager';
|
import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the more page of the app.
|
* Page that displays the more page of the app.
|
||||||
|
@ -46,6 +47,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
|
||||||
protected subscription!: Subscription;
|
protected subscription!: Subscription;
|
||||||
protected langObserver: CoreEventObserver;
|
protected langObserver: CoreEventObserver;
|
||||||
protected updateSiteObserver: CoreEventObserver;
|
protected updateSiteObserver: CoreEventObserver;
|
||||||
|
protected resizeListener?: CoreEventObserver;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.langObserver = CoreEvents.on(CoreEvents.LANGUAGE_CHANGED, this.loadCustomMenuItems.bind(this));
|
this.langObserver = CoreEvents.on(CoreEvents.LANGUAGE_CHANGED, this.loadCustomMenuItems.bind(this));
|
||||||
|
@ -71,7 +73,9 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
|
||||||
this.initHandlers();
|
this.initHandlers();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('resize', this.initHandlers.bind(this));
|
this.resizeListener = CoreDomUtils.onWindowResize(() => {
|
||||||
|
this.initHandlers();
|
||||||
|
});
|
||||||
|
|
||||||
const deepLinkManager = new CoreMainMenuDeepLinkManager();
|
const deepLinkManager = new CoreMainMenuDeepLinkManager();
|
||||||
deepLinkManager.treatLink();
|
deepLinkManager.treatLink();
|
||||||
|
@ -81,10 +85,10 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
window.removeEventListener('resize', this.initHandlers.bind(this));
|
|
||||||
this.langObserver?.off();
|
this.langObserver?.off();
|
||||||
this.updateSiteObserver?.off();
|
this.updateSiteObserver?.off();
|
||||||
this.subscription?.unsubscribe();
|
this.subscription?.unsubscribe();
|
||||||
|
this.resizeListener?.off();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from './mainmenu-d
|
||||||
import { Device, makeSingleton } from '@singletons';
|
import { Device, makeSingleton } from '@singletons';
|
||||||
import { CoreArray } from '@singletons/array';
|
import { CoreArray } from '@singletons/array';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
import { CoreScreen } from '@services/screen';
|
||||||
|
|
||||||
declare module '@singletons/events' {
|
declare module '@singletons/events' {
|
||||||
|
|
||||||
|
@ -47,12 +48,6 @@ export class CoreMainMenuProvider {
|
||||||
static readonly MORE_PAGE_NAME = 'more';
|
static readonly MORE_PAGE_NAME = 'more';
|
||||||
static readonly MAIN_MENU_HANDLER_BADGE_UPDATED = 'main_menu_handler_badge_updated';
|
static readonly MAIN_MENU_HANDLER_BADGE_UPDATED = 'main_menu_handler_badge_updated';
|
||||||
|
|
||||||
protected tablet = false;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.tablet = !!(window?.innerWidth && window.innerWidth >= 576 && window.innerHeight >= 576);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current main menu handlers.
|
* Get the current main menu handlers.
|
||||||
*
|
*
|
||||||
|
@ -227,7 +222,7 @@ export class CoreMainMenuProvider {
|
||||||
if (!this.isResponsiveMainMenuItemsDisabledInCurrentSite() && window && window.innerWidth) {
|
if (!this.isResponsiveMainMenuItemsDisabledInCurrentSite() && window && window.innerWidth) {
|
||||||
let numElements: number;
|
let numElements: number;
|
||||||
|
|
||||||
if (this.tablet) {
|
if (CoreScreen.isTablet) {
|
||||||
// Tablet, menu will be displayed vertically.
|
// Tablet, menu will be displayed vertically.
|
||||||
numElements = Math.floor(window.innerHeight / CoreMainMenuProvider.ITEM_MIN_WIDTH);
|
numElements = Math.floor(window.innerHeight / CoreMainMenuProvider.ITEM_MIN_WIDTH);
|
||||||
} else {
|
} else {
|
||||||
|
@ -250,20 +245,13 @@ export class CoreMainMenuProvider {
|
||||||
* @return Tabs placement including side value.
|
* @return Tabs placement including side value.
|
||||||
*/
|
*/
|
||||||
getTabPlacement(): 'bottom' | 'side' {
|
getTabPlacement(): 'bottom' | 'side' {
|
||||||
const tablet = !!(window.innerWidth && window.innerWidth >= 576 && (window.innerHeight >= 576 ||
|
return CoreScreen.isTablet ? 'side' : 'bottom';
|
||||||
((CoreApp.isKeyboardVisible() || CoreApp.isKeyboardOpening()) && window.innerHeight >= 200)));
|
|
||||||
|
|
||||||
if (tablet != this.tablet) {
|
|
||||||
this.tablet = tablet;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tablet ? 'side' : 'bottom';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a certain page is the root of a main menu tab.
|
* Check if a certain page is the root of a main menu tab.
|
||||||
*
|
*
|
||||||
* @param page Name of the page.
|
* @param pageName Name of the page.
|
||||||
* @return Promise resolved with boolean: whether it's the root of a main menu tab.
|
* @return Promise resolved with boolean: whether it's the root of a main menu tab.
|
||||||
*/
|
*/
|
||||||
async isMainMenuTab(pageName: string): Promise<boolean> {
|
async isMainMenuTab(pageName: string): Promise<boolean> {
|
||||||
|
@ -277,7 +265,7 @@ export class CoreMainMenuProvider {
|
||||||
/**
|
/**
|
||||||
* Check if a certain page is the root of a main menu handler currently displayed.
|
* Check if a certain page is the root of a main menu handler currently displayed.
|
||||||
*
|
*
|
||||||
* @param page Name of the page.
|
* @param pageName Name of the page.
|
||||||
* @return Promise resolved with boolean: whether it's the root of a main menu handler.
|
* @return Promise resolved with boolean: whether it's the root of a main menu handler.
|
||||||
*/
|
*/
|
||||||
async isCurrentMainMenuHandler(pageName: string): Promise<boolean> {
|
async isCurrentMainMenuHandler(pageName: string): Promise<boolean> {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
import { makeSingleton, Translate } from '@singletons';
|
import { makeSingleton, Translate } from '@singletons';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
import { Observable, Subject } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object with space usage and cache entries that can be erased.
|
* Object with space usage and cache entries that can be erased.
|
||||||
|
@ -64,6 +65,7 @@ export class CoreSettingsHelperProvider {
|
||||||
protected prefersDark?: MediaQueryList;
|
protected prefersDark?: MediaQueryList;
|
||||||
protected colorSchemes: CoreColorScheme[] = [];
|
protected colorSchemes: CoreColorScheme[] = [];
|
||||||
protected currentColorScheme = CoreColorScheme.LIGHT;
|
protected currentColorScheme = CoreColorScheme.LIGHT;
|
||||||
|
protected darkModeObservable = new Subject<boolean>();
|
||||||
|
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
this.prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
|
this.prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
@ -93,7 +95,9 @@ export class CoreSettingsHelperProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for changes to the prefers-color-scheme media query.
|
// Listen for changes to the prefers-color-scheme media query.
|
||||||
this.prefersDark.addEventListener && this.prefersDark.addEventListener('change', this.toggleDarkModeListener.bind(this));
|
this.prefersDark.addEventListener && this.prefersDark.addEventListener('change', () => {
|
||||||
|
this.setColorScheme(this.currentColorScheme);
|
||||||
|
});
|
||||||
|
|
||||||
// Init zoom level.
|
// Init zoom level.
|
||||||
await this.upgradeZoomLevel();
|
await this.upgradeZoomLevel();
|
||||||
|
@ -444,23 +448,29 @@ export class CoreSettingsHelperProvider {
|
||||||
return window.matchMedia('(prefers-color-scheme)').media !== 'not all';
|
return window.matchMedia('(prefers-color-scheme)').media !== 'not all';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener function to toggle dark mode.
|
|
||||||
*/
|
|
||||||
protected toggleDarkModeListener(): void {
|
|
||||||
this.setColorScheme(this.currentColorScheme);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles dark mode based on enabled boolean.
|
* Toggles dark mode based on enabled boolean.
|
||||||
*
|
*
|
||||||
* @param enable True to enable dark mode, false to disable.
|
* @param enable True to enable dark mode, false to disable.
|
||||||
*/
|
*/
|
||||||
protected toggleDarkMode(enable: boolean = false): void {
|
protected toggleDarkMode(enable: boolean = false): void {
|
||||||
|
const isDark = document.body.classList.contains('dark');
|
||||||
|
if (isDark !== enable) {
|
||||||
document.body.classList.toggle('dark', enable);
|
document.body.classList.toggle('dark', enable);
|
||||||
|
this.darkModeObservable.next(enable);
|
||||||
|
|
||||||
CoreApp.setStatusBarColor();
|
CoreApp.setStatusBarColor();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns dark mode change observable.
|
||||||
|
*
|
||||||
|
* @return Dark mode change observable.
|
||||||
|
*/
|
||||||
|
onDarkModeChange(): Observable<boolean> {
|
||||||
|
return this.darkModeObservable;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,5 +22,5 @@ export default async function(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
await Platform.ready();
|
await Platform.ready();
|
||||||
await CoreIframeUtils.injectiOSScripts(window);
|
CoreIframeUtils.injectiOSScripts(window);
|
||||||
}
|
}
|
||||||
|
|
|
@ -437,7 +437,7 @@ export class CoreAppProvider {
|
||||||
/**
|
/**
|
||||||
* Set keyboard shown or hidden.
|
* Set keyboard shown or hidden.
|
||||||
*
|
*
|
||||||
* @param Whether the keyboard is shown or hidden.
|
* @param shown Whether the keyboard is shown or hidden.
|
||||||
*/
|
*/
|
||||||
protected setKeyboardShown(shown: boolean): void {
|
protected setKeyboardShown(shown: boolean): void {
|
||||||
this.isKeyboardShown = shown;
|
this.isKeyboardShown = shown;
|
||||||
|
|
|
@ -53,6 +53,7 @@ import { NavigationStart } from '@angular/router';
|
||||||
import { filter } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
|
import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
||||||
|
@ -95,36 +96,74 @@ export class CoreDomUtilsProvider {
|
||||||
* Wait an element to be in dom of another element.
|
* Wait an element to be in dom of another element.
|
||||||
*
|
*
|
||||||
* @param element Element to wait.
|
* @param element Element to wait.
|
||||||
* @return Promise resolved when added. It will be rejected after a timeout of 5s.
|
* @param timeout If defined, timeout to wait before rejecting the promise.
|
||||||
|
* @return Promise CoreSingleTimeEventObserver with a promise.
|
||||||
*/
|
*/
|
||||||
async waitToBeInDOM(
|
waitToBeInDOM(
|
||||||
element: Element,
|
element: Element,
|
||||||
): Promise<void> {
|
timeout?: number,
|
||||||
|
): CoreSingleTimeEventObserver {
|
||||||
let root = element.getRootNode({ composed: true });
|
let root = element.getRootNode({ composed: true });
|
||||||
|
|
||||||
if (root === document) {
|
if (root === document) {
|
||||||
// Already in DOM.
|
// Already in DOM.
|
||||||
return;
|
return {
|
||||||
|
off: (): void => {
|
||||||
|
// Nothing to do here.
|
||||||
|
},
|
||||||
|
promise: Promise.resolve(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
let observer: MutationObserver | undefined;
|
||||||
// Disconnect observer for performance reasons.
|
let observerTimeout: number | undefined;
|
||||||
const timeout = window.setTimeout(() => {
|
if (timeout) {
|
||||||
reject(new Error('Waiting for DOM timeout reached'));
|
observerTimeout = window.setTimeout(() => {
|
||||||
observer.disconnect();
|
observer?.disconnect();
|
||||||
}, 5000);
|
throw new Error('Waiting for DOM timeout reached');
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
const observer = new MutationObserver(() => {
|
return {
|
||||||
|
off: (): void => {
|
||||||
|
observer?.disconnect();
|
||||||
|
clearTimeout(observerTimeout);
|
||||||
|
},
|
||||||
|
promise: new Promise((resolve) => {
|
||||||
|
observer = new MutationObserver(() => {
|
||||||
root = element.getRootNode({ composed: true });
|
root = element.getRootNode({ composed: true });
|
||||||
if (root === document) {
|
if (root === document) {
|
||||||
observer.disconnect();
|
observer?.disconnect();
|
||||||
clearTimeout(timeout);
|
clearTimeout(observerTimeout);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe(document.body, { subtree: true, childList: true });
|
observer.observe(document.body, { subtree: true, childList: true });
|
||||||
});
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -431,6 +470,7 @@ export class CoreDomUtilsProvider {
|
||||||
* @param useBorder Whether to use borders to calculate the measure.
|
* @param useBorder Whether to use borders to calculate the measure.
|
||||||
* @param innerMeasure If inner measure is needed: padding, margin or borders will be substracted.
|
* @param innerMeasure If inner measure is needed: padding, margin or borders will be substracted.
|
||||||
* @return Height in pixels.
|
* @return Height in pixels.
|
||||||
|
* @deprecated since app 4.0 Use getBoundingClientRect.height instead.
|
||||||
*/
|
*/
|
||||||
getElementHeight(
|
getElementHeight(
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
|
@ -452,6 +492,7 @@ export class CoreDomUtilsProvider {
|
||||||
* @param useBorder Whether to use borders to calculate the measure.
|
* @param useBorder Whether to use borders to calculate the measure.
|
||||||
* @param innerMeasure If inner measure is needed: padding, margin or borders will be substracted.
|
* @param innerMeasure If inner measure is needed: padding, margin or borders will be substracted.
|
||||||
* @return Measure in pixels.
|
* @return Measure in pixels.
|
||||||
|
* @deprecated since app 4.0 Use getBoundingClientRect.height or width instead.
|
||||||
*/
|
*/
|
||||||
getElementMeasure(
|
getElementMeasure(
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
|
@ -524,6 +565,7 @@ export class CoreDomUtilsProvider {
|
||||||
* @param useBorder Whether to use borders to calculate the measure.
|
* @param useBorder Whether to use borders to calculate the measure.
|
||||||
* @param innerMeasure If inner measure is needed: padding, margin or borders will be substracted.
|
* @param innerMeasure If inner measure is needed: padding, margin or borders will be substracted.
|
||||||
* @return Width in pixels.
|
* @return Width in pixels.
|
||||||
|
* @deprecated since app 4.0 Use getBoundingClientRect.width instead.
|
||||||
*/
|
*/
|
||||||
getElementWidth(
|
getElementWidth(
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
|
@ -703,6 +745,7 @@ export class CoreDomUtilsProvider {
|
||||||
*
|
*
|
||||||
* @param findFunction The function used to find the element.
|
* @param findFunction The function used to find the element.
|
||||||
* @return Resolved if found, rejected if too many tries.
|
* @return Resolved if found, rejected if too many tries.
|
||||||
|
* @deprecated since app 4.0 Use waitToBeInDOM instead.
|
||||||
*/
|
*/
|
||||||
waitElementToExist(findFunction: () => HTMLElement | null): Promise<HTMLElement> {
|
waitElementToExist(findFunction: () => HTMLElement | null): Promise<HTMLElement> {
|
||||||
const promiseInterval = CoreUtils.promiseDefer<HTMLElement>();
|
const promiseInterval = CoreUtils.promiseDefer<HTMLElement>();
|
||||||
|
@ -1054,7 +1097,7 @@ export class CoreDomUtilsProvider {
|
||||||
const scrollElement = await content.getScrollElement();
|
const scrollElement = await content.getScrollElement();
|
||||||
|
|
||||||
return scrollElement.clientHeight || 0;
|
return scrollElement.clientHeight || 0;
|
||||||
} catch (error) {
|
} catch {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1070,7 +1113,7 @@ export class CoreDomUtilsProvider {
|
||||||
const scrollElement = await content.getScrollElement();
|
const scrollElement = await content.getScrollElement();
|
||||||
|
|
||||||
return scrollElement.scrollHeight || 0;
|
return scrollElement.scrollHeight || 0;
|
||||||
} catch (error) {
|
} catch {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,21 @@ export interface CoreEventObserver {
|
||||||
off: () => void;
|
off: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observer instance to stop listening to an observer.
|
||||||
|
*/
|
||||||
|
export interface CoreSingleTimeEventObserver {
|
||||||
|
/**
|
||||||
|
* Stop the observer.
|
||||||
|
*/
|
||||||
|
off: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise Resolved when event is done (first time).
|
||||||
|
*/
|
||||||
|
promise: Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event payloads.
|
* Event payloads.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
--collapsible-header-floating-title-width: 0px;
|
--collapsible-header-floating-title-width: 0px;
|
||||||
--collapsible-header-floating-title-x-delta: 0px;
|
--collapsible-header-floating-title-x-delta: 0px;
|
||||||
--collapsible-header-floating-title-width-delta: 0px;
|
--collapsible-header-floating-title-width-delta: 0px;
|
||||||
|
|
||||||
ion-header.core-header-shadow {
|
ion-header.core-header-shadow {
|
||||||
--core-header-shadow: none;
|
--core-header-shadow: none;
|
||||||
}
|
}
|
||||||
|
@ -19,9 +20,8 @@
|
||||||
|
|
||||||
.collapsible-header-floating-title {
|
.collapsible-header-floating-title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--collapsible-header-floating-title-top);
|
top: 0;
|
||||||
left: var(--collapsible-header-floating-title-left);
|
left: 0;
|
||||||
transform: translateX(calc(var(--collapsible-header-floating-title-x-delta) * var(--collapsible-header-progress)));
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +52,9 @@
|
||||||
|
|
||||||
.collapsible-header-floating-title {
|
.collapsible-header-floating-title {
|
||||||
opacity: 1;
|
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));
|
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);
|
@include core-transition(width transform, 200ms, linear);
|
||||||
|
|
|
@ -1421,6 +1421,22 @@ ion-grid.core-no-grid > ion-row {
|
||||||
}
|
}
|
||||||
|
|
||||||
[collapsible-footer] {
|
[collapsible-footer] {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
height: var(--core-collapsible-footer-height, auto);
|
||||||
|
@include core-transition(all, 200ms);
|
||||||
|
|
||||||
&.footer-collapsed {
|
&.footer-collapsed {
|
||||||
--core-collapsible-footer-height: 0;
|
--core-collapsible-footer-height: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -1439,19 +1455,8 @@ ion-grid.core-no-grid > ion-row {
|
||||||
--core-collapsible-footer-height: auto;
|
--core-collapsible-footer-height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-iframe-fullscreen [collapsible-footer] {
|
.core-iframe-fullscreen [collapsible-footer] {
|
||||||
|
|
|
@ -329,7 +329,6 @@
|
||||||
--core-courseimage-on-course-size: 72px;
|
--core-courseimage-on-course-size: 72px;
|
||||||
--core-courseimage-radius: var(--medium-radius);
|
--core-courseimage-radius: var(--medium-radius);
|
||||||
|
|
||||||
--core-collapsible-footer-height: 48px;
|
|
||||||
--core-navigation-background: var(--contrast-background);
|
--core-navigation-background: var(--contrast-background);
|
||||||
|
|
||||||
--core-collapsible-footer-background: var(--contrast-background);
|
--core-collapsible-footer-background: var(--contrast-background);
|
||||||
|
|
Loading…
Reference in New Issue