@ng-simplicity/forms-core 1.1.2 → 1.1.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 +22 -2
- package/fesm2022/ng-simplicity-forms-core.mjs +292 -75
- package/fesm2022/ng-simplicity-forms-core.mjs.map +1 -1
- package/index.d.ts +181 -82
- package/package.json +4 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Subject, takeUntil, BehaviorSubject, map,
|
|
1
|
+
import { Subject, takeUntil, BehaviorSubject, switchMap, defer, map, distinctUntilChanged, merge, of } from 'rxjs';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { Directive, InjectionToken, inject, Injectable, ChangeDetectorRef, Injector, ViewContainerRef, Input, computed, ChangeDetectionStrategy, Component, ElementRef, Sanitizer, SecurityContext, NgModule } from '@angular/core';
|
|
3
|
+
import { Directive, InjectionToken, inject, Injectable, ChangeDetectorRef, Injector, ViewContainerRef, Input, computed, ChangeDetectionStrategy, Component, ElementRef, afterNextRender, Sanitizer, SecurityContext, NgModule } from '@angular/core';
|
|
4
4
|
import { v4 } from 'uuid';
|
|
5
5
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
6
|
-
import { UntypedFormGroup, FormControl, UntypedFormArray, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
6
|
+
import { UntypedFormGroup, FormControl, Validators, FormGroup, FormArray, UntypedFormArray, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
7
7
|
import { CommonModule } from '@angular/common';
|
|
8
8
|
|
|
9
9
|
/** Base class that ties RxJS subscriptions to the component's lifetime. */
|
|
@@ -32,8 +32,18 @@ const NGS_FORMS_ITEM_DATA = new InjectionToken('ngs-forms-item-data');
|
|
|
32
32
|
* @internal
|
|
33
33
|
*/
|
|
34
34
|
const NGS_FORMS_CONTROL_ADD_REMOVE_FN = new InjectionToken('ngs-form-control-remove-fn');
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Injection token for the row index when a component is rendered inside a form array list.
|
|
37
|
+
*
|
|
38
|
+
* @remarks This is the index the row had when the component was created. Rows before it can be removed later,
|
|
39
|
+
* so to find a row's current position use `NGS_FORMS_ARRAY_ROW` with `formArray.controls.indexOf(row)`.
|
|
40
|
+
*/
|
|
36
41
|
const NGS_FORMS_ITEM_INDEX = new InjectionToken('ngs-forms-item-index');
|
|
42
|
+
/**
|
|
43
|
+
* Injection token for the `UntypedFormGroup` of the form array row a component is rendered in. Unlike
|
|
44
|
+
* `NGS_FORMS_ITEM_INDEX`, it stays correct when earlier rows are removed.
|
|
45
|
+
*/
|
|
46
|
+
const NGS_FORMS_ARRAY_ROW = new InjectionToken('ngs-forms-array-row');
|
|
37
47
|
/**
|
|
38
48
|
* @internal
|
|
39
49
|
*/
|
|
@@ -43,10 +53,12 @@ const NgsFormsFormControlAddRemoveFunctions = {
|
|
|
43
53
|
remove: (control, controlName) => formGroup.removeControl(controlName),
|
|
44
54
|
}),
|
|
45
55
|
formArray: (formArray) => ({
|
|
46
|
-
add: (control
|
|
47
|
-
remove: (control
|
|
56
|
+
add: (control) => formArray.push(control),
|
|
57
|
+
remove: (control) => {
|
|
48
58
|
const index = formArray.controls.indexOf(control);
|
|
49
|
-
|
|
59
|
+
if (index >= 0) {
|
|
60
|
+
formArray.removeAt(index);
|
|
61
|
+
}
|
|
50
62
|
},
|
|
51
63
|
}),
|
|
52
64
|
};
|
|
@@ -74,8 +86,11 @@ function ngsDefaults(target, ...sources) {
|
|
|
74
86
|
class NgsFormsBaseClassFormComponent extends NgsSubscriber {
|
|
75
87
|
/** Registry key the component is registered under. */
|
|
76
88
|
static key;
|
|
77
|
-
/**
|
|
78
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Element id for this component, handy for `id` and `for` attributes. The config's `id` when it sets one,
|
|
91
|
+
* otherwise a generated unique id.
|
|
92
|
+
*/
|
|
93
|
+
id;
|
|
79
94
|
/** The item's configuration. */
|
|
80
95
|
config;
|
|
81
96
|
/** The full form item: `uuid`, `type` and `config`. */
|
|
@@ -84,6 +99,7 @@ class NgsFormsBaseClassFormComponent extends NgsSubscriber {
|
|
|
84
99
|
super();
|
|
85
100
|
this.itemData = inject(NGS_FORMS_ITEM_DATA);
|
|
86
101
|
this.config = this.itemData.config;
|
|
102
|
+
this.id = this.config.id || v4();
|
|
87
103
|
}
|
|
88
104
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormsBaseClassFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
89
105
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.19", type: NgsFormsBaseClassFormComponent, isStandalone: true, usesInheritance: true, ngImport: i0 });
|
|
@@ -144,10 +160,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
|
|
|
144
160
|
* ```
|
|
145
161
|
*/
|
|
146
162
|
class NgsFormsService {
|
|
147
|
-
/**
|
|
148
|
-
* @internal
|
|
149
|
-
*/
|
|
150
|
-
attempt = 0;
|
|
151
163
|
/**
|
|
152
164
|
* Emits the root form group's value every time it changes. Combine with `toSignal()` to read it in a template.
|
|
153
165
|
*
|
|
@@ -157,8 +169,12 @@ class NgsFormsService {
|
|
|
157
169
|
/** The latest value emitted by `formValue$`. */
|
|
158
170
|
formValue = {};
|
|
159
171
|
internalFormService;
|
|
160
|
-
|
|
172
|
+
formGroupBinding;
|
|
161
173
|
formGroup = new UntypedFormGroup({});
|
|
174
|
+
/** The config from the latest `setFormConfig()` call, re-applied when `<ngs-form>` (re)connects. */
|
|
175
|
+
lastConfig;
|
|
176
|
+
missingFormTimer;
|
|
177
|
+
destroyed = false;
|
|
162
178
|
/** `true` once the user has changed any control in the form. */
|
|
163
179
|
get dirty() {
|
|
164
180
|
return this.formGroup?.dirty ?? false;
|
|
@@ -176,27 +192,48 @@ class NgsFormsService {
|
|
|
176
192
|
/**
|
|
177
193
|
* Renders a form from `formConfig`, replacing any form that is already shown and resetting the submitted state.
|
|
178
194
|
*
|
|
179
|
-
* @remarks It is safe to call before `<ngs-form>` has initialised (for example in `ngOnInit`): the
|
|
180
|
-
*
|
|
195
|
+
* @remarks It is safe to call before `<ngs-form>` has initialised (for example in `ngOnInit`): the latest config
|
|
196
|
+
* is kept and applied as soon as the form component connects, and also when it is destroyed and created again
|
|
197
|
+
* (inside an `@if` or a tab, for example). An error is logged if no form has connected after half a second.
|
|
181
198
|
*/
|
|
182
199
|
setFormConfig(formConfig) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
this.attempt++;
|
|
189
|
-
setTimeout(() => this.setFormConfig(formConfig), 25);
|
|
200
|
+
this.lastConfig = formConfig;
|
|
201
|
+
if (this.internalServiceIsSet) {
|
|
202
|
+
this.internalFormService.setFormData(formConfig);
|
|
190
203
|
return;
|
|
191
204
|
}
|
|
192
|
-
this.
|
|
205
|
+
if (this.missingFormTimer === undefined && !this.destroyed) {
|
|
206
|
+
this.missingFormTimer = setTimeout(() => {
|
|
207
|
+
this.missingFormTimer = undefined;
|
|
208
|
+
if (!this.internalServiceIsSet && !this.destroyed) {
|
|
209
|
+
console.error('Internal form service is unavailable. Is the ngs-form root tag added to the page?');
|
|
210
|
+
}
|
|
211
|
+
}, 500);
|
|
212
|
+
}
|
|
193
213
|
}
|
|
194
214
|
/**
|
|
195
215
|
* @internal
|
|
196
216
|
*/
|
|
197
217
|
setInternalService(internalFormsService) {
|
|
218
|
+
this.formGroupBinding?.unsubscribe();
|
|
198
219
|
this.internalFormService = internalFormsService;
|
|
199
220
|
this.bindFormGroup();
|
|
221
|
+
if (this.lastConfig) {
|
|
222
|
+
internalFormsService.setFormData(this.lastConfig);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* @internal
|
|
227
|
+
* Disconnects `internalFormsService` when its `<ngs-form>` is destroyed. Does nothing if another form has
|
|
228
|
+
* connected since.
|
|
229
|
+
*/
|
|
230
|
+
clearInternalService(internalFormsService) {
|
|
231
|
+
if (this.internalFormService !== internalFormsService)
|
|
232
|
+
return;
|
|
233
|
+
this.formGroupBinding?.unsubscribe();
|
|
234
|
+
this.formGroupBinding = undefined;
|
|
235
|
+
this.internalFormService = undefined;
|
|
236
|
+
this.formGroup = new UntypedFormGroup({});
|
|
200
237
|
}
|
|
201
238
|
/**
|
|
202
239
|
* Marks the form as submitted (or not). While submitted, every invalid control shows its error message even if
|
|
@@ -208,13 +245,14 @@ class NgsFormsService {
|
|
|
208
245
|
this.internalFormService.setIsSubmitted(isSubmitted);
|
|
209
246
|
}
|
|
210
247
|
bindFormGroup() {
|
|
211
|
-
this.internalFormService.formGroup
|
|
248
|
+
this.formGroupBinding = this.internalFormService.formGroup$
|
|
249
|
+
.pipe(switchMap((fg) => {
|
|
212
250
|
this.formGroup = fg;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
251
|
+
return fg.valueChanges;
|
|
252
|
+
}))
|
|
253
|
+
.subscribe((v) => {
|
|
254
|
+
this.formValue = v;
|
|
255
|
+
this.formValue$.next(v);
|
|
218
256
|
});
|
|
219
257
|
}
|
|
220
258
|
/**
|
|
@@ -229,7 +267,11 @@ class NgsFormsService {
|
|
|
229
267
|
this.internalFormService?.updateGlobalState(state);
|
|
230
268
|
}
|
|
231
269
|
ngOnDestroy() {
|
|
232
|
-
this.
|
|
270
|
+
this.destroyed = true;
|
|
271
|
+
clearTimeout(this.missingFormTimer);
|
|
272
|
+
this.missingFormTimer = undefined;
|
|
273
|
+
this.formGroupBinding?.unsubscribe();
|
|
274
|
+
this.formValue$.complete();
|
|
233
275
|
}
|
|
234
276
|
/**
|
|
235
277
|
* Patches values into the form, using the same shape as `formValue$` (including the root group name).
|
|
@@ -261,12 +303,17 @@ class NgsFormsInternalService {
|
|
|
261
303
|
setIsSubmitted(isSubmitted) {
|
|
262
304
|
this.isSubmitted$.next(isSubmitted);
|
|
263
305
|
}
|
|
306
|
+
/** `true` when no rendered control fails its validators. A form whose controls are all disabled counts as valid. */
|
|
264
307
|
checkIsValid() {
|
|
265
|
-
|
|
308
|
+
const formGroup = this.formGroup$.value;
|
|
309
|
+
if (!formGroup)
|
|
266
310
|
return false;
|
|
267
|
-
return
|
|
311
|
+
return formGroup.valid || formGroup.disabled === true;
|
|
268
312
|
}
|
|
269
313
|
setFormData(formConfig) {
|
|
314
|
+
const state = { ...(formConfig.initialState ?? {}) };
|
|
315
|
+
state['global'] = { ...(formConfig.globalState ?? {}) };
|
|
316
|
+
this.state.next(state);
|
|
270
317
|
this.formGroup$.next(new UntypedFormGroup({}));
|
|
271
318
|
this.formConfig$.next(formConfig);
|
|
272
319
|
this.isSubmitted$.next(false);
|
|
@@ -277,25 +324,31 @@ class NgsFormsInternalService {
|
|
|
277
324
|
this.state.value[key] = updated;
|
|
278
325
|
this.state.next(this.state.value);
|
|
279
326
|
}
|
|
327
|
+
/** Sets a component's state from its config's `initialState`, unless state for `uuid` already exists. */
|
|
328
|
+
seedComponentState(uuid, initialState) {
|
|
329
|
+
if (!uuid || !initialState || this.state.value[uuid])
|
|
330
|
+
return;
|
|
331
|
+
this.mergeState(uuid, initialState);
|
|
332
|
+
}
|
|
280
333
|
updateGlobalState(state) {
|
|
281
334
|
this.mergeState('global', state);
|
|
282
335
|
}
|
|
283
336
|
updateComponentState(componentKey, state) {
|
|
284
337
|
this.mergeState(componentKey, state);
|
|
285
338
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Merged state for one component: its own (by `uuid`), then by `name`, then global.
|
|
341
|
+
* `initialState` seeds the component's own state unless state for `uuid` already exists.
|
|
342
|
+
*/
|
|
343
|
+
subscribeToState(uuid, name, initialState) {
|
|
344
|
+
this.seedComponentState(uuid, initialState);
|
|
345
|
+
// `defer` gives every subscriber its own comparison, so each one receives the current state first.
|
|
346
|
+
return defer(() => this.state.pipe(map((allState) => {
|
|
289
347
|
const stateByUuid = allState[uuid] || {};
|
|
290
348
|
const stateByName = name ? (allState[name] || {}) : {};
|
|
291
349
|
const globalState = allState['global'] || {};
|
|
292
350
|
return ngsDefaults(stateByUuid, ngsDefaults(stateByName, globalState));
|
|
293
|
-
}),
|
|
294
|
-
const stringifiedState = JSON.stringify(currentState);
|
|
295
|
-
const isUpdated = stringifiedState !== last;
|
|
296
|
-
last = stringifiedState;
|
|
297
|
-
return isUpdated;
|
|
298
|
-
}));
|
|
351
|
+
}), distinctUntilChanged((previous, current) => JSON.stringify(previous) === JSON.stringify(current))));
|
|
299
352
|
}
|
|
300
353
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormsInternalService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
301
354
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormsInternalService });
|
|
@@ -305,8 +358,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
|
|
|
305
358
|
}] });
|
|
306
359
|
|
|
307
360
|
/**
|
|
308
|
-
* Base class for items that own a form control. Creates the control
|
|
309
|
-
*
|
|
361
|
+
* Base class for items that own a form control. Creates the control (seeded with `value` when the config has one),
|
|
362
|
+
* applies `validators` / `validators$` and `disabled` / `disabled$`, registers it with the parent group under
|
|
363
|
+
* `config.name` and keeps `errorMessage` up to date.
|
|
364
|
+
*
|
|
365
|
+
* @typeParam T - The item's config type.
|
|
366
|
+
* @typeParam C - The type of `control`. Inputs use the default `UntypedFormControl`; groups and arrays set it in
|
|
367
|
+
* their constructor to an `UntypedFormGroup` or `UntypedFormArray`.
|
|
310
368
|
*/
|
|
311
369
|
class NgsFormsFormItemWithVisibleAndValidatorsBase extends NgsFormsBaseClassFormComponent {
|
|
312
370
|
/** The item's form control. Bind it with `[formControl]`. */
|
|
@@ -321,20 +379,31 @@ class NgsFormsFormItemWithVisibleAndValidatorsBase extends NgsFormsBaseClassForm
|
|
|
321
379
|
changeDetectorRef = inject(ChangeDetectorRef);
|
|
322
380
|
/** Whether the form has been marked as submitted with `NgsFormsService.setIsSubmitted()`. */
|
|
323
381
|
submitted = toSignal(this.privateService.isSubmitted$, { initialValue: false });
|
|
382
|
+
/** The latest value of `disabled` / `disabled$`, or `undefined` when the config sets neither. */
|
|
383
|
+
disabledFromConfig;
|
|
324
384
|
ngOnInit() {
|
|
325
385
|
if (!this.control) {
|
|
326
|
-
|
|
386
|
+
const initialValue = this.config.value;
|
|
387
|
+
this.control = new FormControl(initialValue, { validators: [] });
|
|
327
388
|
}
|
|
328
|
-
|
|
389
|
+
const control = this.control;
|
|
329
390
|
this.bindValidators();
|
|
330
|
-
|
|
331
|
-
this.
|
|
332
|
-
//
|
|
333
|
-
|
|
391
|
+
this.formAddRemoveFns.add(control, this.config.name);
|
|
392
|
+
this.bindDisabled();
|
|
393
|
+
// Recompute the error message on value, status and touched changes, and when the form is submitted.
|
|
394
|
+
// `control.events` (Angular 18+) includes touched changes, so blurring an untouched field shows its error.
|
|
395
|
+
// Angular 17 has no `events`, so there the message updates on value and status changes only.
|
|
396
|
+
const controlEvents$ = control.events ?? merge(control.valueChanges, control.statusChanges);
|
|
397
|
+
this.subscribe(merge(controlEvents$, this.privateService.isSubmitted$), () => {
|
|
334
398
|
this.updateErrorMessage();
|
|
335
399
|
this.changeDetectorRef.markForCheck();
|
|
336
400
|
});
|
|
337
401
|
}
|
|
402
|
+
/** Whether the control currently has `Validators.required` or `Validators.requiredTrue`. */
|
|
403
|
+
get isRequired() {
|
|
404
|
+
const control = this.control;
|
|
405
|
+
return !!control && (control.hasValidator(Validators.required) || control.hasValidator(Validators.requiredTrue));
|
|
406
|
+
}
|
|
338
407
|
updateErrorMessage() {
|
|
339
408
|
if (!this.control) {
|
|
340
409
|
this.errorMessage = '';
|
|
@@ -361,14 +430,55 @@ class NgsFormsFormItemWithVisibleAndValidatorsBase extends NgsFormsBaseClassForm
|
|
|
361
430
|
bindValidators() {
|
|
362
431
|
if (!this.config.validators && !this.config.validators$)
|
|
363
432
|
return;
|
|
433
|
+
const control = this.control;
|
|
364
434
|
if (this.config.validators) {
|
|
365
|
-
|
|
435
|
+
control.setValidators(this.config.validators);
|
|
436
|
+
// A group or array computed its status when it was created, before these validators were set.
|
|
437
|
+
control.updateValueAndValidity({ emitEvent: false });
|
|
366
438
|
return;
|
|
367
439
|
}
|
|
368
440
|
if (this.config.validators$) {
|
|
369
|
-
this.subscribe(this.config.validators$, (validators) =>
|
|
441
|
+
this.subscribe(this.config.validators$, (validators) => {
|
|
442
|
+
control.setValidators(validators);
|
|
443
|
+
control.updateValueAndValidity();
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
bindDisabled() {
|
|
448
|
+
const disabled$ = this.config.disabled$ ?? (this.config.disabled !== undefined ? of(this.config.disabled) : undefined);
|
|
449
|
+
if (!disabled$)
|
|
450
|
+
return;
|
|
451
|
+
const control = this.control;
|
|
452
|
+
this.subscribe(disabled$, (disabled) => {
|
|
453
|
+
this.disabledFromConfig = !!disabled;
|
|
454
|
+
this.applyDisabled(control);
|
|
455
|
+
});
|
|
456
|
+
const reapply = () => {
|
|
457
|
+
if (this.disabledFromConfig && control.enabled) {
|
|
458
|
+
control.disable();
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
// Child controls are added to groups and arrays after the container has been disabled, and adding an enabled
|
|
462
|
+
// child re-enables its parent. Re-apply the configured state whenever a container's status changes.
|
|
463
|
+
if (control instanceof FormGroup || control instanceof FormArray) {
|
|
464
|
+
this.subscribe(control.statusChanges, reapply);
|
|
465
|
+
}
|
|
466
|
+
// Enabling a parent group or array enables every child in it. The parent emits its status after its children
|
|
467
|
+
// are enabled, so re-apply the configured state then. (Doing it on the child's own status change would be undone
|
|
468
|
+
// by the end of the child's `enable()`, which re-enables the bound DOM element.)
|
|
469
|
+
if (control.parent) {
|
|
470
|
+
this.subscribe(control.parent.statusChanges, reapply);
|
|
370
471
|
}
|
|
371
472
|
}
|
|
473
|
+
applyDisabled(control) {
|
|
474
|
+
if (this.disabledFromConfig && !control.disabled) {
|
|
475
|
+
control.disable();
|
|
476
|
+
}
|
|
477
|
+
else if (!this.disabledFromConfig && control.disabled) {
|
|
478
|
+
control.enable();
|
|
479
|
+
}
|
|
480
|
+
this.changeDetectorRef.markForCheck();
|
|
481
|
+
}
|
|
372
482
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormsFormItemWithVisibleAndValidatorsBase, deps: null, target: i0.ɵɵFactoryTarget.Directive });
|
|
373
483
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.19", type: NgsFormsFormItemWithVisibleAndValidatorsBase, isStandalone: true, usesInheritance: true, ngImport: i0 });
|
|
374
484
|
}
|
|
@@ -387,13 +497,15 @@ class NgsFormsBaseClassFormInputComponent extends NgsFormsFormItemWithVisibleAnd
|
|
|
387
497
|
myFormService = inject(NgsFormsService);
|
|
388
498
|
internalService = inject(NgsFormsInternalService);
|
|
389
499
|
/** Merged state for this component: its own state, then state by name, then global state. */
|
|
390
|
-
commonState = toSignal(this.internalService.subscribeToState(this.itemData.uuid || '', this.config.name), { initialValue: {} });
|
|
500
|
+
commonState = toSignal(this.internalService.subscribeToState(this.itemData.uuid || '', this.config.name, this.config.initialState), { initialValue: {} });
|
|
391
501
|
/**
|
|
392
|
-
* Switches between input and summary display.
|
|
393
|
-
*
|
|
394
|
-
* @notImplemented
|
|
502
|
+
* Switches this component between `input` and `summary` display by updating its own state.
|
|
503
|
+
* With no argument it flips the current mode.
|
|
395
504
|
*/
|
|
396
505
|
toggleDisplayMode(setTo) {
|
|
506
|
+
const current = this.commonState().displayMode ?? 'input';
|
|
507
|
+
const displayMode = setTo ?? (current === 'summary' ? 'input' : 'summary');
|
|
508
|
+
this.internalService.updateComponentState(this.itemData.uuid || this.config.name, { displayMode });
|
|
397
509
|
}
|
|
398
510
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormsBaseClassFormInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive });
|
|
399
511
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.19", type: NgsFormsBaseClassFormInputComponent, isStandalone: true, usesInheritance: true, ngImport: i0 });
|
|
@@ -434,10 +546,13 @@ class NgsFormItemController extends NgsSubscriber {
|
|
|
434
546
|
addRemoveControlFn;
|
|
435
547
|
index;
|
|
436
548
|
changeDetectorRef;
|
|
549
|
+
arrayRow;
|
|
437
550
|
componentRef;
|
|
438
|
-
parentVisibility = new BehaviorSubject(false);
|
|
439
551
|
lastVisible;
|
|
440
|
-
|
|
552
|
+
/** Incremented whenever a scheduled create becomes stale (hidden again, or destroyed). */
|
|
553
|
+
createToken = 0;
|
|
554
|
+
destroyed = false;
|
|
555
|
+
constructor(injector, viewContainerRef, itemData, addRemoveControlFn, index, changeDetectorRef, arrayRow) {
|
|
441
556
|
super();
|
|
442
557
|
this.injector = injector;
|
|
443
558
|
this.viewContainerRef = viewContainerRef;
|
|
@@ -445,33 +560,58 @@ class NgsFormItemController extends NgsSubscriber {
|
|
|
445
560
|
this.addRemoveControlFn = addRemoveControlFn;
|
|
446
561
|
this.index = index;
|
|
447
562
|
this.changeDetectorRef = changeDetectorRef;
|
|
563
|
+
this.arrayRow = arrayRow;
|
|
448
564
|
this.bindVisibility();
|
|
449
565
|
}
|
|
450
566
|
destroy() {
|
|
567
|
+
this.destroyed = true;
|
|
568
|
+
this.createToken++;
|
|
451
569
|
this.detach();
|
|
452
570
|
super.ngOnDestroy();
|
|
453
571
|
}
|
|
454
572
|
bindVisibility() {
|
|
455
|
-
|
|
456
|
-
|
|
573
|
+
const config = this.itemData.config;
|
|
574
|
+
const visible$ = config.visible$ ?? (config.visible !== undefined ? of(config.visible) : undefined);
|
|
575
|
+
if (!visible$) {
|
|
576
|
+
this.scheduleCreate();
|
|
457
577
|
return;
|
|
458
578
|
}
|
|
459
|
-
this.subscribe(
|
|
579
|
+
this.subscribe(visible$, (visible) => {
|
|
460
580
|
if (this.lastVisible === visible)
|
|
461
581
|
return;
|
|
462
582
|
this.lastVisible = visible;
|
|
463
583
|
if (visible) {
|
|
464
|
-
|
|
584
|
+
this.scheduleCreate();
|
|
465
585
|
return;
|
|
466
586
|
}
|
|
587
|
+
// Cancels a create that was scheduled but has not run yet.
|
|
588
|
+
this.createToken++;
|
|
467
589
|
this.detach();
|
|
468
590
|
});
|
|
469
591
|
}
|
|
592
|
+
/**
|
|
593
|
+
* Creates the component in a microtask. A create that is overtaken by a later visibility change or by
|
|
594
|
+
* `destroy()` before it runs is dropped.
|
|
595
|
+
*/
|
|
596
|
+
scheduleCreate() {
|
|
597
|
+
const token = ++this.createToken;
|
|
598
|
+
Promise.resolve().then(() => {
|
|
599
|
+
if (this.destroyed || token !== this.createToken)
|
|
600
|
+
return;
|
|
601
|
+
this.create();
|
|
602
|
+
});
|
|
603
|
+
}
|
|
470
604
|
create() {
|
|
605
|
+
if (this.componentRef && !this.componentRef.hostView.destroyed) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
471
608
|
const providers = [{ provide: NGS_FORMS_ITEM_DATA, useValue: this.itemData }];
|
|
472
609
|
if (this.index !== undefined) {
|
|
473
610
|
providers.push({ provide: NGS_FORMS_ITEM_INDEX, useValue: this.index });
|
|
474
611
|
}
|
|
612
|
+
if (this.arrayRow) {
|
|
613
|
+
providers.push({ provide: NGS_FORMS_ARRAY_ROW, useValue: this.arrayRow });
|
|
614
|
+
}
|
|
475
615
|
if (this.addRemoveControlFn) {
|
|
476
616
|
providers.push({ provide: NGS_FORMS_CONTROL_ADD_REMOVE_FN, useValue: this.addRemoveControlFn });
|
|
477
617
|
}
|
|
@@ -487,11 +627,10 @@ class NgsFormItemController extends NgsSubscriber {
|
|
|
487
627
|
this.componentRef = this.viewContainerRef.createComponent(componentType, { injector: myInjector });
|
|
488
628
|
this.componentRef.changeDetectorRef?.detectChanges();
|
|
489
629
|
this.changeDetectorRef?.markForCheck();
|
|
490
|
-
this.parentVisibility.next(true);
|
|
491
630
|
}
|
|
492
631
|
detach() {
|
|
493
|
-
this.parentVisibility.next(false);
|
|
494
632
|
this.componentRef?.destroy();
|
|
633
|
+
this.componentRef = undefined;
|
|
495
634
|
this.changeDetectorRef?.markForCheck();
|
|
496
635
|
}
|
|
497
636
|
}
|
|
@@ -510,7 +649,8 @@ class NgsFormsFormItemDirective {
|
|
|
510
649
|
/** Form array that the item's control is added to. */
|
|
511
650
|
formArray;
|
|
512
651
|
/**
|
|
513
|
-
* Index of the item inside a form array row, provided to children as `NGS_FORMS_ITEM_INDEX`.
|
|
652
|
+
* Index of the item inside a form array row, provided to children as `NGS_FORMS_ITEM_INDEX`. When it is set, the
|
|
653
|
+
* `ngs-form-group` is also provided to children as `NGS_FORMS_ARRAY_ROW`.
|
|
514
654
|
*/
|
|
515
655
|
index;
|
|
516
656
|
controller;
|
|
@@ -518,7 +658,24 @@ class NgsFormsFormItemDirective {
|
|
|
518
658
|
injector = inject(Injector);
|
|
519
659
|
changeDetectorRef = inject(ChangeDetectorRef);
|
|
520
660
|
formComponentRegistryService = inject(NgsFormsComponentRegistryService);
|
|
661
|
+
initialized = false;
|
|
521
662
|
ngOnInit() {
|
|
663
|
+
this.initialized = true;
|
|
664
|
+
this.build();
|
|
665
|
+
}
|
|
666
|
+
ngOnChanges(changes) {
|
|
667
|
+
if (!this.initialized) {
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
// A new item, group or array means a different control, so the component is rebuilt. An index change on its own
|
|
671
|
+
// keeps the component: the row it belongs to is still the same, and `NGS_FORMS_ARRAY_ROW` follows it.
|
|
672
|
+
if (changes['itemData'] || changes['formGroup'] || changes['formArray']) {
|
|
673
|
+
this.controller?.destroy();
|
|
674
|
+
this.controller = undefined;
|
|
675
|
+
this.build();
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
build() {
|
|
522
679
|
if (!this.itemData) {
|
|
523
680
|
return console.error('Form item without form data');
|
|
524
681
|
}
|
|
@@ -529,13 +686,13 @@ class NgsFormsFormItemDirective {
|
|
|
529
686
|
if (this.formGroup) {
|
|
530
687
|
addRemoveFn = NgsFormsFormControlAddRemoveFunctions.formGroup(this.formGroup);
|
|
531
688
|
}
|
|
532
|
-
this.controller = new NgsFormItemController(this.injector, this.viewContainerRef, this.itemData, addRemoveFn, this.index, this.changeDetectorRef);
|
|
689
|
+
this.controller = new NgsFormItemController(this.injector, this.viewContainerRef, this.itemData, addRemoveFn, this.index, this.changeDetectorRef, this.index !== undefined ? this.formGroup : undefined);
|
|
533
690
|
}
|
|
534
691
|
ngOnDestroy() {
|
|
535
692
|
this.controller?.destroy();
|
|
536
693
|
}
|
|
537
694
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormsFormItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
538
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.19", type: NgsFormsFormItemDirective, isStandalone: true, selector: "[ngs-form-item]", inputs: { itemData: ["ngs-form-item", "itemData"], formGroup: ["ngs-form-group", "formGroup"], formArray: ["ngs-form-array", "formArray"], index: ["ngs-form-index", "index"] }, ngImport: i0 });
|
|
695
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.19", type: NgsFormsFormItemDirective, isStandalone: true, selector: "[ngs-form-item]", inputs: { itemData: ["ngs-form-item", "itemData"], formGroup: ["ngs-form-group", "formGroup"], formArray: ["ngs-form-array", "formArray"], index: ["ngs-form-index", "index"] }, usesOnChanges: true, ngImport: i0 });
|
|
539
696
|
}
|
|
540
697
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormsFormItemDirective, decorators: [{
|
|
541
698
|
type: Directive,
|
|
@@ -580,6 +737,9 @@ class NgsFormComponent {
|
|
|
580
737
|
constructor() {
|
|
581
738
|
this.ngsFormsService.setInternalService(this.ngsInternalFormsService);
|
|
582
739
|
}
|
|
740
|
+
ngOnDestroy() {
|
|
741
|
+
this.ngsFormsService.clearInternalService(this.ngsInternalFormsService);
|
|
742
|
+
}
|
|
583
743
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
584
744
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NgsFormComponent, isStandalone: true, selector: "ngs-form", providers: [NgsFormsInternalService], ngImport: i0, template: "<!-- Tracking by form group re-creates the tree whenever setFormConfig() is called again, since each call creates a new group. -->\n@for (entry of rendered(); track entry.group) {\n <ng-container [ngs-form-group]=\"entry.group\" [ngs-form-item]=\"entry.root\"></ng-container>\n}\n", dependencies: [{ kind: "directive", type: NgsFormsFormItemDirective, selector: "[ngs-form-item]", inputs: ["ngs-form-item", "ngs-form-group", "ngs-form-array", "ngs-form-index"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
585
745
|
}
|
|
@@ -703,6 +863,8 @@ class NgsFormsFormArrayInternalService {
|
|
|
703
863
|
formArray = new UntypedFormArray([]);
|
|
704
864
|
/** Emits after a row is added or removed. */
|
|
705
865
|
itemsChanged = new Subject();
|
|
866
|
+
/** Emits the index of a row right after it is removed. */
|
|
867
|
+
rowRemoved = new Subject();
|
|
706
868
|
constructor() {
|
|
707
869
|
if (this.config.initialItemCount) {
|
|
708
870
|
for (let i = 0; i < this.config.initialItemCount; i++) {
|
|
@@ -711,16 +873,19 @@ class NgsFormsFormArrayInternalService {
|
|
|
711
873
|
}
|
|
712
874
|
}
|
|
713
875
|
ngOnDestroy() {
|
|
876
|
+
this.itemsChanged.complete();
|
|
877
|
+
this.rowRemoved.complete();
|
|
714
878
|
}
|
|
715
879
|
/** Removes the row at `index`, unless that would go below `minItems`. */
|
|
716
880
|
removeAt(index) {
|
|
717
881
|
if (this.config?.minItems && this.formArray.controls.length <= this.config.minItems) {
|
|
718
882
|
return;
|
|
719
883
|
}
|
|
720
|
-
if (
|
|
884
|
+
if (index < 0 || index >= this.formArray.controls.length) {
|
|
721
885
|
return;
|
|
722
886
|
}
|
|
723
887
|
this.formArray.removeAt(index);
|
|
888
|
+
this.rowRemoved.next(index);
|
|
724
889
|
this.itemsChanged.next();
|
|
725
890
|
}
|
|
726
891
|
/** Appends an empty row, unless the array already has `maxItems` rows. */
|
|
@@ -806,10 +971,33 @@ class NgsFormsFormArrayListComponent extends NgsFormsBaseClassFormComponent {
|
|
|
806
971
|
internalArrayService = inject(NgsFormsFormArrayInternalService);
|
|
807
972
|
myFormArray = this.internalArrayService.formArray;
|
|
808
973
|
changeDetectorRef = inject(ChangeDetectorRef);
|
|
974
|
+
elementRef = inject(ElementRef);
|
|
975
|
+
injector = inject(Injector);
|
|
809
976
|
ngOnInit() {
|
|
810
977
|
this.subscribe(this.internalArrayService.itemsChanged, () => {
|
|
811
978
|
this.changeDetectorRef.markForCheck();
|
|
812
979
|
});
|
|
980
|
+
this.subscribe(this.internalArrayService.rowRemoved, (index) => this.restoreFocusAfterRemove(index));
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* When the focused element (usually the row's Remove button) was removed with its row, moves focus to the
|
|
984
|
+
* same kind of control in the row that took its place, so keyboard users are not sent back to the top of the page.
|
|
985
|
+
*/
|
|
986
|
+
restoreFocusAfterRemove(index) {
|
|
987
|
+
afterNextRender(() => {
|
|
988
|
+
const active = document.activeElement;
|
|
989
|
+
if (active && active !== document.body) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
const host = this.elementRef.nativeElement;
|
|
993
|
+
const rows = Array.from(host.querySelectorAll(':scope > [data-index]'));
|
|
994
|
+
const row = rows[Math.min(index, rows.length - 1)];
|
|
995
|
+
const focusable = 'button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])';
|
|
996
|
+
const target = row?.querySelector('ngs-forms-form-array-remove-item button:not([disabled])') ??
|
|
997
|
+
row?.querySelector(focusable) ??
|
|
998
|
+
host.closest('ngs-forms-form-array')?.querySelector(focusable);
|
|
999
|
+
target?.focus();
|
|
1000
|
+
}, { injector: this.injector });
|
|
813
1001
|
}
|
|
814
1002
|
/** Creates a `form-array-list` item. */
|
|
815
1003
|
static create(config) {
|
|
@@ -841,8 +1029,26 @@ class NgsFormsFormArrayRemoveItemComponent extends NgsFormsBaseClassFormComponen
|
|
|
841
1029
|
internalArrayService = inject(NgsFormsFormArrayInternalService);
|
|
842
1030
|
elementRef = inject(ElementRef);
|
|
843
1031
|
indexFromDi = inject(NGS_FORMS_ITEM_INDEX, { optional: true });
|
|
844
|
-
|
|
1032
|
+
row = inject(NGS_FORMS_ARRAY_ROW, { optional: true });
|
|
1033
|
+
changeDetectorRef = inject(ChangeDetectorRef);
|
|
1034
|
+
/** Accessible name of the button, from `ariaLabel` or `${buttonText} item ${index + 1}`. */
|
|
1035
|
+
get ariaLabel() {
|
|
1036
|
+
const index = this.myIndex;
|
|
1037
|
+
const label = this.config.ariaLabel;
|
|
1038
|
+
if (typeof label === 'function')
|
|
1039
|
+
return label(index);
|
|
1040
|
+
if (label)
|
|
1041
|
+
return label;
|
|
1042
|
+
return `${this.config.buttonText || 'Remove'} item ${index + 1}`;
|
|
1043
|
+
}
|
|
1044
|
+
/** Current index of the row this button belongs to. */
|
|
845
1045
|
get myIndex() {
|
|
1046
|
+
if (this.row) {
|
|
1047
|
+
const index = this.internalArrayService.formArray.controls.indexOf(this.row);
|
|
1048
|
+
if (index >= 0) {
|
|
1049
|
+
return index;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
846
1052
|
if (this.indexFromDi !== null && this.indexFromDi !== undefined) {
|
|
847
1053
|
return this.indexFromDi;
|
|
848
1054
|
}
|
|
@@ -859,6 +1065,10 @@ class NgsFormsFormArrayRemoveItemComponent extends NgsFormsBaseClassFormComponen
|
|
|
859
1065
|
constructor() {
|
|
860
1066
|
super();
|
|
861
1067
|
}
|
|
1068
|
+
ngOnInit() {
|
|
1069
|
+
// The row's position changes when earlier rows are removed, which changes the default accessible name.
|
|
1070
|
+
this.subscribe(this.internalArrayService.itemsChanged, () => this.changeDetectorRef.markForCheck());
|
|
1071
|
+
}
|
|
862
1072
|
/** Creates a `form-array-remove-item` item. */
|
|
863
1073
|
static create(config) {
|
|
864
1074
|
return {
|
|
@@ -871,11 +1081,11 @@ class NgsFormsFormArrayRemoveItemComponent extends NgsFormsBaseClassFormComponen
|
|
|
871
1081
|
this.internalArrayService.removeAt(this.myIndex);
|
|
872
1082
|
}
|
|
873
1083
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormsFormArrayRemoveItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
874
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NgsFormsFormArrayRemoveItemComponent, isStandalone: true, selector: "ngs-forms-form-array-remove-item", usesInheritance: true, ngImport: i0, template: "<button\n (click)=\"removeItem()\"\n [class]=\"config.buttonClass || 'btn btn-outline-danger btn-sm'\"\n type=\"button\">\n @if (config.buttonIcon) {\n <i [class]=\"config.buttonIcon\" aria-hidden=\"true\"></i>\n }\n {{ config.buttonText || 'Remove' }}\n</button>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1084
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NgsFormsFormArrayRemoveItemComponent, isStandalone: true, selector: "ngs-forms-form-array-remove-item", usesInheritance: true, ngImport: i0, template: "<button\n (click)=\"removeItem()\"\n [class]=\"config.buttonClass || 'btn btn-outline-danger btn-sm'\"\n [attr.aria-label]=\"ariaLabel\"\n type=\"button\">\n @if (config.buttonIcon) {\n <i [class]=\"config.buttonIcon\" aria-hidden=\"true\"></i>\n }\n {{ config.buttonText || 'Remove' }}\n</button>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
875
1085
|
}
|
|
876
1086
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormsFormArrayRemoveItemComponent, decorators: [{
|
|
877
1087
|
type: Component,
|
|
878
|
-
args: [{ selector: 'ngs-forms-form-array-remove-item', changeDetection: ChangeDetectionStrategy.OnPush, template: "<button\n (click)=\"removeItem()\"\n [class]=\"config.buttonClass || 'btn btn-outline-danger btn-sm'\"\n type=\"button\">\n @if (config.buttonIcon) {\n <i [class]=\"config.buttonIcon\" aria-hidden=\"true\"></i>\n }\n {{ config.buttonText || 'Remove' }}\n</button>\n" }]
|
|
1088
|
+
args: [{ selector: 'ngs-forms-form-array-remove-item', changeDetection: ChangeDetectionStrategy.OnPush, template: "<button\n (click)=\"removeItem()\"\n [class]=\"config.buttonClass || 'btn btn-outline-danger btn-sm'\"\n [attr.aria-label]=\"ariaLabel\"\n type=\"button\">\n @if (config.buttonIcon) {\n <i [class]=\"config.buttonIcon\" aria-hidden=\"true\"></i>\n }\n {{ config.buttonText || 'Remove' }}\n</button>\n" }]
|
|
879
1089
|
}], ctorParameters: () => [] });
|
|
880
1090
|
|
|
881
1091
|
/**
|
|
@@ -910,6 +1120,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
|
|
|
910
1120
|
args: [{ selector: 'ngs-form-component-text-div', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"{{ config.classes }}\">{{ config.text }}</div>\n" }]
|
|
911
1121
|
}] });
|
|
912
1122
|
|
|
1123
|
+
;
|
|
1124
|
+
|
|
913
1125
|
/**
|
|
914
1126
|
* Renders an HTML string inside the form. The HTML is passed through Angular's sanitizer.
|
|
915
1127
|
*
|
|
@@ -943,8 +1155,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
|
|
|
943
1155
|
args: [{ selector: 'ngs-form-component-html-content', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [innerHTML]=\"html\"></div>\n" }]
|
|
944
1156
|
}], ctorParameters: () => [] });
|
|
945
1157
|
|
|
1158
|
+
/** @internal */
|
|
946
1159
|
const sectionConfigDefaults = {
|
|
947
1160
|
titleClass: 'h3',
|
|
1161
|
+
headingLevel: 3,
|
|
948
1162
|
subtitleClass: 'lead',
|
|
949
1163
|
};
|
|
950
1164
|
|
|
@@ -953,10 +1167,13 @@ const sectionConfigDefaults = {
|
|
|
953
1167
|
* parent group.
|
|
954
1168
|
*
|
|
955
1169
|
* @remarks Registry key: `section`. Config: `title`, `subtitle`, `titleClass` (default `h3`),
|
|
956
|
-
* `subtitleClass` (default `lead`) and `items`.
|
|
1170
|
+
* `headingLevel` (default 3), `subtitleClass` (default `lead`) and `items`. The section is exposed to assistive
|
|
1171
|
+
* technology as a group named by its title.
|
|
957
1172
|
*/
|
|
958
1173
|
class NgsFormsFormSectionComponent extends NgsFormsBaseClassItemsContainerBase {
|
|
959
1174
|
static key = 'section';
|
|
1175
|
+
/** Element id of the title, used to name the section's group. */
|
|
1176
|
+
titleId = `${this.itemData.uuid || v4()}-section-title`;
|
|
960
1177
|
/** Creates a `section` item. */
|
|
961
1178
|
static create(config) {
|
|
962
1179
|
return {
|
|
@@ -969,11 +1186,11 @@ class NgsFormsFormSectionComponent extends NgsFormsBaseClassItemsContainerBase {
|
|
|
969
1186
|
this.config = ngsDefaults(this.config, sectionConfigDefaults);
|
|
970
1187
|
}
|
|
971
1188
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormsFormSectionComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
972
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NgsFormsFormSectionComponent, isStandalone: true, selector: "ngs-forms-form-section", usesInheritance: true, ngImport: i0, template: "<div class=\"form-section\">\n <div class=\"form-section-title {{config.titleClass}}\">{{ config.title }}</div>\n @if (config.subtitle) {\n <div class=\"form-section-subtitle {{config.subtitleClass}}\">{{ config.subtitle }}</div>\n }\n @if (config.items.length) {\n <div class=\"form-section-items\">\n @for (item of config.items; track item.uuid) {\n <ng-container [ngs-form-item]=\"item\"></ng-container>\n }\n </div>\n }\n</div>\n", dependencies: [{ kind: "directive", type: NgsFormsFormItemDirective, selector: "[ngs-form-item]", inputs: ["ngs-form-item", "ngs-form-group", "ngs-form-array", "ngs-form-index"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1189
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NgsFormsFormSectionComponent, isStandalone: true, selector: "ngs-forms-form-section", usesInheritance: true, ngImport: i0, template: "<div class=\"form-section\" role=\"group\" [attr.aria-labelledby]=\"titleId\">\n <div class=\"form-section-title {{config.titleClass}}\" role=\"heading\" [attr.aria-level]=\"config.headingLevel\" [id]=\"titleId\">{{ config.title }}</div>\n @if (config.subtitle) {\n <div class=\"form-section-subtitle {{config.subtitleClass}}\">{{ config.subtitle }}</div>\n }\n @if (config.items.length) {\n <div class=\"form-section-items\">\n @for (item of config.items; track item.uuid) {\n <ng-container [ngs-form-item]=\"item\"></ng-container>\n }\n </div>\n }\n</div>\n", dependencies: [{ kind: "directive", type: NgsFormsFormItemDirective, selector: "[ngs-form-item]", inputs: ["ngs-form-item", "ngs-form-group", "ngs-form-array", "ngs-form-index"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
973
1190
|
}
|
|
974
1191
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NgsFormsFormSectionComponent, decorators: [{
|
|
975
1192
|
type: Component,
|
|
976
|
-
args: [{ selector: 'ngs-forms-form-section', imports: [NgsFormsFormItemDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"form-section\">\n <div class=\"form-section-title {{config.titleClass}}\">{{ config.title }}</div>\n @if (config.subtitle) {\n <div class=\"form-section-subtitle {{config.subtitleClass}}\">{{ config.subtitle }}</div>\n }\n @if (config.items.length) {\n <div class=\"form-section-items\">\n @for (item of config.items; track item.uuid) {\n <ng-container [ngs-form-item]=\"item\"></ng-container>\n }\n </div>\n }\n</div>\n" }]
|
|
1193
|
+
args: [{ selector: 'ngs-forms-form-section', imports: [NgsFormsFormItemDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"form-section\" role=\"group\" [attr.aria-labelledby]=\"titleId\">\n <div class=\"form-section-title {{config.titleClass}}\" role=\"heading\" [attr.aria-level]=\"config.headingLevel\" [id]=\"titleId\">{{ config.title }}</div>\n @if (config.subtitle) {\n <div class=\"form-section-subtitle {{config.subtitleClass}}\">{{ config.subtitle }}</div>\n }\n @if (config.items.length) {\n <div class=\"form-section-items\">\n @for (item of config.items; track item.uuid) {\n <ng-container [ngs-form-item]=\"item\"></ng-container>\n }\n </div>\n }\n</div>\n" }]
|
|
977
1194
|
}] });
|
|
978
1195
|
|
|
979
1196
|
// --
|
|
@@ -1013,5 +1230,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
|
|
|
1013
1230
|
* Generated bundle index. Do not edit.
|
|
1014
1231
|
*/
|
|
1015
1232
|
|
|
1016
|
-
export { NGS_FORMS_CONTROL_ADD_REMOVE_FN, NGS_FORMS_ITEM_DATA, NGS_FORMS_ITEM_INDEX, NgsFormComponent, NgsFormsBaseClassFormComponent, NgsFormsBaseClassFormInputComponent, NgsFormsBaseClassFormItemInputWithOptionsComponent, NgsFormsBaseClassItemsContainerBase, NgsFormsColumnComponent, NgsFormsComponentRegistryService, NgsFormsCoreModule, NgsFormsFormArrayAddItemComponent, NgsFormsFormArrayContainerComponent, NgsFormsFormArrayInternalService, NgsFormsFormArrayListComponent, NgsFormsFormArrayRemoveItemComponent, NgsFormsFormControlAddRemoveFunctions, NgsFormsFormGroupComponent, NgsFormsFormItemDirective, NgsFormsFormItemWithVisibleAndValidatorsBase, NgsFormsFormSectionComponent, NgsFormsHtmlContentComponent, NgsFormsInternalService, NgsFormsRowComponent, NgsFormsService, NgsFormsTextDivComponent, NgsSubscriber, ngsDefaults };
|
|
1233
|
+
export { NGS_FORMS_ARRAY_ROW, NGS_FORMS_CONTROL_ADD_REMOVE_FN, NGS_FORMS_ITEM_DATA, NGS_FORMS_ITEM_INDEX, NgsFormComponent, NgsFormsBaseClassFormComponent, NgsFormsBaseClassFormInputComponent, NgsFormsBaseClassFormItemInputWithOptionsComponent, NgsFormsBaseClassItemsContainerBase, NgsFormsColumnComponent, NgsFormsComponentRegistryService, NgsFormsCoreModule, NgsFormsFormArrayAddItemComponent, NgsFormsFormArrayContainerComponent, NgsFormsFormArrayInternalService, NgsFormsFormArrayListComponent, NgsFormsFormArrayRemoveItemComponent, NgsFormsFormControlAddRemoveFunctions, NgsFormsFormGroupComponent, NgsFormsFormItemDirective, NgsFormsFormItemWithVisibleAndValidatorsBase, NgsFormsFormSectionComponent, NgsFormsHtmlContentComponent, NgsFormsInternalService, NgsFormsRowComponent, NgsFormsService, NgsFormsTextDivComponent, NgsSubscriber, ngsDefaults, sectionConfigDefaults };
|
|
1017
1234
|
//# sourceMappingURL=ng-simplicity-forms-core.mjs.map
|