diff --git a/src/addons/messages/pages/discussion/discussion.page.ts b/src/addons/messages/pages/discussion/discussion.page.ts index d2823438f..257881e38 100644 --- a/src/addons/messages/pages/discussion/discussion.page.ts +++ b/src/addons/messages/pages/discussion/discussion.page.ts @@ -1106,10 +1106,10 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView * Scroll to the first new unread message. */ scrollToFirstUnreadMessage(): void { - if (this.newMessages > 0 && this.content) { - const messages = Array.from(this.hostElement.querySelectorAll('.addon-message-not-mine')); + if (this.newMessages > 0) { + const messages = Array.from(this.hostElement.querySelectorAll('.addon-message-not-mine')); - CoreDomUtils.scrollToElement(this.content, messages[messages.length - this.newMessages]); + CoreDomUtils.scrollViewToElement(messages[messages.length - this.newMessages]); } } diff --git a/src/addons/mod/chat/pages/chat/chat.ts b/src/addons/mod/chat/pages/chat/chat.ts index 809d5106b..595287512 100644 --- a/src/addons/mod/chat/pages/chat/chat.ts +++ b/src/addons/mod/chat/pages/chat/chat.ts @@ -159,7 +159,7 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave { this.messages[this.messages.length - 1].showTail = true; // New messages or beeps, scroll to bottom. - setTimeout(() => this.scrollToBottom()); + this.scrollToBottom(); } protected async loadMessageBeepWho(message: AddonModChatFormattedMessage): Promise { @@ -341,13 +341,12 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave { /** * Scroll bottom when render has finished. */ - scrollToBottom(): void { + async scrollToBottom(): Promise { // Need a timeout to leave time to the view to be rendered. - setTimeout(() => { - if (!this.viewDestroyed) { - this.content?.scrollToBottom(); - } - }); + await CoreUtils.nextTick(); + if (!this.viewDestroyed) { + this.content?.scrollToBottom(); + } } /** diff --git a/src/addons/mod/data/pages/edit/edit.ts b/src/addons/mod/data/pages/edit/edit.ts index e6878e177..87e4d0323 100644 --- a/src/addons/mod/data/pages/edit/edit.ts +++ b/src/addons/mod/data/pages/edit/edit.ts @@ -352,9 +352,7 @@ export class AddonModDataEditPage implements OnInit { } this.jsData!.errors = this.errors; - setTimeout(() => { - this.scrollToFirstError(); - }); + this.scrollToFirstError(); } } finally { modal.dismiss(); @@ -449,8 +447,9 @@ export class AddonModDataEditPage implements OnInit { /** * Scroll to first error or to the top if not found. */ - protected scrollToFirstError(): void { - if (!CoreDomUtils.scrollToElementBySelector(this.formElement.nativeElement, this.content, '.addon-data-error')) { + protected async scrollToFirstError(): Promise { + const scrolled = await CoreDomUtils.scrollViewToElement(this.formElement.nativeElement, '.addon-data-error'); + if (!scrolled) { this.content?.scrollToTop(); } } diff --git a/src/addons/mod/forum/components/post/post.ts b/src/addons/mod/forum/components/post/post.ts index cfd60670f..7d2a3e31c 100644 --- a/src/addons/mod/forum/components/post/post.ts +++ b/src/addons/mod/forum/components/post/post.ts @@ -20,7 +20,6 @@ import { OnChanges, OnDestroy, OnInit, - Optional, Output, SimpleChange, ViewChild, @@ -41,7 +40,6 @@ import { import { CoreTag } from '@features/tag/services/tag'; import { Translate } from '@singletons'; import { CoreFileUploader } from '@features/fileuploader/services/fileuploader'; -import { IonContent } from '@ionic/angular'; import { AddonModForumSync } from '../../services/forum-sync'; import { CoreSync } from '@services/sync'; import { CoreTextUtils } from '@services/utils/text'; @@ -94,7 +92,6 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges constructor( protected elementRef: ElementRef, - @Optional() protected content?: IonContent, ) {} get showForm(): boolean { @@ -308,8 +305,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges this.post.id > 0 ? this.post.id : undefined, ); - this.scrollToForm(5); - } catch (error) { + this.scrollToForm(); + } catch { // Cancelled. } } @@ -540,19 +537,11 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges /** * Scroll to reply/edit form. * - * @param ticksToWait Number of ticks to wait before scrolling. * @return Promise resolved when done. */ - protected async scrollToForm(ticksToWait = 1): Promise { - if (!this.content) { - return; - } - - await CoreUtils.nextTicks(ticksToWait); - - CoreDomUtils.scrollToElementBySelector( + protected async scrollToForm(): Promise { + await CoreDomUtils.scrollViewToElement( this.elementRef.nativeElement, - this.content, '#addon-forum-reply-edit-form-' + this.uniqueId, ); } diff --git a/src/addons/mod/forum/pages/discussion/discussion.page.ts b/src/addons/mod/forum/pages/discussion/discussion.page.ts index 0f735704b..605821169 100644 --- a/src/addons/mod/forum/pages/discussion/discussion.page.ts +++ b/src/addons/mod/forum/pages/discussion/discussion.page.ts @@ -187,13 +187,10 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes const scrollTo = this.postId || this.parent; if (scrollTo) { // Scroll to the post. - setTimeout(() => { - CoreDomUtils.scrollToElementBySelector( - this.elementRef.nativeElement, - this.content, - '#addon-mod_forum-post-' + scrollTo, - ); - }); + CoreDomUtils.scrollViewToElement( + this.elementRef.nativeElement, + '#addon-mod_forum-post-' + scrollTo, + ); } } diff --git a/src/addons/mod/quiz/components/preflight-modal/preflight-modal.ts b/src/addons/mod/quiz/components/preflight-modal/preflight-modal.ts index 2bc91c409..69685cdf3 100644 --- a/src/addons/mod/quiz/components/preflight-modal/preflight-modal.ts +++ b/src/addons/mod/quiz/components/preflight-modal/preflight-modal.ts @@ -14,7 +14,6 @@ import { Component, OnInit, ViewChild, ElementRef, Input, Type } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { IonContent } from '@ionic/angular'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; @@ -32,7 +31,6 @@ import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '../../service }) export class AddonModQuizPreflightModalComponent implements OnInit { - @ViewChild(IonContent) content?: IonContent; @ViewChild('preflightFormEl') formElement?: ElementRef; @Input() title!: string; @@ -111,15 +109,14 @@ export class AddonModQuizPreflightModalComponent implements OnInit { * * @param e Event. */ - sendData(e: Event): void { + async sendData(e: Event): Promise { e.preventDefault(); e.stopPropagation(); if (!this.preflightForm.valid) { // Form not valid. Scroll to the first element with errors. - const hasScrolled = CoreDomUtils.scrollToInputError( + const hasScrolled = await CoreDomUtils.scrollViewToInputError( this.elementRef.nativeElement, - this.content, ); if (!hasScrolled) { diff --git a/src/addons/mod/quiz/pages/player/player.page.ts b/src/addons/mod/quiz/pages/player/player.page.ts index d022c6d5e..4a01e09de 100644 --- a/src/addons/mod/quiz/pages/player/player.page.ts +++ b/src/addons/mod/quiz/pages/player/player.page.ts @@ -318,10 +318,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { this.loaded = true; if (slot !== undefined) { - // Scroll to the question. Give some time to the questions to render. - setTimeout(() => { - this.scrollToQuestion(slot); - }, 2000); + // Scroll to the question. + this.scrollToQuestion(slot); } } } @@ -689,9 +687,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { * @param slot Slot of the question to scroll to. */ protected scrollToQuestion(slot: number): void { - CoreDomUtils.scrollToElementBySelector( + CoreDomUtils.scrollViewToElement( this.elementRef.nativeElement, - this.content, '#addon-mod_quiz-question-' + slot, ); } diff --git a/src/addons/mod/quiz/pages/review/review.page.ts b/src/addons/mod/quiz/pages/review/review.page.ts index 1630ec38b..2ebdebe5e 100644 --- a/src/addons/mod/quiz/pages/review/review.page.ts +++ b/src/addons/mod/quiz/pages/review/review.page.ts @@ -133,10 +133,8 @@ export class AddonModQuizReviewPage implements OnInit { this.loaded = true; if (slot !== undefined) { - // Scroll to the question. Give some time to the questions to render. - setTimeout(() => { - this.scrollToQuestion(slot); - }, 2000); + // Scroll to the question. + this.scrollToQuestion(slot); } } } @@ -249,9 +247,8 @@ export class AddonModQuizReviewPage implements OnInit { * @param slot Slot of the question to scroll to. */ protected scrollToQuestion(slot: number): void { - CoreDomUtils.scrollToElementBySelector( + CoreDomUtils.scrollViewToElement( this.elementRef.nativeElement, - this.content, `#addon-mod_quiz-question-${slot}`, ); } diff --git a/src/core/directives/link.ts b/src/core/directives/link.ts index 31a405e88..18a26de30 100644 --- a/src/core/directives/link.ts +++ b/src/core/directives/link.ts @@ -142,11 +142,13 @@ export class CoreLinkDirective implements OnInit { if (href.charAt(0) == '#') { // Look for id or name. href = href.substring(1); - CoreDomUtils.scrollToElementBySelector( - this.element.closest('ion-content'), - this.content, - `#${href}, [name='${href}']`, - ); + const container = this.element.closest('ion-content'); + if (container) { + CoreDomUtils.scrollViewToElement( + container, + `#${href}, [name='${href}']`, + ); + } return; } diff --git a/src/core/features/course/components/course-format/course-format.ts b/src/core/features/course/components/course-format/course-format.ts index 453d03c2f..4f100845e 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -492,9 +492,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { // Scroll to module if needed. Give more priority to the input. const moduleIdToScroll = this.moduleId && previousValue === undefined ? this.moduleId : moduleId; if (moduleIdToScroll) { - setTimeout(() => { - this.scrollToModule(moduleIdToScroll); - }, 200); + this.scrollToModule(moduleIdToScroll); } else { this.content.scrollToTop(0); } @@ -513,9 +511,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * @param moduleId Module ID. */ protected scrollToModule(moduleId: number): void { - CoreDomUtils.scrollToElementBySelector( + CoreDomUtils.scrollViewToElement( this.elementRef.nativeElement, - this.content, '#core-course-module-' + moduleId, ); } diff --git a/src/core/features/course/components/course-index/course-index.ts b/src/core/features/course/components/course-index/course-index.ts index ce2c1667d..6643c02f8 100644 --- a/src/core/features/course/components/course-index/course-index.ts +++ b/src/core/features/course/components/course-index/course-index.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, ElementRef, Input, OnInit } from '@angular/core'; import { CoreCourseModuleCompletionStatus, CoreCourseModuleCompletionTracking, @@ -21,7 +21,6 @@ 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 { IonContent } from '@ionic/angular'; import { CoreDomUtils } from '@services/utils/dom'; import { ModalController } from '@singletons'; @@ -35,8 +34,6 @@ import { ModalController } from '@singletons'; }) export class CoreCourseCourseIndexComponent implements OnInit { - @ViewChild(IonContent) content?: IonContent; - @Input() sections: CoreCourseSection[] = []; @Input() selectedId?: number; @Input() course?: CoreCourseAnyCourseData; @@ -112,13 +109,10 @@ export class CoreCourseCourseIndexComponent implements OnInit { this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course); - setTimeout(() => { - CoreDomUtils.scrollToElementBySelector( - this.elementRef.nativeElement, - this.content, - '.item.item-current', - ); - }, 300); + CoreDomUtils.scrollViewToElement( + this.elementRef.nativeElement, + '.item.item-current', + ); } /** diff --git a/src/core/features/grades/pages/course/course.page.ts b/src/core/features/grades/pages/course/course.page.ts index f877aba9c..ccb3c92d3 100644 --- a/src/core/features/grades/pages/course/course.page.ts +++ b/src/core/features/grades/pages/course/course.page.ts @@ -13,8 +13,8 @@ // limitations under the License. import { ActivatedRoute } from '@angular/router'; -import { AfterViewInit, Component, ElementRef, OnDestroy, Optional } from '@angular/core'; -import { IonContent, IonRefresher } from '@ionic/angular'; +import { AfterViewInit, Component, ElementRef, OnDestroy } from '@angular/core'; +import { IonRefresher } from '@ionic/angular'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreGrades } from '@features/grades/services/grades'; @@ -59,7 +59,6 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { constructor( protected route: ActivatedRoute, protected element: ElementRef, - @Optional() protected content?: IonContent, ) { try { this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId', { route }); @@ -170,11 +169,9 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { if (row) { this.toggleRow(row, true); - await CoreUtils.nextTick(); - CoreDomUtils.scrollToElementBySelector( + CoreDomUtils.scrollViewToElement( this.element.nativeElement, - this.content, '#grade-' + row.id, ); this.gradeId = undefined; diff --git a/src/core/features/login/pages/email-signup/email-signup.ts b/src/core/features/login/pages/email-signup/email-signup.ts index f74f28463..7e18428b1 100644 --- a/src/core/features/login/pages/email-signup/email-signup.ts +++ b/src/core/features/login/pages/email-signup/email-signup.ts @@ -14,7 +14,7 @@ import { Component, ViewChild, ElementRef, OnInit, ChangeDetectorRef } from '@angular/core'; 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 { CoreDomUtils } from '@services/utils/dom'; @@ -46,7 +46,6 @@ import { CoreText } from '@singletons/text'; }) export class CoreLoginEmailSignupPage implements OnInit { - @ViewChild(IonContent) content?: IonContent; @ViewChild(CoreRecaptchaComponent) recaptchaComponent?: CoreRecaptchaComponent; @ViewChild('ageForm') ageFormElement?: ElementRef; @ViewChild('signupFormEl') signupFormElement?: ElementRef; @@ -285,9 +284,8 @@ export class CoreLoginEmailSignupPage implements OnInit { this.changeDetector.detectChanges(); // Scroll to the first element with errors. - const errorFound = CoreDomUtils.scrollToInputError( + const errorFound = await CoreDomUtils.scrollViewToInputError( this.elementRef.nativeElement, - this.content, ); if (!errorFound) { diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index a0f25b3c0..d718396ca 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -725,10 +725,14 @@ export class CoreDomUtilsProvider { * @param positionParentClass Parent Class where to stop calculating the position. Default inner-scroll. * @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 { - let element: HTMLElement | null = (selector ? container.querySelector(selector) : container); + getElementXY(container: HTMLElement, selector?: string, positionParentClass = 'inner-scroll'): number[] | null { + let element = (selector ? container.querySelector(selector) : container); + if (!element) { + return null; + } + let positionTop = 0; let positionLeft = 0; @@ -736,10 +740,6 @@ export class CoreDomUtilsProvider { positionParentClass = 'inner-scroll'; } - if (!element) { - return null; - } - while (element) { positionLeft += (element.offsetLeft - element.scrollLeft + element.clientLeft); positionTop += (element.offsetTop - element.scrollTop + element.clientTop); @@ -766,6 +766,25 @@ export class CoreDomUtilsProvider { 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. * @@ -1096,11 +1115,9 @@ export class CoreDomUtilsProvider { * @param selector Selector to search. */ removeElement(element: HTMLElement, selector: string): void { - if (element) { - const selected = element.querySelector(selector); - if (selected) { - selected.remove(); - } + const selected = element.querySelector(selector); + if (selected) { + selected.remove(); } } @@ -1198,9 +1215,9 @@ export class CoreDomUtilsProvider { } // Treat video posters. - if (media.tagName == 'VIDEO' && media.getAttribute('poster')) { - const currentPoster = media.getAttribute('poster'); - const newPoster = paths[CoreTextUtils.decodeURIComponent(currentPoster!)]; + const currentPoster = media.getAttribute('poster'); + if (media.tagName == 'VIDEO' && currentPoster) { + const newPoster = paths[CoreTextUtils.decodeURIComponent(currentPoster)]; if (newPoster !== undefined) { media.setAttribute('poster', newPoster); } @@ -1237,8 +1254,8 @@ 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. */ - scrollTo(content: IonContent, x: number, y: number, duration?: number): Promise { - return content.scrollToPoint(x, y, duration || 0); + scrollTo(content: IonContent, x: number, y: number, duration = 0): Promise { + 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. * @deprecated since 3.9.5. Use directly the IonContent class. */ - scrollToTop(content: IonContent, duration?: number): Promise { + scrollToTop(content: IonContent, duration = 0): Promise { return content.scrollToTop(duration); } @@ -1308,7 +1325,7 @@ export class CoreDomUtilsProvider { const scrollElement = await content.getScrollElement(); return scrollElement.scrollTop || 0; - } catch (error) { + } catch { return 0; } } @@ -1316,51 +1333,34 @@ export class CoreDomUtilsProvider { /** * Scroll to a certain element. * - * @param content The content that must be scrolled. * @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. - * @return True if the element is found, false otherwise. + * @return Wether the scroll suceeded. */ - scrollToElement(content: IonContent, element: HTMLElement, scrollParentClass?: string, duration?: number): boolean { - const position = this.getElementXY(element, undefined, scrollParentClass); - if (!position) { - return false; + async scrollViewToElement(element: HTMLElement, selector?: string, duration = 0): Promise { + await CoreDomUtils.waitToBeInDOM(element); + + if (selector) { + const foundElement = element.querySelector(selector); + if (!foundElement) { + // Element not found. + return false; + } + + element = foundElement; } - content.scrollToPoint(position[0], position[1], duration || 0); - - 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 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) { + const content = element.closest('ion-content') ?? undefined; + if (!content) { + // Content to scroll, not found. return false; } try { - const position = this.getElementXY(container, selector, scrollParentClass); - if (!position) { - return false; - } + const position = CoreDomUtils.getRelativeElementPosition(element, content); - content.scrollToPoint(position[0], position[1], duration || 0); + await content.scrollToPoint(position.x, position.y, duration); return true; } catch { @@ -1372,12 +1372,71 @@ export class CoreDomUtilsProvider { * 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 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. */ - scrollToInputError(container: HTMLElement | null, content?: IonContent, scrollParentClass?: string): boolean { - return this.scrollToElementBySelector(container, content, '.core-input-error', scrollParentClass); + async scrollViewToInputError(container: HTMLElement): Promise { + 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; } /** diff --git a/src/theme/components/collapsible-item.scss b/src/theme/components/collapsible-item.scss index 7297a8f0c..a40ea19c4 100644 --- a/src/theme/components/collapsible-item.scss +++ b/src/theme/components/collapsible-item.scss @@ -72,7 +72,7 @@ &:before { content: ''; - height: 60px; + height: 100%; position: absolute; @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));