Merge pull request #4042 from crazyserver/MOBILE-4470

Mobile 4470
main
Dani Palou 2024-05-10 15:41:52 +02:00 committed by GitHub
commit e016875439
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 541 additions and 527 deletions

View File

@ -1,11 +1,11 @@
<ion-content>
<ion-list>
<ion-item button class="ion-text-wrap" (click)="onItemClick(item)" *ngFor="let item of items" [detail]="false"
<ion-item button class="ion-text-wrap icon-margin-reduced" (click)="onItemClick(item)" *ngFor="let item of items" [detail]="false"
[attr.aria-label]="item.text | translate">
<ion-icon [name]="item.icon" slot="start" aria-hidden="true" />
<ion-label>
<p class="item-heading">{{ item.text | translate }}</p>
</ion-label>
<ion-icon [name]="item.icon" slot="end" aria-hidden="true" />
</ion-item>
</ion-list>

View File

@ -69,7 +69,7 @@
<ion-radio-group *ngIf="item.templateName === 'multichoice-r'" [(ngModel)]="item.value" [required]="item.required"
name="{{item.typ}}_{{item.id}}">
<ion-item *ngFor="let option of item.choices" class="ion-text-wrap">
<ion-item *ngFor="let option of item.choices" class="ion-text-wrap" lines="none">
<ion-radio [value]="option.value">
<core-format-text [component]="component" [componentId]="cmId" [text]="option.label"
contextLevel="module" [contextInstanceId]="cmId" [wsNotFiltered]="true" [courseId]="courseId" />
@ -80,7 +80,7 @@
<ng-container *ngIf="item.templateName === 'multichoice-c'">
<ion-item *ngFor="let option of item.choices">
<ion-checkbox [required]="item.required" name="{{item.typ}}_{{item.id}}" [(ngModel)]="option.checked"
value="option.value">
value="option.value" lines="none">
<core-format-text [component]="component" [componentId]="cmId" [text]="option.label"
contextLevel="module" [contextInstanceId]="cmId" [wsNotFiltered]="true" [courseId]="courseId" />
</ion-checkbox>
@ -156,7 +156,7 @@
<ng-template #label let-item="item">
<p *ngIf="item.name" [core-mark-required]="item.required" [slot]="item.slottedLabel ? 'label' : undefined">
<p class="ion-text-wrap" *ngIf="item.name" [core-mark-required]="item.required" [slot]="item.slottedLabel ? 'label' : undefined">
<span *ngIf="feedback!.autonumbering && item.itemnumber">{{item.itemnumber}}. </span>
<core-format-text [component]="component" [componentId]="cmId" [text]="item.name" contextLevel="module" [contextInstanceId]="cmId"
[courseId]="courseId" [wsNotFiltered]="true" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -55,7 +55,7 @@
<!-- Template for units entered using radio buttons. -->
<ng-template #radioUnits>
<ion-radio-group [(ngModel)]="question!.unit" [name]="question!.optionsName">
<ion-item class="ion-text-wrap" *ngFor="let option of question!.options">
<ion-item class="ion-text-wrap" *ngFor="let option of question!.options" lines="none">
<ion-radio [value]="option.value" [disabled]="option.disabled || question!.input?.readOnly"
[color]="question!.input?.correctIconColor">
{{ option.text }}

View File

@ -41,7 +41,7 @@
<!-- Radio buttons for single choice. -->
<ion-radio-group *ngIf="!question.multi" [(ngModel)]="question.singleChoiceModel" [name]="question.optionsName">
<ion-item class="ion-text-wrap answer" *ngFor="let option of question.options">
<ion-item class="ion-text-wrap answer" *ngFor="let option of question.options" lines="none">
<ion-radio [value]="option.value" [disabled]="option.disabled"
[color]='(option.isCorrect === 1 ? "success": "") + (option.isCorrect === 0 ? "danger": "")'>
<div>

View File

@ -133,6 +133,7 @@ export class PageLoadsManager {
component: CoreRefreshButtonModalComponent,
cssClass: 'core-modal-no-background core-modal-fullscreen',
closeOnNavigate: true,
showBackdrop: false,
});
this.onRefreshPage.next();

View File

@ -1,38 +1,41 @@
<ion-content>
<ion-list>
<ion-item button class="ion-text-wrap" (click)="action('download')" *ngIf="downloadCourseEnabled" [detail]="false">
<ion-item button class="ion-text-wrap icon-margin-reduced" (click)="action('download')" *ngIf="downloadCourseEnabled"
[detail]="false">
<ion-icon *ngIf="!prefetch.loading" [name]="prefetch.icon" slot="start" aria-hidden="true" />
<ion-spinner *ngIf="prefetch.loading" slot="start" [attr.aria-label]="'core.loading' | translate" />
<ion-label>
<p class="item-heading">{{ prefetch.statusTranslatable | translate }}</p>
</ion-label>
</ion-item>
<ion-item button class="ion-text-wrap" (click)="action('delete')" [detail]="false"
<ion-item button class="ion-text-wrap icon-margin-reduced" (click)="action('delete')" [detail]="false"
*ngIf="prefetch.status === 'downloaded' || prefetch.status === 'outdated'">
<ion-icon name="fas-trash" slot="start" aria-hidden="true" />
<ion-label>
<p class="item-heading">{{ 'addon.storagemanager.deletedata' | translate }}</p>
</ion-label>
</ion-item>
<ion-item button class="ion-text-wrap" (click)="action('hide')" *ngIf="!course.hidden" [detail]="false">
<ion-item button class="ion-text-wrap icon-margin-reduced" (click)="action('hide')" *ngIf="!course.hidden" [detail]="false">
<ion-icon name="fas-eye" slot="start" aria-hidden="true" />
<ion-label>
<p class="item-heading">{{ 'core.courses.hidecourse' | translate }}</p>
</ion-label>
</ion-item>
<ion-item button class="ion-text-wrap" (click)="action('show')" *ngIf="course.hidden" [detail]="false">
<ion-item button class="ion-text-wrap icon-margin-reduced" (click)="action('show')" *ngIf="course.hidden" [detail]="false">
<ion-icon name="fas-eye-slash" slot="start" aria-hidden="true" />
<ion-label>
<p class="item-heading">{{ 'core.courses.show' | translate }}</p>
</ion-label>
</ion-item>
<ion-item button class="ion-text-wrap" (click)="action('favourite')" *ngIf="!course.isfavourite" [detail]="false">
<ion-item button class="ion-text-wrap icon-margin-reduced" (click)="action('favourite')" *ngIf="!course.isfavourite"
[detail]="false">
<ion-icon name="fas-star" slot="start" aria-hidden="true" />
<ion-label>
<p class="item-heading">{{ 'core.courses.addtofavourites' | translate }}</p>
</ion-label>
</ion-item>
<ion-item button class="ion-text-wrap" (click)="action('unfavourite')" *ngIf="course.isfavourite" [detail]="false">
<ion-item button class="ion-text-wrap icon-margin-reduced" (click)="action('unfavourite')" *ngIf="course.isfavourite"
[detail]="false">
<ion-icon name="far-star" slot="start" aria-hidden="true" />
<ion-label>
<p class="item-heading">{{ 'core.courses.removefromfavourites' | translate }}</p>

View File

@ -1,12 +1,12 @@
<div class="core-rte-editor-container" (click)="focusRTE()" [class.toolbar-hidden]="toolbarHidden">
<div class="core-rte-editor-container" (click)="focusRTE($event)" [class.toolbar-hidden]="toolbarHidden">
<div [hidden]="!rteEnabled" #editor class="core-rte-editor" role="textbox" contenteditable="true" [class.empty]="isEmpty"
[attr.aria-labelledby]="ariaLabelledBy" [attr.data-placeholder-text]="placeholder" (focus)="showToolbar($event)"
(blur)="hideToolbar($event)" (keydown)="onKeyDown($event)">
[attr.aria-labelledby]="ariaLabelledBy" [attr.data-placeholder-text]="placeholder" (focus)="focusRTE($event)"
(blur)="blurRTE($event)" (keydown)="onKeyDown($event)">
</div>
<ion-textarea [hidden]="rteEnabled" #textarea class="core-textarea" role="textbox" [attr.name]="name" ngControl="control"
[placeholder]="placeholder" [attr.aria-labelledby]="ariaLabelledBy" (ionChange)="onChange()" (ionFocus)="showToolbar($event)"
(ionBlur)="hideToolbar($event)" />
[placeholder]="placeholder" [attr.aria-labelledby]="ariaLabelledBy" (ionChange)="onChange()" (ionFocus)="focusRTE($event)"
(ionBlur)="blurRTE($event)" />
<div class="core-rte-info-message" *ngIf="infoMessage">
<ion-icon name="fas-circle-info" aria-hidden="true" />
@ -23,16 +23,16 @@
<swiper-container #swiperRef [slidesPerView]="swiperOpts.slidesPerView" (slidechangetransitionend)="updateToolbarArrows()">
<!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand -->
<swiper-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.strong" [title]="'core.editor.bold' | translate"
(click)="buttonAction($event, 'bold', 'strong')" (keyup)="buttonAction($event, 'bold', 'strong')"
(mousedown)="downAction($event)" (keydown)="downAction($event)" tabindex="0">
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.strong || toolbarStyles.b"
[title]="'core.editor.bold' | translate" (click)="buttonAction($event, 'bold', 'b')"
(keyup)="buttonAction($event, 'bold', 'b')" (mousedown)="downAction($event)" (keydown)="downAction($event)" tabindex="0">
<ion-icon name="fas-bold" aria-hidden="true" />
</button>
</swiper-slide>
<swiper-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.em" [title]="'core.editor.italic' | translate"
(click)="buttonAction($event, 'italic', 'em')" (keyup)="buttonAction($event, 'italic', 'em')"
(mousedown)="downAction($event)" (keydown)="downAction($event)" tabindex="0">
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.em || toolbarStyles.i"
[title]="'core.editor.italic' | translate" (click)="buttonAction($event, 'italic', 'i')"
(keyup)="buttonAction($event, 'italic', 'i')" (mousedown)="downAction($event)" (keydown)="downAction($event)" tabindex="0">
<ion-icon name="fas-italic" aria-hidden="true" />
</button>
</swiper-slide>
@ -94,13 +94,13 @@
</swiper-slide>
<swiper-slide>
<button [disabled]="!rteEnabled" (click)="buttonAction($event, 'removeFormat')" (keyup)="buttonAction($event, 'removeFormat')"
(mousedown)="downAction($event)" (keydown)="downAction($event)" [title]="'core.editor.clear' | translate">
(mousedown)="downAction($event)" (keydown)="downAction($event)" [title]="'core.editor.clear' | translate" tabindex="0">
<ion-icon name="fas-eraser" aria-hidden="true" />
</button>
</swiper-slide>
<swiper-slide *ngIf="canScanQR">
<button [disabled]="!rteEnabled" (click)="scanQR($event)" (keyup)="scanQR($event)" (mousedown)="stopBubble($event)"
(keydown)="stopBubble($event)" [title]="'core.scanqr' | translate">
(keydown)="stopBubble($event)" [title]="'core.scanqr' | translate" tabindex="0">
<ion-icon name="fas-qrcode" aria-hidden="true" />
</button>
</swiper-slide>
@ -110,12 +110,6 @@
<ion-icon name="fas-code" aria-hidden="true" />
</button>
</swiper-slide>
<swiper-slide *ngIf="isPhone">
<button [title]="'core.editor.hidetoolbar' | translate" (click)="hideToolbar($event, true)" (keyup)="hideToolbar($event, true)"
(mousedown)="downAction($event)" (keydown)="downAction($event)" tabindex="0">
<ion-icon name="fas-xmark" aria-hidden="true" />
</button>
</swiper-slide>
</swiper-container>
<button *ngIf="toolbarArrows" class="toolbar-arrow" [attr.disabled]="toolbarNextHidden ? 'true' : null"
[attr.aria-label]="'core.next' | translate" (click)="toolbarNext($event)" (keyup)="toolbarNext($event)"

View File

@ -25,7 +25,7 @@
border: 1px solid var(--stroke);
border-radius: var(--mdl-shape-borderRadius-md);
&:focus-within {
&.has-focus {
border-color: var(--a11y-focus-color);
border-width: 2px;
outline: none !important;
@ -98,18 +98,28 @@
.core-textarea {
position: relative;
--highlight-color: transparent !important;
--full-highlight-height: 0px !important;
::ng-deep textarea {
margin: 0px !important;
padding: 0px;
resize: none;
overflow-x: hidden;
overflow-y: auto;
position: absolute;
height: auto;
top: 0px;
bottom: 0px;
::ng-deep .textarea-wrapper,
::ng-deep .textarea-legacy-wrapper {
height: 100%;
.textarea-wrapper-inner {
height: 100%;
}
::ng-deep textarea {
caret-color: black;
margin: 0px !important;
padding: 0px;
resize: none;
overflow-x: hidden;
overflow-y: auto;
position: absolute;
height: auto;
top: 0px;
bottom: 0px;
}
}
}

View File

@ -78,9 +78,13 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
@Input() draftExtraParams?: Record<string, unknown>; // Extra params to identify the draft.
@Output() contentChanged: EventEmitter<string | undefined | null>;
@ViewChild('editor') editor?: ElementRef; // WYSIWYG editor.
protected editorElement?: HTMLDivElement; // WYSIWYG editor.
@ViewChild('editor') editor?: ElementRef<HTMLDivElement>;
@ViewChild('toolbar') toolbar?: ElementRef<HTMLDivElement>;
protected textareaElement?: HTMLTextAreaElement;
@ViewChild('textarea') textarea?: IonTextarea; // Textarea editor.
@ViewChild('toolbar') toolbar?: ElementRef;
protected toolbarSlides?: Swiper;
@ViewChild('swiperRef') set swiperRef(swiperRef: ElementRef) {
@ -88,7 +92,9 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
* This setTimeout waits for Ionic's async initialization to complete.
* Otherwise, an outdated swiper reference will be used.
*/
setTimeout(() => {
setTimeout(async () => {
await this.waitLoadingsDone();
const swiper = CoreSwiper.initSwiperIfAvailable(this.toolbarSlides, swiperRef, this.swiperOpts);
if (!swiper) {
return;
@ -102,8 +108,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
protected readonly RESTORE_MESSAGE_CLEAR_TIME = 6000;
protected readonly SAVE_MESSAGE_CLEAR_TIME = 2000;
protected element: HTMLDivElement;
protected editorElement?: HTMLDivElement;
protected element: HTMLElement;
protected minHeight = 200; // Minimum height of the editor.
protected valueChangeSubscription?: Subscription;
@ -127,6 +132,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
protected domPromise?: CoreCancellablePromise<void>;
protected buttonsDomPromise?: CoreCancellablePromise<void>;
protected shortcutCommands?: Record<string, EditorCommand>;
protected blurTimeout?: number;
rteEnabled = false;
isPhone = false;
@ -139,7 +145,9 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
infoMessage?: string;
toolbarStyles = {
strong: 'false',
b: 'false',
em: 'false',
i: 'false',
u: 'false',
strike: 'false',
p: 'false',
@ -163,7 +171,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
elementRef: ElementRef,
) {
this.contentChanged = new EventEmitter<string>();
this.element = elementRef.nativeElement as HTMLDivElement;
this.element = elementRef.nativeElement;
this.pageInstance = 'app_' + Date.now(); // Generate a "unique" ID based on timestamp.
}
@ -186,6 +194,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
// Setup the editor.
this.editorElement = this.editor?.nativeElement as HTMLDivElement;
this.textareaElement = await this.textarea?.getInputElement();
this.setContent(this.control?.value);
this.originalContent = this.control?.value ?? undefined;
this.lastDraft = this.control?.value ?? '';
@ -216,11 +225,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
this.deleteDraftOnSubmitOrCancel();
}
const ionItem = this.element.closest<HTMLIonItemElement>('ion-item');
if (!ionItem) {
return;
}
ionItem.classList.add('item-rte');
this.setupIonItem();
if (this.editorElement) {
const debounceMutation = CoreUtils.debounce(() => {
@ -230,9 +235,19 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
this.contentObserver = new MutationObserver(debounceMutation);
this.contentObserver.observe(this.editorElement, { childList: true, subtree: true, characterData: true });
}
}
/**
* Setup Ion Item adding classes and managing aria-labelledby.
*/
protected setupIonItem(): void {
const ionItem = this.element.closest<HTMLIonItemElement>('ion-item');
if (!ionItem) {
return;
}
ionItem.classList.add('item-rte');
const label = ionItem.querySelector('ion-label');
if (!label) {
return;
}
@ -418,65 +433,21 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
* Set the caret position on the character number.
*
* @param parent Parent where to set the position.
* @param chars Number of chars where to place the caret. If not defined it will go to the end.
*/
protected setCurrentCursorPosition(parent: Node, chars?: number): void {
/**
* Loops round all the child text nodes within the supplied node and sets a range from the start of the initial node to
* the characters.
*
* @param node Node where to start.
* @param range Previous calculated range.
* @param chars Object with counting of characters (input-output param).
* @param chars.count Count of characters.
* @returns Selection range.
*/
const setRange = (node: Node, range: Range, chars: { count: number }): Range => {
if (chars.count === 0) {
range.setEnd(node, 0);
} else if (node && chars.count > 0) {
if (node.hasChildNodes()) {
// Navigate through children.
for (let lp = 0; lp < node.childNodes.length; lp++) {
range = setRange(node.childNodes[lp], range, chars);
protected setCurrentCursorPosition(parent: Node): void {
const range = document.createRange();
if (chars.count === 0) {
break;
}
}
} else if ((node.textContent || '').length < chars.count) {
// Jump this node.
// @todo empty nodes will be omitted.
chars.count -= (node.textContent || '').length;
} else {
// The cursor will be placed in this element.
range.setEnd(node, chars.count);
chars.count = 0;
}
}
// Select all so it will go to the end.
range.selectNode(parent);
range.selectNodeContents(parent);
range.collapse(false);
return range;
};
let range = document.createRange();
if (chars === undefined) {
// Select all so it will go to the end.
range.selectNode(parent);
range.selectNodeContents(parent);
} else if (chars < 0 || chars > (parent.textContent || '').length) {
const selection = window.getSelection();
if (!selection) {
return;
} else {
range.selectNode(parent);
range.setStart(parent, 0);
range = setRange(parent, range, { count: chars });
}
if (range) {
const selection = window.getSelection();
range.collapse(false);
selection?.removeAllRanges();
selection?.addRange(range);
}
selection.removeAllRanges();
selection.addRange(range);
}
/**
@ -491,26 +462,27 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
this.stopBubble(event);
// Update tags for a11y.
this.replaceTags(['b', 'i'], ['strong', 'em']);
this.setContent(this.control?.value || '');
this.rteEnabled = !this.rteEnabled;
// Set focus and cursor at the end.
// Modify the DOM directly so the keyboard stays open.
if (this.rteEnabled) {
// Update tags for a11y.
this.replaceTags(['b', 'i'], ['strong', 'em']);
if (this.rteEnabled) {
this.editorElement?.removeAttribute('hidden');
const textareaInputElement = await this.textarea?.getInputElement();
textareaInputElement?.setAttribute('hidden', '');
this.editorElement?.focus();
this.textareaElement?.setAttribute('hidden', '');
} else {
this.editorElement?.setAttribute('hidden', '');
const textareaInputElement = await this.textarea?.getInputElement();
textareaInputElement?.removeAttribute('hidden');
this.textarea?.setFocus();
this.textareaElement?.removeAttribute('hidden');
}
await CoreUtils.nextTick();
this.focusRTE(event);
}
/**
@ -679,13 +651,6 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
// eslint-disable-next-line deprecation/deprecation
document.execCommand(command, false);
// Modern browsers are using non a11y tags, so replace them.
if (command === 'bold') {
this.replaceTags(['b'], ['strong']);
} else if (command === 'italic') {
this.replaceTags(['i'], ['em']);
}
}
/**
@ -706,40 +671,29 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
}
/**
* Focus editor when click the area.
*/
focusRTE(): void {
if (this.rteEnabled) {
this.editorElement?.focus();
} else {
this.textarea?.setFocus();
}
}
/**
* Hide the toolbar in phone mode.
* Blur and hide the toolbar in phone mode.
*
* @param event Event.
* @param force If true it will not check the target of the event.
*/
hideToolbar(event: FocusEvent | KeyboardEvent | MouseEvent, 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.
blurRTE(event: FocusEvent): void {
const doBlur = (event: FocusEvent) => {
if (this.element.contains(document.activeElement)) {
// Do not hide if clicked inside the editor area, except hideButton.
return;
}
return;
}
if (event.type === 'keyup' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
return;
}
this.element.classList.remove('has-focus');
this.element.classList.remove('has-focus');
this.stopBubble(event);
this.stopBubble(event);
if (this.isPhone) {
this.toolbarHidden = true;
}
};
if (this.isPhone) {
this.toolbarHidden = true;
}
// There are many cases when focus is fired after blur, so we need to delay the blur action.
this.blurTimeout = window.setTimeout(() => doBlur(event),300);
}
/**
@ -753,19 +707,28 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
}
/**
* Show the toolbar.
* Focus editor when click the area and show toolbar.
*
* @param event Event.
*/
showToolbar(event: FocusEvent): void {
this.updateToolbarButtons();
focusRTE(event: Event): void {
clearTimeout(this.blurTimeout);
if (this.rteEnabled) {
this.editorElement?.focus();
} else {
this.textarea?.setFocus();
}
this.element.classList.add('ion-touched');
this.element.classList.remove('ion-untouched');
this.element.classList.add('has-focus');
this.stopBubble(event);
event && this.stopBubble(event);
this.editorElement?.focus();
this.toolbarHidden = false;
this.updateToolbarButtons();
}
/**
@ -838,7 +801,8 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
* Update the number of toolbar buttons displayed.
*/
async updateToolbarButtons(): Promise<void> {
if (!this.isCurrentView || !this.toolbar || !this.toolbarSlides || this.element.offsetParent === null) {
if (!this.isCurrentView || !this.toolbar || !this.toolbarSlides ||
this.toolbarHidden || this.element.offsetParent === null) {
// Don't calculate if component isn't in current view, the calculations are wrong.
return;
}
@ -847,10 +811,10 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
// Cancel previous one, if any.
this.buttonsDomPromise?.cancel();
this.buttonsDomPromise = CoreDom.waitToBeInDOM(this.toolbar.nativeElement);
this.buttonsDomPromise = CoreDom.waitToBeInDOM(this.toolbar?.nativeElement);
await this.buttonsDomPromise;
const width = this.toolbar.nativeElement.getBoundingClientRect().width;
const width = this.toolbar?.nativeElement.getBoundingClientRect().width;
if (length > 0 && width > length * this.toolbarButtonWidth) {
this.swiperOpts.slidesPerView = length;
@ -1068,7 +1032,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
const text = await CoreUtils.scanQR();
if (text) {
this.editorElement?.focus(); // Make sure the editor is focused.
this.focusRTE(event); // Make sure the editor is focused.
// eslint-disable-next-line deprecation/deprecation
document.execCommand('insertText', false, text);
}

View File

@ -17,6 +17,7 @@
import { SQLiteObject } from '@awesome-cordova-plugins/sqlite/ngx';
import { CorePromisedValue } from '@classes/promised-value';
import { CoreLogger } from '@singletons/logger';
import { Sqlite3Worker1Promiser, sqlite3Worker1Promiser } from '@sqlite.org/sqlite-wasm';
/**
@ -36,10 +37,13 @@ export class WasmSQLiteObject implements SQLiteObject {
private name: string;
private promisedPromiser: CorePromisedValue<Sqlite3Worker1Promiser>;
private promiser: Sqlite3Worker1Promiser;
protected logger: CoreLogger;
constructor(name: string) {
this.name = name;
this.promisedPromiser = new CorePromisedValue();
this.logger = CoreLogger.getInstance('WasmSQLiteObject');
this.promiser = async (...args) => {
const promiser = await this.promisedPromiser;
@ -50,7 +54,7 @@ export class WasmSQLiteObject implements SQLiteObject {
/**
* Delete the database.
*/
async delete(): Promise<any> {
async delete(): Promise<void> {
if (!this.promisedPromiser.isResolved()) {
await this.open();
}
@ -61,20 +65,35 @@ export class WasmSQLiteObject implements SQLiteObject {
/**
* @inheritdoc
*/
async open(): Promise<any> {
async open(): Promise<void> {
const promiser = await new Promise<Sqlite3Worker1Promiser>((resolve) => {
const _promiser = sqlite3Worker1Promiser(() => resolve(_promiser));
});
await promiser('open', { filename: `file:${this.name}.sqlite3`, vfs: 'opfs' });
const response = await promiser('config-get', {});
const isEnabled = (response as any).result.opfsEnabled;
if (!isEnabled) {
this.logger.error('opfsEnabled flag is disabled. Reloading the page.');
// @TODO Fix Workaround for the issue with the opfsEnabled flag.
// The flag gets disabled when opening a database, so we need to reload the page to make it work.
window.location.reload();
}
try {
await promiser('open', { filename: `file:${this.name}.sqlite3`, vfs: 'opfs' });
} catch (error) {
this.logger.error(`Error opening database: ${this.name}. Details: ${error.result.message}`);
throw error;
}
this.promisedPromiser.resolve(promiser);
}
/**
* @inheritdoc
*/
async close(): Promise<any> {
async close(): Promise<void> {
await this.promiser('close', {});
}

View File

@ -19,7 +19,7 @@
<core-spacer />
<ion-item class="ion-text-wrap help">
<ion-label>
{{ 'core.search.filtercategories' | translate }}
<p class="item-heading">{{ 'core.search.filtercategories' | translate }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap">
@ -37,7 +37,7 @@
<core-spacer />
<ion-item class="ion-text-wrap help">
<ion-label>
{{ 'core.search.filtercourses' | translate }}
<p class="item-heading">{{ 'core.search.filtercourses' | translate }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap">

View File

@ -449,7 +449,7 @@ export class CoreFileHelperProvider {
Translate.instant('core.dontshowagain'),
'checkbox',
{ okText: okButton },
{ cssClass: 'core-modal-force-on-top' },
{ cssClass: 'core-alert-force-on-top' },
);
if (dontShowAgain) {

View File

@ -35,3 +35,16 @@ ion-action-sheet {
}
}
}
// File uploader.
.action-sheet-button input.core-fileuploader-file-handler-input {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
min-width: 100%;
opacity: 0;
z-index: 100;
cursor: pointer;
}

View File

@ -1,7 +1,75 @@
@keyframes scaleFrom0 {
from {
/* More performant than animating `width` */
transform: scaleX(0);
}
}
ion-alert {
--border-radius: var(--modal-radius);
&.md, &.ios {
--max-width: 80%;
@include media-breakpoint-up(md) {
--max-width: 384px;
}
}
.alert-wrapper {
overflow: auto;
border-radius: var(--border-radius) !important;
button.alert-button.alert-button-role-destructive {
color: var(--danger);
}
}
.alert-message {
user-select: text;
flex-shrink: 0;
ion-card {
margin: 0;
margin-top: 10px;
}
}
&.md .alert-button {
// Remove padding from alert buttons, they already have min accessibility height.
padding-top: 0px;
padding-bottom: 0px;
}
.alert-button.timed-button{
position: relative;
&::before {
content: '';
position: absolute;
width: 100%;
left: 0;
right: 0;
bottom: 0;
top: 0;
background-color: var(--primary-tint);
animation: scaleFrom0 10s forwards linear;
transform-origin: left;
@include rtl() {
transform-origin: right;
}
z-index: -1;
}
}
&.core-alert-force-on-top {
z-index: 100000 !important;
}
&.core-nohead .alert-head,
.alert-head:empty {
padding-bottom: 0;
}
}

View File

@ -17,6 +17,10 @@ ion-button {
color: inherit;
}
&.button-disabled {
opacity: var(--mdl-button-disabled-opacity) !important;
}
&.button-outline {
--border-width: var(--core-input-border-width);
--border-color: var(--core-input-stroke);

View File

@ -1,5 +1,26 @@
ion-checkbox {
&.checkbox-disabled::part(label) {
opacity: 0.8;
// Checkbox.
ion-checkbox,
input[type=checkbox] {
--border-radius: 2px;
--border-width: 2px;
--outer-border-width: 2px;
--border-style: solid;
--size: 20px;
&:not(.ion-color) {
--border-color-checked: var(--text-color);
--checkbox-background-checked: var(--text-color);
--checkmark-color: var(--contrast-background);
}
}
ion-checkbox {
&.checkbox-disabled::part(label) {
opacity: var(--mdl-input-disabled-opacity);
}
}
.ios input[type=checkbox] {
--outer-border-width: 1px;
}

View File

@ -0,0 +1,14 @@
ion-item.item-label-stacked ion-datetime-button {
margin-top: 8px;
margin-bottom: 8px;
align-self: self-end;
}
ion-datetime-button p {
margin-top: 4px;
margin-bottom: 4px;
}
ion-datetime.datetime-disabled {
opacity: var(--mdl-input-disabled-opacity) !important;
}

View File

@ -0,0 +1,35 @@
// Hide close button because when present is read on voice over.
ion-fab[core-fab] {
ion-fab-button::part(close-icon) {
display: none;
}
}
// The following 4 selectors can probably be removed after Ionic migration to 7+
ion-fab.fab-horizontal-start {
left: calc(10px + var(--ion-safe-area-right, 0px));
}
&[dir=rtl] ion-fab.fab-horizontal-start {
right: calc(10px + var(--ion-safe-area-right, 0px));
left: unset
}
ion-fab.fab-horizontal-end {
right: calc(10px + var(--ion-safe-area-right, 0px));
}
&[dir=rtl] ion-fab.fab-horizontal-end {
left: calc(10px + var(--ion-safe-area-right, 0px));
right: unset
}
ion-content.has-collapsible-footer ion-fab {
bottom: calc(var(--core-collapsible-footer-height, 0px) + 10px);
@include core-transition(all, 200ms);
}
ion-fab-button {
--box-shadow: 0 3px 5px -1px rgb(0 0 0 / 20%), 0 6px 10px 0 rgb(0 0 0 / 14%), 0 1px 18px 0 rgb(0 0 0 / 12%);
}

View File

@ -1,5 +1,5 @@
ion-input {
&.input-disabled.md, &.input-disabled.ios {
opacity: 0.8;
opacity: var(--mdl-input-disabled-opacity);
}
}

View File

@ -19,7 +19,8 @@ ion-item.item {
}
&.ion-valid,
&.ion-invalid {
&.ion-invalid,
&.item-has-interactive-control {
&.item-lines-default {
--border-width: 0 0 1px 0;
}
@ -51,6 +52,12 @@ ion-item.item {
&.item-has-interactive-control:focus-within {
@include core-focus-outline();
}
&.icon-margin-reduced {
[slot=start] {
@include margin-horizontal(null, var(--mdl-spacing-4));
}
}
}
// Fake item.
@ -71,7 +78,7 @@ div.fake-ion-item {
font-size: var(--text-size);
font-weight: normal;
text-transform: none;
@include padding(null, 16px, null, 16px);
@include padding(null, var(--mdl-spacing-4), null, var(--mdl-spacing-4));
@include margin(11px, null, 10px, null);
h1 {
@ -183,10 +190,12 @@ ion-item .in-item {
// Correctly inherit ion-text-wrap onto labels.
.item > ion-label,
.fake-ion-item,
.item.ion-text-wrap > ion-checkbox::part(label),
ion-checkbox.ion-text-wrap::part(label)
.item.ion-text-wrap ion-toggle::part(label),
ion-toggle.ion-text-wrap::part(label) {
.item > ion-checkbox::part(label),
ion-checkbox::part(label),
.item ion-toggle::part(label),
ion-toggle::part(label),
.item > ion-input > label,
ion-input > label {
core-format-text,
core-format-text > *:not(pre) {
white-space: nowrap;
@ -200,7 +209,12 @@ ion-item > .in-item,
.fake-ion-item.ion-text-wrap,
.item.ion-text-wrap > ion-checkbox::part(label),
ion-checkbox.ion-text-wrap::part(label),
ion-toggle.ion-text-wrap::part(label) {
ion-toggle.ion-text-wrap::part(label),
.item.ion-text-wrap > ion-input > label,
ion-input.ion-text-wrap > label {
white-space: normal;
overflow: inherit;
core-format-text,
core-format-text > *:not(pre) {
white-space: normal;
@ -208,16 +222,6 @@ ion-toggle.ion-text-wrap::part(label) {
}
}
.item.ion-text-wrap > ion-label,
.item.ion-text-wrap ion-checkbox::part(label),
ion-checkbox.ion-text-wrap::part(label),
.item.ion-text-wrap ion-radio::part(label),
ion-radio.ion-text-wrap::part(label),
.item.ion-text-wrap ion-toggle::part(label),
ion-toggle.ion-text-wrap::part(label) {
white-space: normal !important;
}
ion-item .core-input-errors-wrapper {
width: 100%;
}
@ -247,11 +251,11 @@ ion-item.item.item-file {
}
&.item-directory ion-icon {
@include margin-horizontal(0px, 16px);
@include margin-horizontal(0px, var(--mdl-spacing-4));
}
[slot=end] {
@include margin-horizontal(16px, null);
@include margin-horizontal(var(--mdl-spacing-4), null);
}
}
@ -263,6 +267,11 @@ ion-item.item.item-file {
}
}
// No highlight on RTE.
ion-item.item-rte {
--full-highlight-height: 0px !important;
}
// Make links clickable when inside radio or checkbox items. Style part.
@media (hover: hover) {
ion-item.item-multiple-inputs:not(.item-rte):hover::part(native) {

View File

@ -0,0 +1,119 @@
ion-modal {
&.show-modal {
@media only screen and (min-width: 768px) and (min-height: 600px) {
--border-radius: var(--modal-radius);
}
}
&.ion-datetime-button-overlay {
--border-radius: var(--modal-radius);
}
&.core-modal-lateral,
&.core-modal-fullscreen {
--border-radius: 0px;
}
&.core-modal-no-background {
--background: transparent;
--box-shadow: none !important;
pointer-events: none;
&::part(backdrop) {
display: none;
}
&::part(content) {
pointer-events: none;
}
}
&.modal-sheet::part(handle) {
background: var(--core-header-buttons-color);
}
&.core-modal-fullscreen::part(content) {
position: absolute;
@include position(0 !important, null, null, 0 !important);
display: block;
width: 100% !important;
height: 100% !important;
}
&.core-modal-transparent,
&.core-modal-transparent-no-filter {
&::part(backdrop) {
backdrop-filter: none;
}
&::part(content) {
backdrop-filter: none;
--background: rgb(10 10 10 / 20%);
ion-content {
--background: transparent !important;
}
position: absolute;
@include position(0 !important, null, null, 0 !important);
display: block;
width: 100% !important;
height: 100% !important;
}
}
&.core-modal-transparent {
&::part(backdrop) {
backdrop-filter: blur(8px);
}
&::part(content) {
backdrop-filter: blur(12px);
}
}
&.core-modal-lateral {
--ion-safe-area-left: 0px;
--ion-safe-area-right: 0px;
&::part(content) {
@include margin-horizontal(var(--modal-lateral-margin), null);
position: absolute;
@include position(0 !important, 0 !important, 0 !important, unset !important);
display: block;
height: 100% !important;
width: calc(100% - var(--modal-lateral-margin));
max-width: calc(var(--modal-lateral-max-width));
box-shadow: 0 28px 48px rgb(0 0 0 / 40%);
}
&::part(backdrop) {
visibility: visible;
}
.modal-shadow {
display: none;
}
}
@each $breakpoint, $width in $screen-breakpoints {
&.core-modal-lateral-#{$breakpoint} {
--modal-lateral-max-width: #{$width};
}
}
&.core-password-modal {
--border-radius: var(--mdl-shape-borderRadius-sm);
--min-width: auto;
--min-height: 300px;
--width: 384px;
--height: auto;
form {
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
}
}
}

View File

@ -1,5 +1,63 @@
ion-radio {
&.radio-disabled::part(label) {
opacity: 0.8;
// Radio.
ion-radio,
input[type=radio],
.select-alert .alert-radio-icon {
--border-radius: 50%;
--border-width: 2px;
--outer-border-width: 2px;
--border-style: solid;
--size: 20px;
&:not(.ion-color) {
--color: var(--text-color);
--color-checked: var(--color);
}
}
.ios ion-radio,
.ios input[type=radio],
.select-alert.ios .alert-radio-icon {
--border-width: 1px;
--outer-border-width: 1px;
}
.ios ion-radio {
&::part(container) {
width: var(--size);
height: var(--size);
margin: 0px;
border-radius: var(--border-radius);
border-width: var(--outer-border-width);
border-style: var(--border-style);
border-color: var(--color);
}
&::part(mark) {
border-radius: var(--inner-border-radius);
width: calc(50% + var(--outer-border-width));
height: calc(50% + var(--outer-border-width));
transform: scale3d(0, 0, 0);
transition: transform 280ms cubic-bezier(.4, 0, .2, 1);
background: var(--contrast-background);
border: 0 !important;
}
&.radio-checked {
&::part(container) {
border-color: var(--color);
background: var(--color);
}
&::part(mark) {
transform: scale3d(1, 1, 1);
}
}
}
ion-radio {
&.radio-disabled::part(label) {
opacity: var(--mdl-input-disabled-opacity);
}
}

View File

@ -7,7 +7,7 @@ ion-select {
opacity: 1;
}
&.select-disabled {
opacity: 0.8;
opacity: var(--mdl-input-disabled-opacity);
}
}

View File

@ -111,10 +111,6 @@ button,
}
}
ion-fab-button {
--box-shadow: 0 3px 5px -1px rgb(0 0 0 / 20%), 0 6px 10px 0 rgb(0 0 0 / 14%), 0 1px 18px 0 rgb(0 0 0 / 12%);
}
[role="button"],
.clickable {
cursor: pointer;
@ -123,14 +119,14 @@ ion-fab-button {
[disabled],
[aria-disabled="true"] {
cursor: default;
opacity: .4;
opacity: var(--mdl-button-disabled-opacity);
pointer-events: none;
}
}
button[disabled] {
cursor: default;
opacity: .4;
opacity: var(--mdl-button-disabled-opacity);
pointer-events: none;
}
@ -188,72 +184,6 @@ div.core-iframe-network-error {
left: -15%;
}
ion-alert.core-nohead .alert-head,
ion-alert .alert-head:empty {
padding-bottom: 0;
}
@keyframes scaleFrom0 {
from {
/* More performant than animating `width` */
transform: scaleX(0);
}
}
ion-alert .alert-button.timed-button{
position: relative;
&::before {
content: '';
position: absolute;
width: 100%;
left: 0;
right: 0;
bottom: 0;
top: 0;
background-color: var(--primary-tint);
animation: scaleFrom0 10s forwards linear;
transform-origin: left;
@include rtl() {
transform-origin: right;
}
z-index: -1;
}
}
ion-alert {
--border-radius: var(--modal-radius);
&.md, &.ios {
--max-width: 80%;
@include media-breakpoint-up(md) {
--max-width: 384px;
}
}
.alert-wrapper {
overflow: auto;
border-radius: var(--border-radius) !important;
button.alert-button.alert-button-role-destructive {
color: var(--danger);
}
}
.alert-message {
user-select: text;
flex-shrink: 0;
ion-card {
margin: 0;
margin-top: 10px;
}
}
}
// Ionic list.
ion-list {
padding: 0 !important;
@ -375,96 +305,6 @@ body.core-iframe-fullscreen ion-router-outlet {
}
}
// Modals.
.core-modal-fullscreen::part(content) {
position: absolute;
@include position(0 !important, null, null, 0 !important);
display: block;
width: 100% !important;
height: 100% !important;
}
.core-modal-transparent {
&::part(backdrop) {
backdrop-filter: blur(8px);
}
&::part(content) {
backdrop-filter: blur(12px);
--background: rgb(10 10 10 / 20%);
ion-content {
--background: transparent !important;
}
position: absolute;
@include position(0 !important, null, null, 0 !important);
display: block;
width: 100% !important;
height: 100% !important;
}
}
.core-modal-force-on-top {
z-index: 100000 !important;
}
.core-modal-lateral {
--ion-safe-area-left: 0px;
--ion-safe-area-right: 0px;
&::part(content) {
@include margin-horizontal(var(--modal-lateral-margin), null);
position: absolute;
@include position(0 !important, 0 !important, 0 !important, unset !important);
display: block;
height: 100% !important;
width: calc(100% - var(--modal-lateral-margin));
max-width: calc(var(--modal-lateral-max-width));
box-shadow: 0 28px 48px rgb(0 0 0 / 40%);
}
&::part(backdrop) {
visibility: visible;
}
.modal-shadow {
display: none;
}
}
.core-modal-transparent-no-filter {
@extend .core-modal-transparent;
&::part(backdrop) {
backdrop-filter: none;
}
&::part(content) {
backdrop-filter: none;
}
}
@each $breakpoint, $width in $screen-breakpoints {
.core-modal-lateral-#{$breakpoint} {
--modal-lateral-max-width: #{$width};
}
}
.core-password-modal {
--border-radius: var(--mdl-shape-borderRadius-sm);
--min-width: auto;
--min-height: 300px;
--width: 384px;
--height: auto;
form {
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
}
}
// Hidden submit button.
.core-submit-hidden-enter {
position: absolute;
@ -635,103 +475,12 @@ ion-content.limited-width > :not([slot]) {
min-height: 100%;
}
// Radio.
ion-radio,
input[type=radio],
.select-alert .alert-radio-icon {
--border-radius: 50%;
--border-width: 2px;
--outer-border-width: 2px;
--border-style: solid;
--size: 20px;
&:not(.ion-color) {
--color: var(--text-color);
--color-checked: var(--color);
}
}
.ios ion-radio,
.ios input[type=radio],
.select-alert.ios .alert-radio-icon {
--border-width: 1px;
--outer-border-width: 1px;
}
.ios ion-radio {
&::part(container) {
width: var(--size);
height: var(--size);
margin: 0px;
border-radius: var(--border-radius);
border-width: var(--outer-border-width);
border-style: var(--border-style);
border-color: var(--color);
}
&::part(mark) {
border-radius: var(--inner-border-radius);
width: calc(50% + var(--outer-border-width));
height: calc(50% + var(--outer-border-width));
transform: scale3d(0, 0, 0);
transition: transform 280ms cubic-bezier(.4, 0, .2, 1);
background: var(--contrast-background);
border: 0 !important;
}
&.radio-checked {
&::part(container) {
border-color: var(--color);
background: var(--color);
}
&::part(mark) {
transform: scale3d(1, 1, 1);
}
}
}
// Checkbox.
ion-checkbox,
input[type=checkbox] {
--border-radius: 2px;
--border-width: 2px;
--outer-border-width: 2px;
--border-style: solid;
--size: 20px;
&:not(.ion-color) {
--border-color-checked: var(--text-color);
--checkbox-background-checked: var(--text-color);
--checkmark-color: var(--contrast-background);
}
}
.ios input[type=checkbox] {
--outer-border-width: 1px;
}
ion-badge {
line-height: 1.1;
padding: 2px 8px;
border-radius: var(--mdl-shape-borderRadius-lg);
}
// File uploader.
.action-sheet-button input.core-fileuploader-file-handler-input {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
min-width: 100%;
opacity: 0;
z-index: 100;
cursor: pointer;
}
.core-anchor, core-format-text a {
color: var(--core-link-color);
cursor: pointer;
@ -811,37 +560,6 @@ textarea:not([core-auto-rows]) {
height: 200px;
}
// Hide close button because when present is read on voice over.
ion-fab[core-fab] {
ion-fab-button::part(close-icon) {
display: none;
}
}
// The following 4 selectors can probably be removed after Ionic migration to 7+
ion-fab.fab-horizontal-start {
left: calc(10px + var(--ion-safe-area-right, 0px));
}
[dir=rtl] ion-fab.fab-horizontal-start {
right: calc(10px + var(--ion-safe-area-right, 0px));
left: unset
}
ion-fab.fab-horizontal-end {
right: calc(10px + var(--ion-safe-area-right, 0px));
}
[dir=rtl] ion-fab.fab-horizontal-end {
left: calc(10px + var(--ion-safe-area-right, 0px));
right: unset
}
ion-content.has-collapsible-footer ion-fab {
bottom: calc(var(--core-collapsible-footer-height, 0px) + 10px);
@include core-transition(all, 200ms);
}
.core-media-adapt-width {
max-width: 100%;
}
@ -856,10 +574,8 @@ audio.core-media-adapt-width {
// Disabled items.
ion-item.item-disabled,
ion-button.button-disabled,
ion-item.item-interactive-disabled:not(.item-multiple-inputs) ion-label,
ion-datetime.datetime-disabled {
opacity: .65 !important;
ion-item.item-interactive-disabled:not(.item-multiple-inputs) ion-label {
opacity: var(--mdl-item-disabled-opacity) !important;
}
ion-item-divider.item,
@ -893,7 +609,7 @@ ion-input input,
ion-textarea,
core-rich-text-editor {
--placeholder-color: var(--ion-placeholder-color);
--placeholder-opacity: .65;
--placeholder-opacity: var(--mdl-placeholder-opacity);
}
// Disable scroll on parent ion contents to enabled PTR on the ones inside the splitview. See split-view component for more info.
@ -1057,37 +773,6 @@ video::-webkit-media-text-track-display {
white-space: normal !important;
}
ion-modal {
&.show-modal {
@media only screen and (min-width: 768px) and (min-height: 600px) {
--border-radius: var(--modal-radius);
}
}
&.ion-datetime-button-overlay {
--border-radius: var(--modal-radius);
}
&.core-modal-lateral,
&.core-modal-fullscreen {
--border-radius: 0px;
}
&.core-modal-no-background {
--background: transparent;
--box-shadow: none !important;
pointer-events: none;
&::part(backdrop) {
display: none;
}
}
&.modal-sheet::part(handle) {
background: var(--core-header-buttons-color);
}
}
/*
* This is to solve popver issue in chrome 114
* For more info see: https://github.com/ionic-team/ionic-framework/issues/27599
@ -1113,18 +798,6 @@ ion-modal {
display: none !important;
}
// Ion Datetime
ion-item.item-label-stacked ion-datetime-button {
margin-top: 8px;
margin-bottom: 8px;
align-self: self-end;
}
ion-datetime-button p {
margin-top: 4px;
margin-bottom: 4px;
}
.x-scrollable {
overflow-x: auto;
display: block;

View File

@ -140,6 +140,12 @@ html {
--mdl-shadow-boxShadow-16: 0px 7px 8px 0px rgb(var(--mdl-shadow-boxShadowColor) / 20%),0px 5px 22px 0px rgb(var(--mdl-shadow-boxShadowColor) / 12%),0px 12px 17px 0px rgb(var(--mdl-shadow-boxShadowColor) / 14%);
--mdl-shadow-boxShadow-24: 0px 11px 15px 0px rgb(var(--mdl-shadow-boxShadowColor) / 20%), 0px 9px 46px 0px rgb(var(--mdl-shadow-boxShadowColor) / 12%), 0px 24px 38px 0px rgb(var(--mdl-shadow-boxShadowColor) / 14%);
// ***** OPACITY category ***** //
--mdl-button-disabled-opacity: 0.6;
--mdl-input-disabled-opacity: 0.6;
--mdl-item-disabled-opacity: 0.4;
--mdl-placeholder-opacity: 0.6;
// ***** ACCESSIBILITY ***** //
--a11y-sizing-minTargetSize: 44px;
--a11y-shadow-focus-borderWidth: 2px;

View File

@ -36,11 +36,14 @@ html {
@import "components/ion-checkbox.scss";
@import "components/ion-chip.scss";
@import "components/ion-content.scss";
@import "components/ion-datetime.scss";
@import "components/ion-fab.scss";
@import "components/ion-header.scss";
@import "components/ion-icon.scss";
@import "components/ion-input.scss";
@import "components/ion-item.scss";
@import "components/ion-item-divider.scss";
@import "components/ion-modal.scss";
@import "components/ion-loading.scss";
@import "components/ion-note.scss";
@import "components/ion-popover.scss";