Skip to content
Snippets Groups Projects
Commit 7e7f8d30 authored by P. FONTANET's avatar P. FONTANET Committed by S. Richard
Browse files

feat(password) ajout de autocomplete

parent b94a0478
No related branches found
No related tags found
No related merge requests found
......@@ -2,12 +2,15 @@
# 1.6.0
- feat(form-password): ajout de la propriété `autocomplete="current-password"` par défaut
- feat(radio-rich): ajout du support des pictogrammes
- fix(form-password): `aria-describedby` présent même en l'absence de message
- fix(form-password): `undefined` dans les `id`
- fix(form-password): quelques messages non traduits
- fix(franceConnect): pas de traduction 'en'
- fix: type `button` par défaut, sauf exception, pour tous les boutons des composants
- chore(storybook): migration 7.4.5
# 1.5.0
- feat: montée de version DSFR 1.10.1
......
<div class="fr-password" id="password-{{ id }}">
<label class="fr-label" for="password-{{ id }}-input">
<div class="fr-password">
<label class="fr-label" [for]="inputId">
{{ label }}
<span *ngIf="hint" class="fr-hint-text">{{ hint }}</span>
</label>
<div class="fr-input-wrap">
<input
class="fr-password__input fr-input"
autocapitalize="off"
autocorrect="off"
[attr.aria-describedby]="'password-' + id + '-input-messages'"
[attr.aria-describedby]="hasMessages() ? messagesGroupId : null"
aria-required="true"
[(ngModel)]="value"
[disabled]="disabled || null"
[attr.name]="name || null"
autocomplete="new-password"
id="password-{{ id }}-input"
[autocomplete]="autocomplete"
[id]="inputId"
type="password" />
</div>
<div
*ngIf="validationRules && validationRules.length > 0"
class="fr-messages-group"
id="password-{{ id }}-input-messages"
aria-live="polite">
<p class="fr-message" id="password-{{ id }}-input-message">Votre mot de passe doit contenir :</p>
<div *ngIf="hasMessages()" class="fr-messages-group" [id]="messagesGroupId" aria-live="polite">
<p class="fr-message">{{ i18n.t('password.message') }}</p>
<p
class="fr-message"
[id]="getIdRule(rule, i)"
......@@ -31,21 +29,16 @@
'fr-message--info': rule.onError === undefined,
'fr-message--valid': rule.onError === false
}"
[attr.data-fr-valid]="i18n.t('password.dataFrValid')"
[attr.data-fr-error]="i18n.t('password.dataFrError')"
*ngFor="let rule of validationRules; let i = index">
{{ rule.message }}
</p>
</div>
<div class="fr-password__checkbox fr-checkbox-group fr-checkbox-group--sm">
<input
aria-label="Afficher le mot de passe"
id="password-{{ id }}-show"
type="checkbox"
[attr.aria-describedby]="'password-' + id + '-show-messages'" />
<label class="fr-password__checkbox fr-label" for="password-{{ id }}-show"> {{ i18n.t('password.show') }} </label>
<div class="fr-messages-group" id="password-1138-show-messages" aria-live="polite"></div>
<input [attr.aria-label]="i18n.t('password.aria.label')" [id]="checkboxId" type="checkbox" />
<label class="fr-password__checkbox fr-label" [for]="checkboxId"> {{ i18n.t('password.show') }} </label>
</div>
<p *ngIf="hasRecovery()">
<dsfr-link
[label]="i18n.t('password.forgotPassword')"
......
import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { NavigationExtras } from '@angular/router';
import { DefaultControlComponent } from '../../_commons';
import { DsfrLinkTarget, I18nService, doLinkEvent } from '../../shared';
import { DefaultControlComponent, newUniqueId } from '../../_commons';
import { doLinkEvent, DsfrLinkTarget, I18nService } from '../../shared';
import { DsfrFormPasswordValidationRule } from './form-password.model';
@Component({
......@@ -17,7 +17,10 @@ import { DsfrFormPasswordValidationRule } from './form-password.model';
},
],
})
export class DsfrFormPasswordComponent extends DefaultControlComponent<string> implements Required<RecoveryNavigation> {
export class DsfrFormPasswordComponent extends DefaultControlComponent<string> implements OnInit {
/** @since 1.6 'current-password' par défaut */
@Input() autocomplete = 'current-password';
/**
* Liste des règles de sécurisation du mot de passe, valides ou en erreur - Optionnel.
* Sous la forme [{message: '', onError: false}]
......@@ -45,12 +48,22 @@ export class DsfrFormPasswordComponent extends DefaultControlComponent<string> i
/** Propage l'évènement Event du DOM à la sélection d'un lien. */
@Output() recoveryRouteSelect = new EventEmitter<string>();
/** @internal */ checkboxId: string;
/** @internal */ messagesGroupId: string;
/** @internal */
constructor(public i18n: I18nService) {
super();
this.label = i18n.t('password.label');
}
/** @internal */
ngOnInit() {
super.ngOnInit();
this.checkboxId = newUniqueId();
this.messagesGroupId = newUniqueId();
}
/** @internal */
getIdRule(rule: DsfrFormPasswordValidationRule, i: number): string {
let status: string = 'info';
......@@ -68,17 +81,13 @@ export class DsfrFormPasswordComponent extends DefaultControlComponent<string> i
return this.recoveryLink || this.recoveryRoute || this.recoveryRouterLink;
}
/** @internal */
onRecovery(event: Event) {
doLinkEvent(event, this.recoveryRouteSelect, this.recoveryRoute);
}
}
/** Pense-bête uniquement, @see DsfrNavigation */
interface RecoveryNavigation {
recoveryLink?: string;
recoveryTargetLink?: DsfrLinkTarget;
recoveryRoute?: string;
recoveryRouterLink?: string | string[];
recoveryRouterLinkActive?: string | string[];
recoveryRouterLinkExtras?: NavigationExtras;
/** @internal */
hasMessages(): boolean {
return this.validationRules && this.validationRules.length > 0;
}
}
import { FormsModule } from '@angular/forms';
import { Meta, moduleMetadata, StoryFn } from '@storybook/angular';
import { dsfrDecorator } from '.storybook/decorators.stories';
import { Meta, moduleMetadata, StoryObj } from '@storybook/angular';
import { DsfrLinkModule } from '../../components';
import { DsfrFormPasswordComponent } from './form-password.component';
import descriptionMd from './form-password.doc.md';
import { dsfrDecorator } from '.storybook/decorators.stories';
const meta: Meta = {
title: 'FORMS/Password',
......@@ -12,10 +12,7 @@ const meta: Meta = {
decorators: [moduleMetadata({ imports: [FormsModule, DsfrLinkModule] })],
};
export default meta;
const Template: StoryFn<DsfrFormPasswordComponent> = (args) => ({
props: args,
});
type Story = StoryObj<DsfrFormPasswordComponent>;
const label = 'Mot de passe';
......@@ -31,42 +28,58 @@ const validationMessagesError = [
{ message: '1 chiffre minimum', onError: false },
];
const DefaultValues: Story = {
args: {
autocomplete: 'current-password',
},
};
/** Default */
export const Default = Template.bind({});
Default.decorators = dsfrDecorator('Mot de passe');
Default.args = {
label: label,
export const Default: Story = {
decorators: dsfrDecorator('Mot de passe'),
args: {
...DefaultValues,
label: label,
},
};
/** Hint */
export const Hint = Template.bind({});
Hint.decorators = dsfrDecorator('Mot de passe avec description');
Hint.args = {
label: label,
hint: 'Texte de description additionnel',
export const Hint: Story = {
decorators: dsfrDecorator('Mot de passe avec description'),
args: {
...DefaultValues,
label: label,
hint: 'Texte de description additionnel',
},
};
/** Valid */
export const Valid = Template.bind({});
Valid.decorators = dsfrDecorator('Mot de passe après validation');
Valid.args = {
label: label,
value: 'abcd',
validationRules: validationMessagesError,
export const Valid: Story = {
decorators: dsfrDecorator('Mot de passe après validation'),
args: {
...DefaultValues,
label: label,
value: 'abcd',
validationRules: validationMessagesError,
},
};
/** Recovery */
export const Recovery = Template.bind({});
Recovery.decorators = dsfrDecorator('Mot de passe de connexion, recoveryLink');
Recovery.args = {
label: label,
recoveryLink: 'https://my-recovery-link',
export const Recovery: Story = {
decorators: dsfrDecorator('Mot de passe de connexion, recoveryLink'),
args: {
...DefaultValues,
label: label,
recoveryLink: 'https://my-recovery-link',
},
};
/** RouterLink */
export const RouterLink = Template.bind({});
Recovery.decorators = dsfrDecorator('Mot de passe de connexion, recoveryRouterLink');
Recovery.args = {
label: label,
recoveryRouterLink: '/recovery',
export const RouterLink: Story = {
decorators: dsfrDecorator('Mot de passe de connexion, recoveryRouterLink'),
args: {
...DefaultValues,
label: label,
recoveryRouterLink: '/recovery',
},
};
......@@ -81,8 +81,10 @@ export const MESSAGES_EN = {
lastPage: 'Last page',
},
password: {
label: 'Password',
aria: { label: 'Show password' },
forgotPassword: 'Mot de passe oublié ?',
label: 'Password',
message: 'Your password must contain:',
show: 'Show',
dataFrValid: 'valid',
dataFrError: 'in error',
......
......@@ -81,8 +81,10 @@ export const MESSAGES_FR = {
lastPage: 'Dernière page',
},
password: {
label: 'Mot de passe',
aria: { label: 'Afficher le mot de passe' },
forgotPassword: 'Mot de passe oublié ?',
label: 'Mot de passe',
message: 'Votre mot de passe doit contenir :',
show: 'Afficher',
dataFrValid: 'validé',
dataFrError: 'en erreur',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment