ngx-form-stepper 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.fr.md ADDED
@@ -0,0 +1,374 @@
1
+ # ngx-form-stepper
2
+
3
+ **ngx-form-stepper** est une librairie Angular pour créer des formulaires à étapes avec validations par champ, **extrêmement typée**.
4
+
5
+ Elle empêche la création d’états invalides **au moment du développement**, pas à l’exécution.
6
+
7
+ Destinée aux développeurs Angular qui veulent des formulaires robustes, typés et maintenables sans configuration complexe.
8
+
9
+ ## Pourquoi ?
10
+
11
+ - Formulaires multi-étapes simples à déclarer
12
+ - Validation par champ rapide à mettre en place
13
+ - Impossible d’associer un mauvais `validator` à un `Input`
14
+ - Valeurs toujours cohérentes avec leur type
15
+ - Clés de retour uniques obligatoires
16
+ - **Aucun `as const` requis**
17
+
18
+ ## Exemple rapide
19
+
20
+ ```typescript
21
+ step1 = new Step([
22
+ new Input(InputType.Text, null, 'firstName', 'First name', [required('First name is required')]),
23
+ new Input(InputType.Text, null, 'lastName', 'Last name', [required('Last name is required')]),
24
+ ]);
25
+
26
+ step2 = new Step([
27
+ new Input(InputType.Email, null, 'email', 'E-mail', [
28
+ required('E-mail is required'),
29
+ email('E-mail is invalid'),
30
+ ]),
31
+ new Input(InputType.Password, null, 'password', 'Password', [
32
+ required('Password is required'),
33
+ strongPassword('Password is too weak'),
34
+ ]),
35
+ ]);
36
+
37
+ signupForm = new FormStepper([step1, step2], {
38
+ title: 'Sign in',
39
+ buttonText: { next: 'Next', previous: 'Previous', final: 'Sign up' },
40
+ });
41
+
42
+ onComplete() {
43
+ console.log(signupForm.values);
44
+ }
45
+ ```
46
+
47
+ ```html
48
+ <app-form-stepper [formStepper]="signupForm" (completed)="onComplete()" />
49
+ ```
50
+
51
+ ## Input
52
+
53
+ Chaque type d’`Input` accepte uniquement les `validators` compatibles.
54
+
55
+ Exemples :
56
+
57
+ - `email` ❌ interdit sur `number`
58
+ - `minLength` ❌ interdit sur `checkbox`
59
+ - `confirm` ❌ interdit sur `select`
60
+
61
+ Et uniquement les valeurs par défaut compatibles.
62
+
63
+ Exemples :
64
+
65
+ - `string` ❌ interdit sur `number`
66
+ - `number` ❌ interdit sur `checkbox`
67
+ - `string | number` ❌ interdit sur `select`
68
+
69
+ Clé de retour au format camelCase.
70
+
71
+ Impossible de dupliquer un `validator`.
72
+
73
+ ```typescript
74
+ export class Input<
75
+ T extends InputType,
76
+ D extends InputDefaultValue<T>,
77
+ K extends string,
78
+ V extends ValidatorTuple<ValidatorsNamesOfType<T>>
79
+ > {
80
+ readonly defaultValue: D;
81
+
82
+ constructor(
83
+ readonly type: T,
84
+ defaultValue: D,
85
+ readonly returnKey: IsCamelCase<K> extends true ? K : never,
86
+ readonly label: string,
87
+ readonly validators?: HasDuplicateValidators<V> extends true ? never : V
88
+ ) {
89
+ this.defaultValue = (
90
+ type === InputType.Checkbox ? (defaultValue === null ? false : defaultValue) : defaultValue
91
+ ) as D;
92
+ }
93
+ }
94
+
95
+ export enum InputType {
96
+ Text = 'text',
97
+ Password = 'password',
98
+ Email = 'email',
99
+ Number = 'number',
100
+ Tel = 'tel',
101
+ Checkbox = 'checkbox',
102
+ Date = 'date',
103
+ Select = 'select',
104
+ }
105
+ ```
106
+
107
+ ## Validator
108
+
109
+ Un `validator` est une fonction qu’on peut passer à un `Input`. Elle prend différents arguments comme la valeur conditionnelle ou le texte de l’erreur.
110
+
111
+ ```typescript
112
+ export function minLength(min: number, errorText: string): Validator<'minLength'> {
113
+ const name: StandardValidatorNameFn<'minLength'> = (params: { key: string }) =>
114
+ `${params.key}-minLength`;
115
+
116
+ const fn = (params: { key: string }) => (control: AbstractControl<string>) => {
117
+ const customName: StandardValidatorName<'minLength'> = `${params.key}-minLength`;
118
+
119
+ return control.value.length < min ? { [customName]: true } : null;
120
+ };
121
+
122
+ return {
123
+ kind: 'minLength',
124
+ name,
125
+ fn,
126
+ errorText,
127
+ };
128
+ }
129
+
130
+ export type ValidatorsNames =
131
+ | 'required'
132
+ | 'check'
133
+ | 'confirm'
134
+ | 'minLength'
135
+ | 'maxLength'
136
+ | 'min'
137
+ | 'max'
138
+ | 'integer'
139
+ | 'pattern'
140
+ | 'strongPassword'
141
+ | 'email'
142
+ | 'phone'
143
+ | 'minDate'
144
+ | 'maxDate';
145
+ ```
146
+
147
+ ## Select
148
+
149
+ Tuple d'un ou plusieurs `SelectItem`.
150
+
151
+ Le `currentIndex` doit obligatoirement être un index valide du tuple ou null.
152
+
153
+ ```typescript
154
+ select = new Input(
155
+ InputType.Select,
156
+ new Select(
157
+ [
158
+ { label: 'Male', value: 'male' },
159
+ { label: 'Female', value: 'female' },
160
+ ],
161
+ 0
162
+ ),
163
+ 'gender',
164
+ 'Gender'
165
+ );
166
+
167
+ export class Select<T extends SelectItemTuple, I extends number | null> {
168
+ current: SelectItem | null;
169
+
170
+ constructor(readonly items: T, readonly currentIndex: HasIndex<T, I> extends true ? I : never) {
171
+ this.current = currentIndex === null ? null : this.items[currentIndex];
172
+ }
173
+ }
174
+
175
+ export type SelectItem = {
176
+ label: string;
177
+ value: string;
178
+ };
179
+ ```
180
+
181
+ ## Step
182
+
183
+ Impossible de dupliquer la clé de retour d’un `Input`.
184
+
185
+ Tuple d’un ou plusieurs `Inputs`.
186
+
187
+ ```typescript
188
+ export class Step<T extends InputTuple> {
189
+ constructor(
190
+ readonly inputs: HasDuplicateReturnKeys<T> extends true ? never : T,
191
+ readonly config?: StepConfig
192
+ ) {}
193
+ }
194
+
195
+ export type StepConfig = Readonly<{
196
+ title: string;
197
+ }>;
198
+ ```
199
+
200
+ ## FormStepper
201
+
202
+ Impossible de dupliquer la clé de retour d’un `Input` entre deux `Steps`.
203
+
204
+ Objet de configuration qui dépend du nombre de `Steps`.
205
+
206
+ Tuple d’une ou plusieurs `Steps`.
207
+
208
+ ```typescript
209
+ export class FormStepper<T extends StepTuple> {
210
+ readonly values: FormStepperValues<T>;
211
+
212
+ constructor(
213
+ readonly steps: HasDuplicateReturnKeys<T> extends true ? never : T,
214
+ readonly config: T extends MultiStepTuple ? MultiStepConfig : SingleStepConfig
215
+ ) {
216
+ this.values = Object.fromEntries(
217
+ steps.flatMap((step) => step.inputs.map((input) => [input.returnKey, input.defaultValue]))
218
+ ) as FormStepperValues<T>;
219
+ }
220
+ }
221
+
222
+ export type SingleStepConfig = Readonly<{
223
+ title?: string;
224
+ actionText?: RedirectItem[];
225
+ buttonText: SingleStepButtonText;
226
+ footerText?: RedirectItem[];
227
+ classNames?: SingleStepClassNames;
228
+ }>;
229
+
230
+ export type MultiStepConfig = Readonly<{
231
+ title?: string;
232
+ actionText?: RedirectItem[];
233
+ buttonText: MultiStepButtonText;
234
+ footerText?: RedirectItem[];
235
+ classNames?: MultiStepClassNames;
236
+ }>;
237
+ ```
238
+
239
+ ## RedirectItem[]
240
+
241
+ Un `RedirectItem[]` est un tableau de string ou d’objet `RedirectUrl`, une sorte de mini langage TS permettant de créer des textes avec lien cliquable.
242
+
243
+ ```typescript
244
+ actionText = ['You already have an account ?', { url: '/signin', urlText: 'Sign in' }];
245
+
246
+ export type RedirectUrl = Readonly<{ url: string; urlText: string }>;
247
+
248
+ export type RedirectText = string;
249
+
250
+ export type RedirectItem = RedirectText | RedirectUrl;
251
+ ```
252
+
253
+ ## ButtonText
254
+
255
+ La propriété `buttonText` du `FormStepper` dépend du nombre de `Steps`.
256
+
257
+ ```typescript
258
+ export type SingleStepButtonText = string;
259
+
260
+ export type MultiStepButtonText = Readonly<{
261
+ final: string;
262
+ previous: string;
263
+ next: string;
264
+ }>;
265
+ ```
266
+
267
+ ## ClassNames
268
+
269
+ Pour ajouter vos propres styles sur un `FormStepper`, je vous conseille de créer un fichier de style séparé et d'y ajouter vos class. Vous devrez ensuite importer le fichier créé dans le fichier global de styles de l'app.
270
+
271
+ ```typescript
272
+ classNames: SingleStepClassNames = {
273
+ title: 'fs-title',
274
+ input: {
275
+ error: 'fs-input-error',
276
+ },
277
+ };
278
+
279
+ form = new FormStepper([this.step], { buttonText: 'Submit', classNames: this.classNames });
280
+ ```
281
+
282
+ ```css
283
+ /* app/fs.css */
284
+
285
+ .fs-title {
286
+ color: blue;
287
+ }
288
+
289
+ .fs-input-error {
290
+ color: red;
291
+ }
292
+ ```
293
+
294
+ ```css
295
+ /* styles.css */
296
+
297
+ @import 'app/fs.css';
298
+ ```
299
+
300
+ La propriété `classNames` du `FormStepper` dépend du nombre de `Steps`.
301
+
302
+ ```typescript
303
+ export type SingleStepClassNames = DeepPartial<{
304
+ container: string;
305
+ title: string;
306
+ actionText: {
307
+ container: string;
308
+ text: string;
309
+ url: string;
310
+ };
311
+ step: {
312
+ container: string;
313
+ title: string;
314
+ form: string;
315
+ inputContainer: string;
316
+ };
317
+ input: {
318
+ container: string;
319
+ label: string;
320
+ required: string;
321
+ input: string;
322
+ errorContainer: string;
323
+ error: string;
324
+ };
325
+ button: {
326
+ container: string;
327
+ button: string;
328
+ disabled: string;
329
+ };
330
+ footerText: {
331
+ container: string;
332
+ text: string;
333
+ url: string;
334
+ };
335
+ }>;
336
+
337
+ export type MultiStepClassNames = DeepPartial<{
338
+ container: string;
339
+ title: string;
340
+ actionText: {
341
+ container: string;
342
+ text: string;
343
+ url: string;
344
+ };
345
+ step: {
346
+ container: string;
347
+ title: string;
348
+ form: string;
349
+ inputContainer: string;
350
+ };
351
+ input: {
352
+ container: string;
353
+ label: string;
354
+ required: string;
355
+ input: string;
356
+ errorContainer: string;
357
+ error: string;
358
+ };
359
+ button: {
360
+ container: string;
361
+ button: string;
362
+ disabled: string;
363
+ first: string;
364
+ final: string;
365
+ previous: string;
366
+ next: string;
367
+ };
368
+ footerText: {
369
+ container: string;
370
+ text: string;
371
+ url: string;
372
+ };
373
+ }>;
374
+ ```