MOBILE-4594 core: Migrate core-show-password 2 ion-input-password-toggle
parent
d590ac23e6
commit
b84fdaff7a
|
@ -31,7 +31,7 @@
|
||||||
<ion-input labelPlacement="stacked" name="password" type="password"
|
<ion-input labelPlacement="stacked" name="password" type="password"
|
||||||
placeholder="{{ 'core.login.password' | translate }}" core-auto-focus #passwordinput [clearOnEdit]="false"
|
placeholder="{{ 'core.login.password' | translate }}" core-auto-focus #passwordinput [clearOnEdit]="false"
|
||||||
[label]="'addon.mod_lesson.enterpassword' | translate">
|
[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-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-button expand="block" type="submit">
|
<ion-button expand="block" type="submit">
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
<ion-input id="addon-mod_quiz-accessrule-password-input" name="quizpassword" type="password"
|
<ion-input id="addon-mod_quiz-accessrule-password-input" name="quizpassword" type="password"
|
||||||
placeholder="{{ 'addon.mod_quiz.quizpassword' | translate }}" [formControlName]="'quizpassword'" [clearOnEdit]="false"
|
placeholder="{{ 'addon.mod_quiz.quizpassword' | translate }}" [formControlName]="'quizpassword'" [clearOnEdit]="false"
|
||||||
[attr.aria-label]="'addon.mod_quiz.quizpassword' | translate">
|
[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-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
|
@ -97,7 +97,7 @@ import { CoreSitesListComponent } from './sites-list/sites-list';
|
||||||
CoreProgressBarComponent,
|
CoreProgressBarComponent,
|
||||||
CoreRecaptchaComponent,
|
CoreRecaptchaComponent,
|
||||||
CoreSendMessageFormComponent,
|
CoreSendMessageFormComponent,
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent, // eslint-disable-line deprecation/deprecation
|
||||||
CoreSitePickerComponent,
|
CoreSitePickerComponent,
|
||||||
CoreSplitViewComponent,
|
CoreSplitViewComponent,
|
||||||
// eslint-disable-next-line deprecation/deprecation
|
// eslint-disable-next-line deprecation/deprecation
|
||||||
|
@ -153,7 +153,7 @@ import { CoreSitesListComponent } from './sites-list/sites-list';
|
||||||
CoreProgressBarComponent,
|
CoreProgressBarComponent,
|
||||||
CoreRecaptchaComponent,
|
CoreRecaptchaComponent,
|
||||||
CoreSendMessageFormComponent,
|
CoreSendMessageFormComponent,
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent, // eslint-disable-line deprecation/deprecation
|
||||||
CoreSitePickerComponent,
|
CoreSitePickerComponent,
|
||||||
CoreSplitViewComponent,
|
CoreSplitViewComponent,
|
||||||
// eslint-disable-next-line deprecation/deprecation
|
// eslint-disable-next-line deprecation/deprecation
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<ion-input [ariaLabel]="placeholder | translate" class="ion-text-wrap core-ioninput-password" name="password"
|
<ion-input [ariaLabel]="placeholder | translate" class="ion-text-wrap core-ioninput-password" name="password"
|
||||||
type="password" placeholder="{{ placeholder | translate }}" [(ngModel)]="password" core-auto-focus
|
type="password" placeholder="{{ placeholder | translate }}" [(ngModel)]="password" core-auto-focus
|
||||||
[clearOnEdit]="false">
|
[clearOnEdit]="false">
|
||||||
<core-show-password slot="end" />
|
<ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" />
|
||||||
</ion-input>
|
</ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="error" class="ion-text-wrap ion-padding-top text-danger">
|
<ion-item *ngIf="error" class="ion-text-wrap ion-padding-top text-danger">
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
<ng-content />
|
<ng-content />
|
||||||
<ion-button fill="clear" [ariaLabel]="(shown ? 'core.hide' : 'core.show') | translate" core-suppress-events (onClick)="toggle($event)"
|
<ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" *ngIf="!this.ionInput" />
|
||||||
(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>
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -12,173 +12,80 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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 { IonInput } from '@ionic/angular';
|
||||||
|
|
||||||
import { CorePlatform } from '@services/platform';
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component allows to show/hide a password.
|
* This component allows to show/hide a password.
|
||||||
* It's meant to be used with ion-input.
|
* It's meant to be used with ion-input as a slot of the input.
|
||||||
* It's recommended to use it as a slot of the input.
|
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
*
|
*
|
||||||
* There are 2 ways to use ths component:
|
* There are 2 ways to use ths component:
|
||||||
* - Slot it to start or end on the ion-input element.
|
* - Slot it to start or end on the ion-input element.
|
||||||
* - Surround the ion-input with the password with this component. This is deprecated.
|
* - Surround the ion-input with the password with this component. Not recommended.
|
||||||
*
|
|
||||||
* In order to help finding the input you can specify the name of the input or the ion-input element.
|
|
||||||
*
|
|
||||||
*
|
*
|
||||||
* Example of new usage:
|
* Example of new usage:
|
||||||
*
|
*
|
||||||
* <ion-input type="password" name="password">
|
* <ion-input type="password">
|
||||||
* <core-show-password slot="end" />
|
* <core-show-password slot="end" />
|
||||||
* </ion-input>
|
* </ion-input>
|
||||||
*
|
*
|
||||||
* Example deprecated usage:
|
* Example surrounding usage:
|
||||||
*
|
*
|
||||||
* <core-show-password>
|
* <core-show-password>
|
||||||
* <ion-input type="password" name="password"></ion-input>
|
* <ion-input type="password" />
|
||||||
* </core-show-password>
|
* </core-show-password>
|
||||||
|
*
|
||||||
|
* @deprecated since 4.5. Use <ion-input-password-toggle slot="end" showIcon="fas-eye" hideIcon="fas-eye-slash" /> instead.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'core-show-password',
|
selector: 'core-show-password',
|
||||||
templateUrl: 'core-show-password.html',
|
templateUrl: 'core-show-password.html',
|
||||||
styleUrls: ['show-password.scss'],
|
styles: 'core-show-password { display: contents; }',
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
})
|
})
|
||||||
export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
export class CoreShowPasswordComponent implements 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';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @deprecated since 4.5. Not used anymore.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
@Input() initialShown = '';
|
||||||
this.shown = CoreUtils.isTrueOrOne(this.initialShown);
|
|
||||||
}
|
/**
|
||||||
|
* @deprecated since 4.4. Not used anymore.
|
||||||
|
*/
|
||||||
|
@Input() name = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.4. Use slotted solution instead.
|
||||||
|
*/
|
||||||
|
@ContentChild(IonInput) ionInput?: IonInput | HTMLIonInputElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngAfterViewInit(): Promise<void> {
|
async ngAfterViewInit(): Promise<void> {
|
||||||
await this.setInputElement();
|
CoreLogger.getInstance('CoreShowPasswordComponent')
|
||||||
|
.warn('Deprecated component, use <ion-input-password-toggle /> instead.');
|
||||||
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.');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line deprecation/deprecation
|
||||||
if (!this.ionInput) {
|
if (!this.ionInput) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// eslint-disable-next-line deprecation/deprecation
|
||||||
this.input = await this.ionInput.getInputElement();
|
const input = await CoreUtils.ignoreErrors(this.ionInput.getInputElement());
|
||||||
} catch {
|
if (!input) {
|
||||||
// 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)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
const toggle = CoreDomUtils.convertToElement('<ion-input-password-toggle slot="end" />');
|
||||||
event.stopPropagation();
|
input.parentElement?.appendChild(toggle.children[0]);
|
||||||
|
|
||||||
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';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
|
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
|
||||||
formControlName="password" [clearOnEdit]="false" autocomplete="current-password" enterkeyhint="go"
|
formControlName="password" [clearOnEdit]="false" autocomplete="current-password" enterkeyhint="go"
|
||||||
required="true" [attr.aria-label]="'core.login.password' | translate ">
|
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-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-button expand="block" type="submit" [disabled]="!credForm.valid"
|
<ion-button expand="block" type="submit" [disabled]="!credForm.valid"
|
||||||
|
|
|
@ -103,7 +103,7 @@
|
||||||
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
|
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
|
||||||
autocomplete="new-password" required="true">
|
autocomplete="new-password" required="true">
|
||||||
<div slot="label" [core-mark-required]="true">{{ 'core.login.password' | translate }}</div>
|
<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>
|
</ion-input>
|
||||||
<p *ngIf="settings.passwordpolicy" class="core-input-footnote">
|
<p *ngIf="settings.passwordpolicy" class="core-input-footnote">
|
||||||
{{settings.passwordpolicy}}
|
{{settings.passwordpolicy}}
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
|
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
|
||||||
autocomplete="current-password" enterkeyhint="go" required="true"
|
autocomplete="current-password" enterkeyhint="go" required="true"
|
||||||
[attr.aria-label]="'core.login.password' | translate">
|
[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-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-button type="submit" expand="block" [disabled]="!credForm.valid"
|
<ion-button type="submit" expand="block" [disabled]="!credForm.valid"
|
||||||
|
|
|
@ -2,6 +2,10 @@ ion-input {
|
||||||
&.input-disabled.md, &.input-disabled.ios {
|
&.input-disabled.md, &.input-disabled.ios {
|
||||||
opacity: var(--mdl-input-disabled-opacity);
|
opacity: var(--mdl-input-disabled-opacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-input-password-toggle {
|
||||||
|
--ion-color-primary: var(--text-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[disabled],
|
input[disabled],
|
||||||
|
|
Loading…
Reference in New Issue