commit
ef296a70d1
|
@ -45,6 +45,7 @@ import { CoreIonLoadingElement } from '@classes/ion-loading';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { AddonMessagesConversationInfoComponent } from '../../components/conversation-info/conversation-info';
|
import { AddonMessagesConversationInfoComponent } from '../../components/conversation-info/conversation-info';
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a message discussion page.
|
* Page that displays a message discussion page.
|
||||||
|
@ -1109,7 +1110,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
|
||||||
if (this.newMessages > 0) {
|
if (this.newMessages > 0) {
|
||||||
const messages = Array.from(this.hostElement.querySelectorAll<HTMLElement>('.addon-message-not-mine'));
|
const messages = Array.from(this.hostElement.querySelectorAll<HTMLElement>('.addon-message-not-mine'));
|
||||||
|
|
||||||
CoreDomUtils.scrollViewToElement(messages[messages.length - this.newMessages]);
|
CoreDom.scrollToElement(messages[messages.length - this.newMessages]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ import {
|
||||||
AddonModDataEntryWSField,
|
AddonModDataEntryWSField,
|
||||||
} from '../../services/data';
|
} from '../../services/data';
|
||||||
import { AddonModDataHelper } from '../../services/data-helper';
|
import { AddonModDataHelper } from '../../services/data-helper';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the view edit page.
|
* Page that displays the view edit page.
|
||||||
|
@ -448,7 +449,7 @@ export class AddonModDataEditPage implements OnInit {
|
||||||
* Scroll to first error or to the top if not found.
|
* Scroll to first error or to the top if not found.
|
||||||
*/
|
*/
|
||||||
protected async scrollToFirstError(): Promise<void> {
|
protected async scrollToFirstError(): Promise<void> {
|
||||||
const scrolled = await CoreDomUtils.scrollViewToElement(this.formElement.nativeElement, '.addon-data-error');
|
const scrolled = await CoreDom.scrollToElement(this.formElement.nativeElement, '.addon-data-error');
|
||||||
if (!scrolled) {
|
if (!scrolled) {
|
||||||
this.content?.scrollToTop();
|
this.content?.scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ import { CoreRatingInfo } from '@features/rating/services/rating';
|
||||||
import { CoreForms } from '@singletons/form';
|
import { CoreForms } from '@singletons/form';
|
||||||
import { CoreFileEntry } from '@services/file-helper';
|
import { CoreFileEntry } from '@services/file-helper';
|
||||||
import { AddonModForumSharedPostFormData } from '../../pages/discussion/discussion.page';
|
import { AddonModForumSharedPostFormData } from '../../pages/discussion/discussion.page';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.).
|
* Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.).
|
||||||
|
@ -540,7 +541,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async scrollToForm(): Promise<void> {
|
protected async scrollToForm(): Promise<void> {
|
||||||
await CoreDomUtils.scrollViewToElement(
|
await CoreDom.scrollToElement(
|
||||||
this.elementRef.nativeElement,
|
this.elementRef.nativeElement,
|
||||||
'#addon-forum-reply-edit-form-' + this.uniqueId,
|
'#addon-forum-reply-edit-form-' + this.uniqueId,
|
||||||
);
|
);
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Network, NgZone, Translate } from '@singletons';
|
import { Network, NgZone, Translate } from '@singletons';
|
||||||
import { CoreArray } from '@singletons/array';
|
import { CoreArray } from '@singletons/array';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { AddonModForumDiscussionsSource } from '../../classes/forum-discussions-source';
|
import { AddonModForumDiscussionsSource } from '../../classes/forum-discussions-source';
|
||||||
|
@ -187,7 +188,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
|
||||||
const scrollTo = this.postId || this.parent;
|
const scrollTo = this.postId || this.parent;
|
||||||
if (scrollTo) {
|
if (scrollTo) {
|
||||||
// Scroll to the post.
|
// Scroll to the post.
|
||||||
CoreDomUtils.scrollViewToElement(
|
CoreDom.scrollToElement(
|
||||||
this.elementRef.nativeElement,
|
this.elementRef.nativeElement,
|
||||||
'#addon-mod_forum-post-' + scrollTo,
|
'#addon-mod_forum-post-' + scrollTo,
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { CoreForms } from '@singletons/form';
|
||||||
import { ModalController, Translate } from '@singletons';
|
import { ModalController, Translate } from '@singletons';
|
||||||
import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate';
|
import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate';
|
||||||
import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '../../services/quiz';
|
import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '../../services/quiz';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modal that renders the access rules for a quiz.
|
* Modal that renders the access rules for a quiz.
|
||||||
|
@ -115,7 +116,7 @@ export class AddonModQuizPreflightModalComponent implements OnInit {
|
||||||
|
|
||||||
if (!this.preflightForm.valid) {
|
if (!this.preflightForm.valid) {
|
||||||
// Form not valid. Scroll to the first element with errors.
|
// Form not valid. Scroll to the first element with errors.
|
||||||
const hasScrolled = await CoreDomUtils.scrollViewToInputError(
|
const hasScrolled = await CoreDom.scrollToInputError(
|
||||||
this.elementRef.nativeElement,
|
this.elementRef.nativeElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ import { AddonModQuizAttempt, AddonModQuizHelper } from '../../services/quiz-hel
|
||||||
import { AddonModQuizSync } from '../../services/quiz-sync';
|
import { AddonModQuizSync } from '../../services/quiz-sync';
|
||||||
import { CanLeave } from '@guards/can-leave';
|
import { CanLeave } from '@guards/can-leave';
|
||||||
import { CoreForms } from '@singletons/form';
|
import { CoreForms } from '@singletons/form';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that allows attempting a quiz.
|
* Page that allows attempting a quiz.
|
||||||
|
@ -687,7 +688,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
||||||
* @param slot Slot of the question to scroll to.
|
* @param slot Slot of the question to scroll to.
|
||||||
*/
|
*/
|
||||||
protected scrollToQuestion(slot: number): void {
|
protected scrollToQuestion(slot: number): void {
|
||||||
CoreDomUtils.scrollViewToElement(
|
CoreDom.scrollToElement(
|
||||||
this.elementRef.nativeElement,
|
this.elementRef.nativeElement,
|
||||||
'#addon-mod_quiz-question-' + slot,
|
'#addon-mod_quiz-question-' + slot,
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreTimeUtils } from '@services/utils/time';
|
import { CoreTimeUtils } from '@services/utils/time';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
import {
|
import {
|
||||||
AddonModQuizNavigationModalComponent,
|
AddonModQuizNavigationModalComponent,
|
||||||
AddonModQuizNavigationModalReturn,
|
AddonModQuizNavigationModalReturn,
|
||||||
|
@ -247,7 +248,7 @@ export class AddonModQuizReviewPage implements OnInit {
|
||||||
* @param slot Slot of the question to scroll to.
|
* @param slot Slot of the question to scroll to.
|
||||||
*/
|
*/
|
||||||
protected scrollToQuestion(slot: number): void {
|
protected scrollToQuestion(slot: number): void {
|
||||||
CoreDomUtils.scrollViewToElement(
|
CoreDom.scrollToElement(
|
||||||
this.elementRef.nativeElement,
|
this.elementRef.nativeElement,
|
||||||
`#addon-mod_quiz-question-${slot}`,
|
`#addon-mod_quiz-question-${slot}`,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 { CoreDom } from '@singletons/dom';
|
||||||
import { CoreEventObserver } from '@singletons/events';
|
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';
|
||||||
|
@ -83,7 +84,7 @@ export class AddonQtypeDdImageOrTextQuestion {
|
||||||
return bgImgXY;
|
return bgImgXY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const position = CoreDomUtils.getRelativeElementPosition(bgImg, ddArea);
|
const position = CoreDom.getRelativeElementPosition(bgImg, ddArea);
|
||||||
|
|
||||||
// Render the position related to the current image dimensions.
|
// Render the position related to the current image dimensions.
|
||||||
bgImgXY[0] *= this.proportion;
|
bgImgXY[0] *= this.proportion;
|
||||||
|
@ -419,7 +420,7 @@ export class AddonQtypeDdImageOrTextQuestion {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const position = CoreDomUtils.getRelativeElementPosition(drop, ddArea);
|
const position = CoreDom.getRelativeElementPosition(drop, ddArea);
|
||||||
const choice = drag.getAttribute('choice');
|
const choice = drag.getAttribute('choice');
|
||||||
drag.style.left = position.x + 'px';
|
drag.style.left = position.x + 'px';
|
||||||
drag.style.top = position.y + 'px';
|
drag.style.top = position.y + 'px';
|
||||||
|
@ -473,7 +474,7 @@ export class AddonQtypeDdImageOrTextQuestion {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const position = CoreDomUtils.getRelativeElementPosition(dragItemHome, ddArea);
|
const position = CoreDom.getRelativeElementPosition(dragItemHome, ddArea);
|
||||||
drag.style.left = position.x + 'px';
|
drag.style.left = position.x + 'px';
|
||||||
drag.style.top = position.y + 'px';
|
drag.style.top = position.y + 'px';
|
||||||
drag.classList.remove('placed');
|
drag.classList.remove('placed');
|
||||||
|
|
|
@ -12,8 +12,9 @@
|
||||||
// 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 { CoreCoordinates, CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
import { CoreCoordinates, CoreDom } from '@singletons/dom';
|
||||||
import { CoreEventObserver } from '@singletons/events';
|
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';
|
||||||
|
@ -134,7 +135,7 @@ export class AddonQtypeDdMarkerQuestion {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const position = CoreDomUtils.getRelativeElementPosition(element, ddArea);
|
const position = CoreDom.getRelativeElementPosition(element, ddArea);
|
||||||
|
|
||||||
return [position.x, position.y];
|
return [position.x, position.y];
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// 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 { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDom } from '@singletons/dom';
|
||||||
import { AddonQtypeDdMarkerQuestion } from './ddmarker';
|
import { AddonQtypeDdMarkerQuestion } from './ddmarker';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,7 +59,7 @@ export class AddonQtypeDdMarkerGraphicsApi {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const position = CoreDomUtils.getRelativeElementPosition(bgImg, ddArea);
|
const position = CoreDom.getRelativeElementPosition(bgImg, ddArea);
|
||||||
|
|
||||||
dropZones.style.left = position.x + 'px';
|
dropZones.style.left = position.x + 'px';
|
||||||
dropZones.style.top = position.y + 'px';
|
dropZones.style.top = position.y + 'px';
|
||||||
|
|
|
@ -13,10 +13,11 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { CoreFormatTextDirective } from '@directives/format-text';
|
import { CoreFormatTextDirective } from '@directives/format-text';
|
||||||
import { CoreCoordinates, 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 { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
|
import { CoreCoordinates, CoreDom } from '@singletons/dom';
|
||||||
import { CoreEventObserver } from '@singletons/events';
|
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';
|
||||||
|
@ -389,13 +390,13 @@ export class AddonQtypeDdwtosQuestion {
|
||||||
const choiceNo = this.getChoice(drag) ?? -1;
|
const choiceNo = this.getChoice(drag) ?? -1;
|
||||||
const dragHome = this.container.querySelector<HTMLElement>(this.selectors.dragHome(groupNo, choiceNo));
|
const dragHome = this.container.querySelector<HTMLElement>(this.selectors.dragHome(groupNo, choiceNo));
|
||||||
if (dragHome) {
|
if (dragHome) {
|
||||||
position = CoreDomUtils.getRelativeElementPosition(dragHome, parent);
|
position = CoreDom.getRelativeElementPosition(dragHome, parent);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Get the drop zone position.
|
// Get the drop zone position.
|
||||||
const dropZone = this.container.querySelector<HTMLElement>(this.selectors.dropForPlace(placeNo));
|
const dropZone = this.container.querySelector<HTMLElement>(this.selectors.dropForPlace(placeNo));
|
||||||
if (dropZone) {
|
if (dropZone) {
|
||||||
position = CoreDomUtils.getRelativeElementPosition(dropZone, parent);
|
position = CoreDom.getRelativeElementPosition(dropZone, parent);
|
||||||
// Avoid the border.
|
// Avoid the border.
|
||||||
position.x++;
|
position.x++;
|
||||||
position.y++;
|
position.y++;
|
||||||
|
@ -425,13 +426,13 @@ export class AddonQtypeDdwtosQuestion {
|
||||||
* @return Promise resolved when ready in the DOM.
|
* @return Promise resolved when ready in the DOM.
|
||||||
*/
|
*/
|
||||||
protected async waitForReady(): Promise<void> {
|
protected async waitForReady(): Promise<void> {
|
||||||
await CoreDomUtils.waitToBeInDOM(this.container);
|
await CoreDom.waitToBeInDOM(this.container);
|
||||||
|
|
||||||
await CoreComponentsRegistry.waitComponentsReady(this.container, 'core-format-text', CoreFormatTextDirective);
|
await CoreComponentsRegistry.waitComponentsReady(this.container, 'core-format-text', CoreFormatTextDirective);
|
||||||
|
|
||||||
const drag = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.dragHomes()))[0];
|
const drag = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.dragHomes()))[0];
|
||||||
|
|
||||||
await CoreDomUtils.waitToBeInDOM(drag);
|
await CoreDom.waitToBeInDOM(drag);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -480,7 +481,7 @@ export class AddonQtypeDdwtosQuestion {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await CoreDomUtils.waitToBeInDOM(groupItems[0]);
|
await CoreDom.waitToBeInDOM(groupItems[0]);
|
||||||
|
|
||||||
let maxWidth = 0;
|
let maxWidth = 0;
|
||||||
let maxHeight = 0;
|
let maxHeight = 0;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreContextMenuComponent } from '../context-menu/context-menu';
|
import { CoreContextMenuComponent } from '../context-menu/context-menu';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
const BUTTON_HIDDEN_CLASS = 'core-navbar-button-hidden';
|
const BUTTON_HIDDEN_CLASS = 'core-navbar-button-hidden';
|
||||||
|
|
||||||
|
@ -197,7 +198,7 @@ export class CoreNavBarButtonsComponent implements OnInit, OnDestroy {
|
||||||
* @return Promise resolved with the header element.
|
* @return Promise resolved with the header element.
|
||||||
*/
|
*/
|
||||||
protected async searchHeader(): Promise<HTMLIonHeaderElement> {
|
protected async searchHeader(): Promise<HTMLIonHeaderElement> {
|
||||||
await CoreDomUtils.waitToBeInDOM(this.element);
|
await CoreDom.waitToBeInDOM(this.element);
|
||||||
|
|
||||||
let parentPage: HTMLElement | null = this.element;
|
let parentPage: HTMLElement | null = this.element;
|
||||||
while (parentPage && parentPage.parentElement) {
|
while (parentPage && parentPage.parentElement) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { Directive, Input, ElementRef, AfterViewInit } from '@angular/core';
|
||||||
|
|
||||||
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 { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive to auto focus an element when a view is loaded.
|
* Directive to auto focus an element when a view is loaded.
|
||||||
|
@ -46,7 +47,7 @@ export class CoreAutoFocusDirective implements AfterViewInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await CoreDomUtils.waitToBeInDOM(this.element);
|
await CoreDom.waitToBeInDOM(this.element);
|
||||||
|
|
||||||
let focusElement = this.element;
|
let focusElement = this.element;
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { CoreEventObserver } from '@singletons/events';
|
||||||
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 { CoreCancellablePromise } from '@classes/cancellable-promise';
|
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
|
import { CoreDom } from '@singletons/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.
|
||||||
|
@ -62,7 +63,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
// Only if not present or explicitly falsy it will be false.
|
// Only if not present or explicitly falsy it will be false.
|
||||||
this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom);
|
this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom);
|
||||||
this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
this.domPromise = CoreDom.waitToBeInDOM(this.element);
|
||||||
|
|
||||||
await this.domPromise;
|
await this.domPromise;
|
||||||
await this.waitLoadingsDone();
|
await this.waitLoadingsDone();
|
||||||
|
@ -72,7 +73,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
||||||
|
|
||||||
await this.calculateHeight();
|
await this.calculateHeight();
|
||||||
|
|
||||||
CoreDomUtils.onElementSlot(this.element, () => {
|
CoreDom.onElementSlot(this.element, () => {
|
||||||
this.calculateHeight();
|
this.calculateHeight();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ 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 { CoreDom } from '@singletons/dom';
|
||||||
import { CoreEventObserver } from '@singletons/events';
|
import { CoreEventObserver } from '@singletons/events';
|
||||||
import { CoreFormatTextDirective } from './format-text';
|
import { CoreFormatTextDirective } from './format-text';
|
||||||
|
|
||||||
|
@ -100,7 +101,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
|
||||||
* @return Promise resolved when loadings are done.
|
* @return Promise resolved when loadings are done.
|
||||||
*/
|
*/
|
||||||
protected async waitLoadingsDone(): Promise<void> {
|
protected async waitLoadingsDone(): Promise<void> {
|
||||||
this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
this.domPromise = CoreDom.waitToBeInDOM(this.element);
|
||||||
|
|
||||||
await this.domPromise;
|
await this.domPromise;
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
import { Directive, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
import { Directive, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive to move ion-fab components as direct children of the nearest ion-content.
|
* Directive to move ion-fab components as direct children of the nearest ion-content.
|
||||||
|
@ -42,7 +42,7 @@ export class CoreFabDirective implements OnInit, OnDestroy {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
this.domPromise = CoreDom.waitToBeInDOM(this.element);
|
||||||
await this.domPromise;
|
await this.domPromise;
|
||||||
|
|
||||||
this.content = this.element.closest('ion-content');
|
this.content = this.element.closest('ion-content');
|
||||||
|
@ -56,7 +56,7 @@ export class CoreFabDirective implements OnInit, OnDestroy {
|
||||||
|
|
||||||
await this.calculatePlace();
|
await this.calculatePlace();
|
||||||
|
|
||||||
CoreDomUtils.onElementSlot(this.element, () => {
|
CoreDom.onElementSlot(this.element, () => {
|
||||||
this.calculatePlace();
|
this.calculatePlace();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import { CoreCollapsibleItemDirective } from './collapsible-item';
|
||||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
import { AsyncComponent } from '@classes/async-component';
|
import { AsyncComponent } from '@classes/async-component';
|
||||||
import { CoreText } from '@singletons/text';
|
import { CoreText } from '@singletons/text';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -552,7 +553,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
*/
|
*/
|
||||||
protected async getElementWidth(): Promise<number> {
|
protected async getElementWidth(): Promise<number> {
|
||||||
if (!this.domElementPromise) {
|
if (!this.domElementPromise) {
|
||||||
this.domElementPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
this.domElementPromise = CoreDom.waitToBeInDOM(this.element);
|
||||||
}
|
}
|
||||||
await this.domElementPromise;
|
await this.domElementPromise;
|
||||||
|
|
||||||
|
@ -704,7 +705,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
newUrl += `&h=${privacyHash}`;
|
newUrl += `&h=${privacyHash}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domPromise = CoreDomUtils.waitToBeInDOM(iframe);
|
const domPromise = CoreDom.waitToBeInDOM(iframe);
|
||||||
this.domPromises.push(domPromise);
|
this.domPromises.push(domPromise);
|
||||||
|
|
||||||
await domPromise;
|
await domPromise;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { CoreCustomURLSchemes } from '@services/urlschemes';
|
||||||
import { DomSanitizer } from '@singletons';
|
import { DomSanitizer } from '@singletons';
|
||||||
import { CoreFilepool } from '@services/filepool';
|
import { CoreFilepool } from '@services/filepool';
|
||||||
import { CoreUrl } from '@singletons/url';
|
import { CoreUrl } from '@singletons/url';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive to open a link in external browser or in the app.
|
* Directive to open a link in external browser or in the app.
|
||||||
|
@ -144,7 +145,7 @@ export class CoreLinkDirective implements OnInit {
|
||||||
href = href.substring(1);
|
href = href.substring(1);
|
||||||
const container = this.element.closest('ion-content');
|
const container = this.element.closest('ion-content');
|
||||||
if (container) {
|
if (container) {
|
||||||
CoreDomUtils.scrollViewToElement(
|
CoreDom.scrollToElement(
|
||||||
container,
|
container,
|
||||||
`#${href}, [name='${href}']`,
|
`#${href}, [name='${href}']`,
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
|
import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive to listen when an element becomes visible.
|
* Directive to listen when an element becomes visible.
|
||||||
|
@ -37,7 +37,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.visiblePromise = CoreDomUtils.waitToBeInViewport(this.element);
|
this.visiblePromise = CoreDom.waitToBeInViewport(this.element);
|
||||||
|
|
||||||
await this.visiblePromise;
|
await this.visiblePromise;
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,7 @@ import { ADDON_PRIVATEFILES_SERVICES } from '@addons/privatefiles/privatefiles.m
|
||||||
// Import some addon modules that define components, directives and pipes. Only import the important ones.
|
// Import some addon modules that define components, directives and pipes. Only import the important ones.
|
||||||
import { AddonModAssignComponentsModule } from '@addons/mod/assign/components/components.module';
|
import { AddonModAssignComponentsModule } from '@addons/mod/assign/components/components.module';
|
||||||
import { AddonModWorkshopComponentsModule } from '@addons/mod/workshop/components/components.module';
|
import { AddonModWorkshopComponentsModule } from '@addons/mod/workshop/components/components.module';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to provide functionalities regarding compiling dynamic HTML and Javascript.
|
* Service to provide functionalities regarding compiling dynamic HTML and Javascript.
|
||||||
|
@ -341,6 +342,7 @@ export class CoreCompileProvider {
|
||||||
instance['Md5'] = Md5;
|
instance['Md5'] = Md5;
|
||||||
instance['CoreSyncBaseProvider'] = CoreSyncBaseProvider;
|
instance['CoreSyncBaseProvider'] = CoreSyncBaseProvider;
|
||||||
instance['CoreArray'] = CoreArray;
|
instance['CoreArray'] = CoreArray;
|
||||||
|
instance['CoreDom'] = CoreDom;
|
||||||
instance['CoreText'] = CoreText;
|
instance['CoreText'] = CoreText;
|
||||||
instance['CoreUrl'] = CoreUrl;
|
instance['CoreUrl'] = CoreUrl;
|
||||||
instance['CoreWindow'] = CoreWindow;
|
instance['CoreWindow'] = CoreWindow;
|
||||||
|
|
|
@ -48,6 +48,7 @@ import { CoreCourseModuleDelegate } from '@features/course/services/module-deleg
|
||||||
import { CoreCourseViewedModulesDBRecord } from '@features/course/services/database/course';
|
import { CoreCourseViewedModulesDBRecord } from '@features/course/services/database/course';
|
||||||
import { CoreUserTours, CoreUserToursAlignment, CoreUserToursSide } from '@features/usertours/services/user-tours';
|
import { CoreUserTours, CoreUserToursAlignment, CoreUserToursSide } from '@features/usertours/services/user-tours';
|
||||||
import { CoreCourseCourseIndexTourComponent } from '../course-index-tour/course-index-tour';
|
import { CoreCourseCourseIndexTourComponent } from '../course-index-tour/course-index-tour';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display course contents using a certain format. If the format isn't found, use default one.
|
* Component to display course contents using a certain format. If the format isn't found, use default one.
|
||||||
|
@ -511,9 +512,10 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
* @param moduleId Module ID.
|
* @param moduleId Module ID.
|
||||||
*/
|
*/
|
||||||
protected scrollToModule(moduleId: number): void {
|
protected scrollToModule(moduleId: number): void {
|
||||||
CoreDomUtils.scrollViewToElement(
|
CoreDom.scrollToElement(
|
||||||
this.elementRef.nativeElement,
|
this.elementRef.nativeElement,
|
||||||
'#core-course-module-' + moduleId,
|
'#core-course-module-' + moduleId,
|
||||||
|
{ addYAxis: -10 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,8 @@ import {
|
||||||
import { CoreCourseHelper, CoreCourseSection } from '@features/course/services/course-helper';
|
import { CoreCourseHelper, CoreCourseSection } from '@features/course/services/course-helper';
|
||||||
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
||||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
|
||||||
import { ModalController } from '@singletons';
|
import { ModalController } from '@singletons';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display course index modal.
|
* Component to display course index modal.
|
||||||
|
@ -109,9 +109,10 @@ export class CoreCourseCourseIndexComponent implements OnInit {
|
||||||
|
|
||||||
this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course);
|
this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course);
|
||||||
|
|
||||||
CoreDomUtils.scrollViewToElement(
|
CoreDom.scrollToElement(
|
||||||
this.elementRef.nativeElement,
|
this.elementRef.nativeElement,
|
||||||
'.item.item-current',
|
'.item.item-current',
|
||||||
|
{ addYAxis: -10 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,44 +54,9 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-card *ngIf="(canPrefetch && displayOptions.displayPrefetch) || (sizeReadable && displayOptions.displaySize)">
|
|
||||||
<ion-item lines="full" class="ion-text-wrap">
|
|
||||||
<ion-label>
|
|
||||||
<h2>
|
|
||||||
<ion-icon name="fas-cloud-download-alt" aria-hidden="true"></ion-icon>
|
|
||||||
{{ 'addon.storagemanager.downloads' | translate }}
|
|
||||||
</h2>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item *ngIf="sizeReadable && displayOptions.displaySize" class="ion-text-wrap">
|
|
||||||
<ion-label>
|
|
||||||
<p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totalspaceusage' | translate }}</p>
|
|
||||||
<ion-badge color="light">{{ sizeReadable | coreBytesToSize }}</ion-badge>
|
|
||||||
</ion-label>
|
|
||||||
<ion-button *ngIf="!removeFilesLoading" [disabled]="prefetchLoading" (click)="removeFiles()" color="danger" fill="clear"
|
|
||||||
[attr.aria-label]="'core.clearstoreddata' | translate:{$a: sizeReadable}" slot="end">
|
|
||||||
<ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
<ion-spinner *ngIf="removeFilesLoading" slot="end" aria-hidden="true"></ion-spinner>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item *ngIf="downloadTimeReadable" class="ion-text-wrap">
|
|
||||||
<ion-label>
|
|
||||||
<p class="ion-text-wrap">{{ 'core.lastdownloaded' | translate }} {{ downloadTimeReadable }}</p>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<ion-button fill="outline" expand="block" *ngIf="canPrefetch && displayOptions.displayPrefetch" class="ion-text-wrap"
|
|
||||||
(click)="prefetch()" [disabled]="prefetchDisabled">
|
|
||||||
<ion-icon *ngIf="!prefetchLoading" name="fas-cloud-download-alt" slot="start" aria-hidden="true"></ion-icon>
|
|
||||||
<ion-spinner *ngIf="prefetchLoading" slot="start" aria-hidden="true"></ion-spinner>
|
|
||||||
<ion-label>
|
|
||||||
{{ 'core.download' | translate }}
|
|
||||||
</ion-label>
|
|
||||||
</ion-button>
|
|
||||||
</ion-card>
|
|
||||||
|
|
||||||
<ion-card *ngIf="displayOptions.displayGrades && grades?.length > 0">
|
<ion-card *ngIf="displayOptions.displayGrades && grades?.length > 0">
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item lines="full" class="ion-text-wrap">
|
<ion-item lines="full" class="ion-text-wrap card-header">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>
|
<h2>
|
||||||
<ion-icon name="fas-chart-bar" slot="end" aria-hidden="true"></ion-icon>{{ 'core.grades.gradebook' | translate
|
<ion-icon name="fas-chart-bar" slot="end" aria-hidden="true"></ion-icon>{{ 'core.grades.gradebook' | translate
|
||||||
|
@ -188,6 +153,41 @@
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|
||||||
|
<ion-card *ngIf="(canPrefetch && displayOptions.displayPrefetch) || (sizeReadable && displayOptions.displaySize)">
|
||||||
|
<ion-item lines="full" class="ion-text-wrap card-header">
|
||||||
|
<ion-label>
|
||||||
|
<h2>
|
||||||
|
<ion-icon name="fas-cloud-download-alt" aria-hidden="true"></ion-icon>
|
||||||
|
{{ 'addon.storagemanager.downloads' | translate }}
|
||||||
|
</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="sizeReadable && displayOptions.displaySize" class="ion-text-wrap">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totalspaceusage' | translate }}</p>
|
||||||
|
<ion-badge color="light">{{ sizeReadable | coreBytesToSize }}</ion-badge>
|
||||||
|
</ion-label>
|
||||||
|
<ion-button *ngIf="!removeFilesLoading" [disabled]="prefetchLoading" (click)="removeFiles()" color="danger" fill="clear"
|
||||||
|
[attr.aria-label]="'core.clearstoreddata' | translate:{$a: sizeReadable}" slot="end">
|
||||||
|
<ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<ion-spinner *ngIf="removeFilesLoading" slot="end" aria-hidden="true"></ion-spinner>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="downloadTimeReadable" class="ion-text-wrap">
|
||||||
|
<ion-label>
|
||||||
|
<p class="ion-text-wrap">{{ 'core.lastdownloaded' | translate }} {{ downloadTimeReadable }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-button fill="outline" expand="block" *ngIf="canPrefetch && displayOptions.displayPrefetch" class="ion-text-wrap"
|
||||||
|
(click)="prefetch()" [disabled]="prefetchDisabled">
|
||||||
|
<ion-icon *ngIf="!prefetchLoading" name="fas-cloud-download-alt" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-spinner *ngIf="prefetchLoading" slot="start" aria-hidden="true"></ion-spinner>
|
||||||
|
<ion-label>
|
||||||
|
{{ 'core.download' | translate }}
|
||||||
|
</ion-label>
|
||||||
|
</ion-button>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
<ion-item button *ngIf="blog && displayOptions.displayBlog" (click)="gotoBlog()" [detail]="true">
|
<ion-item button *ngIf="blog && displayOptions.displayBlog" (click)="gotoBlog()" [detail]="true">
|
||||||
<ion-icon name="far-newspaper" slot="start" aria-hidden="true"></ion-icon>
|
<ion-icon name="far-newspaper" slot="start" aria-hidden="true"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
|
|
@ -22,3 +22,16 @@ ion-item ion-label ion-icon {
|
||||||
@include margin-horizontal(0, 4px);
|
@include margin-horizontal(0, 4px);
|
||||||
vertical-align: text-top;
|
vertical-align: text-top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ion-item.card-header {
|
||||||
|
--padding-start: 8px;
|
||||||
|
--padding-end: 8px;
|
||||||
|
--inner-padding-start: 0px;
|
||||||
|
--inner-padding-end: 0px;
|
||||||
|
--min-height: 40px;
|
||||||
|
|
||||||
|
ion-label {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
import { CoreLoadingComponent } from '@components/loading/loading';
|
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||||
import { CoreScreen } from '@services/screen';
|
import { CoreScreen } from '@services/screen';
|
||||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display a rich text editor if enabled.
|
* Component to display a rich text editor if enabled.
|
||||||
|
@ -290,7 +291,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
|
||||||
* @return Promise resolved when loadings are done.
|
* @return Promise resolved when loadings are done.
|
||||||
*/
|
*/
|
||||||
protected async waitLoadingsDone(): Promise<void> {
|
protected async waitLoadingsDone(): Promise<void> {
|
||||||
this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
this.domPromise = CoreDom.waitToBeInDOM(this.element);
|
||||||
|
|
||||||
await this.domPromise;
|
await this.domPromise;
|
||||||
|
|
||||||
|
@ -846,7 +847,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
|
||||||
|
|
||||||
// Cancel previous one, if any.
|
// Cancel previous one, if any.
|
||||||
this.buttonsDomPromise?.cancel();
|
this.buttonsDomPromise?.cancel();
|
||||||
this.buttonsDomPromise = CoreDomUtils.waitToBeInDOM(this.toolbar.nativeElement);
|
this.buttonsDomPromise = CoreDom.waitToBeInDOM(this.toolbar.nativeElement);
|
||||||
await this.buttonsDomPromise;
|
await this.buttonsDomPromise;
|
||||||
|
|
||||||
const width = this.toolbar.nativeElement.getBoundingClientRect().width;
|
const width = this.toolbar.nativeElement.getBoundingClientRect().width;
|
||||||
|
|
|
@ -31,6 +31,7 @@ import { Translate } from '@singletons';
|
||||||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||||
import { CoreGradesCoursesSource } from '@features/grades/classes/grades-courses-source';
|
import { CoreGradesCoursesSource } from '@features/grades/classes/grades-courses-source';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a course grades.
|
* Page that displays a course grades.
|
||||||
|
@ -170,7 +171,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
if (row) {
|
if (row) {
|
||||||
this.toggleRow(row, true);
|
this.toggleRow(row, true);
|
||||||
|
|
||||||
CoreDomUtils.scrollViewToElement(
|
CoreDom.scrollToElement(
|
||||||
this.element.nativeElement,
|
this.element.nativeElement,
|
||||||
'#grade-' + row.id,
|
'#grade-' + row.id,
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,6 +35,7 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreForms } from '@singletons/form';
|
import { CoreForms } from '@singletons/form';
|
||||||
import { CoreRecaptchaComponent } from '@components/recaptcha/recaptcha';
|
import { CoreRecaptchaComponent } from '@components/recaptcha/recaptcha';
|
||||||
import { CoreText } from '@singletons/text';
|
import { CoreText } from '@singletons/text';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page to signup using email.
|
* Page to signup using email.
|
||||||
|
@ -284,7 +285,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
||||||
this.changeDetector.detectChanges();
|
this.changeDetector.detectChanges();
|
||||||
|
|
||||||
// Scroll to the first element with errors.
|
// Scroll to the first element with errors.
|
||||||
const errorFound = await CoreDomUtils.scrollViewToInputError(
|
const errorFound = await CoreDom.scrollToInputError(
|
||||||
this.elementRef.nativeElement,
|
this.elementRef.nativeElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { CoreUserTours, CoreUserToursAlignment, CoreUserToursSide } from '@featu
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { AngularFrameworkDelegate } from '@singletons';
|
import { AngularFrameworkDelegate } from '@singletons';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
const ANIMATION_DURATION = 200;
|
const ANIMATION_DURATION = 200;
|
||||||
const USER_TOURS_BACK_BUTTON_PRIORITY = 100;
|
const USER_TOURS_BACK_BUTTON_PRIORITY = 100;
|
||||||
|
@ -86,7 +87,7 @@ export class CoreUserToursUserTourComponent implements AfterViewInit {
|
||||||
await CoreDomUtils.waitForImages(tour);
|
await CoreDomUtils.waitForImages(tour);
|
||||||
|
|
||||||
// Calculate focus styles or dismiss if the element is gone.
|
// Calculate focus styles or dismiss if the element is gone.
|
||||||
if (this.focus && !CoreDomUtils.isElementVisible(this.focus)) {
|
if (this.focus && !CoreDom.isElementVisible(this.focus)) {
|
||||||
await this.dismiss(false);
|
await this.dismiss(false);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -54,7 +54,7 @@ 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 } from '@singletons/events';
|
import { CoreEventObserver } from '@singletons/events';
|
||||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
||||||
|
@ -93,180 +93,6 @@ export class CoreDomUtilsProvider {
|
||||||
this.debugDisplay = debugDisplay != 0;
|
this.debugDisplay = debugDisplay != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait an element to be in dom of another element.
|
|
||||||
*
|
|
||||||
* @param element Element to wait.
|
|
||||||
* @return Cancellable promise.
|
|
||||||
*/
|
|
||||||
waitToBeInDOM(element: HTMLElement): CoreCancellablePromise<void> {
|
|
||||||
const root = element.getRootNode({ composed: true });
|
|
||||||
|
|
||||||
if (root === document) {
|
|
||||||
// Already in DOM.
|
|
||||||
return CoreCancellablePromise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
let observer: MutationObserver;
|
|
||||||
|
|
||||||
return new CoreCancellablePromise<void>(
|
|
||||||
(resolve) => {
|
|
||||||
observer = new MutationObserver(() => {
|
|
||||||
const root = element.getRootNode({ composed: true });
|
|
||||||
|
|
||||||
if (root !== document) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
observer?.disconnect();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, { subtree: true, childList: true });
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
observer?.disconnect();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait an element to be in dom and visible.
|
|
||||||
*
|
|
||||||
* @param element Element to wait.
|
|
||||||
* @return Cancellable promise.
|
|
||||||
*/
|
|
||||||
waitToBeVisible(element: HTMLElement): CoreCancellablePromise<void> {
|
|
||||||
const domPromise = CoreDomUtils.waitToBeInDOM(element);
|
|
||||||
|
|
||||||
let interval: number | undefined;
|
|
||||||
|
|
||||||
// Mutations did not observe for visibility properties.
|
|
||||||
return new CoreCancellablePromise<void>(
|
|
||||||
async (resolve) => {
|
|
||||||
await domPromise;
|
|
||||||
|
|
||||||
if (CoreDomUtils.isElementVisible(element)) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
interval = window.setInterval(() => {
|
|
||||||
if (!CoreDomUtils.isElementVisible(element)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
window.clearInterval(interval);
|
|
||||||
}, 50);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
domPromise.cancel();
|
|
||||||
window.clearInterval(interval);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait an element to be in dom and visible.
|
|
||||||
*
|
|
||||||
* @param element Element to wait.
|
|
||||||
* @param intersectionRatio Intersection ratio (From 0 to 1).
|
|
||||||
* @return Cancellable promise.
|
|
||||||
*/
|
|
||||||
waitToBeInViewport(element: HTMLElement, intersectionRatio = 1): CoreCancellablePromise<void> {
|
|
||||||
const visiblePromise = CoreDomUtils.waitToBeVisible(element);
|
|
||||||
|
|
||||||
let intersectionObserver: IntersectionObserver;
|
|
||||||
let interval: number | undefined;
|
|
||||||
|
|
||||||
return new CoreCancellablePromise<void>(
|
|
||||||
async (resolve) => {
|
|
||||||
await visiblePromise;
|
|
||||||
|
|
||||||
if (CoreDomUtils.isElementInViewport(element, intersectionRatio)) {
|
|
||||||
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('IntersectionObserver' in window) {
|
|
||||||
intersectionObserver = new IntersectionObserver((observerEntries) => {
|
|
||||||
const isIntersecting = observerEntries
|
|
||||||
.some((entry) => entry.isIntersecting && entry.intersectionRatio >= intersectionRatio);
|
|
||||||
if (!isIntersecting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
intersectionObserver?.disconnect();
|
|
||||||
});
|
|
||||||
|
|
||||||
intersectionObserver.observe(element);
|
|
||||||
} else {
|
|
||||||
interval = window.setInterval(() => {
|
|
||||||
if (!CoreDomUtils.isElementInViewport(element, intersectionRatio)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
window.clearInterval(interval);
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
visiblePromise.cancel();
|
|
||||||
intersectionObserver?.disconnect();
|
|
||||||
window.clearInterval(interval);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs a function when an element has been slotted.
|
|
||||||
*
|
|
||||||
* @param element HTML Element inside an ion-content to wait for slot.
|
|
||||||
* @param callback Function to execute on resize.
|
|
||||||
*/
|
|
||||||
onElementSlot(element: HTMLElement, callback: (ev?: Event) => void): void {
|
|
||||||
if (!element.slot) {
|
|
||||||
// Element not declared to be slotted.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const slotName = element.slot;
|
|
||||||
if (element.assignedSlot?.name === slotName) {
|
|
||||||
// Slot already assigned.
|
|
||||||
callback();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = element.closest('ion-content');
|
|
||||||
if (!content || !content.shadowRoot) {
|
|
||||||
// Cannot find content.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const slots = content.shadowRoot.querySelectorAll('slot');
|
|
||||||
const slot = Array.from(slots).find((slot) => slot.name === slotName);
|
|
||||||
|
|
||||||
if (!slot) {
|
|
||||||
// Slot not found.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const slotListener = () => {
|
|
||||||
if (element.assignedSlot?.name !== slotName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
// It would happen only once.
|
|
||||||
slot.removeEventListener('slotchange', slotListener);
|
|
||||||
};
|
|
||||||
|
|
||||||
slot.addEventListener('slotchange', slotListener);;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Window resize is widely checked and may have many performance issues, debouce usage is needed to avoid calling it too much.
|
* 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.
|
* This function helps setting up the debounce feature and remove listener easily.
|
||||||
|
@ -571,11 +397,9 @@ export class CoreDomUtilsProvider {
|
||||||
* @return Selection contents. Undefined if not found.
|
* @return Selection contents. Undefined if not found.
|
||||||
*/
|
*/
|
||||||
getContentsOfElement(element: HTMLElement, selector: string): string | undefined {
|
getContentsOfElement(element: HTMLElement, selector: string): string | undefined {
|
||||||
if (element) {
|
const selected = element.querySelector(selector);
|
||||||
const selected = element.querySelector(selector);
|
if (selected) {
|
||||||
if (selected) {
|
return selected.innerHTML;
|
||||||
return selected.innerHTML;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,7 +548,7 @@ export class CoreDomUtilsProvider {
|
||||||
* @param selector Selector to find the element to gets the position.
|
* @param selector Selector to find the element to gets the position.
|
||||||
* @param positionParentClass Parent Class where to stop calculating the position. Default inner-scroll.
|
* @param positionParentClass Parent Class where to stop calculating the position. Default inner-scroll.
|
||||||
* @return positionLeft, positionTop of the element relative to.
|
* @return positionLeft, positionTop of the element relative to.
|
||||||
* @deprecated since app 4.0. Use getRelativeElementPosition instead.
|
* @deprecated since app 4.0. Use CoreDom.getRelativeElementPosition instead.
|
||||||
*/
|
*/
|
||||||
getElementXY(element: HTMLElement, selector?: string, positionParentClass = 'inner-scroll'): [number, number] | null {
|
getElementXY(element: HTMLElement, selector?: string, positionParentClass = 'inner-scroll'): [number, number] | null {
|
||||||
if (selector) {
|
if (selector) {
|
||||||
|
@ -742,7 +566,7 @@ export class CoreDomUtilsProvider {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const position = CoreDomUtils.getRelativeElementPosition(element, parent);
|
const position = CoreDom.getRelativeElementPosition(element, parent);
|
||||||
|
|
||||||
// Calculate the top and left positions.
|
// Calculate the top and left positions.
|
||||||
return [
|
return [
|
||||||
|
@ -751,25 +575,6 @@ export class CoreDomUtilsProvider {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the position of a element relative to another element.
|
|
||||||
*
|
|
||||||
* @param element Element to get the position.
|
|
||||||
* @param parent Parent element to get relative position.
|
|
||||||
* @return X and Y position.
|
|
||||||
*/
|
|
||||||
getRelativeElementPosition(element: HTMLElement, parent: HTMLElement): CoreCoordinates {
|
|
||||||
// Get the top, left coordinates of two elements
|
|
||||||
const elementRectangle = element.getBoundingClientRect();
|
|
||||||
const parentRectangle = parent.getBoundingClientRect();
|
|
||||||
|
|
||||||
// Calculate the top and left positions.
|
|
||||||
return {
|
|
||||||
x: elementRectangle.x - parentRectangle.x,
|
|
||||||
y: elementRectangle.y - parentRectangle.y,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a message, it deduce if it's a network error.
|
* Given a message, it deduce if it's a network error.
|
||||||
*
|
*
|
||||||
|
@ -889,7 +694,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.
|
* @deprecated since app 4.0 Use CoreDom.waitToBeInsideElement instead.
|
||||||
*/
|
*/
|
||||||
waitElementToExist(findFunction: () => HTMLElement | null): Promise<HTMLElement> {
|
waitElementToExist(findFunction: () => HTMLElement | null): Promise<HTMLElement> {
|
||||||
const promiseInterval = CoreUtils.promiseDefer<HTMLElement>();
|
const promiseInterval = CoreUtils.promiseDefer<HTMLElement>();
|
||||||
|
@ -993,63 +798,6 @@ export class CoreDomUtilsProvider {
|
||||||
return elementPoint > window.innerHeight || elementPoint < scrollTopPos;
|
return elementPoint > window.innerHeight || elementPoint < scrollTopPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether an element has been added to the DOM.
|
|
||||||
*
|
|
||||||
* @param element Element.
|
|
||||||
* @return True if element has been added to the DOM, false otherwise.
|
|
||||||
*/
|
|
||||||
isElementInDom(element: HTMLElement): boolean {
|
|
||||||
return element.getRootNode({ composed: true }) === document;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether an element is visible or not.
|
|
||||||
*
|
|
||||||
* @param element Element.
|
|
||||||
* @return True if element is visible inside the DOM.
|
|
||||||
*/
|
|
||||||
isElementVisible(element: HTMLElement): boolean {
|
|
||||||
if (element.clientWidth === 0 || element.clientHeight === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const style = getComputedStyle(element);
|
|
||||||
if (style.opacity === '0' || style.display === 'none' || style.visibility === 'hidden') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CoreDomUtils.isElementInDom(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether an element is intersecting the intersectionRatio in viewport.
|
|
||||||
*
|
|
||||||
* @param element
|
|
||||||
* @param intersectionRatio Intersection ratio (From 0 to 1).
|
|
||||||
* @return True if in viewport.
|
|
||||||
*/
|
|
||||||
isElementInViewport(element: HTMLElement, intersectionRatio = 1): boolean {
|
|
||||||
const elementRectangle = element.getBoundingClientRect();
|
|
||||||
|
|
||||||
const elementArea = elementRectangle.width * elementRectangle.height;
|
|
||||||
if (elementArea == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const intersectionRectangle = {
|
|
||||||
top: Math.max(0, elementRectangle.top),
|
|
||||||
left: Math.max(0, elementRectangle.left),
|
|
||||||
bottom: Math.min(window.innerHeight, elementRectangle.bottom),
|
|
||||||
right: Math.min(window.innerWidth, elementRectangle.right),
|
|
||||||
};
|
|
||||||
|
|
||||||
const intersectionArea = (intersectionRectangle.right - intersectionRectangle.left) *
|
|
||||||
(intersectionRectangle.bottom - intersectionRectangle.top);
|
|
||||||
|
|
||||||
return intersectionArea / elementArea >= intersectionRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if rich text editor is enabled.
|
* Check if rich text editor is enabled.
|
||||||
*
|
*
|
||||||
|
@ -1251,7 +999,7 @@ export class CoreDomUtilsProvider {
|
||||||
* @return Returns a promise which is resolved when the scroll has completed.
|
* @return Returns a promise which is resolved when the scroll has completed.
|
||||||
* @deprecated since 3.9.5. Use directly the IonContent class.
|
* @deprecated since 3.9.5. Use directly the IonContent class.
|
||||||
*/
|
*/
|
||||||
scrollToBottom(content: IonContent, duration?: number): Promise<void> {
|
scrollToBottom(content: IonContent, duration = 0): Promise<void> {
|
||||||
return content.scrollToBottom(duration);
|
return content.scrollToBottom(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1315,54 +1063,6 @@ export class CoreDomUtilsProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Scroll to a certain element.
|
|
||||||
*
|
|
||||||
* @param element The element to scroll to.
|
|
||||||
* @param selector Selector to find the element to scroll to inside the defined element.
|
|
||||||
* @param duration Duration of the scroll animation in milliseconds.
|
|
||||||
* @return Wether the scroll suceeded.
|
|
||||||
*/
|
|
||||||
async scrollViewToElement(element: HTMLElement, selector?: string, duration = 0): Promise<boolean> {
|
|
||||||
await CoreDomUtils.waitToBeInDOM(element);
|
|
||||||
|
|
||||||
if (selector) {
|
|
||||||
const foundElement = element.querySelector<HTMLElement>(selector);
|
|
||||||
if (!foundElement) {
|
|
||||||
// Element not found.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
element = foundElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = element.closest<HTMLIonContentElement>('ion-content') ?? undefined;
|
|
||||||
if (!content) {
|
|
||||||
// Content to scroll, not found.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const position = CoreDomUtils.getRelativeElementPosition(element, content);
|
|
||||||
|
|
||||||
await content.scrollToPoint(position.x, position.y, duration);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search for an input with error (core-input-error directive) and scrolls to it if found.
|
|
||||||
*
|
|
||||||
* @param container The element that contains the element that must be scrolled.
|
|
||||||
* @return True if the element is found, false otherwise.
|
|
||||||
*/
|
|
||||||
async scrollViewToInputError(container: HTMLElement): Promise<boolean> {
|
|
||||||
return this.scrollViewToElement(container, '.core-input-error');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll to a certain element.
|
* Scroll to a certain element.
|
||||||
*
|
*
|
||||||
|
@ -1371,10 +1071,10 @@ export class CoreDomUtilsProvider {
|
||||||
* @param scrollParentClass Not used anymore.
|
* @param scrollParentClass Not used anymore.
|
||||||
* @param duration Duration of the scroll animation in milliseconds.
|
* @param duration Duration of the scroll animation in milliseconds.
|
||||||
* @return True if the element is found, false otherwise.
|
* @return True if the element is found, false otherwise.
|
||||||
* @deprecated since app 4.0 Use scrollViewToElement instead.
|
* @deprecated since app 4.0 Use CoreDom.scrollToElement instead.
|
||||||
*/
|
*/
|
||||||
scrollToElement(content: IonContent, element: HTMLElement, scrollParentClass?: string, duration = 0): boolean {
|
scrollToElement(content: IonContent, element: HTMLElement, scrollParentClass?: string, duration?: number): boolean {
|
||||||
CoreDomUtils.scrollViewToElement(element, undefined, duration);
|
CoreDom.scrollToElement(element, undefined, { duration });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1388,20 +1088,20 @@ export class CoreDomUtilsProvider {
|
||||||
* @param scrollParentClass Not used anymore.
|
* @param scrollParentClass Not used anymore.
|
||||||
* @param duration Duration of the scroll animation in milliseconds.
|
* @param duration Duration of the scroll animation in milliseconds.
|
||||||
* @return True if the element is found, false otherwise.
|
* @return True if the element is found, false otherwise.
|
||||||
* @deprecated since app 4.0 Use scrollViewToElement instead.
|
* @deprecated since app 4.0 Use CoreDom.scrollToElement instead.
|
||||||
*/
|
*/
|
||||||
scrollToElementBySelector(
|
scrollToElementBySelector(
|
||||||
container: HTMLElement | null,
|
container: HTMLElement | null,
|
||||||
content: unknown | null,
|
content: unknown | null,
|
||||||
selector: string,
|
selector: string,
|
||||||
scrollParentClass?: string,
|
scrollParentClass?: string,
|
||||||
duration = 0,
|
duration?: number,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!container || !content) {
|
if (!container || !content) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreDomUtils.scrollViewToElement(container, selector, duration);
|
CoreDom.scrollToElement(container, selector, { duration });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -1412,14 +1112,14 @@ export class CoreDomUtilsProvider {
|
||||||
*
|
*
|
||||||
* @param container The element that contains the element that must be scrolled.
|
* @param container The element that contains the element that must be scrolled.
|
||||||
* @return True if the element is found, false otherwise.
|
* @return True if the element is found, false otherwise.
|
||||||
* @deprecated since app 4.0 Use scrollViewToInputError instead.
|
* @deprecated since app 4.0 Use CoreDom.scrollToInputError instead.
|
||||||
*/
|
*/
|
||||||
scrollToInputError(container: HTMLElement | null): boolean {
|
scrollToInputError(container: HTMLElement | null): boolean {
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scrollViewToInputError(container);
|
CoreDom.scrollToInputError(container);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2427,11 +2127,3 @@ export enum VerticalPoint {
|
||||||
MID = 'mid',
|
MID = 'mid',
|
||||||
BOTTOM = 'bottom',
|
BOTTOM = 'bottom',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Coordinates of an element.
|
|
||||||
*/
|
|
||||||
export type CoreCoordinates = {
|
|
||||||
x: number; // X axis coordinates.
|
|
||||||
y: number; // Y axis coordinates.
|
|
||||||
};
|
|
||||||
|
|
|
@ -0,0 +1,390 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton with helper functions for dom.
|
||||||
|
*/
|
||||||
|
export class CoreDom {
|
||||||
|
|
||||||
|
// Avoid creating singleton instances.
|
||||||
|
private constructor() {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the position of a element relative to another element.
|
||||||
|
*
|
||||||
|
* @param element Element to get the position.
|
||||||
|
* @param parent Parent element to get relative position.
|
||||||
|
* @return X and Y position.
|
||||||
|
*/
|
||||||
|
static getRelativeElementPosition(element: HTMLElement, parent: HTMLElement): CoreCoordinates {
|
||||||
|
// Get the top, left coordinates of two elements
|
||||||
|
const elementRectangle = element.getBoundingClientRect();
|
||||||
|
const parentRectangle = parent.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Calculate the top and left positions.
|
||||||
|
return {
|
||||||
|
x: elementRectangle.x - parentRectangle.x,
|
||||||
|
y: elementRectangle.y - parentRectangle.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether an element has been added to the DOM.
|
||||||
|
*
|
||||||
|
* @param element Element.
|
||||||
|
* @return True if element has been added to the DOM, false otherwise.
|
||||||
|
*/
|
||||||
|
static isElementInDom(element: HTMLElement): boolean {
|
||||||
|
return element.getRootNode({ composed: true }) === document;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether an element is intersecting the intersectionRatio in viewport.
|
||||||
|
*
|
||||||
|
* @param element
|
||||||
|
* @param intersectionRatio Intersection ratio (From 0 to 1).
|
||||||
|
* @return True if in viewport.
|
||||||
|
*/
|
||||||
|
static isElementInViewport(element: HTMLElement, intersectionRatio = 1): boolean {
|
||||||
|
const elementRectangle = element.getBoundingClientRect();
|
||||||
|
|
||||||
|
const elementArea = elementRectangle.width * elementRectangle.height;
|
||||||
|
if (elementArea == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const intersectionRectangle = {
|
||||||
|
top: Math.max(0, elementRectangle.top),
|
||||||
|
left: Math.max(0, elementRectangle.left),
|
||||||
|
bottom: Math.min(window.innerHeight, elementRectangle.bottom),
|
||||||
|
right: Math.min(window.innerWidth, elementRectangle.right),
|
||||||
|
};
|
||||||
|
|
||||||
|
const intersectionArea = (intersectionRectangle.right - intersectionRectangle.left) *
|
||||||
|
(intersectionRectangle.bottom - intersectionRectangle.top);
|
||||||
|
|
||||||
|
return intersectionArea / elementArea >= intersectionRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether an element is visible or not.
|
||||||
|
*
|
||||||
|
* @param element Element.
|
||||||
|
* @return True if element is visible inside the DOM.
|
||||||
|
*/
|
||||||
|
static isElementVisible(element: HTMLElement): boolean {
|
||||||
|
if (element.clientWidth === 0 || element.clientHeight === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = getComputedStyle(element);
|
||||||
|
if (style.opacity === '0' || style.display === 'none' || style.visibility === 'hidden') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CoreDom.isElementInDom(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a function when an element has been slotted.
|
||||||
|
*
|
||||||
|
* @param element HTML Element inside an ion-content to wait for slot.
|
||||||
|
* @param callback Function to execute on resize.
|
||||||
|
*/
|
||||||
|
static onElementSlot(element: HTMLElement, callback: (ev?: Event) => void): void {
|
||||||
|
if (!element.slot) {
|
||||||
|
// Element not declared to be slotted.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const slotName = element.slot;
|
||||||
|
if (element.assignedSlot?.name === slotName) {
|
||||||
|
// Slot already assigned.
|
||||||
|
callback();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = element.closest('ion-content');
|
||||||
|
if (!content || !content.shadowRoot) {
|
||||||
|
// Cannot find content.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const slots = content.shadowRoot.querySelectorAll('slot');
|
||||||
|
const slot = Array.from(slots).find((slot) => slot.name === slotName);
|
||||||
|
|
||||||
|
if (!slot) {
|
||||||
|
// Slot not found.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const slotListener = () => {
|
||||||
|
if (element.assignedSlot?.name !== slotName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
// It would happen only once.
|
||||||
|
slot.removeEventListener('slotchange', slotListener);
|
||||||
|
};
|
||||||
|
|
||||||
|
slot.addEventListener('slotchange', slotListener);;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll to a certain element.
|
||||||
|
*
|
||||||
|
* @param element The element to scroll to.
|
||||||
|
* @param selector Selector to find the element to scroll to inside the defined element.
|
||||||
|
* @param scrollOptions Scroll Options.
|
||||||
|
* @return Wether the scroll suceeded.
|
||||||
|
*/
|
||||||
|
static async scrollToElement(element: HTMLElement, selector?: string, scrollOptions: CoreScrollOptions = {}): Promise<boolean> {
|
||||||
|
if (selector) {
|
||||||
|
const foundElement = await CoreDom.waitToBeInsideElement(element, selector);
|
||||||
|
if (!foundElement) {
|
||||||
|
// Element not found.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
element = foundElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
await CoreDom.waitToBeVisible(element);
|
||||||
|
|
||||||
|
const content = element.closest<HTMLIonContentElement>('ion-content') ?? undefined;
|
||||||
|
if (!content) {
|
||||||
|
|
||||||
|
// Content to scroll, not found.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const position = CoreDom.getRelativeElementPosition(element, content);
|
||||||
|
const scrollElement = await content.getScrollElement();
|
||||||
|
|
||||||
|
scrollOptions.duration = scrollOptions.duration ?? 200;
|
||||||
|
scrollOptions.addXAxis = scrollOptions.addXAxis ?? 0;
|
||||||
|
scrollOptions.addYAxis = scrollOptions.addYAxis ?? 0;
|
||||||
|
|
||||||
|
await content.scrollToPoint(
|
||||||
|
position.x + scrollElement.scrollLeft + scrollOptions.addXAxis,
|
||||||
|
position.y + scrollElement.scrollTop + scrollOptions.addYAxis,
|
||||||
|
scrollOptions.duration,
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for an input with error (core-input-error directive) and scrolls to it if found.
|
||||||
|
*
|
||||||
|
* @param container The element that contains the element that must be scrolled.
|
||||||
|
* @return True if the element is found, false otherwise.
|
||||||
|
*/
|
||||||
|
static async scrollToInputError(container: HTMLElement): Promise<boolean> {
|
||||||
|
return CoreDom.scrollToElement(container, '.core-input-error');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait an element to be added to the root DOM.
|
||||||
|
*
|
||||||
|
* @param element Element to wait.
|
||||||
|
* @return Cancellable promise.
|
||||||
|
*/
|
||||||
|
static waitToBeInDOM(element: HTMLElement): CoreCancellablePromise<void> {
|
||||||
|
const root = element.getRootNode({ composed: true });
|
||||||
|
|
||||||
|
if (root === document) {
|
||||||
|
// Already in DOM.
|
||||||
|
return CoreCancellablePromise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
let observer: MutationObserver;
|
||||||
|
|
||||||
|
return new CoreCancellablePromise<void>(
|
||||||
|
(resolve) => {
|
||||||
|
observer = new MutationObserver(() => {
|
||||||
|
const root = element.getRootNode({ composed: true });
|
||||||
|
|
||||||
|
if (root !== document) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observer?.disconnect();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, { subtree: true, childList: true });
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
observer?.disconnect();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait an element to be in dom of another element using a selector
|
||||||
|
*
|
||||||
|
* @param container Element to wait.
|
||||||
|
* @return Cancellable promise.
|
||||||
|
*/
|
||||||
|
static async waitToBeInsideElement(container: HTMLElement, selector: string): Promise<CoreCancellablePromise<HTMLElement>> {
|
||||||
|
await CoreDom.waitToBeInDOM(container);
|
||||||
|
|
||||||
|
let element = container.querySelector<HTMLElement>(selector);
|
||||||
|
if (element) {
|
||||||
|
// Already in DOM.
|
||||||
|
return CoreCancellablePromise.resolve(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
let observer: MutationObserver;
|
||||||
|
|
||||||
|
return new CoreCancellablePromise<HTMLElement>(
|
||||||
|
(resolve) => {
|
||||||
|
observer = new MutationObserver(() => {
|
||||||
|
element = container.querySelector<HTMLElement>(selector);
|
||||||
|
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observer?.disconnect();
|
||||||
|
resolve(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(container, { subtree: true, childList: true });
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
observer?.disconnect();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait an element to be in dom and visible.
|
||||||
|
*
|
||||||
|
* @param element Element to wait.
|
||||||
|
* @param intersectionRatio Intersection ratio (From 0 to 1).
|
||||||
|
* @return Cancellable promise.
|
||||||
|
*/
|
||||||
|
static waitToBeInViewport(element: HTMLElement, intersectionRatio = 1): CoreCancellablePromise<void> {
|
||||||
|
const visiblePromise = CoreDom.waitToBeVisible(element);
|
||||||
|
|
||||||
|
let intersectionObserver: IntersectionObserver;
|
||||||
|
let interval: number | undefined;
|
||||||
|
|
||||||
|
return new CoreCancellablePromise<void>(
|
||||||
|
async (resolve) => {
|
||||||
|
await visiblePromise;
|
||||||
|
|
||||||
|
if (CoreDom.isElementInViewport(element, intersectionRatio)) {
|
||||||
|
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
intersectionObserver = new IntersectionObserver((observerEntries) => {
|
||||||
|
const isIntersecting = observerEntries
|
||||||
|
.some((entry) => entry.isIntersecting && entry.intersectionRatio >= intersectionRatio);
|
||||||
|
if (!isIntersecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
intersectionObserver?.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
intersectionObserver.observe(element);
|
||||||
|
} else {
|
||||||
|
interval = window.setInterval(() => {
|
||||||
|
if (!CoreDom.isElementInViewport(element, intersectionRatio)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
window.clearInterval(interval);
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
visiblePromise.cancel();
|
||||||
|
intersectionObserver?.disconnect();
|
||||||
|
window.clearInterval(interval);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait an element to be in dom and visible.
|
||||||
|
*
|
||||||
|
* @param element Element to wait.
|
||||||
|
* @return Cancellable promise.
|
||||||
|
*/
|
||||||
|
static waitToBeVisible(element: HTMLElement): CoreCancellablePromise<void> {
|
||||||
|
const domPromise = CoreDom.waitToBeInDOM(element);
|
||||||
|
|
||||||
|
let interval: number | undefined;
|
||||||
|
|
||||||
|
// Mutations did not observe for visibility properties.
|
||||||
|
return new CoreCancellablePromise<void>(
|
||||||
|
async (resolve) => {
|
||||||
|
await domPromise;
|
||||||
|
|
||||||
|
if (CoreDom.isElementVisible(element)) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
interval = window.setInterval(() => {
|
||||||
|
if (!CoreDom.isElementVisible(element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
window.clearInterval(interval);
|
||||||
|
}, 50);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
domPromise.cancel();
|
||||||
|
window.clearInterval(interval);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinates of an element.
|
||||||
|
*/
|
||||||
|
export type CoreCoordinates = {
|
||||||
|
x: number; // X axis coordinates.
|
||||||
|
y: number; // Y axis coordinates.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll options.
|
||||||
|
*/
|
||||||
|
export type CoreScrollOptions = {
|
||||||
|
duration?: number;
|
||||||
|
addYAxis?: number;
|
||||||
|
addXAxis?: number;
|
||||||
|
};
|
Loading…
Reference in New Issue