MOBILE-3814 dom: Improve scroll handling

main
Pau Ferrer Ocaña 2022-03-21 13:34:32 +01:00
parent dbc91004e4
commit a76914f25a
15 changed files with 166 additions and 144 deletions

View File

@ -1106,10 +1106,10 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
* Scroll to the first new unread message. * Scroll to the first new unread message.
*/ */
scrollToFirstUnreadMessage(): void { scrollToFirstUnreadMessage(): void {
if (this.newMessages > 0 && this.content) { if (this.newMessages > 0) {
const messages = Array.from(this.hostElement.querySelectorAll('.addon-message-not-mine')); const messages = Array.from(this.hostElement.querySelectorAll<HTMLElement>('.addon-message-not-mine'));
CoreDomUtils.scrollToElement(this.content, <HTMLElement> messages[messages.length - this.newMessages]); CoreDomUtils.scrollViewToElement(messages[messages.length - this.newMessages]);
} }
} }

View File

@ -159,7 +159,7 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave {
this.messages[this.messages.length - 1].showTail = true; this.messages[this.messages.length - 1].showTail = true;
// New messages or beeps, scroll to bottom. // New messages or beeps, scroll to bottom.
setTimeout(() => this.scrollToBottom()); this.scrollToBottom();
} }
protected async loadMessageBeepWho(message: AddonModChatFormattedMessage): Promise<void> { protected async loadMessageBeepWho(message: AddonModChatFormattedMessage): Promise<void> {
@ -341,13 +341,12 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave {
/** /**
* Scroll bottom when render has finished. * Scroll bottom when render has finished.
*/ */
scrollToBottom(): void { async scrollToBottom(): Promise<void> {
// Need a timeout to leave time to the view to be rendered. // Need a timeout to leave time to the view to be rendered.
setTimeout(() => { await CoreUtils.nextTick();
if (!this.viewDestroyed) { if (!this.viewDestroyed) {
this.content?.scrollToBottom(); this.content?.scrollToBottom();
} }
});
} }
/** /**

View File

@ -352,9 +352,7 @@ export class AddonModDataEditPage implements OnInit {
} }
this.jsData!.errors = this.errors; this.jsData!.errors = this.errors;
setTimeout(() => { this.scrollToFirstError();
this.scrollToFirstError();
});
} }
} finally { } finally {
modal.dismiss(); modal.dismiss();
@ -449,8 +447,9 @@ 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 scrollToFirstError(): void { protected async scrollToFirstError(): Promise<void> {
if (!CoreDomUtils.scrollToElementBySelector(this.formElement.nativeElement, this.content, '.addon-data-error')) { const scrolled = await CoreDomUtils.scrollViewToElement(this.formElement.nativeElement, '.addon-data-error');
if (!scrolled) {
this.content?.scrollToTop(); this.content?.scrollToTop();
} }
} }

View File

@ -20,7 +20,6 @@ import {
OnChanges, OnChanges,
OnDestroy, OnDestroy,
OnInit, OnInit,
Optional,
Output, Output,
SimpleChange, SimpleChange,
ViewChild, ViewChild,
@ -41,7 +40,6 @@ import {
import { CoreTag } from '@features/tag/services/tag'; import { CoreTag } from '@features/tag/services/tag';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader'; import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
import { IonContent } from '@ionic/angular';
import { AddonModForumSync } from '../../services/forum-sync'; import { AddonModForumSync } from '../../services/forum-sync';
import { CoreSync } from '@services/sync'; import { CoreSync } from '@services/sync';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
@ -94,7 +92,6 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
constructor( constructor(
protected elementRef: ElementRef, protected elementRef: ElementRef,
@Optional() protected content?: IonContent,
) {} ) {}
get showForm(): boolean { get showForm(): boolean {
@ -308,8 +305,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
this.post.id > 0 ? this.post.id : undefined, this.post.id > 0 ? this.post.id : undefined,
); );
this.scrollToForm(5); this.scrollToForm();
} catch (error) { } catch {
// Cancelled. // Cancelled.
} }
} }
@ -540,19 +537,11 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
/** /**
* Scroll to reply/edit form. * Scroll to reply/edit form.
* *
* @param ticksToWait Number of ticks to wait before scrolling.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
protected async scrollToForm(ticksToWait = 1): Promise<void> { protected async scrollToForm(): Promise<void> {
if (!this.content) { await CoreDomUtils.scrollViewToElement(
return;
}
await CoreUtils.nextTicks(ticksToWait);
CoreDomUtils.scrollToElementBySelector(
this.elementRef.nativeElement, this.elementRef.nativeElement,
this.content,
'#addon-forum-reply-edit-form-' + this.uniqueId, '#addon-forum-reply-edit-form-' + this.uniqueId,
); );
} }

View File

@ -187,13 +187,10 @@ 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.
setTimeout(() => { CoreDomUtils.scrollViewToElement(
CoreDomUtils.scrollToElementBySelector( this.elementRef.nativeElement,
this.elementRef.nativeElement, '#addon-mod_forum-post-' + scrollTo,
this.content, );
'#addon-mod_forum-post-' + scrollTo,
);
});
} }
} }

View File

@ -14,7 +14,6 @@
import { Component, OnInit, ViewChild, ElementRef, Input, Type } from '@angular/core'; import { Component, OnInit, ViewChild, ElementRef, Input, Type } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { IonContent } from '@ionic/angular';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
@ -32,7 +31,6 @@ import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '../../service
}) })
export class AddonModQuizPreflightModalComponent implements OnInit { export class AddonModQuizPreflightModalComponent implements OnInit {
@ViewChild(IonContent) content?: IonContent;
@ViewChild('preflightFormEl') formElement?: ElementRef; @ViewChild('preflightFormEl') formElement?: ElementRef;
@Input() title!: string; @Input() title!: string;
@ -111,15 +109,14 @@ export class AddonModQuizPreflightModalComponent implements OnInit {
* *
* @param e Event. * @param e Event.
*/ */
sendData(e: Event): void { async sendData(e: Event): Promise<void> {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
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 = CoreDomUtils.scrollToInputError( const hasScrolled = await CoreDomUtils.scrollViewToInputError(
this.elementRef.nativeElement, this.elementRef.nativeElement,
this.content,
); );
if (!hasScrolled) { if (!hasScrolled) {

View File

@ -318,10 +318,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
this.loaded = true; this.loaded = true;
if (slot !== undefined) { if (slot !== undefined) {
// Scroll to the question. Give some time to the questions to render. // Scroll to the question.
setTimeout(() => { this.scrollToQuestion(slot);
this.scrollToQuestion(slot);
}, 2000);
} }
} }
} }
@ -689,9 +687,8 @@ 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.scrollToElementBySelector( CoreDomUtils.scrollViewToElement(
this.elementRef.nativeElement, this.elementRef.nativeElement,
this.content,
'#addon-mod_quiz-question-' + slot, '#addon-mod_quiz-question-' + slot,
); );
} }

View File

@ -133,10 +133,8 @@ export class AddonModQuizReviewPage implements OnInit {
this.loaded = true; this.loaded = true;
if (slot !== undefined) { if (slot !== undefined) {
// Scroll to the question. Give some time to the questions to render. // Scroll to the question.
setTimeout(() => { this.scrollToQuestion(slot);
this.scrollToQuestion(slot);
}, 2000);
} }
} }
} }
@ -249,9 +247,8 @@ 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.scrollToElementBySelector( CoreDomUtils.scrollViewToElement(
this.elementRef.nativeElement, this.elementRef.nativeElement,
this.content,
`#addon-mod_quiz-question-${slot}`, `#addon-mod_quiz-question-${slot}`,
); );
} }

View File

@ -142,11 +142,13 @@ export class CoreLinkDirective implements OnInit {
if (href.charAt(0) == '#') { if (href.charAt(0) == '#') {
// Look for id or name. // Look for id or name.
href = href.substring(1); href = href.substring(1);
CoreDomUtils.scrollToElementBySelector( const container = this.element.closest('ion-content');
this.element.closest('ion-content'), if (container) {
this.content, CoreDomUtils.scrollViewToElement(
`#${href}, [name='${href}']`, container,
); `#${href}, [name='${href}']`,
);
}
return; return;
} }

View File

@ -492,9 +492,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
// Scroll to module if needed. Give more priority to the input. // Scroll to module if needed. Give more priority to the input.
const moduleIdToScroll = this.moduleId && previousValue === undefined ? this.moduleId : moduleId; const moduleIdToScroll = this.moduleId && previousValue === undefined ? this.moduleId : moduleId;
if (moduleIdToScroll) { if (moduleIdToScroll) {
setTimeout(() => { this.scrollToModule(moduleIdToScroll);
this.scrollToModule(moduleIdToScroll);
}, 200);
} else { } else {
this.content.scrollToTop(0); this.content.scrollToTop(0);
} }
@ -513,9 +511,8 @@ 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.scrollToElementBySelector( CoreDomUtils.scrollViewToElement(
this.elementRef.nativeElement, this.elementRef.nativeElement,
this.content,
'#core-course-module-' + moduleId, '#core-course-module-' + moduleId,
); );
} }

View File

@ -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 { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; import { Component, ElementRef, Input, OnInit } from '@angular/core';
import { import {
CoreCourseModuleCompletionStatus, CoreCourseModuleCompletionStatus,
CoreCourseModuleCompletionTracking, CoreCourseModuleCompletionTracking,
@ -21,7 +21,6 @@ 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 { IonContent } from '@ionic/angular';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { ModalController } from '@singletons'; import { ModalController } from '@singletons';
@ -35,8 +34,6 @@ import { ModalController } from '@singletons';
}) })
export class CoreCourseCourseIndexComponent implements OnInit { export class CoreCourseCourseIndexComponent implements OnInit {
@ViewChild(IonContent) content?: IonContent;
@Input() sections: CoreCourseSection[] = []; @Input() sections: CoreCourseSection[] = [];
@Input() selectedId?: number; @Input() selectedId?: number;
@Input() course?: CoreCourseAnyCourseData; @Input() course?: CoreCourseAnyCourseData;
@ -112,13 +109,10 @@ export class CoreCourseCourseIndexComponent implements OnInit {
this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course); this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course);
setTimeout(() => { CoreDomUtils.scrollViewToElement(
CoreDomUtils.scrollToElementBySelector( this.elementRef.nativeElement,
this.elementRef.nativeElement, '.item.item-current',
this.content, );
'.item.item-current',
);
}, 300);
} }
/** /**

View File

@ -13,8 +13,8 @@
// limitations under the License. // limitations under the License.
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { AfterViewInit, Component, ElementRef, OnDestroy, Optional } from '@angular/core'; import { AfterViewInit, Component, ElementRef, OnDestroy } from '@angular/core';
import { IonContent, IonRefresher } from '@ionic/angular'; import { IonRefresher } from '@ionic/angular';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreGrades } from '@features/grades/services/grades'; import { CoreGrades } from '@features/grades/services/grades';
@ -59,7 +59,6 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
constructor( constructor(
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected element: ElementRef<HTMLElement>, protected element: ElementRef<HTMLElement>,
@Optional() protected content?: IonContent,
) { ) {
try { try {
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId', { route }); this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId', { route });
@ -170,11 +169,9 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
if (row) { if (row) {
this.toggleRow(row, true); this.toggleRow(row, true);
await CoreUtils.nextTick();
CoreDomUtils.scrollToElementBySelector( CoreDomUtils.scrollViewToElement(
this.element.nativeElement, this.element.nativeElement,
this.content,
'#grade-' + row.id, '#grade-' + row.id,
); );
this.gradeId = undefined; this.gradeId = undefined;

View File

@ -14,7 +14,7 @@
import { Component, ViewChild, ElementRef, OnInit, ChangeDetectorRef } from '@angular/core'; import { Component, ViewChild, ElementRef, OnInit, ChangeDetectorRef } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms'; import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { IonContent, IonRefresher } from '@ionic/angular'; import { IonRefresher } from '@ionic/angular';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
@ -46,7 +46,6 @@ import { CoreText } from '@singletons/text';
}) })
export class CoreLoginEmailSignupPage implements OnInit { export class CoreLoginEmailSignupPage implements OnInit {
@ViewChild(IonContent) content?: IonContent;
@ViewChild(CoreRecaptchaComponent) recaptchaComponent?: CoreRecaptchaComponent; @ViewChild(CoreRecaptchaComponent) recaptchaComponent?: CoreRecaptchaComponent;
@ViewChild('ageForm') ageFormElement?: ElementRef; @ViewChild('ageForm') ageFormElement?: ElementRef;
@ViewChild('signupFormEl') signupFormElement?: ElementRef; @ViewChild('signupFormEl') signupFormElement?: ElementRef;
@ -285,9 +284,8 @@ 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 = CoreDomUtils.scrollToInputError( const errorFound = await CoreDomUtils.scrollViewToInputError(
this.elementRef.nativeElement, this.elementRef.nativeElement,
this.content,
); );
if (!errorFound) { if (!errorFound) {

View File

@ -725,10 +725,14 @@ export class CoreDomUtilsProvider {
* @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.
*/ */
getElementXY(container: HTMLElement, selector: undefined, positionParentClass?: string): number[]; getElementXY(container: HTMLElement, selector?: undefined, positionParentClass?: string): number[];
getElementXY(container: HTMLElement, selector: string, positionParentClass?: string): number[] | null; getElementXY(container: HTMLElement, selector: string, positionParentClass?: string): number[] | null;
getElementXY(container: HTMLElement, selector?: string, positionParentClass?: string): number[] | null { getElementXY(container: HTMLElement, selector?: string, positionParentClass = 'inner-scroll'): number[] | null {
let element: HTMLElement | null = <HTMLElement> (selector ? container.querySelector(selector) : container); let element = (selector ? container.querySelector<HTMLElement>(selector) : container);
if (!element) {
return null;
}
let positionTop = 0; let positionTop = 0;
let positionLeft = 0; let positionLeft = 0;
@ -736,10 +740,6 @@ export class CoreDomUtilsProvider {
positionParentClass = 'inner-scroll'; positionParentClass = 'inner-scroll';
} }
if (!element) {
return null;
}
while (element) { while (element) {
positionLeft += (element.offsetLeft - element.scrollLeft + element.clientLeft); positionLeft += (element.offsetLeft - element.scrollLeft + element.clientLeft);
positionTop += (element.offsetTop - element.scrollTop + element.clientTop); positionTop += (element.offsetTop - element.scrollTop + element.clientTop);
@ -766,6 +766,25 @@ export class CoreDomUtilsProvider {
return [positionLeft, positionTop]; return [positionLeft, positionTop];
} }
/**
* 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): { x: number; y: number} {
// 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.
* *
@ -1096,11 +1115,9 @@ export class CoreDomUtilsProvider {
* @param selector Selector to search. * @param selector Selector to search.
*/ */
removeElement(element: HTMLElement, selector: string): void { removeElement(element: HTMLElement, selector: string): void {
if (element) { const selected = element.querySelector(selector);
const selected = element.querySelector(selector); if (selected) {
if (selected) { selected.remove();
selected.remove();
}
} }
} }
@ -1198,9 +1215,9 @@ export class CoreDomUtilsProvider {
} }
// Treat video posters. // Treat video posters.
if (media.tagName == 'VIDEO' && media.getAttribute('poster')) { const currentPoster = media.getAttribute('poster');
const currentPoster = media.getAttribute('poster'); if (media.tagName == 'VIDEO' && currentPoster) {
const newPoster = paths[CoreTextUtils.decodeURIComponent(currentPoster!)]; const newPoster = paths[CoreTextUtils.decodeURIComponent(currentPoster)];
if (newPoster !== undefined) { if (newPoster !== undefined) {
media.setAttribute('poster', newPoster); media.setAttribute('poster', newPoster);
} }
@ -1237,8 +1254,8 @@ 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.
*/ */
scrollTo(content: IonContent, x: number, y: number, duration?: number): Promise<void> { scrollTo(content: IonContent, x: number, y: number, duration = 0): Promise<void> {
return content.scrollToPoint(x, y, duration || 0); return content.scrollToPoint(x, y, duration);
} }
/** /**
@ -1261,7 +1278,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.
*/ */
scrollToTop(content: IonContent, duration?: number): Promise<void> { scrollToTop(content: IonContent, duration = 0): Promise<void> {
return content.scrollToTop(duration); return content.scrollToTop(duration);
} }
@ -1308,7 +1325,7 @@ export class CoreDomUtilsProvider {
const scrollElement = await content.getScrollElement(); const scrollElement = await content.getScrollElement();
return scrollElement.scrollTop || 0; return scrollElement.scrollTop || 0;
} catch (error) { } catch {
return 0; return 0;
} }
} }
@ -1316,51 +1333,34 @@ export class CoreDomUtilsProvider {
/** /**
* Scroll to a certain element. * Scroll to a certain element.
* *
* @param content The content that must be scrolled.
* @param element The element to scroll to. * @param element The element to scroll to.
* @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll. * @param selector Selector to find the element to scroll to inside the defined element.
* @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 Wether the scroll suceeded.
*/ */
scrollToElement(content: IonContent, element: HTMLElement, scrollParentClass?: string, duration?: number): boolean { async scrollViewToElement(element: HTMLElement, selector?: string, duration = 0): Promise<boolean> {
const position = this.getElementXY(element, undefined, scrollParentClass); await CoreDomUtils.waitToBeInDOM(element);
if (!position) {
return false; if (selector) {
const foundElement = element.querySelector<HTMLElement>(selector);
if (!foundElement) {
// Element not found.
return false;
}
element = foundElement;
} }
content.scrollToPoint(position[0], position[1], duration || 0); const content = element.closest<HTMLIonContentElement>('ion-content') ?? undefined;
if (!content) {
return true; // Content to scroll, not found.
}
/**
* Scroll to a certain element using a selector to find it.
*
* @param container The element that contains the element that must be scrolled.
* @param content The content that must be scrolled.
* @param selector Selector to find the element to scroll to.
* @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll.
* @param duration Duration of the scroll animation in milliseconds.
* @return True if the element is found, false otherwise.
*/
scrollToElementBySelector(
container: HTMLElement | null,
content: IonContent | undefined,
selector: string,
scrollParentClass?: string,
duration?: number,
): boolean {
if (!container || !content) {
return false; return false;
} }
try { try {
const position = this.getElementXY(container, selector, scrollParentClass); const position = CoreDomUtils.getRelativeElementPosition(element, content);
if (!position) {
return false;
}
content.scrollToPoint(position[0], position[1], duration || 0); await content.scrollToPoint(position.x, position.y, duration);
return true; return true;
} catch { } catch {
@ -1372,12 +1372,71 @@ export class CoreDomUtilsProvider {
* Search for an input with error (core-input-error directive) and scrolls to it if found. * 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. * @param container The element that contains the element that must be scrolled.
* @param content The content that must be scrolled.
* @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll.
* @return True if the element is found, false otherwise. * @return True if the element is found, false otherwise.
*/ */
scrollToInputError(container: HTMLElement | null, content?: IonContent, scrollParentClass?: string): boolean { async scrollViewToInputError(container: HTMLElement): Promise<boolean> {
return this.scrollToElementBySelector(container, content, '.core-input-error', scrollParentClass); return this.scrollViewToElement(container, '.core-input-error');
}
/**
* Scroll to a certain element.
*
* @param content Not used anymore.
* @param element The element to scroll to.
* @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.
*/
scrollToElement(content: IonContent, element: HTMLElement, scrollParentClass?: string, duration = 0): boolean {
CoreDomUtils.scrollViewToElement(element, undefined, duration);
return true;
}
/**
* Scroll to a certain element using a selector to find it.
*
* @param container The element that contains the element that must be scrolled.
* @param content Not used anymore.
* @param selector Selector to find the element to scroll to.
* @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.
*/
scrollToElementBySelector(
container: HTMLElement | null,
content: unknown | null,
selector: string,
scrollParentClass?: string,
duration = 0,
): boolean {
if (!container || !content) {
return false;
}
CoreDomUtils.scrollViewToElement(container, selector, duration);
return true;
}
/**
* 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.
* @deprecated since app 4.0 Use scrollViewToInputError instead.
*/
scrollToInputError(container: HTMLElement | null): boolean {
if (!container) {
return false;
}
this.scrollViewToInputError(container);
return true;
} }
/** /**

View File

@ -72,7 +72,7 @@
&:before { &:before {
content: ''; content: '';
height: 60px; height: 100%;
position: absolute; position: absolute;
@include position(null, 0, 0, 0); @include position(null, 0, 0, 0);
background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - var(--gradient-size)), rgba(var(--background-gradient-rgb), 1) calc(100% - 4px)); background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - var(--gradient-size)), rgba(var(--background-gradient-rgb), 1) calc(100% - 4px));