MOBILE-4594 core: Migrate core-show-password 2 ion-input-password-toggle

main
Pau Ferrer Ocaña 2024-07-03 14:28:08 +02:00
parent d590ac23e6
commit b84fdaff7a
11 changed files with 45 additions and 168 deletions

View File

@ -31,7 +31,7 @@
<ion-input labelPlacement="stacked" name="password" type="password"
placeholder="{{ 'core.login.password' | translate }}" core-auto-focus #passwordinput [clearOnEdit]="false"
[label]="'addon.mod_lesson.enterpassword' | translate">
<core-show-password slot="end" />
<ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" />
</ion-input>
</ion-item>
<ion-button expand="block" type="submit">

View File

@ -8,6 +8,6 @@
<ion-input id="addon-mod_quiz-accessrule-password-input" name="quizpassword" type="password"
placeholder="{{ 'addon.mod_quiz.quizpassword' | translate }}" [formControlName]="'quizpassword'" [clearOnEdit]="false"
[attr.aria-label]="'addon.mod_quiz.quizpassword' | translate">
<core-show-password slot="end" />
<ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" />
</ion-input>
</ion-item>

View File

@ -97,7 +97,7 @@ import { CoreSitesListComponent } from './sites-list/sites-list';
CoreProgressBarComponent,
CoreRecaptchaComponent,
CoreSendMessageFormComponent,
CoreShowPasswordComponent,
CoreShowPasswordComponent, // eslint-disable-line deprecation/deprecation
CoreSitePickerComponent,
CoreSplitViewComponent,
// eslint-disable-next-line deprecation/deprecation
@ -153,7 +153,7 @@ import { CoreSitesListComponent } from './sites-list/sites-list';
CoreProgressBarComponent,
CoreRecaptchaComponent,
CoreSendMessageFormComponent,
CoreShowPasswordComponent,
CoreShowPasswordComponent, // eslint-disable-line deprecation/deprecation
CoreSitePickerComponent,
CoreSplitViewComponent,
// eslint-disable-next-line deprecation/deprecation

View File

@ -17,7 +17,7 @@
<ion-input [ariaLabel]="placeholder | translate" class="ion-text-wrap core-ioninput-password" name="password"
type="password" placeholder="{{ placeholder | translate }}" [(ngModel)]="password" core-auto-focus
[clearOnEdit]="false">
<core-show-password slot="end" />
<ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" />
</ion-input>
</ion-item>
<ion-item *ngIf="error" class="ion-text-wrap ion-padding-top text-danger">

View File

@ -1,5 +1,2 @@
<ng-content />
<ion-button fill="clear" [ariaLabel]="(shown ? 'core.hide' : 'core.show') | translate" core-suppress-events (onClick)="toggle($event)"
(mousedown)="doNotBlur($event)" (keydown)="doNotBlur($event)" (keyup)="toggle($event)">
<ion-icon [name]="shown ? 'fas-eye-slash' : 'fas-eye'" slot="icon-only" aria-hidden="true" />
</ion-button>
<ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" *ngIf="!this.ionInput" />

View File

@ -1,31 +0,0 @@
@use "theme/globals" as *;
:host {
display: contents;
// Only applies to deprecated way (surrounding).
::ng-deep ion-input + ion-button {
background: transparent;
padding: 0 var(--inner-padding-end) 0 4px;
margin-top: 0;
margin-bottom: 0;
position: absolute;
@include safe-area-position(null, 0px, null, null);
top: 0;
}
// Only applies to deprecated way (surrounding).
::ng-deep ion-input {
--padding-end: 56px !important;
}
::ng-deep ion-input.input-label-placement-stacked + ion-button {
top: 14px;
}
}
ion-button {
z-index: 5;
pointer-events: visible;
}

View File

@ -12,173 +12,80 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, AfterViewInit, Input, ElementRef, ContentChild } from '@angular/core';
import { Component, AfterViewInit, Input, ContentChild, ViewEncapsulation } from '@angular/core';
import { IonInput } from '@ionic/angular';
import { CorePlatform } from '@services/platform';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { CoreLogger } from '@singletons/logger';
/**
* This component allows to show/hide a password.
* It's meant to be used with ion-input.
* It's recommended to use it as a slot of the input.
* It's meant to be used with ion-input as a slot of the input.
*
* @description
*
* There are 2 ways to use ths component:
* - Slot it to start or end on the ion-input element.
* - Surround the ion-input with the password with this component. This is deprecated.
*
* In order to help finding the input you can specify the name of the input or the ion-input element.
*
* - Surround the ion-input with the password with this component. Not recommended.
*
* Example of new usage:
*
* <ion-input type="password" name="password">
* <ion-input type="password">
* <core-show-password slot="end" />
* </ion-input>
*
* Example deprecated usage:
* Example surrounding usage:
*
* <core-show-password>
* <ion-input type="password" name="password"></ion-input>
* <ion-input type="password" />
* </core-show-password>
*
* @deprecated since 4.5. Use <ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" /> instead.
*/
@Component({
selector: 'core-show-password',
templateUrl: 'core-show-password.html',
styleUrls: ['show-password.scss'],
styles: 'core-show-password { display: contents; }',
encapsulation: ViewEncapsulation.None,
})
export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
@Input() initialShown?: boolean | string; // Whether the password should be shown at start.
@Input() name = ''; // Deprecated. Not used anymore.
@ContentChild(IonInput) ionInput?: IonInput | HTMLIonInputElement; // Deprecated. Use slot instead.
protected input?: HTMLInputElement;
protected hostElement: HTMLElement;
protected logger: CoreLogger;
constructor(element: ElementRef) {
this.hostElement = element.nativeElement;
this.logger = CoreLogger.getInstance('CoreShowPasswordComponent');
}
get shown(): boolean {
return this.input?.type === 'text';
}
set shown(shown: boolean) {
if (!this.input) {
return;
}
this.input.type = shown ? 'text' : 'password';
}
export class CoreShowPasswordComponent implements AfterViewInit {
/**
* @inheritdoc
* @deprecated since 4.5. Not used anymore.
*/
ngOnInit(): void {
this.shown = CoreUtils.isTrueOrOne(this.initialShown);
}
@Input() initialShown = '';
/**
* @deprecated since 4.4. Not used anymore.
*/
@Input() name = '';
/**
* @deprecated since 4.4. Use slotted solution instead.
*/
@ContentChild(IonInput) ionInput?: IonInput | HTMLIonInputElement;
/**
* @inheritdoc
*/
async ngAfterViewInit(): Promise<void> {
await this.setInputElement();
if (!this.input) {
return;
}
// By default, don't autocapitalize and autocorrect.
if (!this.input.getAttribute('autocorrect')) {
this.input.setAttribute('autocorrect', 'off');
}
if (!this.input.getAttribute('autocapitalize')) {
this.input.setAttribute('autocapitalize', 'none');
}
}
/**
* Set the input element to affect.
*/
protected async setInputElement(): Promise<void> {
if (!this.ionInput) {
this.ionInput = this.hostElement.closest('ion-input') ?? undefined;
this.hostElement.setAttribute('slot', 'end');
} else {
// It's outside ion-input, warn devs.
this.logger.warn('Deprecated CoreShowPasswordComponent usage, it\'s not needed to surround ion-input anymore.');
}
CoreLogger.getInstance('CoreShowPasswordComponent')
.warn('Deprecated component, use <ion-input-password-toggle /> instead.');
// eslint-disable-next-line deprecation/deprecation
if (!this.ionInput) {
return;
}
try {
this.input = await this.ionInput.getInputElement();
} catch {
// This should never fail, but it does in some testing environment because Ionic elements are not
// rendered properly. So in case this fails it will try to find through the name and ignore the error.
const name = this.ionInput.name;
if (!name) {
return;
}
this.input = this.hostElement.querySelector<HTMLInputElement>('input[name="' + name + '"]') ?? undefined;
}
}
/**
* Toggle show/hide password.
*
* @param event The mouse event.
*/
toggle(event: Event): void {
if (event.type === 'keyup' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
// eslint-disable-next-line deprecation/deprecation
const input = await CoreUtils.ignoreErrors(this.ionInput.getInputElement());
if (!input) {
return;
}
event.preventDefault();
event.stopPropagation();
const isFocused = document.activeElement === this.input;
this.shown = !this.shown;
// In Android, the keyboard is closed when the input type changes. Focus it again.
if (this.input && isFocused && CorePlatform.isAndroid()) {
CoreDomUtils.focusElement(this.input);
}
}
/**
* Do not loose focus.
*
* @param event The mouse event.
*/
doNotBlur(event: Event): void {
if (event.type === 'keydown' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
return;
}
event.preventDefault();
event.stopPropagation();
}
/**
* Checks if Space or Enter have been pressed.
*
* @param event Keyboard Event.
* @returns Wether space or enter have been pressed.
*/
protected isValidKeyboardKey(event: KeyboardEvent): boolean {
return event.key === ' ' || event.key === 'Enter';
const toggle = CoreDomUtils.convertToElement('<ion-input-password-toggle slot="end" />');
input.parentElement?.appendChild(toggle.children[0]);
}
}

View File

@ -53,7 +53,7 @@
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
formControlName="password" [clearOnEdit]="false" autocomplete="current-password" enterkeyhint="go"
required="true" [attr.aria-label]="'core.login.password' | translate ">
<core-show-password slot="end" />
<ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" />
</ion-input>
</ion-item>
<ion-button expand="block" type="submit" [disabled]="!credForm.valid"

View File

@ -103,7 +103,7 @@
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
autocomplete="new-password" required="true">
<div slot="label" [core-mark-required]="true">{{ 'core.login.password' | translate }}</div>
<core-show-password slot="end" />
<ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" />
</ion-input>
<p *ngIf="settings.passwordpolicy" class="core-input-footnote">
{{settings.passwordpolicy}}

View File

@ -65,7 +65,7 @@
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
autocomplete="current-password" enterkeyhint="go" required="true"
[attr.aria-label]="'core.login.password' | translate">
<core-show-password slot="end" />
<ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" />
</ion-input>
</ion-item>
<ion-button type="submit" expand="block" [disabled]="!credForm.valid"

View File

@ -2,6 +2,10 @@ ion-input {
&.input-disabled.md, &.input-disabled.ios {
opacity: var(--mdl-input-disabled-opacity);
}
ion-input-password-toggle {
--ion-color-primary: var(--text-color);
}
}
input[disabled],