ngx-form-stepper 0.0.2 → 0.0.4
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.md +7 -1
- package/package.json +1 -1
- package/README.fr.md +0 -374
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ It prevents creating invalid states **at development time**, not at runtime.
|
|
|
8
8
|
|
|
9
9
|
Intended for Angular developers who want robust, typed, and maintainable forms without complex configuration.
|
|
10
10
|
|
|
11
|
-
## Why
|
|
11
|
+
## Why?
|
|
12
12
|
|
|
13
13
|
- Simple multi-step forms declaration
|
|
14
14
|
- Quick per-field validation setup
|
|
@@ -17,6 +17,12 @@ Intended for Angular developers who want robust, typed, and maintainable forms w
|
|
|
17
17
|
- Unique return keys required
|
|
18
18
|
- **No `as const` needed**
|
|
19
19
|
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install ngx-form-stepper
|
|
24
|
+
```
|
|
25
|
+
|
|
20
26
|
## Quick example
|
|
21
27
|
|
|
22
28
|
```typescript
|
package/package.json
CHANGED
package/README.fr.md
DELETED
|
@@ -1,374 +0,0 @@
|
|
|
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
|
-
```
|