@rxdi/forms 0.7.212 → 0.7.214
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 +203 -228
- package/dist/form.array.d.ts +31 -0
- package/dist/form.array.js +122 -0
- package/dist/form.decorator.d.ts +1 -1
- package/dist/form.decorator.js +8 -4
- package/dist/form.group.d.ts +19 -13
- package/dist/form.group.js +148 -31
- package/dist/form.tokens.d.ts +38 -1
- package/dist/form.tokens.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +6 -2
- package/dist/rx-fake.d.ts +0 -34
- package/dist/rx-fake.js +0 -99
package/README.md
CHANGED
|
@@ -1,315 +1,290 @@
|
|
|
1
|
-
# Reactive
|
|
2
|
-
|
|
3
|
-
#### Install
|
|
4
|
-
```bash
|
|
5
|
-
npm i @rxdi/forms
|
|
6
|
-
```
|
|
1
|
+
# Reactive Forms for LitHtml (Enhanced)
|
|
7
2
|
|
|
3
|
+
A lightweight, strongly-typed, reactive forms library for LitHtml applications.
|
|
8
4
|
|
|
5
|
+
## Features
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
- **Strict Typing**: Full TypeScript support with `UnwrapValue` and `NestedKeyOf` for deep property inference.
|
|
8
|
+
- **Nested Forms**: Support for deep `FormGroup` nesting and `FormArray`.
|
|
9
|
+
- **Automatic Binding**: Bind component models directly to forms with `@Form({ model: 'myModel' })`.
|
|
10
|
+
- **Reactive**: based on `rxjs` `BehaviorSubject` for value streams.
|
|
11
|
+
- **Recursive Updates**: `patchValue` updates deep structures recursively.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
## Installation
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
```bash
|
|
16
|
+
npm i @rxdi/forms
|
|
17
|
+
```
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
## Basic Usage
|
|
17
20
|
|
|
21
|
+
### 1. Define Model & Component
|
|
18
22
|
|
|
19
23
|
```typescript
|
|
20
|
-
import { html, Component } from '@rxdi/lit-html';
|
|
21
|
-
import {
|
|
22
|
-
|
|
24
|
+
import { html, Component, LitElement } from '@rxdi/lit-html';
|
|
25
|
+
import { Form, FormGroup } from '@rxdi/forms';
|
|
26
|
+
|
|
27
|
+
interface UserParams {
|
|
28
|
+
firstName: string;
|
|
29
|
+
address: {
|
|
30
|
+
city: string;
|
|
31
|
+
street: string;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
23
34
|
|
|
24
|
-
/**
|
|
25
|
-
* @customElement login-component
|
|
26
|
-
*/
|
|
27
35
|
@Component({
|
|
28
|
-
selector: '
|
|
29
|
-
template(this:
|
|
36
|
+
selector: 'user-profile',
|
|
37
|
+
template(this: UserProfile) {
|
|
30
38
|
return html`
|
|
31
|
-
<form name="
|
|
39
|
+
<form name="user-form" @submit=${this.onSubmit}>
|
|
40
|
+
|
|
41
|
+
<!-- Deep Binding with Dot Notation -->
|
|
32
42
|
<input
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
value=${this.form.value.email}
|
|
37
|
-
placeholder="Email address"
|
|
38
|
-
required
|
|
39
|
-
autofocus
|
|
43
|
+
name="firstName"
|
|
44
|
+
.value=${this.form.value.firstName}
|
|
45
|
+
@blur=${() => this.requestUpdate()}
|
|
40
46
|
/>
|
|
47
|
+
|
|
48
|
+
<!-- Nested Group Binding -->
|
|
41
49
|
<input
|
|
42
|
-
|
|
43
|
-
value=${this.form.value.
|
|
44
|
-
|
|
45
|
-
placeholder="Password"
|
|
46
|
-
required=""
|
|
50
|
+
name="address.city"
|
|
51
|
+
.value=${this.form.value.address.city}
|
|
52
|
+
@blur=${() => this.requestUpdate()}
|
|
47
53
|
/>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<input name="rememberMe" type="checkbox" /> Remember me
|
|
51
|
-
</label>
|
|
52
|
-
</div>
|
|
53
|
-
<button type="submit">
|
|
54
|
-
Sign in
|
|
55
|
-
</button>
|
|
54
|
+
|
|
55
|
+
<button type="submit">Save</button>
|
|
56
56
|
</form>
|
|
57
57
|
`;
|
|
58
58
|
}
|
|
59
59
|
})
|
|
60
|
-
export class
|
|
60
|
+
export class UserProfile extends LitElement {
|
|
61
|
+
|
|
62
|
+
// Model to bind
|
|
63
|
+
@property({ type: Object })
|
|
64
|
+
user: UserParams = {
|
|
65
|
+
firstName: 'John',
|
|
66
|
+
address: { city: 'New York', street: '5th Ave' }
|
|
67
|
+
};
|
|
68
|
+
|
|
61
69
|
@Form({
|
|
70
|
+
name: 'user-form',
|
|
62
71
|
strategy: 'change',
|
|
63
|
-
|
|
72
|
+
model: 'user' // Automatic Model Binding!
|
|
64
73
|
})
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
form = new FormGroup({
|
|
75
|
+
firstName: '',
|
|
76
|
+
address: new FormGroup({
|
|
77
|
+
city: '',
|
|
78
|
+
street: ''
|
|
79
|
+
})
|
|
69
80
|
});
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
this.form.getValue('password');
|
|
76
|
-
this.form.setValue('email', 'blabla');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
onSubmit(event: Event) {
|
|
80
|
-
this.form.values;
|
|
82
|
+
onSubmit(e: Event) {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
console.log(this.form.value);
|
|
85
|
+
// Output: { firstName: 'John', address: { city: 'New York', street: '5th Ave' } }
|
|
81
86
|
}
|
|
82
87
|
}
|
|
88
|
+
```
|
|
83
89
|
|
|
90
|
+
## New Features
|
|
91
|
+
|
|
92
|
+
### Automatic Model Binding
|
|
93
|
+
Use the `model` property in the `@Form` decorator to automatically populate the form from a component property.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
@Form({
|
|
97
|
+
name: 'my-form',
|
|
98
|
+
model: 'myData' // matches this.myData
|
|
99
|
+
})
|
|
100
|
+
form = new FormGroup({ ... });
|
|
84
101
|
```
|
|
102
|
+
The library reads `this.myData` during initialization and calls `form.patchValue(this.myData)`.
|
|
85
103
|
|
|
104
|
+
### Nested FormGroups & FormArray
|
|
105
|
+
You can nest `FormGroup`s arbitrarily deep.
|
|
86
106
|
|
|
107
|
+
```typescript
|
|
108
|
+
form = new FormGroup({
|
|
109
|
+
meta: new FormGroup({
|
|
110
|
+
id: 1,
|
|
111
|
+
flags: new FormGroup({
|
|
112
|
+
isActive: true,
|
|
113
|
+
isAdmin: false
|
|
114
|
+
})
|
|
115
|
+
}),
|
|
116
|
+
tags: new FormArray([
|
|
117
|
+
new FormGroup({ label: 'red' })
|
|
118
|
+
])
|
|
119
|
+
});
|
|
120
|
+
```
|
|
87
121
|
|
|
88
|
-
|
|
122
|
+
**Template Binding:**
|
|
123
|
+
Use dot notation for nested controls:
|
|
124
|
+
```html
|
|
125
|
+
<input name="meta.flags.isActive" type="checkbox" />
|
|
126
|
+
```
|
|
89
127
|
|
|
128
|
+
### Type Safety & Autosuggestion
|
|
129
|
+
The library now extensively uses advanced TypeScript features:
|
|
130
|
+
- **`form.value`**: Returns the unwrapped pure object type (e.g., `{ meta: { flags: { isActive: boolean } } }`).
|
|
131
|
+
- **`form.get('path.to.prop')`**: Provides autocomplete for deep paths!
|
|
90
132
|
```typescript
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
import { BaseComponent } from '../shared/base.component';
|
|
133
|
+
// TypeScript knows this is valid:
|
|
134
|
+
this.form.get('meta.flags.isActive');
|
|
94
135
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@Component({
|
|
99
|
-
selector: 'login-component',
|
|
100
|
-
template(this: LoginComponent) {
|
|
101
|
-
return html`
|
|
102
|
-
<form name="my-form" @submit=${this.onSubmit}>
|
|
103
|
-
<input
|
|
104
|
-
style="margin-bottom: 20px;"
|
|
105
|
-
name="email"
|
|
106
|
-
type="email"
|
|
107
|
-
placeholder="Email address"
|
|
108
|
-
required
|
|
109
|
-
autofocus
|
|
110
|
-
/>
|
|
111
|
-
${this.form.hasError('email', 'blabla') ? html`${this.form.getError('email', 'blabla')}` : ''}
|
|
112
|
-
<input
|
|
113
|
-
type="password"
|
|
114
|
-
name="password"
|
|
115
|
-
placeholder="Password"
|
|
116
|
-
required=""
|
|
117
|
-
/>
|
|
118
|
-
<div>
|
|
119
|
-
<label>
|
|
120
|
-
<input name="rememberMe" type="checkbox" /> Remember me
|
|
121
|
-
</label>
|
|
122
|
-
</div>
|
|
123
|
-
<button type="submit">
|
|
124
|
-
Sign in
|
|
125
|
-
</button>
|
|
126
|
-
</form>
|
|
127
|
-
`;
|
|
128
|
-
}
|
|
129
|
-
})
|
|
130
|
-
export class LoginComponent extends BaseComponent {
|
|
131
|
-
@Form({
|
|
132
|
-
strategy: 'change',
|
|
133
|
-
name: 'my-form'
|
|
134
|
-
})
|
|
135
|
-
private form = new FormGroup({
|
|
136
|
-
password: '',
|
|
137
|
-
email: ['', [this.validateEmail]],
|
|
138
|
-
rememberMe: ''
|
|
139
|
-
});
|
|
136
|
+
// And this is invalid:
|
|
137
|
+
this.form.get('meta.flags.wrongProp'); // Error!
|
|
138
|
+
```
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
this.form.setValue('email', 'blabla');
|
|
140
|
+
### Recursive PatchValue
|
|
141
|
+
Update multiple fields deeply at once:
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
```typescript
|
|
144
|
+
this.form.patchValue({
|
|
145
|
+
meta: {
|
|
146
|
+
flags: {
|
|
147
|
+
isActive: false
|
|
148
|
+
}
|
|
147
149
|
}
|
|
150
|
+
});
|
|
151
|
+
// Only updates 'isActive', leaves other fields untouched.
|
|
152
|
+
```
|
|
148
153
|
|
|
149
|
-
|
|
150
|
-
this.form.values;
|
|
151
|
-
}
|
|
154
|
+
## API Reference
|
|
152
155
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
### Validators
|
|
157
|
+
Validators are async functions returning `InputErrorMessage` or `void`.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
export function CustomValidator(element: AbstractInput) {
|
|
161
|
+
if (element.value === 'invalid') {
|
|
162
|
+
return { key: 'customError', message: 'Value is invalid' };
|
|
157
163
|
}
|
|
158
164
|
}
|
|
159
165
|
|
|
166
|
+
// Usage
|
|
167
|
+
new FormGroup({
|
|
168
|
+
field: ['', [CustomValidator]]
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Error Display Information
|
|
173
|
+
Use the `touched` and `validity.valid` properties for clean UI.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
function ErrorTemplate(input: AbstractInput) {
|
|
177
|
+
if (input?.touched && !input.validity.valid) {
|
|
178
|
+
return html`<div class="error">${input.validationMessage}</div>`;
|
|
179
|
+
}
|
|
180
|
+
return html``;
|
|
181
|
+
}
|
|
160
182
|
```
|
|
161
183
|
|
|
184
|
+
## Advanced Usage
|
|
162
185
|
|
|
186
|
+
### 1. Grouping Multiple Inputs (Checkbox Groups)
|
|
163
187
|
|
|
164
|
-
|
|
188
|
+
By default, inputs with the same `name` attribute are treated as a single value (last write wins). However, for checkboxes, you often want an array of values.
|
|
165
189
|
|
|
166
|
-
|
|
190
|
+
**Scenario:** A list of permissions where multiple can be selected.
|
|
167
191
|
|
|
168
192
|
```typescript
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
193
|
+
@Form({
|
|
194
|
+
name: 'permissions-form',
|
|
195
|
+
multi: true // Enable multi-value binding for same-name inputs
|
|
196
|
+
})
|
|
197
|
+
form = new FormGroup({
|
|
198
|
+
roles: [] // Will be an array of values
|
|
199
|
+
});
|
|
176
200
|
```
|
|
177
201
|
|
|
178
202
|
```html
|
|
179
203
|
<label>
|
|
180
|
-
<input
|
|
181
|
-
name="condition"
|
|
182
|
-
type="checkbox"
|
|
183
|
-
value='none'
|
|
184
|
-
/>
|
|
185
|
-
None
|
|
204
|
+
<input name="roles" type="checkbox" value="admin" /> Admin
|
|
186
205
|
</label>
|
|
187
|
-
|
|
188
206
|
<label>
|
|
189
|
-
<input
|
|
190
|
-
name="condition"
|
|
191
|
-
type="checkbox"
|
|
192
|
-
value='checked'
|
|
193
|
-
/>
|
|
194
|
-
Checked
|
|
207
|
+
<input name="roles" type="checkbox" value="editor" /> Editor
|
|
195
208
|
</label>
|
|
196
|
-
|
|
197
209
|
<label>
|
|
198
|
-
<input
|
|
199
|
-
name="condition"
|
|
200
|
-
type="checkbox"
|
|
201
|
-
value='not-checked'
|
|
202
|
-
/>
|
|
203
|
-
Not checked
|
|
210
|
+
<input name="roles" type="checkbox" value="viewer" /> Viewer
|
|
204
211
|
</label>
|
|
205
212
|
```
|
|
206
213
|
|
|
214
|
+
If the user checks "Admin" and "Viewer", `form.value.roles` will be `['admin', 'viewer']`.
|
|
207
215
|
|
|
208
|
-
|
|
209
|
-
|
|
216
|
+
### 2. Single Selection Checkbox (Radio Behavior with Checkboxes)
|
|
210
217
|
|
|
211
|
-
|
|
218
|
+
If you want multiple checkboxes to act like a radio button (only one valid at a time) but with uncheck capability:
|
|
212
219
|
|
|
213
220
|
```typescript
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
});
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
#### Native browser errors
|
|
227
|
-
|
|
228
|
-
By default this library uses native error messages provided by HTML5 form validation API
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
You can create your error template as follow:
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
import { html } from '@rxdi/lit-html';
|
|
235
|
-
|
|
236
|
-
export function InputErrorTemplate(input: HTMLInputElement) {
|
|
237
|
-
if (input && !input.checkValidity()) {
|
|
238
|
-
return html`
|
|
239
|
-
<div>${input.validationMessage}</div>
|
|
240
|
-
`;
|
|
241
|
-
}
|
|
242
|
-
return '';
|
|
243
|
-
}
|
|
221
|
+
@Form({
|
|
222
|
+
name: 'settings-form',
|
|
223
|
+
multi: false // Default behavior
|
|
224
|
+
})
|
|
225
|
+
form = new FormGroup({
|
|
226
|
+
mode: ''
|
|
227
|
+
});
|
|
244
228
|
```
|
|
245
229
|
|
|
246
|
-
|
|
247
|
-
Usage
|
|
248
|
-
|
|
249
230
|
```html
|
|
250
|
-
<
|
|
251
|
-
<input
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
placeholder="Email address"
|
|
257
|
-
required
|
|
258
|
-
autofocus
|
|
259
|
-
/>
|
|
260
|
-
${InputErrorTemplate(this.form.get('email'))}
|
|
261
|
-
</form>
|
|
231
|
+
<label>
|
|
232
|
+
<input name="mode" type="checkbox" value="dark" /> Dark
|
|
233
|
+
</label>
|
|
234
|
+
<label>
|
|
235
|
+
<input name="mode" type="checkbox" value="light" /> Light
|
|
236
|
+
</label>
|
|
262
237
|
```
|
|
238
|
+
Checking "Dark" unchecks "Light" automatically.
|
|
263
239
|
|
|
240
|
+
### 3. Framework-Agnostic Usage (Vanilla JS)
|
|
264
241
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
##### Native HTML with JS
|
|
242
|
+
You can use this library without Decorators or LitHtml, with any UI library or vanilla HTML.
|
|
268
243
|
|
|
269
244
|
```typescript
|
|
270
|
-
|
|
271
245
|
import { FormGroup } from '@rxdi/forms';
|
|
272
246
|
|
|
273
|
-
export function EmailValidator(element: HTMLInputElement) {
|
|
274
|
-
const regex = /^([a-zA-Z0-9_\.\-]+)@([a-zA-Z0-9_\.\-]+)\.([a-zA-Z]{2,5})$/;
|
|
275
|
-
if (!regex.test(element.value)) {
|
|
276
|
-
element.classList.add('is-invalid');
|
|
277
|
-
return {
|
|
278
|
-
key: 'email-validator',
|
|
279
|
-
message: 'Email is not valid'
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
element.classList.remove('is-invalid');
|
|
283
|
-
}
|
|
284
|
-
|
|
285
247
|
const form = new FormGroup({
|
|
286
|
-
email:
|
|
287
|
-
password: ''
|
|
248
|
+
email: '',
|
|
249
|
+
password: ''
|
|
288
250
|
});
|
|
289
251
|
|
|
252
|
+
// manually attach to DOM
|
|
253
|
+
const formElement = document.querySelector('form');
|
|
290
254
|
form
|
|
291
255
|
.setParentElement(document.body)
|
|
292
256
|
.setOptions({ name: 'my-form' })
|
|
257
|
+
.setFormElement(formElement)
|
|
293
258
|
.prepareValues()
|
|
294
|
-
.setFormElement(form.querySelectForm(document.body))
|
|
295
259
|
.setInputs(form.mapEventToInputs(form.querySelectorAllInputs()));
|
|
296
260
|
|
|
261
|
+
// Listen to changes
|
|
262
|
+
form.valueChanges.subscribe(val => console.log(val));
|
|
297
263
|
```
|
|
298
264
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
265
|
+
### 4. Custom Error Handling Strategies
|
|
266
|
+
|
|
267
|
+
By default, verification happens on `change` or `blur`. You can control this via `strategy`.
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
@Form({
|
|
271
|
+
name: 'login',
|
|
272
|
+
strategy: 'input' // Validate on every keystroke
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
You can also manually check error states (e.g. for async validation):
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
async validateEmail(element: HTMLInputElement) {
|
|
280
|
+
const exists = await checkServer(element.value);
|
|
281
|
+
if (exists) {
|
|
282
|
+
return { key: 'emailExists', message: 'Email already taken' };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// In Template
|
|
287
|
+
${this.form.hasError('email', 'emailExists')
|
|
288
|
+
? html`<div class="error">Email taken!</div>`
|
|
289
|
+
: ''}
|
|
315
290
|
```
|
|
@@ -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,122 @@
|
|
|
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
|
+
// eslint-disable-next-line @typescript-eslint/adjacent-overload-signatures
|
|
110
|
+
set value(values) {
|
|
111
|
+
if (!Array.isArray(values)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
values.forEach((v, i) => {
|
|
115
|
+
if (this.controls[i]) {
|
|
116
|
+
this.controls[i].value = v;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
this.updateValue();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
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;
|