MOBILE-3814 dom: Move new function to a singleton class
parent
ac5e4b1d79
commit
6a1b692dc5
|
@ -45,6 +45,7 @@ import { CoreIonLoadingElement } from '@classes/ion-loading';
|
|||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AddonMessagesConversationInfoComponent } from '../../components/conversation-info/conversation-info';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
/**
|
||||
* Page that displays a message discussion page.
|
||||
|
@ -1109,7 +1110,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
|
|||
if (this.newMessages > 0) {
|
||||
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,
|
||||
} from '../../services/data';
|
||||
import { AddonModDataHelper } from '../../services/data-helper';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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) {
|
||||
this.content?.scrollToTop();
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ import { CoreRatingInfo } from '@features/rating/services/rating';
|
|||
import { CoreForms } from '@singletons/form';
|
||||
import { CoreFileEntry } from '@services/file-helper';
|
||||
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.).
|
||||
|
@ -540,7 +541,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async scrollToForm(): Promise<void> {
|
||||
await CoreDomUtils.scrollViewToElement(
|
||||
await CoreDom.scrollToElement(
|
||||
this.elementRef.nativeElement,
|
||||
'#addon-forum-reply-edit-form-' + this.uniqueId,
|
||||
);
|
||||
|
|
|
@ -33,6 +33,7 @@ import { CoreDomUtils } from '@services/utils/dom';
|
|||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Network, NgZone, Translate } from '@singletons';
|
||||
import { CoreArray } from '@singletons/array';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { AddonModForumDiscussionsSource } from '../../classes/forum-discussions-source';
|
||||
|
@ -187,7 +188,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
|
|||
const scrollTo = this.postId || this.parent;
|
||||
if (scrollTo) {
|
||||
// Scroll to the post.
|
||||
CoreDomUtils.scrollViewToElement(
|
||||
CoreDom.scrollToElement(
|
||||
this.elementRef.nativeElement,
|
||||
'#addon-mod_forum-post-' + scrollTo,
|
||||
);
|
||||
|
|
|
@ -21,6 +21,7 @@ import { CoreForms } from '@singletons/form';
|
|||
import { ModalController, Translate } from '@singletons';
|
||||
import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate';
|
||||
import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '../../services/quiz';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
/**
|
||||
* Modal that renders the access rules for a quiz.
|
||||
|
@ -115,7 +116,7 @@ export class AddonModQuizPreflightModalComponent implements OnInit {
|
|||
|
||||
if (!this.preflightForm.valid) {
|
||||
// Form not valid. Scroll to the first element with errors.
|
||||
const hasScrolled = await CoreDomUtils.scrollViewToInputError(
|
||||
const hasScrolled = await CoreDom.scrollToInputError(
|
||||
this.elementRef.nativeElement,
|
||||
);
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ import { AddonModQuizAttempt, AddonModQuizHelper } from '../../services/quiz-hel
|
|||
import { AddonModQuizSync } from '../../services/quiz-sync';
|
||||
import { CanLeave } from '@guards/can-leave';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected scrollToQuestion(slot: number): void {
|
||||
CoreDomUtils.scrollViewToElement(
|
||||
CoreDom.scrollToElement(
|
||||
this.elementRef.nativeElement,
|
||||
'#addon-mod_quiz-question-' + slot,
|
||||
);
|
||||
|
|
|
@ -22,6 +22,7 @@ import { CoreDomUtils } from '@services/utils/dom';
|
|||
import { CoreTimeUtils } from '@services/utils/time';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import {
|
||||
AddonModQuizNavigationModalComponent,
|
||||
AddonModQuizNavigationModalReturn,
|
||||
|
@ -247,7 +248,7 @@ export class AddonModQuizReviewPage implements OnInit {
|
|||
* @param slot Slot of the question to scroll to.
|
||||
*/
|
||||
protected scrollToQuestion(slot: number): void {
|
||||
CoreDomUtils.scrollViewToElement(
|
||||
CoreDom.scrollToElement(
|
||||
this.elementRef.nativeElement,
|
||||
`#addon-mod_quiz-question-${slot}`,
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreEventObserver } from '@singletons/events';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { AddonModQuizDdImageOrTextQuestionData } from '../component/ddimageortext';
|
||||
|
@ -83,7 +84,7 @@ export class AddonQtypeDdImageOrTextQuestion {
|
|||
return bgImgXY;
|
||||
}
|
||||
|
||||
const position = CoreDomUtils.getRelativeElementPosition(bgImg, ddArea);
|
||||
const position = CoreDom.getRelativeElementPosition(bgImg, ddArea);
|
||||
|
||||
// Render the position related to the current image dimensions.
|
||||
bgImgXY[0] *= this.proportion;
|
||||
|
@ -419,7 +420,7 @@ export class AddonQtypeDdImageOrTextQuestion {
|
|||
return;
|
||||
}
|
||||
|
||||
const position = CoreDomUtils.getRelativeElementPosition(drop, ddArea);
|
||||
const position = CoreDom.getRelativeElementPosition(drop, ddArea);
|
||||
const choice = drag.getAttribute('choice');
|
||||
drag.style.left = position.x + 'px';
|
||||
drag.style.top = position.y + 'px';
|
||||
|
@ -473,7 +474,7 @@ export class AddonQtypeDdImageOrTextQuestion {
|
|||
return;
|
||||
}
|
||||
|
||||
const position = CoreDomUtils.getRelativeElementPosition(dragItemHome, ddArea);
|
||||
const position = CoreDom.getRelativeElementPosition(dragItemHome, ddArea);
|
||||
drag.style.left = position.x + 'px';
|
||||
drag.style.top = position.y + 'px';
|
||||
drag.classList.remove('placed');
|
||||
|
|
|
@ -12,8 +12,9 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreCoordinates, CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreCoordinates, CoreDom } from '@singletons/dom';
|
||||
import { CoreEventObserver } from '@singletons/events';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { AddonQtypeDdMarkerQuestionData } from '../component/ddmarker';
|
||||
|
@ -134,7 +135,7 @@ export class AddonQtypeDdMarkerQuestion {
|
|||
return [];
|
||||
}
|
||||
|
||||
const position = CoreDomUtils.getRelativeElementPosition(element, ddArea);
|
||||
const position = CoreDom.getRelativeElementPosition(element, ddArea);
|
||||
|
||||
return [position.x, position.y];
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { AddonQtypeDdMarkerQuestion } from './ddmarker';
|
||||
|
||||
/**
|
||||
|
@ -59,7 +59,7 @@ export class AddonQtypeDdMarkerGraphicsApi {
|
|||
return;
|
||||
}
|
||||
|
||||
const position = CoreDomUtils.getRelativeElementPosition(bgImg, ddArea);
|
||||
const position = CoreDom.getRelativeElementPosition(bgImg, ddArea);
|
||||
|
||||
dropZones.style.left = position.x + 'px';
|
||||
dropZones.style.top = position.y + 'px';
|
||||
|
|
|
@ -13,10 +13,11 @@
|
|||
// limitations under the License.
|
||||
|
||||
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 { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreCoordinates, CoreDom } from '@singletons/dom';
|
||||
import { CoreEventObserver } from '@singletons/events';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { AddonModQuizDdwtosQuestionData } from '../component/ddwtos';
|
||||
|
@ -389,13 +390,13 @@ export class AddonQtypeDdwtosQuestion {
|
|||
const choiceNo = this.getChoice(drag) ?? -1;
|
||||
const dragHome = this.container.querySelector<HTMLElement>(this.selectors.dragHome(groupNo, choiceNo));
|
||||
if (dragHome) {
|
||||
position = CoreDomUtils.getRelativeElementPosition(dragHome, parent);
|
||||
position = CoreDom.getRelativeElementPosition(dragHome, parent);
|
||||
}
|
||||
} else {
|
||||
// Get the drop zone position.
|
||||
const dropZone = this.container.querySelector<HTMLElement>(this.selectors.dropForPlace(placeNo));
|
||||
if (dropZone) {
|
||||
position = CoreDomUtils.getRelativeElementPosition(dropZone, parent);
|
||||
position = CoreDom.getRelativeElementPosition(dropZone, parent);
|
||||
// Avoid the border.
|
||||
position.x++;
|
||||
position.y++;
|
||||
|
@ -425,13 +426,13 @@ export class AddonQtypeDdwtosQuestion {
|
|||
* @return Promise resolved when ready in the DOM.
|
||||
*/
|
||||
protected async waitForReady(): Promise<void> {
|
||||
await CoreDomUtils.waitToBeInDOM(this.container);
|
||||
await CoreDom.waitToBeInDOM(this.container);
|
||||
|
||||
await CoreComponentsRegistry.waitComponentsReady(this.container, 'core-format-text', CoreFormatTextDirective);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
await CoreDomUtils.waitToBeInDOM(groupItems[0]);
|
||||
await CoreDom.waitToBeInDOM(groupItems[0]);
|
||||
|
||||
let maxWidth = 0;
|
||||
let maxHeight = 0;
|
||||
|
|
|
@ -26,6 +26,7 @@ import { CoreLogger } from '@singletons/logger';
|
|||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreContextMenuComponent } from '../context-menu/context-menu';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
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.
|
||||
*/
|
||||
protected async searchHeader(): Promise<HTMLIonHeaderElement> {
|
||||
await CoreDomUtils.waitToBeInDOM(this.element);
|
||||
await CoreDom.waitToBeInDOM(this.element);
|
||||
|
||||
let parentPage: HTMLElement | null = this.element;
|
||||
while (parentPage && parentPage.parentElement) {
|
||||
|
|
|
@ -16,6 +16,7 @@ import { Directive, Input, ElementRef, AfterViewInit } from '@angular/core';
|
|||
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
/**
|
||||
* Directive to auto focus an element when a view is loaded.
|
||||
|
@ -46,7 +47,7 @@ export class CoreAutoFocusDirective implements AfterViewInit {
|
|||
return;
|
||||
}
|
||||
|
||||
await CoreDomUtils.waitToBeInDOM(this.element);
|
||||
await CoreDom.waitToBeInDOM(this.element);
|
||||
|
||||
let focusElement = this.element;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import { CoreEventObserver } from '@singletons/events';
|
|||
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
// Only if not present or explicitly falsy it will be false.
|
||||
this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom);
|
||||
this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
||||
this.domPromise = CoreDom.waitToBeInDOM(this.element);
|
||||
|
||||
await this.domPromise;
|
||||
await this.waitLoadingsDone();
|
||||
|
@ -72,7 +73,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
|||
|
||||
await this.calculateHeight();
|
||||
|
||||
CoreDomUtils.onElementSlot(this.element, () => {
|
||||
CoreDom.onElementSlot(this.element, () => {
|
||||
this.calculateHeight();
|
||||
});
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import { CoreDomUtils } from '@services/utils/dom';
|
|||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreEventObserver } from '@singletons/events';
|
||||
import { CoreFormatTextDirective } from './format-text';
|
||||
|
||||
|
@ -100,7 +101,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
|
|||
* @return Promise resolved when loadings are done.
|
||||
*/
|
||||
protected async waitLoadingsDone(): Promise<void> {
|
||||
this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
||||
this.domPromise = CoreDom.waitToBeInDOM(this.element);
|
||||
|
||||
await this.domPromise;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Directive, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
||||
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.
|
||||
|
@ -42,7 +42,7 @@ export class CoreFabDirective implements OnInit, OnDestroy {
|
|||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
||||
this.domPromise = CoreDom.waitToBeInDOM(this.element);
|
||||
await this.domPromise;
|
||||
|
||||
this.content = this.element.closest('ion-content');
|
||||
|
@ -56,7 +56,7 @@ export class CoreFabDirective implements OnInit, OnDestroy {
|
|||
|
||||
await this.calculatePlace();
|
||||
|
||||
CoreDomUtils.onElementSlot(this.element, () => {
|
||||
CoreDom.onElementSlot(this.element, () => {
|
||||
this.calculatePlace();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ import { CoreCollapsibleItemDirective } from './collapsible-item';
|
|||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||
import { AsyncComponent } from '@classes/async-component';
|
||||
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
|
||||
|
@ -552,7 +553,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
*/
|
||||
protected async getElementWidth(): Promise<number> {
|
||||
if (!this.domElementPromise) {
|
||||
this.domElementPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
||||
this.domElementPromise = CoreDom.waitToBeInDOM(this.element);
|
||||
}
|
||||
await this.domElementPromise;
|
||||
|
||||
|
@ -704,7 +705,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
newUrl += `&h=${privacyHash}`;
|
||||
}
|
||||
|
||||
const domPromise = CoreDomUtils.waitToBeInDOM(iframe);
|
||||
const domPromise = CoreDom.waitToBeInDOM(iframe);
|
||||
this.domPromises.push(domPromise);
|
||||
|
||||
await domPromise;
|
||||
|
|
|
@ -28,6 +28,7 @@ import { CoreCustomURLSchemes } from '@services/urlschemes';
|
|||
import { DomSanitizer } from '@singletons';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreUrl } from '@singletons/url';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
/**
|
||||
* 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);
|
||||
const container = this.element.closest('ion-content');
|
||||
if (container) {
|
||||
CoreDomUtils.scrollViewToElement(
|
||||
CoreDom.scrollToElement(
|
||||
container,
|
||||
`#${href}, [name='${href}']`,
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
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.
|
||||
|
@ -37,7 +37,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy {
|
|||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.visiblePromise = CoreDomUtils.waitToBeInViewport(this.element);
|
||||
this.visiblePromise = CoreDom.waitToBeInViewport(this.element);
|
||||
|
||||
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 { AddonModAssignComponentsModule } from '@addons/mod/assign/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.
|
||||
|
@ -341,6 +342,7 @@ export class CoreCompileProvider {
|
|||
instance['Md5'] = Md5;
|
||||
instance['CoreSyncBaseProvider'] = CoreSyncBaseProvider;
|
||||
instance['CoreArray'] = CoreArray;
|
||||
instance['CoreDom'] = CoreDom;
|
||||
instance['CoreText'] = CoreText;
|
||||
instance['CoreUrl'] = CoreUrl;
|
||||
instance['CoreWindow'] = CoreWindow;
|
||||
|
|
|
@ -48,6 +48,7 @@ import { CoreCourseModuleDelegate } from '@features/course/services/module-deleg
|
|||
import { CoreCourseViewedModulesDBRecord } from '@features/course/services/database/course';
|
||||
import { CoreUserTours, CoreUserToursAlignment, CoreUserToursSide } from '@features/usertours/services/user-tours';
|
||||
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.
|
||||
|
@ -511,7 +512,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @param moduleId Module ID.
|
||||
*/
|
||||
protected scrollToModule(moduleId: number): void {
|
||||
CoreDomUtils.scrollViewToElement(
|
||||
CoreDom.scrollToElement(
|
||||
this.elementRef.nativeElement,
|
||||
'#core-course-module-' + moduleId,
|
||||
{ addYAxis: -10 },
|
||||
|
|
|
@ -21,8 +21,8 @@ import {
|
|||
import { CoreCourseHelper, CoreCourseSection } from '@features/course/services/course-helper';
|
||||
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { ModalController } from '@singletons';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
/**
|
||||
* Component to display course index modal.
|
||||
|
@ -109,7 +109,7 @@ export class CoreCourseCourseIndexComponent implements OnInit {
|
|||
|
||||
this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course);
|
||||
|
||||
CoreDomUtils.scrollViewToElement(
|
||||
CoreDom.scrollToElement(
|
||||
this.elementRef.nativeElement,
|
||||
'.item.item-current',
|
||||
{ addYAxis: -10 },
|
||||
|
|
|
@ -41,6 +41,7 @@ import { CoreComponentsRegistry } from '@singletons/components-registry';
|
|||
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected async waitLoadingsDone(): Promise<void> {
|
||||
this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
||||
this.domPromise = CoreDom.waitToBeInDOM(this.element);
|
||||
|
||||
await this.domPromise;
|
||||
|
||||
|
@ -846,7 +847,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
|
|||
|
||||
// Cancel previous one, if any.
|
||||
this.buttonsDomPromise?.cancel();
|
||||
this.buttonsDomPromise = CoreDomUtils.waitToBeInDOM(this.toolbar.nativeElement);
|
||||
this.buttonsDomPromise = CoreDom.waitToBeInDOM(this.toolbar.nativeElement);
|
||||
await this.buttonsDomPromise;
|
||||
|
||||
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 { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { CoreGradesCoursesSource } from '@features/grades/classes/grades-courses-source';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
/**
|
||||
* Page that displays a course grades.
|
||||
|
@ -170,7 +171,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
|||
if (row) {
|
||||
this.toggleRow(row, true);
|
||||
|
||||
CoreDomUtils.scrollViewToElement(
|
||||
CoreDom.scrollToElement(
|
||||
this.element.nativeElement,
|
||||
'#grade-' + row.id,
|
||||
);
|
||||
|
|
|
@ -35,6 +35,7 @@ import { CoreNavigator } from '@services/navigator';
|
|||
import { CoreForms } from '@singletons/form';
|
||||
import { CoreRecaptchaComponent } from '@components/recaptcha/recaptcha';
|
||||
import { CoreText } from '@singletons/text';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
/**
|
||||
* Page to signup using email.
|
||||
|
@ -284,7 +285,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
this.changeDetector.detectChanges();
|
||||
|
||||
// Scroll to the first element with errors.
|
||||
const errorFound = await CoreDomUtils.scrollViewToInputError(
|
||||
const errorFound = await CoreDom.scrollToInputError(
|
||||
this.elementRef.nativeElement,
|
||||
);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import { CoreUserTours, CoreUserToursAlignment, CoreUserToursSide } from '@featu
|
|||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { AngularFrameworkDelegate } from '@singletons';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
const ANIMATION_DURATION = 200;
|
||||
const USER_TOURS_BACK_BUTTON_PRIORITY = 100;
|
||||
|
@ -86,7 +87,7 @@ export class CoreUserToursUserTourComponent implements AfterViewInit {
|
|||
await CoreDomUtils.waitForImages(tour);
|
||||
|
||||
// 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);
|
||||
|
||||
return;
|
||||
|
|
|
@ -54,7 +54,7 @@ import { filter } from 'rxjs/operators';
|
|||
import { Subscription } from 'rxjs';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
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.
|
||||
|
@ -93,218 +93,6 @@ export class CoreDomUtilsProvider {
|
|||
this.debugDisplay = debugDisplay != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait an element to be added to the root DOM.
|
||||
*
|
||||
* @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 of another element using a selector
|
||||
*
|
||||
* @param container Element to wait.
|
||||
* @return Cancellable promise.
|
||||
*/
|
||||
async waitToBeInsideElement(container: HTMLElement, selector: string): Promise<CoreCancellablePromise<HTMLElement>> {
|
||||
await CoreDomUtils.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.
|
||||
* @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.
|
||||
* This function helps setting up the debounce feature and remove listener easily.
|
||||
|
@ -609,13 +397,11 @@ export class CoreDomUtilsProvider {
|
|||
* @return Selection contents. Undefined if not found.
|
||||
*/
|
||||
getContentsOfElement(element: HTMLElement, selector: string): string | undefined {
|
||||
if (element) {
|
||||
const selected = element.querySelector(selector);
|
||||
if (selected) {
|
||||
return selected.innerHTML;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data from a form. It will only collect elements that have a name.
|
||||
|
@ -762,7 +548,7 @@ export class CoreDomUtilsProvider {
|
|||
* @param selector Selector to find the element to gets the position.
|
||||
* @param positionParentClass Parent Class where to stop calculating the position. Default inner-scroll.
|
||||
* @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 {
|
||||
if (selector) {
|
||||
|
@ -780,7 +566,7 @@ export class CoreDomUtilsProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
const position = CoreDomUtils.getRelativeElementPosition(element, parent);
|
||||
const position = CoreDom.getRelativeElementPosition(element, parent);
|
||||
|
||||
// Calculate the top and left positions.
|
||||
return [
|
||||
|
@ -789,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.
|
||||
*
|
||||
|
@ -927,7 +694,7 @@ export class CoreDomUtilsProvider {
|
|||
*
|
||||
* @param findFunction The function used to find the element.
|
||||
* @return Resolved if found, rejected if too many tries.
|
||||
* @deprecated since app 4.0 Use waitToBeInsideElement instead.
|
||||
* @deprecated since app 4.0 Use CoreDom.waitToBeInsideElement instead.
|
||||
*/
|
||||
waitElementToExist(findFunction: () => HTMLElement | null): Promise<HTMLElement> {
|
||||
const promiseInterval = CoreUtils.promiseDefer<HTMLElement>();
|
||||
|
@ -1031,63 +798,6 @@ export class CoreDomUtilsProvider {
|
|||
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.
|
||||
*
|
||||
|
@ -1289,7 +999,7 @@ export class CoreDomUtilsProvider {
|
|||
* @return Returns a promise which is resolved when the scroll has completed.
|
||||
* @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);
|
||||
}
|
||||
|
||||
|
@ -1353,64 +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 scrollOptions Scroll Options.
|
||||
* @return Wether the scroll suceeded.
|
||||
*/
|
||||
async scrollViewToElement(element: HTMLElement, selector?: string, scrollOptions: CoreScrollOptions = {}): Promise<boolean> {
|
||||
if (selector) {
|
||||
const foundElement = await CoreDomUtils.waitToBeInsideElement(element, selector);
|
||||
if (!foundElement) {
|
||||
// Element not found.
|
||||
return false;
|
||||
}
|
||||
|
||||
element = foundElement;
|
||||
}
|
||||
|
||||
await CoreDomUtils.waitToBeVisible(element);
|
||||
|
||||
const content = element.closest<HTMLIonContentElement>('ion-content') ?? undefined;
|
||||
if (!content) {
|
||||
|
||||
// Content to scroll, not found.
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const position = CoreDomUtils.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.
|
||||
*/
|
||||
async scrollViewToInputError(container: HTMLElement): Promise<boolean> {
|
||||
return this.scrollViewToElement(container, '.core-input-error');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to a certain element.
|
||||
*
|
||||
|
@ -1419,10 +1071,10 @@ export class CoreDomUtilsProvider {
|
|||
* @param scrollParentClass Not used anymore.
|
||||
* @param duration Duration of the scroll animation in milliseconds.
|
||||
* @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?: number): boolean {
|
||||
CoreDomUtils.scrollViewToElement(element, undefined, { duration });
|
||||
CoreDom.scrollToElement(element, undefined, { duration });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1436,7 +1088,7 @@ export class CoreDomUtilsProvider {
|
|||
* @param scrollParentClass Not used anymore.
|
||||
* @param duration Duration of the scroll animation in milliseconds.
|
||||
* @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(
|
||||
container: HTMLElement | null,
|
||||
|
@ -1449,7 +1101,7 @@ export class CoreDomUtilsProvider {
|
|||
return false;
|
||||
}
|
||||
|
||||
CoreDomUtils.scrollViewToElement(container, selector, { duration });
|
||||
CoreDom.scrollToElement(container, selector, { duration });
|
||||
|
||||
return true;
|
||||
|
||||
|
@ -1460,14 +1112,14 @@ export class CoreDomUtilsProvider {
|
|||
*
|
||||
* @param container The element that contains the element that must be scrolled.
|
||||
* @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 {
|
||||
if (!container) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.scrollViewToInputError(container);
|
||||
CoreDom.scrollToInputError(container);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2475,20 +2127,3 @@ export enum VerticalPoint {
|
|||
MID = 'mid',
|
||||
BOTTOM = 'bottom',
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
|
|
@ -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