From bea73940edd23e40c888782cbd4168c506fa7f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 12 Dec 2023 16:23:14 +0100 Subject: [PATCH] MOBILE-3947 core: Avoid item overflow on input errors --- .../calendar/pages/edit-event/edit-event.html | 4 +- .../calendar/pages/edit-event/edit-event.ts | 4 -- .../input-errors/core-input-errors.html | 2 +- .../components/input-errors/input-errors.ts | 68 ++++++++++++------- .../components/mark-required/mark-required.ts | 17 +++-- .../login/pages/email-signup/email-signup.ts | 19 +++--- .../features/login/services/login-helper.ts | 54 --------------- src/theme/theme.base.scss | 4 ++ upgrade.txt | 1 + 9 files changed, 72 insertions(+), 101 deletions(-) diff --git a/src/addons/calendar/pages/edit-event/edit-event.html b/src/addons/calendar/pages/edit-event/edit-event.html index bca2701b6..fde3faec0 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.html +++ b/src/addons/calendar/pages/edit-event/edit-event.html @@ -21,7 +21,7 @@ formControlName="name">
{{ 'addon.calendar.eventname' | translate }}
- + @@ -37,7 +37,7 @@ - + diff --git a/src/addons/calendar/pages/edit-event/edit-event.ts b/src/addons/calendar/pages/edit-event/edit-event.ts index 7eb187dc4..a30321547 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.ts +++ b/src/addons/calendar/pages/edit-event/edit-event.ts @@ -69,7 +69,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { groups: CoreGroup[] = []; loadingGroups = false; courseGroupSet = false; - errors: Record; error = false; eventRepeatId?: number; otherEventsCount = 0; @@ -100,9 +99,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { ) { this.currentSite = CoreSites.getRequiredCurrentSite(); this.remindersEnabled = CoreReminders.isEnabled(); - this.errors = { - required: Translate.instant('core.required'), - }; this.form = new FormGroup({}); diff --git a/src/core/components/input-errors/core-input-errors.html b/src/core/components/input-errors/core-input-errors.html index ac6829dff..c4a3a8a07 100644 --- a/src/core/components/input-errors/core-input-errors.html +++ b/src/core/components/input-errors/core-input-errors.html @@ -1,7 +1,7 @@
- {{errorMessages[error]}} + {{ errorMessages[error] | translate }} {{ 'core.login.invalidvaluemax' | translate:{$a: control.errors!.max.max} }} diff --git a/src/core/components/input-errors/input-errors.ts b/src/core/components/input-errors/input-errors.ts index 117867649..570386555 100644 --- a/src/core/components/input-errors/input-errors.ts +++ b/src/core/components/input-errors/input-errors.ts @@ -14,7 +14,6 @@ import { Component, ElementRef, HostBinding, Input, OnChanges, OnInit, SimpleChange } from '@angular/core'; import { FormControl } from '@angular/forms'; -import { Translate } from '@singletons'; /** * Component to show errors if an input isn't valid. @@ -30,8 +29,7 @@ import { Translate } from '@singletons'; * Example usage: * * - * {{ 'core.login.username' | translate }} - * + * * * */ @@ -42,12 +40,12 @@ import { Translate } from '@singletons'; }) export class CoreInputErrorsComponent implements OnInit, OnChanges { - @Input() control?: FormControl; - @Input() errorMessages: Record = {}; + @Input() control?: FormControl; // Needed to be able to check the validity of the input. + @Input() errorMessages: Record = {}; // Error messages to show. Keys must be the name of the error. @Input() errorText = ''; // Set other non automatic errors. errorKeys: string[] = []; - protected element: HTMLElement; + protected hostElement: HTMLElement; @HostBinding('class.has-errors') get hasErrors(): boolean { @@ -59,31 +57,60 @@ export class CoreInputErrorsComponent implements OnInit, OnChanges { constructor( element: ElementRef, ) { - this.element = element.nativeElement; + this.hostElement = element.nativeElement; } /** * Initialize some common errors if they aren't set. */ protected initErrorMessages(): void { - this.errorMessages.required = this.errorMessages.required || Translate.instant('core.required'); - this.errorMessages.email = this.errorMessages.email || Translate.instant('core.login.invalidemail'); - this.errorMessages.date = this.errorMessages.date || Translate.instant('core.login.invaliddate'); - this.errorMessages.datetime = this.errorMessages.datetime || Translate.instant('core.login.invaliddate'); - this.errorMessages.datetimelocal = this.errorMessages.datetimelocal || Translate.instant('core.login.invaliddate'); - this.errorMessages.time = this.errorMessages.time || Translate.instant('core.login.invalidtime'); - this.errorMessages.url = this.errorMessages.url || Translate.instant('core.login.invalidurl'); + this.errorMessages = { + required: this.errorMessages.required || 'core.required', + email: this.errorMessages.email || 'core.login.invalidemail', + date: this.errorMessages.date || 'core.login.invaliddate', + datetime: this.errorMessages.datetime || 'core.login.invaliddate', + datetimelocal: this.errorMessages.datetimelocal || 'core.login.invaliddate', + time: this.errorMessages.time || 'core.login.invalidtime', + url: this.errorMessages.url || 'core.login.invalidurl', + // Set empty values by default, the default error messages will be built in the template when needed. + max: this.errorMessages.max || '', + min: this.errorMessages.min || '', + }; - // Set empty values by default, the default error messages will be built in the template when needed. - this.errorMessages.max = this.errorMessages.max || ''; - this.errorMessages.min = this.errorMessages.min || ''; + this.errorMessages.requiredTrue = this.errorMessages.required; + + this.errorKeys = Object.keys(this.errorMessages); } /** * @inheritdoc */ ngOnInit(): void { - this.element.closest('ion-item')?.classList.add('has-core-input-errors'); + const parent = this.hostElement.parentElement; + let item: HTMLElement | null = null; + + if (parent?.tagName === 'ION-ITEM') { + item = parent; + + // Get all elements on the parent and wrap them with a div. + // This is needed because otherwise the error message will be shown on the right of the input. Or overflowing the item. + const wrapper = document.createElement('div'); + + wrapper.classList.add('core-input-errors-wrapper'); + + Array.from(parent.children).forEach((child) => { + if (!child.slot) { + wrapper.appendChild(child); + } + }); + + parent.appendChild(wrapper); + } else { + item = this.hostElement.closest('ion-item'); + } + + item?.classList.add('has-core-input-errors'); + } /** @@ -92,11 +119,6 @@ export class CoreInputErrorsComponent implements OnInit, OnChanges { ngOnChanges(changes: { [name: string]: SimpleChange }): void { if ((changes.control || changes.errorMessages) && this.control) { this.initErrorMessages(); - - this.errorKeys = this.errorMessages ? Object.keys(this.errorMessages) : []; - } - if (changes.errorText) { - this.errorText = changes.errorText.currentValue; } } diff --git a/src/core/components/mark-required/mark-required.ts b/src/core/components/mark-required/mark-required.ts index 5b72cf378..f0bc34b51 100644 --- a/src/core/components/mark-required/mark-required.ts +++ b/src/core/components/mark-required/mark-required.ts @@ -37,13 +37,13 @@ export class CoreMarkRequiredComponent implements OnInit, AfterViewInit { @Input('core-mark-required') coreMarkRequired: boolean | string = true; - protected element: HTMLElement; + protected hostElement: HTMLElement; requiredLabel = Translate.instant('core.required'); constructor( element: ElementRef, ) { - this.element = element.nativeElement; + this.hostElement = element.nativeElement; } /** @@ -59,18 +59,21 @@ export class CoreMarkRequiredComponent implements OnInit, AfterViewInit { ngAfterViewInit(): void { if (this.coreMarkRequired) { // Add the "required" to the aria-label. - const ariaLabel = this.element.getAttribute('aria-label') || - CoreTextUtils.cleanTags(this.element.innerHTML, { singleLine: true }); + const ariaLabel = this.hostElement.getAttribute('aria-label') || + CoreTextUtils.cleanTags(this.hostElement.innerHTML, { singleLine: true }); if (ariaLabel) { - this.element.setAttribute('aria-label', ariaLabel + ' ' + this.requiredLabel); + this.hostElement.setAttribute('aria-label', ariaLabel + '. ' + this.requiredLabel); } } else { // Remove the "required" from the aria-label. - const ariaLabel = this.element.getAttribute('aria-label'); + const ariaLabel = this.hostElement.getAttribute('aria-label'); if (ariaLabel) { - this.element.setAttribute('aria-label', ariaLabel.replace(' ' + this.requiredLabel, '')); + this.hostElement.setAttribute('aria-label', ariaLabel.replace('. ' + this.requiredLabel, '')); } } + + const input = this.hostElement.closest('ion-input, ion-textarea'); + input?.setAttribute('required', this.coreMarkRequired ? 'true' : 'false'); } } 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 8938b13eb..43797e233 100644 --- a/src/core/features/login/pages/email-signup/email-signup.ts +++ b/src/core/features/login/pages/email-signup/email-signup.ts @@ -105,15 +105,14 @@ export class CoreLoginEmailSignupPage implements OnInit { }); // Setup validation errors. - this.usernameErrors = CoreLoginHelper.getErrorMessages('core.login.usernamerequired'); - this.passwordErrors = CoreLoginHelper.getErrorMessages('core.login.passwordrequired'); - this.emailErrors = CoreLoginHelper.getErrorMessages('core.login.missingemail'); - this.policyErrors = CoreLoginHelper.getErrorMessages('core.login.policyagree'); - this.email2Errors = CoreLoginHelper.getErrorMessages( - 'core.login.missingemail', - undefined, - 'core.login.emailnotmatch', - ); + this.usernameErrors = { required: 'core.login.usernamerequired' }; + this.passwordErrors = { required: 'core.login.passwordrequired' }; + this.emailErrors = { required: 'core.login.missingemail' }; + this.policyErrors = { required: 'core.login.policyagree' }; + this.email2Errors = { + required: 'core.login.missingemail', + pattern: 'core.login.emailnotmatch', + }; } /** @@ -224,7 +223,7 @@ export class CoreLoginEmailSignupPage implements OnInit { const namefieldsErrors = {}; if (this.settings.namefields) { this.settings.namefields.forEach((field) => { - namefieldsErrors[field] = CoreLoginHelper.getErrorMessages('core.login.missing' + field); + namefieldsErrors[field] = { required: 'core.login.missing' + field }; }); } this.namefieldsErrors = namefieldsErrors; diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index aed48d583..a8c22c345 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -252,60 +252,6 @@ export class CoreLoginHelperProvider { return CoreTextUtils.treatDisabledFeatures(disabledFeatures); } - /** - * Builds an object with error messages for some common errors. - * Please notice that this function doesn't support all possible error types. - * - * @param requiredMsg Code of the string for required error. - * @param emailMsg Code of the string for invalid email error. - * @param patternMsg Code of the string for pattern not match error. - * @param urlMsg Code of the string for invalid url error. - * @param minlengthMsg Code of the string for "too short" error. - * @param maxlengthMsg Code of the string for "too long" error. - * @param minMsg Code of the string for min value error. - * @param maxMsg Code of the string for max value error. - * @returns Object with the errors. - */ - getErrorMessages( - requiredMsg?: string, - emailMsg?: string, - patternMsg?: string, - urlMsg?: string, - minlengthMsg?: string, - maxlengthMsg?: string, - minMsg?: string, - maxMsg?: string, - ): Record { - const errors: Record = {}; - - if (requiredMsg) { - errors.required = errors.requiredTrue = Translate.instant(requiredMsg); - } - if (emailMsg) { - errors.email = Translate.instant(emailMsg); - } - if (patternMsg) { - errors.pattern = Translate.instant(patternMsg); - } - if (urlMsg) { - errors.url = Translate.instant(urlMsg); - } - if (minlengthMsg) { - errors.minlength = Translate.instant(minlengthMsg); - } - if (maxlengthMsg) { - errors.maxlength = Translate.instant(maxlengthMsg); - } - if (minMsg) { - errors.min = Translate.instant(minMsg); - } - if (maxMsg) { - errors.max = Translate.instant(maxMsg); - } - - return errors; - } - /** * Get logo URL from a site public config. * diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 929209ef8..c50f2471a 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -126,6 +126,10 @@ ion-item > .in-item, white-space: normal !important; } +ion-item .core-input-errors-wrapper { + width: 100%; +} + @each $color-name, $unused in $colors { .text-#{$color-name}, p.text-#{$color-name} { diff --git a/upgrade.txt b/upgrade.txt index 76fb4cca7..f188871bc 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -10,6 +10,7 @@ For more information about upgrading, read the official documentation: https://m - Removed CoreToLocaleStringPipe deprecated since 3.6.0 - With the upgrade to Ionic 7 ion-slides is no longer supported and now you need to use swiper-container and swiper-slide. More info here: https://ionicframework.com/docs/angular/slides - With the upgrade to Ionic7 ion-datetime has changed its usage. We recommend using ion-datetime-button. More info here: https://ionicframework.com/docs/updating/6-0#datetime + - CoreLoginHelper.getErrorMessages has been removed. Please create the messages object yourself. === 4.3.0 ===