@rxdi/forms 0.7.211 → 0.7.213
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 +283 -3
- package/dist/form.array.d.ts +31 -0
- package/dist/form.array.js +121 -0
- package/dist/form.decorator.d.ts +1 -1
- package/dist/form.decorator.js +4 -4
- package/dist/form.group.d.ts +13 -8
- package/dist/form.group.js +120 -33
- package/dist/form.tokens.d.ts +24 -1
- package/dist/form.tokens.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +25 -21
- package/dist/rx-fake.d.ts +0 -34
- package/dist/rx-fake.js +0 -99
package/README.md
CHANGED
|
@@ -233,16 +233,21 @@ You can create your error template as follow:
|
|
|
233
233
|
```typescript
|
|
234
234
|
import { html } from '@rxdi/lit-html';
|
|
235
235
|
|
|
236
|
-
export function InputErrorTemplate(input: HTMLInputElement) {
|
|
237
|
-
|
|
236
|
+
export function InputErrorTemplate(input: HTMLInputElement | AbstractInput) {
|
|
237
|
+
// Check 'touched' to show errors only after interaction
|
|
238
|
+
// Check 'validity.valid' silently to avoid side effects
|
|
239
|
+
if (input && input.touched && !input.validity.valid) {
|
|
238
240
|
return html`
|
|
239
|
-
<div>${input.validationMessage}</div>
|
|
241
|
+
<div style="color:red; font-size: 13px;">${input.validationMessage}</div>
|
|
240
242
|
`;
|
|
241
243
|
}
|
|
242
244
|
return '';
|
|
243
245
|
}
|
|
244
246
|
```
|
|
245
247
|
|
|
248
|
+
> **Note:** Using `!input.validity.valid` is preferred over `!input.checkValidity()` inside templates because `checkValidity()` fires an `invalid` event which can cause recursive validation loops.
|
|
249
|
+
```
|
|
250
|
+
|
|
246
251
|
|
|
247
252
|
Usage
|
|
248
253
|
|
|
@@ -312,4 +317,279 @@ form
|
|
|
312
317
|
/>
|
|
313
318
|
</form>
|
|
314
319
|
<script src="./main.ts"></script>
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### Nested Forms (FormArray)
|
|
323
|
+
|
|
324
|
+
You can create nested forms using `FormArray` and `FormGroup`.
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { FormArray, FormGroup } from '@rxdi/forms';
|
|
328
|
+
|
|
329
|
+
const form = new FormGroup({
|
|
330
|
+
users: new FormArray([
|
|
331
|
+
new FormGroup({
|
|
332
|
+
name: 'User 1',
|
|
333
|
+
email: 'user1@gmail.com'
|
|
334
|
+
})
|
|
335
|
+
])
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Template usage:
|
|
340
|
+
|
|
341
|
+
```html
|
|
342
|
+
${this.form.get('users').controls.map((group, index) => html`
|
|
343
|
+
<div>
|
|
344
|
+
<input
|
|
345
|
+
name="users[${index}].name"
|
|
346
|
+
.value=${group.value.name}
|
|
347
|
+
/>
|
|
348
|
+
<input
|
|
349
|
+
name="users[${index}].email"
|
|
350
|
+
.value=${group.value.email}
|
|
351
|
+
/>
|
|
352
|
+
<button @click=${() => this.removeUser(index)}>Remove</button>
|
|
353
|
+
</div>
|
|
354
|
+
`)}
|
|
355
|
+
<button @click=${() => this.addUser()}>Add User</button>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Adding items dynamically:
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
addUser() {
|
|
362
|
+
(this.form.get('users') as FormArray).push(
|
|
363
|
+
new FormGroup({ name: '', email: '' })
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
removeUser(index: number) {
|
|
368
|
+
(this.form.get('users') as FormArray).removeAt(index);
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
#### Type Safety & Decorator Checking
|
|
373
|
+
|
|
374
|
+
The `@Form` decorator now proactively checks that the decorated property is strongly typed as `FormGroup`.
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
export class MyComponent {
|
|
378
|
+
@Form({ name: 'my-form' })
|
|
379
|
+
form: FormGroup; // Must be typed as FormGroup!
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
If you incorrectly type it (e.g., `form: string`), the library will throw a helpful error at runtime (requires `emitDecoratorMetadata: true` in `tsconfig`).
|
|
384
|
+
|
|
385
|
+
Validators are also type-safe using the `ValidatorFn` type:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
import { ValidatorFn, AbstractInput, InputErrorMessage } from '@rxdi/forms';
|
|
389
|
+
|
|
390
|
+
const myValidator: ValidatorFn = async (element: AbstractInput) => {
|
|
391
|
+
// ... validation logic
|
|
392
|
+
};
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### Complete Component Example
|
|
396
|
+
|
|
397
|
+
Here is a complete, copy-pasteable example of a component using `@rxdi/forms` with best practices for validation and type safety.
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
import { Component, html, LitElement } from '@rxdi/lit-html';
|
|
401
|
+
import { Form, FormGroup, AbstractInput } from '@rxdi/forms';
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Helper to display errors safely.
|
|
405
|
+
* Checks 'touched' (user interacted) and 'validity.valid' (silent check).
|
|
406
|
+
*/
|
|
407
|
+
function ErrorTemplate(input: AbstractInput) {
|
|
408
|
+
if (input && input.touched && !input.validity.valid) {
|
|
409
|
+
return html`<div class="error">${input.validationMessage}</div>`;
|
|
410
|
+
}
|
|
411
|
+
return html``;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
@Component({
|
|
415
|
+
selector: 'user-profile-form',
|
|
416
|
+
template(this: UserProfileForm) {
|
|
417
|
+
return html`
|
|
418
|
+
<form
|
|
419
|
+
name="user-form"
|
|
420
|
+
@submit=${(e: Event) => {
|
|
421
|
+
e.preventDefault();
|
|
422
|
+
console.log('Form Value:', this.form.value);
|
|
423
|
+
}}
|
|
424
|
+
>
|
|
425
|
+
<!-- Email Field -->
|
|
426
|
+
<div>
|
|
427
|
+
<label>Email</label>
|
|
428
|
+
<input
|
|
429
|
+
type="email"
|
|
430
|
+
name="email"
|
|
431
|
+
required
|
|
432
|
+
.value=${this.form.value.email}
|
|
433
|
+
@blur=${() => this.requestUpdate()}
|
|
434
|
+
/>
|
|
435
|
+
<!--
|
|
436
|
+
@blur triggers a re-render so 'touched' state is reflected.
|
|
437
|
+
The library handles 'touched' internally on blur, but we need
|
|
438
|
+
to request an update to show the error message immediately.
|
|
439
|
+
-->
|
|
440
|
+
${ErrorTemplate(this.form.get('email'))}
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<!-- Password Field -->
|
|
444
|
+
<div>
|
|
445
|
+
<label>Password</label>
|
|
446
|
+
<input
|
|
447
|
+
type="password"
|
|
448
|
+
name="password"
|
|
449
|
+
required
|
|
450
|
+
minlength="6"
|
|
451
|
+
.value=${this.form.value.password}
|
|
452
|
+
@blur=${() => this.requestUpdate()}
|
|
453
|
+
/>
|
|
454
|
+
${ErrorTemplate(this.form.get('password'))}
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<button type="submit" ?disabled=${this.form.invalid}>
|
|
458
|
+
Save Profile
|
|
459
|
+
</button>
|
|
460
|
+
</form>
|
|
461
|
+
`;
|
|
462
|
+
},
|
|
463
|
+
})
|
|
464
|
+
export class UserProfileForm extends LitElement {
|
|
465
|
+
@Form({
|
|
466
|
+
name: 'user-form', // Must match <form name="...">
|
|
467
|
+
strategy: 'change', // Validate on change/blur
|
|
468
|
+
})
|
|
469
|
+
form = new FormGroup({
|
|
470
|
+
email: '',
|
|
471
|
+
password: '',
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Key Takeaways for Templates
|
|
477
|
+
|
|
478
|
+
1. **Name Matching**: The `<form name="X">` attribute must match the `@Form({ name: 'X' })` name.
|
|
479
|
+
2. **Input Binding**: Use `.value=${this.form.value.fieldName}` to bind data. The library updates the model on user input automatically.
|
|
480
|
+
3. **Error Display**: Use a helper like `ErrorTemplate` that checks `.touched` and `.validity.valid`.
|
|
481
|
+
* **Wait until touched**: `if (input.touched)` prevents errors from showing on load.
|
|
482
|
+
* **Silent Check**: `!input.validity.valid` prevents side effects (looping).
|
|
483
|
+
4. **Re-rendering**: Since validation often updates on `blur`, ensure your component re-renders to show the error state (e.g., via `@blur=${() => this.requestUpdate()}` or generic event handlers).
|
|
484
|
+
|
|
485
|
+
#### Complete Nested Form Example (FormArray)
|
|
486
|
+
|
|
487
|
+
This example demonstrates managing a dynamic list of items (e.g., Team Members) using `FormArray`.
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
import { Component, html, LitElement } from '@rxdi/lit-html';
|
|
491
|
+
import { Form, FormGroup, FormArray, AbstractInput } from '@rxdi/forms';
|
|
492
|
+
|
|
493
|
+
function ErrorTemplate(input: AbstractInput) {
|
|
494
|
+
if (input && input.touched && !input.validity.valid) {
|
|
495
|
+
return html`<div class="error">${input.validationMessage}</div>`;
|
|
496
|
+
}
|
|
497
|
+
return html``;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
@Component({
|
|
501
|
+
selector: 'team-form',
|
|
502
|
+
template(this: TeamForm) {
|
|
503
|
+
return html`
|
|
504
|
+
<form @submit=${(e) => e.preventDefault()}>
|
|
505
|
+
|
|
506
|
+
<!-- Main Form Field -->
|
|
507
|
+
<div>
|
|
508
|
+
<label>Team Name</label>
|
|
509
|
+
<input
|
|
510
|
+
name="teamName"
|
|
511
|
+
required
|
|
512
|
+
.value=${this.form.value.teamName}
|
|
513
|
+
@blur=${() => this.requestUpdate()}
|
|
514
|
+
/>
|
|
515
|
+
${ErrorTemplate(this.form.get('teamName'))}
|
|
516
|
+
</div>
|
|
517
|
+
|
|
518
|
+
<h3>Members</h3>
|
|
519
|
+
|
|
520
|
+
<!-- FormArray Iteration -->
|
|
521
|
+
${this.members.controls.map((control, index) => html`
|
|
522
|
+
<div class="member-row">
|
|
523
|
+
|
|
524
|
+
<!-- Nested Field: Name -->
|
|
525
|
+
<!-- Notice the name syntax: arrayName[index].fieldName -->
|
|
526
|
+
<div class="field">
|
|
527
|
+
<input
|
|
528
|
+
placeholder="Member Name"
|
|
529
|
+
name="members[${index}].name"
|
|
530
|
+
required
|
|
531
|
+
.value=${control.value.name}
|
|
532
|
+
@blur=${() => this.requestUpdate()}
|
|
533
|
+
/>
|
|
534
|
+
<!-- Access nested control using .get() on the group -->
|
|
535
|
+
${ErrorTemplate(control.get('name'))}
|
|
536
|
+
</div>
|
|
537
|
+
|
|
538
|
+
<!-- Nested Field: Role -->
|
|
539
|
+
<div class="field">
|
|
540
|
+
<input
|
|
541
|
+
placeholder="Role"
|
|
542
|
+
name="members[${index}].role"
|
|
543
|
+
required
|
|
544
|
+
.value=${control.value.role}
|
|
545
|
+
@blur=${() => this.requestUpdate()}
|
|
546
|
+
/>
|
|
547
|
+
${ErrorTemplate(control.get('role'))}
|
|
548
|
+
</div>
|
|
549
|
+
|
|
550
|
+
<button type="button" @click=${() => this.removeMember(index)}>
|
|
551
|
+
Remove
|
|
552
|
+
</button>
|
|
553
|
+
</div>
|
|
554
|
+
`)}
|
|
555
|
+
|
|
556
|
+
<button type="button" @click=${() => this.addMember()}>
|
|
557
|
+
Add Member
|
|
558
|
+
</button>
|
|
559
|
+
|
|
560
|
+
<button type="submit" ?disabled=${this.form.invalid}>
|
|
561
|
+
Submit Team
|
|
562
|
+
</button>
|
|
563
|
+
</form>
|
|
564
|
+
`;
|
|
565
|
+
},
|
|
566
|
+
})
|
|
567
|
+
export class TeamForm extends LitElement {
|
|
568
|
+
@Form({
|
|
569
|
+
name: 'team-form',
|
|
570
|
+
strategy: 'change',
|
|
571
|
+
})
|
|
572
|
+
form = new FormGroup({
|
|
573
|
+
teamName: '',
|
|
574
|
+
members: new FormArray<{ name: string; role: string }>([]),
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// Helper to cast the control to FormArray for better typing
|
|
578
|
+
get members() {
|
|
579
|
+
return this.form.get('members') as FormArray;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
addMember() {
|
|
583
|
+
this.members.push(
|
|
584
|
+
new FormGroup({
|
|
585
|
+
name: '',
|
|
586
|
+
role: '',
|
|
587
|
+
})
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
removeMember(index: number) {
|
|
592
|
+
this.members.removeAt(index);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
315
595
|
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { LitElement } from '@rxdi/lit-html';
|
|
2
|
+
import { BehaviorSubject } from 'rxjs';
|
|
3
|
+
import { AbstractControl, FormOptions } from './form.tokens';
|
|
4
|
+
export declare class FormArray<T = any> implements AbstractControl<T[]> {
|
|
5
|
+
controls: AbstractControl<T>[];
|
|
6
|
+
readonly valueChanges: BehaviorSubject<T[]>;
|
|
7
|
+
private readonly _valueChanges;
|
|
8
|
+
parentElement: LitElement;
|
|
9
|
+
name: string;
|
|
10
|
+
valid: boolean;
|
|
11
|
+
invalid: boolean;
|
|
12
|
+
errors: {};
|
|
13
|
+
private form;
|
|
14
|
+
private options;
|
|
15
|
+
private subscriptions;
|
|
16
|
+
constructor(controls?: AbstractControl<T>[], name?: string);
|
|
17
|
+
get value(): T[];
|
|
18
|
+
getOptions(): FormOptions;
|
|
19
|
+
setOptions(options: FormOptions): void;
|
|
20
|
+
push(control: AbstractControl<T>): Promise<void>;
|
|
21
|
+
removeAt(index: number): void;
|
|
22
|
+
private subscribeToControl;
|
|
23
|
+
unsubscribe(): void;
|
|
24
|
+
updateValue(): void;
|
|
25
|
+
requestUpdate(): void;
|
|
26
|
+
setParentElement(parent: LitElement): void;
|
|
27
|
+
setFormElement(form: HTMLFormElement): void;
|
|
28
|
+
init(): void;
|
|
29
|
+
getParentElement(): LitElement;
|
|
30
|
+
set value(values: T[]);
|
|
31
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.FormArray = void 0;
|
|
13
|
+
const rxjs_1 = require("rxjs");
|
|
14
|
+
class FormArray {
|
|
15
|
+
constructor(controls = [], name = '') {
|
|
16
|
+
this.controls = [];
|
|
17
|
+
this.valid = true;
|
|
18
|
+
this.invalid = false;
|
|
19
|
+
this.errors = {};
|
|
20
|
+
this.options = {};
|
|
21
|
+
this.subscriptions = new Map();
|
|
22
|
+
this.controls = controls;
|
|
23
|
+
this.name = name;
|
|
24
|
+
this._valueChanges = new rxjs_1.BehaviorSubject(this.value);
|
|
25
|
+
this.valueChanges = this._valueChanges;
|
|
26
|
+
this.controls.forEach((c) => this.subscribeToControl(c));
|
|
27
|
+
}
|
|
28
|
+
get value() {
|
|
29
|
+
return this.controls.map((c) => c.value);
|
|
30
|
+
}
|
|
31
|
+
getOptions() {
|
|
32
|
+
return this.options;
|
|
33
|
+
}
|
|
34
|
+
setOptions(options) {
|
|
35
|
+
this.options = options;
|
|
36
|
+
this.controls.forEach((c, index) => {
|
|
37
|
+
c.setOptions(Object.assign(Object.assign({}, options), { namespace: `${this.name}[${index}]` }));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
push(control) {
|
|
41
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
+
this.controls.push(control);
|
|
43
|
+
this.subscribeToControl(control);
|
|
44
|
+
const index = this.controls.length - 1;
|
|
45
|
+
control.setOptions(Object.assign(Object.assign(Object.assign({}, this.options), control.getOptions()), { namespace: `${this.name}[${index}]` }));
|
|
46
|
+
this.updateValue();
|
|
47
|
+
this.requestUpdate();
|
|
48
|
+
if (this.parentElement) {
|
|
49
|
+
yield this.parentElement.updateComplete;
|
|
50
|
+
control.setParentElement(this.parentElement);
|
|
51
|
+
if (this.form) {
|
|
52
|
+
control.setFormElement(this.form);
|
|
53
|
+
control.init();
|
|
54
|
+
}
|
|
55
|
+
else if (control.getFormElement()) {
|
|
56
|
+
control.init();
|
|
57
|
+
}
|
|
58
|
+
else if (this.controls[0] && this.controls[0].getFormElement()) {
|
|
59
|
+
control.setFormElement(this.controls[0].getFormElement());
|
|
60
|
+
control.init();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
removeAt(index) {
|
|
66
|
+
const control = this.controls[index];
|
|
67
|
+
if (this.subscriptions.has(control)) {
|
|
68
|
+
this.subscriptions.get(control).unsubscribe();
|
|
69
|
+
this.subscriptions.delete(control);
|
|
70
|
+
}
|
|
71
|
+
this.controls.splice(index, 1);
|
|
72
|
+
this.updateValue();
|
|
73
|
+
this.requestUpdate();
|
|
74
|
+
}
|
|
75
|
+
subscribeToControl(control) {
|
|
76
|
+
if (control.valueChanges) {
|
|
77
|
+
this.subscriptions.set(control, control.valueChanges.subscribe(() => {
|
|
78
|
+
this.updateValue();
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
unsubscribe() {
|
|
83
|
+
this.subscriptions.forEach((sub) => sub.unsubscribe());
|
|
84
|
+
this.subscriptions.clear();
|
|
85
|
+
this.controls.forEach((c) => c.unsubscribe && c.unsubscribe());
|
|
86
|
+
}
|
|
87
|
+
updateValue() {
|
|
88
|
+
this._valueChanges.next(this.value);
|
|
89
|
+
}
|
|
90
|
+
requestUpdate() {
|
|
91
|
+
if (this.parentElement) {
|
|
92
|
+
this.parentElement.requestUpdate();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
setParentElement(parent) {
|
|
96
|
+
this.parentElement = parent;
|
|
97
|
+
this.controls.forEach((c) => c.setParentElement(parent));
|
|
98
|
+
}
|
|
99
|
+
setFormElement(form) {
|
|
100
|
+
this.form = form;
|
|
101
|
+
this.controls.forEach((c) => c.setFormElement(form));
|
|
102
|
+
}
|
|
103
|
+
init() {
|
|
104
|
+
this.controls.forEach((c) => c.init());
|
|
105
|
+
}
|
|
106
|
+
getParentElement() {
|
|
107
|
+
return this.parentElement;
|
|
108
|
+
}
|
|
109
|
+
set value(values) {
|
|
110
|
+
if (!Array.isArray(values)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
values.forEach((v, i) => {
|
|
114
|
+
if (this.controls[i]) {
|
|
115
|
+
this.controls[i].value = v;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
this.updateValue();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.FormArray = FormArray;
|
package/dist/form.decorator.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { FormOptions } from './form.tokens';
|
|
2
|
-
export declare function Form(options?: FormOptions): (clazz:
|
|
2
|
+
export declare function Form(options?: FormOptions): (clazz: any, name: string | number | symbol) => void;
|
package/dist/form.decorator.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Form = Form;
|
|
4
4
|
const form_group_1 = require("./form.group");
|
|
5
|
-
const
|
|
5
|
+
const rxjs_1 = require("rxjs");
|
|
6
6
|
function Form(options = {
|
|
7
7
|
strategy: 'none',
|
|
8
8
|
}) {
|
|
@@ -10,9 +10,9 @@ function Form(options = {
|
|
|
10
10
|
if (!options.name) {
|
|
11
11
|
throw new Error('Missing form name');
|
|
12
12
|
}
|
|
13
|
-
const Destroy = clazz.constructor.prototype.disconnectedCallback ||
|
|
14
|
-
const UpdateFirst = clazz.constructor.prototype.firstUpdated ||
|
|
15
|
-
const Connect = clazz.constructor.prototype.connectedCallback ||
|
|
13
|
+
const Destroy = clazz.constructor.prototype.disconnectedCallback || rxjs_1.noop;
|
|
14
|
+
const UpdateFirst = clazz.constructor.prototype.firstUpdated || rxjs_1.noop;
|
|
15
|
+
const Connect = clazz.constructor.prototype.connectedCallback || rxjs_1.noop;
|
|
16
16
|
clazz.constructor.prototype.connectedCallback = function () {
|
|
17
17
|
if (!(this[name] instanceof form_group_1.FormGroup)) {
|
|
18
18
|
throw new Error('Value provided is not an instance of FormGroup!');
|
package/dist/form.group.d.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { FormInputOptions, FormOptions, ErrorObject, AbstractInput } from './form.tokens';
|
|
2
1
|
import { LitElement } from '@rxdi/lit-html';
|
|
2
|
+
import { AbstractControl, AbstractInput, ErrorObject, FormInputOptions, FormOptions, ValidatorFn } from './form.tokens';
|
|
3
3
|
export declare class FormGroup<T = FormInputOptions, E = {
|
|
4
4
|
[key: string]: never;
|
|
5
|
-
}> {
|
|
6
|
-
validators: Map<string,
|
|
5
|
+
}> implements AbstractControl<T> {
|
|
6
|
+
validators: Map<string, ValidatorFn[]>;
|
|
7
7
|
valid: boolean;
|
|
8
8
|
invalid: boolean;
|
|
9
9
|
errors: T;
|
|
10
|
+
private controls;
|
|
10
11
|
private readonly _valueChanges;
|
|
11
12
|
private form;
|
|
12
13
|
private errorMap;
|
|
13
14
|
private inputs;
|
|
14
15
|
private options;
|
|
15
16
|
private parentElement;
|
|
17
|
+
private subscriptions;
|
|
16
18
|
constructor(value?: T, errors?: E);
|
|
17
19
|
init(): void;
|
|
18
20
|
prepareValues(): this;
|
|
@@ -21,7 +23,9 @@ export declare class FormGroup<T = FormInputOptions, E = {
|
|
|
21
23
|
setOptions(options: FormOptions): this;
|
|
22
24
|
getOptions(): FormOptions;
|
|
23
25
|
get valueChanges(): import("rxjs").Observable<T>;
|
|
24
|
-
updateValueAndValidity(): Promise<ErrorObject
|
|
26
|
+
updateValueAndValidity(): Promise<(ErrorObject | {
|
|
27
|
+
message: string;
|
|
28
|
+
})[]>;
|
|
25
29
|
private updateValueAndValidityOnEvent;
|
|
26
30
|
applyValidationContext({ errors, element }: ErrorObject): boolean;
|
|
27
31
|
querySelectForm(shadowRoot: HTMLElement | ShadowRoot): HTMLFormElement;
|
|
@@ -31,11 +35,12 @@ export declare class FormGroup<T = FormInputOptions, E = {
|
|
|
31
35
|
setElementValidity(el: AbstractInput, validity?: boolean): Promise<void>;
|
|
32
36
|
setElementDirty(input: AbstractInput): void;
|
|
33
37
|
isInputPresentOnStage(input: AbstractInput): number;
|
|
38
|
+
private getModelKeyName;
|
|
34
39
|
validate(element: AbstractInput): Promise<ErrorObject>;
|
|
35
40
|
private mapInputErrors;
|
|
36
|
-
get(name: keyof T): AbstractInput;
|
|
37
|
-
getError(inputName: keyof T, errorKey:
|
|
38
|
-
hasError(inputName: keyof T, errorKey:
|
|
41
|
+
get(name: keyof T): AbstractControl<any> | AbstractInput;
|
|
42
|
+
getError(inputName: keyof T, errorKey: string): never;
|
|
43
|
+
hasError(inputName: keyof T, errorKey: string): boolean;
|
|
39
44
|
reset(): void;
|
|
40
45
|
setFormValidity(validity?: boolean): void;
|
|
41
46
|
resetErrors(): void;
|
|
@@ -43,7 +48,7 @@ export declare class FormGroup<T = FormInputOptions, E = {
|
|
|
43
48
|
set value(value: T);
|
|
44
49
|
unsubscribe(): void;
|
|
45
50
|
getValue(name: keyof T): T[keyof T];
|
|
46
|
-
setValue(name: keyof T, value:
|
|
51
|
+
setValue(name: keyof T, value: unknown): void;
|
|
47
52
|
setFormValue(value: T): void;
|
|
48
53
|
setFormElement(form: HTMLFormElement): this;
|
|
49
54
|
setInputs(inputs: AbstractInput[]): void;
|
package/dist/form.group.js
CHANGED
|
@@ -10,36 +10,60 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.FormGroup = void 0;
|
|
13
|
+
const rxjs_1 = require("rxjs");
|
|
13
14
|
const form_tokens_1 = require("./form.tokens");
|
|
14
|
-
const rx_fake_1 = require("./rx-fake");
|
|
15
15
|
class FormGroup {
|
|
16
16
|
constructor(value, errors) {
|
|
17
17
|
this.validators = new Map();
|
|
18
18
|
this.valid = true;
|
|
19
19
|
this.invalid = false;
|
|
20
20
|
this.errors = {};
|
|
21
|
+
this.controls = new Map();
|
|
21
22
|
this.errorMap = new Map();
|
|
22
23
|
this.inputs = new Map();
|
|
23
24
|
this.options = {};
|
|
24
|
-
this.
|
|
25
|
+
this.subscriptions = new Map();
|
|
26
|
+
this._valueChanges = new rxjs_1.BehaviorSubject(value);
|
|
27
|
+
if (value) {
|
|
28
|
+
Object.keys(value).forEach((key) => {
|
|
29
|
+
if (typeof value[key] === 'object' && value[key] !== null && (value[key]['controls'] || value[key]['push'])) {
|
|
30
|
+
// It's likely a FormGroup or FormArray
|
|
31
|
+
const control = value[key];
|
|
32
|
+
if (control.name === '' || control.name === undefined) {
|
|
33
|
+
control.name = key;
|
|
34
|
+
}
|
|
35
|
+
this.controls.set(key, control);
|
|
36
|
+
if (control.valueChanges) {
|
|
37
|
+
this.subscriptions.set(key, control.valueChanges.subscribe(() => {
|
|
38
|
+
this._valueChanges.next(this.value);
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
25
44
|
}
|
|
26
45
|
init() {
|
|
27
46
|
this.setFormElement(this.querySelectForm(this.parentElement.shadowRoot || this.parentElement)).setInputs(this.mapEventToInputs(this.querySelectorAllInputs()));
|
|
47
|
+
this.controls.forEach((c) => {
|
|
48
|
+
if (c.init)
|
|
49
|
+
c.init();
|
|
50
|
+
});
|
|
28
51
|
}
|
|
29
52
|
prepareValues() {
|
|
30
53
|
Object.keys(this.value).forEach((v) => {
|
|
54
|
+
// Skip nested controls
|
|
55
|
+
if (this.controls.has(v))
|
|
56
|
+
return;
|
|
31
57
|
const value = this.value[v];
|
|
32
58
|
this.errors[v] = this.errors[v] || {};
|
|
33
|
-
if (value.constructor === Array) {
|
|
59
|
+
if (value && value.constructor === Array) {
|
|
34
60
|
if (value[1] && value[1].constructor === Array) {
|
|
35
61
|
value[1].forEach((val) => {
|
|
36
62
|
const oldValidators = this.validators.get(v) || [];
|
|
37
63
|
this.validators.set(v, [...oldValidators, val]);
|
|
38
64
|
});
|
|
39
65
|
}
|
|
40
|
-
if (value[0].constructor === String ||
|
|
41
|
-
value[0].constructor === Number ||
|
|
42
|
-
value[0].constructor === Boolean) {
|
|
66
|
+
if (value[0].constructor === String || value[0].constructor === Number || value[0].constructor === Boolean) {
|
|
43
67
|
this.value[v] = value[0];
|
|
44
68
|
}
|
|
45
69
|
else {
|
|
@@ -51,6 +75,11 @@ class FormGroup {
|
|
|
51
75
|
}
|
|
52
76
|
setParentElement(parent) {
|
|
53
77
|
this.parentElement = parent;
|
|
78
|
+
this.controls.forEach((c) => {
|
|
79
|
+
if (c.setParentElement) {
|
|
80
|
+
c.setParentElement(parent);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
54
83
|
return this;
|
|
55
84
|
}
|
|
56
85
|
getParentElement() {
|
|
@@ -58,6 +87,10 @@ class FormGroup {
|
|
|
58
87
|
}
|
|
59
88
|
setOptions(options) {
|
|
60
89
|
this.options = options;
|
|
90
|
+
this.controls.forEach((c) => {
|
|
91
|
+
if (c.setOptions)
|
|
92
|
+
c.setOptions(options);
|
|
93
|
+
});
|
|
61
94
|
return this;
|
|
62
95
|
}
|
|
63
96
|
getOptions() {
|
|
@@ -66,6 +99,7 @@ class FormGroup {
|
|
|
66
99
|
get valueChanges() {
|
|
67
100
|
return this._valueChanges.asObservable();
|
|
68
101
|
}
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
103
|
updateValueAndValidity() {
|
|
70
104
|
return __awaiter(this, void 0, void 0, function* () {
|
|
71
105
|
this.resetErrors();
|
|
@@ -75,11 +109,21 @@ class FormGroup {
|
|
|
75
109
|
this.setElementDirty(i);
|
|
76
110
|
return yield this.validate(i);
|
|
77
111
|
})));
|
|
112
|
+
for (const [key, control] of this.controls.entries()) {
|
|
113
|
+
if (control.updateValueAndValidity) {
|
|
114
|
+
yield control.updateValueAndValidity();
|
|
115
|
+
if (control.invalid) {
|
|
116
|
+
this.invalid = true;
|
|
117
|
+
this.valid = false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
78
121
|
this.getParentElement().requestUpdate();
|
|
79
|
-
return inputs.filter((e) => e.errors.length);
|
|
122
|
+
return inputs.filter((e) => e.errors.length) || (this.invalid ? [{ message: 'Invalid Form' }] : []);
|
|
80
123
|
});
|
|
81
124
|
}
|
|
82
125
|
updateValueAndValidityOnEvent(method) {
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
83
127
|
const self = this;
|
|
84
128
|
return function (event) {
|
|
85
129
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -101,7 +145,7 @@ class FormGroup {
|
|
|
101
145
|
value = Number(value);
|
|
102
146
|
}
|
|
103
147
|
const inputsWithBindings = [
|
|
104
|
-
...
|
|
148
|
+
...self.getFormElement().querySelectorAll(`input[name="${this.name}"]:checked`).values(),
|
|
105
149
|
];
|
|
106
150
|
if (hasMultipleBindings > 1) {
|
|
107
151
|
if (!self.options.multi && this.type === 'checkbox') {
|
|
@@ -117,13 +161,13 @@ class FormGroup {
|
|
|
117
161
|
if (self.options.strict) {
|
|
118
162
|
if (isValid) {
|
|
119
163
|
yield self.setElementValidity(this, isValid);
|
|
120
|
-
self.setValue(this.name, value);
|
|
164
|
+
self.setValue(self.getModelKeyName(this.name), value);
|
|
121
165
|
}
|
|
122
166
|
self.parentElement.requestUpdate();
|
|
123
167
|
return method.call(self.parentElement, event);
|
|
124
168
|
}
|
|
125
169
|
yield self.setElementValidity(this, isValid);
|
|
126
|
-
self.setValue(this.name, value);
|
|
170
|
+
self.setValue(self.getModelKeyName(this.name), value);
|
|
127
171
|
self.parentElement.requestUpdate();
|
|
128
172
|
return method.call(self.parentElement, event);
|
|
129
173
|
});
|
|
@@ -132,18 +176,21 @@ class FormGroup {
|
|
|
132
176
|
applyValidationContext({ errors, element }) {
|
|
133
177
|
const form = this.getFormElement();
|
|
134
178
|
if (errors.length) {
|
|
135
|
-
this.invalid = form
|
|
136
|
-
this.valid = form
|
|
179
|
+
this.invalid = form['invalid'] = true;
|
|
180
|
+
this.valid = form['valid'] = false;
|
|
137
181
|
return false;
|
|
138
182
|
}
|
|
139
183
|
else {
|
|
140
|
-
this.errors[element.name] = {};
|
|
141
|
-
this.invalid = form
|
|
142
|
-
this.valid = form
|
|
184
|
+
this.errors[this.getModelKeyName(element.name)] = {};
|
|
185
|
+
this.invalid = form['invalid'] = !form.checkValidity();
|
|
186
|
+
this.valid = form['valid'] = form.checkValidity();
|
|
143
187
|
return true;
|
|
144
188
|
}
|
|
145
189
|
}
|
|
146
190
|
querySelectForm(shadowRoot) {
|
|
191
|
+
if (this.options['form']) {
|
|
192
|
+
return this.options['form'];
|
|
193
|
+
}
|
|
147
194
|
const form = shadowRoot.querySelector(`form[name="${this.options.name}"]`);
|
|
148
195
|
if (!form) {
|
|
149
196
|
throw new Error(`Form element with name "${this.options.name}" not present inside ${this.getParentElement().outerHTML} component`);
|
|
@@ -155,10 +202,9 @@ class FormGroup {
|
|
|
155
202
|
return form;
|
|
156
203
|
}
|
|
157
204
|
querySelectAll(name) {
|
|
158
|
-
return [
|
|
159
|
-
...this.form.querySelectorAll(name).values(),
|
|
160
|
-
];
|
|
205
|
+
return [...this.form.querySelectorAll(name).values()];
|
|
161
206
|
}
|
|
207
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
208
|
querySelectorAllInputs() {
|
|
163
209
|
var _a;
|
|
164
210
|
return [...((_a = this.options.customElements) !== null && _a !== void 0 ? _a : []), 'input', 'select', 'textarea']
|
|
@@ -167,11 +213,14 @@ class FormGroup {
|
|
|
167
213
|
.filter((el) => this.isInputPresentOnStage(el))
|
|
168
214
|
.filter((el) => !!el.name);
|
|
169
215
|
}
|
|
216
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
170
217
|
mapEventToInputs(inputs = []) {
|
|
171
218
|
return inputs.map((el) => {
|
|
172
219
|
const strategy = `on${this.options.strategy}`;
|
|
173
220
|
if (!el[strategy]) {
|
|
174
|
-
el[strategy] = function () {
|
|
221
|
+
el[strategy] = function () {
|
|
222
|
+
//
|
|
223
|
+
};
|
|
175
224
|
}
|
|
176
225
|
const customAttributes = Object.keys(el.attributes)
|
|
177
226
|
.map((k) => (el.attributes[k].name.startsWith('#') ? el.attributes[k] : null))
|
|
@@ -202,20 +251,39 @@ class FormGroup {
|
|
|
202
251
|
}
|
|
203
252
|
isInputPresentOnStage(input) {
|
|
204
253
|
if (input.outerHTML === '<input type="submit" style="display: none;">') {
|
|
205
|
-
return;
|
|
254
|
+
return 0;
|
|
206
255
|
}
|
|
207
|
-
const
|
|
256
|
+
const keyIndex = this.getModelKeyName(input.name);
|
|
257
|
+
const isInputPresent = Object.keys(this.value).filter((v) => v === keyIndex);
|
|
208
258
|
return isInputPresent.length;
|
|
209
259
|
}
|
|
260
|
+
getModelKeyName(domName) {
|
|
261
|
+
if (this.options['namespace']) {
|
|
262
|
+
// pattern: namespace[key] or namespace.key
|
|
263
|
+
// Example: allowedIps[0].ip -> namespace=allowedIps[0] -> key=ip
|
|
264
|
+
if (domName.startsWith(this.options['namespace'])) {
|
|
265
|
+
const suffix = domName.replace(this.options['namespace'], '');
|
|
266
|
+
// Handle .key or [key]
|
|
267
|
+
if (suffix.startsWith('.')) {
|
|
268
|
+
return suffix.substring(1);
|
|
269
|
+
}
|
|
270
|
+
if (suffix.startsWith('[')) {
|
|
271
|
+
return suffix.substring(1, suffix.length - 1); // Not dealing with that yet
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return domName;
|
|
275
|
+
}
|
|
276
|
+
return domName;
|
|
277
|
+
}
|
|
278
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
210
279
|
validate(element) {
|
|
211
280
|
return __awaiter(this, void 0, void 0, function* () {
|
|
212
281
|
let errors = [];
|
|
213
282
|
element.setCustomValidity('');
|
|
214
|
-
this.setFormValidity(true);
|
|
215
283
|
if (!element.checkValidity()) {
|
|
216
284
|
return {
|
|
217
285
|
errors: errors.concat(Object.keys(form_tokens_1.InputValidityState)
|
|
218
|
-
.map((key) => element.validity[key] ? { key, message: element.validationMessage } : null)
|
|
286
|
+
.map((key) => (element.validity[key] ? { key, message: element.validationMessage } : null))
|
|
219
287
|
.filter((i) => !!i)),
|
|
220
288
|
element,
|
|
221
289
|
};
|
|
@@ -224,32 +292,36 @@ class FormGroup {
|
|
|
224
292
|
if (!errors.length) {
|
|
225
293
|
return { errors: [], element };
|
|
226
294
|
}
|
|
227
|
-
this.setFormValidity(false);
|
|
228
295
|
element.setCustomValidity(errors[0].message);
|
|
229
296
|
return { element, errors };
|
|
230
297
|
});
|
|
231
298
|
}
|
|
299
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
232
300
|
mapInputErrors(element) {
|
|
233
301
|
return __awaiter(this, void 0, void 0, function* () {
|
|
234
|
-
const
|
|
235
|
-
|
|
302
|
+
const modelKey = this.getModelKeyName(element.name);
|
|
303
|
+
const res = yield Promise.all((this.validators.get(modelKey) || []).map((v) => __awaiter(this, void 0, void 0, function* () {
|
|
304
|
+
this.errors[modelKey] = this.errors[modelKey] || {};
|
|
236
305
|
const error = yield v.bind(this.getParentElement())(element);
|
|
237
306
|
// if (error) {
|
|
238
307
|
// element.focus();
|
|
239
308
|
// }
|
|
240
309
|
if (error && error.key) {
|
|
241
|
-
this.errors[
|
|
310
|
+
this.errors[modelKey][error.key] = error.message;
|
|
242
311
|
this.errorMap.set(v, error.key);
|
|
243
312
|
return { key: error.key, message: error.message };
|
|
244
313
|
}
|
|
245
314
|
else if (this.errorMap.has(v)) {
|
|
246
|
-
delete this.errors[
|
|
315
|
+
delete this.errors[modelKey][this.errorMap.get(v)];
|
|
247
316
|
}
|
|
248
317
|
})));
|
|
249
318
|
return res.filter((i) => !!i);
|
|
250
319
|
});
|
|
251
320
|
}
|
|
252
321
|
get(name) {
|
|
322
|
+
if (this.controls.has(name)) {
|
|
323
|
+
return this.controls.get(name);
|
|
324
|
+
}
|
|
253
325
|
return this.inputs.get(name);
|
|
254
326
|
}
|
|
255
327
|
getError(inputName, errorKey) {
|
|
@@ -277,7 +349,11 @@ class FormGroup {
|
|
|
277
349
|
this.errorMap.clear();
|
|
278
350
|
}
|
|
279
351
|
get value() {
|
|
280
|
-
|
|
352
|
+
const values = this._valueChanges.getValue();
|
|
353
|
+
this.controls.forEach((control, key) => {
|
|
354
|
+
values[key] = control.value;
|
|
355
|
+
});
|
|
356
|
+
return values;
|
|
281
357
|
}
|
|
282
358
|
set value(value) {
|
|
283
359
|
this._valueChanges.next(value);
|
|
@@ -285,6 +361,9 @@ class FormGroup {
|
|
|
285
361
|
unsubscribe() {
|
|
286
362
|
this.reset();
|
|
287
363
|
this._valueChanges.unsubscribe();
|
|
364
|
+
this.subscriptions.forEach((s) => s.unsubscribe());
|
|
365
|
+
this.subscriptions.clear();
|
|
366
|
+
this.controls.forEach((c) => { var _a; return (_a = c.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(c); });
|
|
288
367
|
}
|
|
289
368
|
getValue(name) {
|
|
290
369
|
return this.value[name];
|
|
@@ -292,9 +371,12 @@ class FormGroup {
|
|
|
292
371
|
setValue(name, value) {
|
|
293
372
|
const input = this.get(name);
|
|
294
373
|
if (!input) {
|
|
295
|
-
|
|
374
|
+
// If no input, maybe just set the value in model?
|
|
375
|
+
// User code had return; but we might want to update model even if no input?
|
|
376
|
+
// return;
|
|
296
377
|
}
|
|
297
|
-
input.value
|
|
378
|
+
if (input && input.value !== undefined)
|
|
379
|
+
input.value = value;
|
|
298
380
|
const values = this.value;
|
|
299
381
|
values[name] = value;
|
|
300
382
|
this.value = values;
|
|
@@ -304,12 +386,17 @@ class FormGroup {
|
|
|
304
386
|
}
|
|
305
387
|
setFormElement(form) {
|
|
306
388
|
this.form = form;
|
|
389
|
+
this.controls.forEach((c) => {
|
|
390
|
+
if (c.setFormElement)
|
|
391
|
+
c.setFormElement(form);
|
|
392
|
+
});
|
|
307
393
|
return this;
|
|
308
394
|
}
|
|
309
395
|
setInputs(inputs) {
|
|
310
396
|
this.inputs = new Map(inputs.map((e) => {
|
|
311
|
-
|
|
312
|
-
|
|
397
|
+
const key = this.getModelKeyName(e.name);
|
|
398
|
+
e.value = this.getValue(key);
|
|
399
|
+
return [key, e];
|
|
313
400
|
}));
|
|
314
401
|
}
|
|
315
402
|
getFormElement() {
|
package/dist/form.tokens.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LitElement } from '@rxdi/lit-html';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
1
3
|
export type FormStrategies = keyof WindowEventMap;
|
|
2
4
|
export interface FormOptions {
|
|
3
5
|
/** Name of the form element */
|
|
@@ -14,9 +16,30 @@ export interface FormOptions {
|
|
|
14
16
|
* Example can be found here https://gist.github.com/Stradivario/57acf0fa19900867a7f55b0f01251d6e
|
|
15
17
|
* */
|
|
16
18
|
customElements?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Internal property for handling nested forms.
|
|
21
|
+
*/
|
|
22
|
+
namespace?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface AbstractControl<T = any> {
|
|
25
|
+
setOptions(options: FormOptions): this | void;
|
|
26
|
+
getOptions(): FormOptions;
|
|
27
|
+
init(): void;
|
|
28
|
+
setParentElement(parent: LitElement): this | void;
|
|
29
|
+
setFormElement(form: HTMLFormElement): this | void;
|
|
30
|
+
unsubscribe(): void;
|
|
31
|
+
valueChanges: Observable<T>;
|
|
32
|
+
value: T;
|
|
33
|
+
valid: boolean;
|
|
34
|
+
invalid: boolean;
|
|
35
|
+
updateValueAndValidity?(): Promise<any>;
|
|
36
|
+
name?: string;
|
|
37
|
+
push?(control: AbstractControl): void;
|
|
38
|
+
getFormElement?(): HTMLFormElement;
|
|
17
39
|
}
|
|
40
|
+
export type ValidatorFn = (element: AbstractInput | HTMLInputElement) => Promise<InputErrorMessage | void> | InputErrorMessage | void;
|
|
18
41
|
export interface FormInputOptions {
|
|
19
|
-
[key: string]: [string,
|
|
42
|
+
[key: string]: [string | number | boolean | Date, ValidatorFn[]];
|
|
20
43
|
}
|
|
21
44
|
export interface InputErrorMessage<T = any> {
|
|
22
45
|
key: T;
|
package/dist/form.tokens.js
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./form.decorator"), exports);
|
|
18
18
|
__exportStar(require("./form.group"), exports);
|
|
19
19
|
__exportStar(require("./form.tokens"), exports);
|
|
20
|
+
__exportStar(require("./form.array"), exports);
|
package/package.json
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
2
|
+
"name": "@rxdi/forms",
|
|
3
|
+
"version": "0.7.213",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"author": "Kristiyan Tachev",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/rxdi/forms"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@rxdi/lit-html": "^0.7.212",
|
|
16
|
+
"@types/node": "^25.0.3",
|
|
17
|
+
"rxjs": "^7.8.2",
|
|
18
|
+
"typescript": "^5.9.3"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"rxjs": "^7.8.2",
|
|
22
|
+
"@rxdi/lit-html": "*"
|
|
23
|
+
},
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"module": "./dist/index.js",
|
|
26
|
+
"typings": "./dist/index.d.ts"
|
|
23
27
|
}
|
package/dist/rx-fake.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { BehaviorSubject as BS, Observable as O, Subscription as S } from 'rxjs';
|
|
2
|
-
type OBS<T> = (o: $Observable<T>) => void | Function;
|
|
3
|
-
type FN<T> = (a: T) => void;
|
|
4
|
-
export declare class $Subscription<T> {
|
|
5
|
-
o: Map<Function, FN<T>>;
|
|
6
|
-
unsubscribe(): void;
|
|
7
|
-
}
|
|
8
|
-
export declare class $Observable<T> extends $Subscription<T> {
|
|
9
|
-
fn: OBS<T>;
|
|
10
|
-
init: boolean;
|
|
11
|
-
constructor(fn?: OBS<T>);
|
|
12
|
-
subscribe(c: FN<T>): $Subscription<T>;
|
|
13
|
-
complete(): void;
|
|
14
|
-
next(s: T): void;
|
|
15
|
-
}
|
|
16
|
-
export declare class $BehaviorSubject<T> extends $Observable<T> {
|
|
17
|
-
v: T;
|
|
18
|
-
constructor(v: T);
|
|
19
|
-
private setValue;
|
|
20
|
-
next(s: T): void;
|
|
21
|
-
getValue(): T;
|
|
22
|
-
asObservable(): this;
|
|
23
|
-
}
|
|
24
|
-
export declare function noop(): void;
|
|
25
|
-
export declare function BehaviorSubject<T>(init: T): void;
|
|
26
|
-
export declare function Observable<T>(fn?: OBS<T>): void;
|
|
27
|
-
export declare function Subscription<T>(): void;
|
|
28
|
-
export interface BehaviorSubject<T> extends BS<T> {
|
|
29
|
-
}
|
|
30
|
-
export interface Observable<T> extends O<T> {
|
|
31
|
-
}
|
|
32
|
-
export interface Subscription extends S {
|
|
33
|
-
}
|
|
34
|
-
export {};
|
package/dist/rx-fake.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.$BehaviorSubject = exports.$Observable = exports.$Subscription = void 0;
|
|
4
|
-
exports.noop = noop;
|
|
5
|
-
exports.BehaviorSubject = BehaviorSubject;
|
|
6
|
-
exports.Observable = Observable;
|
|
7
|
-
exports.Subscription = Subscription;
|
|
8
|
-
class $Subscription {
|
|
9
|
-
constructor() {
|
|
10
|
-
this.o = new Map();
|
|
11
|
-
}
|
|
12
|
-
unsubscribe() {
|
|
13
|
-
[...this.o.values()].forEach(v => this.o.delete(v));
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
exports.$Subscription = $Subscription;
|
|
17
|
-
class $Observable extends $Subscription {
|
|
18
|
-
constructor(fn) {
|
|
19
|
-
super();
|
|
20
|
-
this.init = true;
|
|
21
|
-
this.fn = fn;
|
|
22
|
-
}
|
|
23
|
-
subscribe(c) {
|
|
24
|
-
this.o.set(c, c);
|
|
25
|
-
if (typeof this.fn === 'function' && this.init) {
|
|
26
|
-
this.fn(this);
|
|
27
|
-
this.init = false;
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
unsubscribe: () => {
|
|
31
|
-
this.o.delete(c);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
complete() {
|
|
36
|
-
this.unsubscribe();
|
|
37
|
-
}
|
|
38
|
-
next(s) {
|
|
39
|
-
[...this.o.values()].forEach(f => f(s));
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
exports.$Observable = $Observable;
|
|
43
|
-
class $BehaviorSubject extends $Observable {
|
|
44
|
-
constructor(v) {
|
|
45
|
-
if (typeof v === 'function') {
|
|
46
|
-
super(v);
|
|
47
|
-
}
|
|
48
|
-
super(null);
|
|
49
|
-
this.setValue(v);
|
|
50
|
-
}
|
|
51
|
-
setValue(v) {
|
|
52
|
-
this.v = v;
|
|
53
|
-
}
|
|
54
|
-
next(s) {
|
|
55
|
-
this.setValue(s);
|
|
56
|
-
super.next(s);
|
|
57
|
-
}
|
|
58
|
-
getValue() {
|
|
59
|
-
return this.v;
|
|
60
|
-
}
|
|
61
|
-
asObservable() {
|
|
62
|
-
return this;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
exports.$BehaviorSubject = $BehaviorSubject;
|
|
66
|
-
function behaviorOrFake() {
|
|
67
|
-
try {
|
|
68
|
-
return require('rxjs').BehaviorSubject;
|
|
69
|
-
}
|
|
70
|
-
catch (e) { }
|
|
71
|
-
return $BehaviorSubject;
|
|
72
|
-
}
|
|
73
|
-
function observableOrFake() {
|
|
74
|
-
try {
|
|
75
|
-
return require('rxjs').Observable;
|
|
76
|
-
}
|
|
77
|
-
catch (e) { }
|
|
78
|
-
return $Observable;
|
|
79
|
-
}
|
|
80
|
-
function subscriptionOrFake() {
|
|
81
|
-
try {
|
|
82
|
-
return require('rxjs').Subscription;
|
|
83
|
-
}
|
|
84
|
-
catch (e) { }
|
|
85
|
-
return $Subscription;
|
|
86
|
-
}
|
|
87
|
-
function noop() { }
|
|
88
|
-
function BehaviorSubject(init) {
|
|
89
|
-
const b = behaviorOrFake();
|
|
90
|
-
return new b(init);
|
|
91
|
-
}
|
|
92
|
-
function Observable(fn) {
|
|
93
|
-
const o = observableOrFake();
|
|
94
|
-
return new o(fn);
|
|
95
|
-
}
|
|
96
|
-
function Subscription() {
|
|
97
|
-
const s = subscriptionOrFake();
|
|
98
|
-
return new s();
|
|
99
|
-
}
|