diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.scss b/src/addons/storagemanager/pages/course-storage/course-storage.scss index bf803e5ab..9f3ad5c36 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.scss +++ b/src/addons/storagemanager/pages/course-storage/course-storage.scss @@ -26,7 +26,6 @@ min-height: var(--course-storage-max-activity-height); position: absolute; @include position(0, 0, null, 0); - background: -webkit-linear-gradient(top, rgba(var(--background-gradient-rgb), 0) calc(100% - 30px), rgba(var(--background-gradient-rgb), 1) calc(100% - 20px)); background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - 30px), rgba(var(--background-gradient-rgb), 1) calc(100% - 20px)); z-index: 6; pointer-events: none; diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts index afd53beae..4582f38ce 100644 --- a/src/core/directives/format-text.ts +++ b/src/core/directives/format-text.ts @@ -90,8 +90,8 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo protected element: HTMLElement; protected emptyText = ''; - protected contentSpan: HTMLElement; - protected domPromise?: CoreCancellablePromise; + protected domPromises: CoreCancellablePromise[] = []; + protected domElementPromise?: CoreCancellablePromise; constructor( element: ElementRef, @@ -101,18 +101,10 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo CoreComponentsRegistry.register(element.nativeElement, this); this.element = element.nativeElement; - this.element.classList.add('core-format-text-loading'); // Hide contents until they're treated. - - const placeholder = document.createElement('span'); - placeholder.classList.add('core-format-text-loader'); - this.element.appendChild(placeholder); - - this.contentSpan = document.createElement('span'); - this.contentSpan.classList.add('core-format-text-content'); - this.element.appendChild(this.contentSpan); + this.element.classList.add('core-loading'); // Hide contents until they're treated. this.emptyText = this.hideIfEmpty ? '' : ' '; - this.contentSpan.innerHTML = this.emptyText; + this.element.innerHTML = this.emptyText; this.afterRender = new EventEmitter(); @@ -134,14 +126,15 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo * @inheritdoc */ ngOnDestroy(): void { - this.domPromise?.cancel(); + this.domElementPromise?.cancel(); + this.domPromises.forEach((promise) => { promise.cancel();}); } /** * @inheritdoc */ async ready(): Promise { - if (!this.element.classList.contains('core-format-text-loading')) { + if (!this.element.classList.contains('core-loading')) { return; } @@ -229,7 +222,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo * Add magnifying glass icons to view adapted images at full size. */ async addMagnifyingGlasses(): Promise { - const imgs = Array.from(this.contentSpan.querySelectorAll('.core-adapted-img-container > img')); + const imgs = Array.from(this.element.querySelectorAll('.core-adapted-img-container > img')); if (!imgs.length) { return; } @@ -311,7 +304,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo */ protected async finishRender(): Promise { // Show the element again. - this.element.classList.remove('core-format-text-loading'); + this.element.classList.remove('core-loading'); await CoreUtils.nextTick(); @@ -324,7 +317,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo */ protected async formatAndRenderContents(): Promise { if (!this.text) { - this.contentSpan.innerHTML = this.emptyText; // Remove current contents. + this.element.innerHTML = this.emptyText; // Remove current contents. await this.finishRender(); @@ -342,10 +335,10 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo // Disable media adapt to correctly calculate the height. this.element.classList.add('core-disable-media-adapt'); - this.contentSpan.innerHTML = ''; // Remove current contents. + this.element.innerHTML = ''; // Remove current contents. // Move the children to the current element to be able to calculate the height. - CoreDomUtils.moveChildren(result.div, this.contentSpan); + CoreDomUtils.moveChildren(result.div, this.element); await CoreUtils.nextTick(); @@ -362,7 +355,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo if (result.options.filter) { // Let filters handle HTML. We do it here because we don't want them to block the render of the text. CoreFilterDelegate.handleHtml( - this.contentSpan, + this.element, result.filters, this.viewContainerRef, result.options, @@ -557,9 +550,10 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo * @return The width of the element in pixels. */ protected async getElementWidth(): Promise { - this.domPromise = CoreDomUtils.waitToBeInDOM(this.element); - - await this.domPromise; + if (!this.domElementPromise) { + this.domElementPromise = CoreDomUtils.waitToBeInDOM(this.element); + } + await this.domElementPromise; let width = this.element.getBoundingClientRect().width; if (!width) { @@ -709,12 +703,15 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo newUrl += `&h=${privacyHash}`; } + const domPromise = CoreDomUtils.waitToBeInDOM(iframe); + this.domPromises.push(domPromise); + + await domPromise; + // Width and height are mandatory, we need to calculate them. let width: string | number; let height: string | number; - await CoreDomUtils.waitToBeInDOM(iframe, 5000); - if (iframe.width) { width = iframe.width; } else { diff --git a/src/core/directives/on-appear.ts b/src/core/directives/on-appear.ts index c894878dc..d3e9bd2da 100644 --- a/src/core/directives/on-appear.ts +++ b/src/core/directives/on-appear.ts @@ -37,7 +37,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy { * @inheritdoc */ async ngOnInit(): Promise { - this.visiblePromise = CoreDomUtils.waitToBeVisible(this.element); + this.visiblePromise = CoreDomUtils.waitToBeInViewport(this.element); await this.visiblePromise; diff --git a/src/core/directives/tests/format-text.test.ts b/src/core/directives/tests/format-text.test.ts index 8be19f17e..48cb3ca44 100644 --- a/src/core/directives/tests/format-text.test.ts +++ b/src/core/directives/tests/format-text.test.ts @@ -58,7 +58,7 @@ describe('CoreFormatTextDirective', () => { ); // Assert - const text = fixture.nativeElement.querySelector('core-format-text .core-format-text-content'); + const text = fixture.nativeElement.querySelector('core-format-text'); expect(text).not.toBeNull(); expect(text.innerHTML).toEqual(sentence); }); diff --git a/src/core/features/course/pages/index/index.html b/src/core/features/course/pages/index/index.html index 7c445a8af..dec61a652 100644 --- a/src/core/features/course/pages/index/index.html +++ b/src/core/features/course/pages/index/index.html @@ -29,7 +29,9 @@ -

{{ title }}

+

+ +

diff --git a/src/core/features/editor/components/rich-text-editor/core-editor-rich-text-editor.html b/src/core/features/editor/components/rich-text-editor/core-editor-rich-text-editor.html index 5e1ad41d8..a89f6356f 100644 --- a/src/core/features/editor/components/rich-text-editor/core-editor-rich-text-editor.html +++ b/src/core/features/editor/components/rich-text-editor/core-editor-rich-text-editor.html @@ -110,7 +110,7 @@ - diff --git a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts index 07bf8b799..c152c6c41 100644 --- a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts +++ b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts @@ -107,6 +107,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn protected languageChangedSubscription?: Subscription; protected resizeListener?: CoreEventObserver; protected domPromise?: CoreCancellablePromise; + protected buttonsDomPromise?: CoreCancellablePromise; rteEnabled = false; isPhone = false; @@ -176,8 +177,6 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn this.editorElement.oninput = this.onChange.bind(this); this.editorElement.onkeydown = this.moveCursor.bind(this); - await CoreDomUtils.waitToBeVisible(this.editorElement); - // Use paragraph on enter. document.execCommand('DefaultParagraphSeparator', false, 'p'); @@ -339,7 +338,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn return; } - if (this.isNullOrWhiteSpace(this.editorElement.innerText)) { + if (this.isNullOrWhiteSpace(this.editorElement.textContent)) { this.clearText(); } else { // The textarea and the form control must receive the original URLs. @@ -718,8 +717,17 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn /** * Hide the toolbar in phone mode. + * + * @param event Event. + * @param force If true it will not check the target of the event. */ - hideToolbar(event: Event): void { + hideToolbar(event: Event, force = false): void { + if (!force && event.target && this.element.contains(event.target as HTMLElement)) { + // Do not hide if clicked inside the editor area, except forced. + + return; + } + if (event.type == 'keyup' && !this.isValidKeyboardKey(event)) { return; } @@ -836,7 +844,10 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn const length = await this.toolbarSlides.length(); - await CoreDomUtils.waitToBeInDOM(this.toolbar.nativeElement, 5000); + // Cancel previous one, if any. + this.buttonsDomPromise?.cancel(); + this.buttonsDomPromise = CoreDomUtils.waitToBeInDOM(this.toolbar.nativeElement); + await this.buttonsDomPromise; const width = this.toolbar.nativeElement.getBoundingClientRect().width; @@ -1097,6 +1108,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn this.labelObserver?.disconnect(); this.resizeListener?.off(); this.domPromise?.cancel(); + this.buttonsDomPromise?.cancel(); } } diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index 0e8f1dfc1..2df0fcfc1 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -97,10 +97,9 @@ export class CoreDomUtilsProvider { * Wait an element to be in dom of another element. * * @param element Element to wait. - * @param timeout If defined, timeout to wait before rejecting the promise. * @return Cancellable promise. */ - waitToBeInDOM(element: HTMLElement, timeout?: number): CoreCancellablePromise { + waitToBeInDOM(element: HTMLElement): CoreCancellablePromise { const root = element.getRootNode({ composed: true }); if (root === document) { @@ -109,33 +108,24 @@ export class CoreDomUtilsProvider { } let observer: MutationObserver; - let observerTimeout: number | undefined; return new CoreCancellablePromise( - (resolve, reject) => { + (resolve) => { observer = new MutationObserver(() => { const root = element.getRootNode({ composed: true }); - if (root === document) { - observer?.disconnect(); - observerTimeout && clearTimeout(observerTimeout); - resolve(); + if (root !== document) { + return; } + + observer?.disconnect(); + resolve(); }); - if (timeout) { - observerTimeout = window.setTimeout(() => { - observer?.disconnect(); - - reject(new Error('Waiting for DOM timeout reached')); - }, timeout); - } - observer.observe(document.body, { subtree: true, childList: true }); }, () => { observer?.disconnect(); - observerTimeout && clearTimeout(observerTimeout); }, ); } @@ -151,6 +141,7 @@ export class CoreDomUtilsProvider { let interval: number | undefined; + // Mutations did not observe for visibility properties. return new CoreCancellablePromise( async (resolve) => { await domPromise; @@ -175,6 +166,60 @@ export class CoreDomUtilsProvider { ); } + /** + * 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 { + const visiblePromise = CoreDomUtils.waitToBeVisible(element); + + let intersectionObserver: IntersectionObserver; + let interval: number | undefined; + + return new CoreCancellablePromise( + 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); + }, + ); + } + /** * 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. @@ -880,10 +925,21 @@ 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) { @@ -895,7 +951,35 @@ export class CoreDomUtilsProvider { return false; } - return element.offsetParent !== null; + 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; } /** diff --git a/src/core/services/utils/iframe.ts b/src/core/services/utils/iframe.ts index 2e93414c7..7a2fb3727 100644 --- a/src/core/services/utils/iframe.ts +++ b/src/core/services/utils/iframe.ts @@ -51,7 +51,7 @@ export class CoreIframeUtilsProvider { protected waitAutoLoginDefer?: PromiseDefer; constructor() { - this.logger = CoreLogger.getInstance('CoreUtilsProvider'); + this.logger = CoreLogger.getInstance('CoreIframeUtilsProvider'); } /** @@ -237,7 +237,7 @@ export class CoreIframeUtilsProvider { contentDocument = 'contentDocument' in element && element.contentDocument ? element.contentDocument : contentWindow && contentWindow.document; - } catch (ex) { + } catch { // Ignore errors. } @@ -250,7 +250,7 @@ export class CoreIframeUtilsProvider { // It's probably an . Try to get the window and the document. try { contentDocument = element.getSVGDocument(); - } catch (ex) { + } catch { // Ignore errors. } @@ -310,10 +310,10 @@ export class CoreIframeUtilsProvider { }; } - if (contentDocument) { + if (contentDocument.body) { // Search sub frames. CoreIframeUtilsProvider.FRAME_TAGS.forEach((tag) => { - const elements = Array.from(contentDocument.querySelectorAll(tag)); + const elements = Array.from(contentDocument.body.querySelectorAll(tag)); elements.forEach((subElement: CoreFrameElement) => { this.treatFrame(subElement, true); }); @@ -333,6 +333,8 @@ export class CoreIframeUtilsProvider { return; } + element.classList.add('core-loading'); + const treatElement = (sendResizeEvent: boolean = false) => { this.checkOnlineFrameInOffline(element, isSubframe); @@ -348,9 +350,12 @@ export class CoreIframeUtilsProvider { this.treatFrameLinks(element, document); } + // Iframe content has been loaded. // Send a resize events to the iframe so it calculates the right size if needed. if (window && sendResizeEvent) { - setTimeout(() => window.dispatchEvent(new Event('resize')), 1000); + element.classList.remove('core-loading'); + + setTimeout(() => window.dispatchEvent && window.dispatchEvent(new Event('resize')), 1000); } }; diff --git a/src/theme/components/collapsible-item.scss b/src/theme/components/collapsible-item.scss index 33d010fda..7297a8f0c 100644 --- a/src/theme/components/collapsible-item.scss +++ b/src/theme/components/collapsible-item.scss @@ -75,7 +75,6 @@ height: 60px; position: absolute; @include position(null, 0, 0, 0); - background: -webkit-linear-gradient(top, 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)); z-index: 6; } diff --git a/src/theme/components/format-text.scss b/src/theme/components/format-text.scss index 3b35bff23..a242fe175 100644 --- a/src/theme/components/format-text.scss +++ b/src/theme/components/format-text.scss @@ -5,77 +5,38 @@ core-format-text { --core-format-text-background: var(--background, var(--ion-item-background)); --core-format-text-viewer-icon-background: rgba(255, 255, 255, .5); - --core-format-text-loader-shine: 251,251,251; } body.dark core-format-text { --core-format-text-viewer-icon-background: rgba(0, 0, 0, .5); - --core-format-text-loader-shine: 90,90,90; } core-format-text { display: contents; + user-select: text; + word-break: break-word; + word-wrap: break-word; - .core-format-text-loader { - opacity: 0; - @include core-transition(opacity, 200ms); - display: contents; - } - - &.core-format-text-loading { - position: relative; + @include core-transition(background-color color, 200ms); + &.core-loading { width: 100%; - height: 100%; - opacity: 1; - background-color: rgba(0,0,0,.1); - overflow: hidden; - display: block; - border-radius: var(--small-radius); - - .core-format-text-loader { - position: absolute; - left: -45%; - height: 100%; - width: 45%; - background-image: -webkit-linear-gradient(to left, rgba(var(--core-format-text-loader-shine), .05), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .6), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .05)); - background-image: linear-gradient(to left, rgba(var(--core-format-text-loader-shine), .05), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .6), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .05)); - animation: loading 1s infinite; - opacity: 1; - display: inline; - } - - .core-format-text-content { + &:empty:before { + content: 'E'; // Set a minimum empty text to have a minimum height of one line. opacity: 0; - display: inline; } } - .core-format-text-content { - opacity: 1; - @include core-transition(opacity, 200ms); - - display: contents; - user-select: text; - word-break: break-word; - word-wrap: break-word; - } - &.collapsible-item { display: block; // This is to allow clicks in radio/checkbox content. cursor: pointer; pointer-events: auto; - .core-format-text-content { - display: block; - } - &.collapsible-enabled { - .core-format-text-content { - display: block; - max-height: none; - } - &.collapsible-collapsed .core-format-text-content { + display: block; + max-height: none; + + &.collapsible-collapsed { overflow: hidden; } } @@ -97,7 +58,7 @@ core-format-text { } &.collapsible-item.inline { display: inline-block; - &.collapsible-enabled .core-format-text-content { + &.collapsible-enabled { display: inline-block; } } @@ -141,7 +102,7 @@ core-format-text { } // Disable clicks in links inside MathJax equations. - .core-format-text-content .filter_mathjaxloader_equation .MathJax_Preview a { + .filter_mathjaxloader_equation .MathJax_Preview a { pointer-events: none; } @@ -243,7 +204,7 @@ core-format-text { } -core-format-text .core-format-text-content, +core-format-text, core-rich-text-editor .core-rte-editor { @include core-headings(); @@ -657,14 +618,10 @@ core-rich-text-editor .core-rte-editor { // h1 is too big and ugly, reduce size when loading. ion-header.ios h1 core-format-text { - &.core-format-text-loading { + &.core-loading { max-height: 30px; margin-top: 10px; } - &.core-format-text-content { - display: block; - margin-top: -10px; - } } body.dark core-format-text select, diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 250f07ba4..ac9e6756d 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -120,15 +120,15 @@ body { } // Correctly inherit ion-text-wrap onto labels. -.item ion-label core-format-text .core-format-text-content > *, -.fake-ion-item core-format-text .core-format-text-content > * { +.item ion-label core-format-text > *, +.fake-ion-item core-format-text > * { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.item.ion-text-wrap > ion-label core-format-text .core-format-text-content > *, -.fake-ion-item.ion-text-wrap core-format-text .core-format-text-content > * { +.item.ion-text-wrap > ion-label core-format-text > *, +.fake-ion-item.ion-text-wrap core-format-text > * { white-space: normal; overflow: inherit; } @@ -334,7 +334,7 @@ ion-button.button-solid { --box-shadow: none; } -ion-button core-format-text .core-format-text-content { +ion-button core-format-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -350,7 +350,7 @@ ion-button > * { ion-button.ion-text-wrap { white-space: normal; - core-format-text .core-format-text-content { + core-format-text { white-space: normal; display: contents; } @@ -1516,3 +1516,36 @@ ion-header.no-title { flex-grow: 1; } } + + +// Loader animation. +.core-loading { + position: relative; + background-color: var(--loader-background-color); + color: transparent; // Hide contents. + overflow: hidden; + display: var(--loader-display); + border-radius: var(--loader-radius); + @include core-transition(all, 200ms); + min-height: 8px; + min-width: 50px; + + // Hide contents. + > * { + opacity: 0; + @include core-transition(opacity, 200ms); + } + + &::after { + content: ''; + position: absolute; + left: -45%; + height: 100%; + width: 45%; + background-image: linear-gradient(to left, rgba(var(--loader-shine), .05), rgba(var(--loader-shine), .3), rgba(var(--loader-shine), .6), rgba(var(--loader-shine), .3), rgba(var(--loader-shine), .05)); + animation: loading 1s infinite; + display: block; + top: 0; + bottom: 0; + } +} diff --git a/src/theme/theme.dark.scss b/src/theme/theme.dark.scss index 15d9b7e14..ecd188b0c 100644 --- a/src/theme/theme.dark.scss +++ b/src/theme/theme.dark.scss @@ -41,6 +41,8 @@ --contrast-background: var(--gray-900); + --loader-shine: 90, 90, 90; + --drop-shadow-color: 0, 0, 0, 1; --drop-shadow-top: 0px 2px 5px rgba(var(--drop-shadow-color)); --drop-shadow-bottom: 0px -2px 5px rgba(var(--drop-shadow-color)); diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss index 533c22f1d..284d216cb 100644 --- a/src/theme/theme.light.scss +++ b/src/theme/theme.light.scss @@ -85,6 +85,11 @@ --contrast-background: white; + --loader-background-color: rgba(0, 0, 0, .1); + --loader-shine: 251, 251, 251; + --loader-radius: var(--small-radius); + --loader-display: block; + --drop-shadow-color: 0, 0, 0, 0.5; --drop-shadow-top: 0px 2px 5px rgba(var(--drop-shadow-color)); --drop-shadow-bottom: 0px -2px 5px rgba(var(--drop-shadow-color));