@stemy/ngx-dynamic-form 19.2.14 → 19.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import * as i2 from '@stemy/ngx-utils';
2
- import { cachedFactory, ReflectUtils, ObjectUtils, LANGUAGE_SERVICE, API_SERVICE, StringUtils, TOASTER_SERVICE, EventsService, NgxUtilsModule } from '@stemy/ngx-utils';
3
- import { Subject, BehaviorSubject, startWith, distinctUntilChanged, switchMap, first, from, isObservable, firstValueFrom } from 'rxjs';
2
+ import { cachedFactory, ReflectUtils, ObjectUtils, LANGUAGE_SERVICE, ForbiddenZone, API_SERVICE, StringUtils, TOASTER_SERVICE, EventsService, NgxUtilsModule } from '@stemy/ngx-utils';
3
+ import { Subject, BehaviorSubject, startWith, distinctUntilChanged, switchMap, from, isObservable, firstValueFrom, first } from 'rxjs';
4
4
  import * as i0 from '@angular/core';
5
5
  import { Inject, Injectable, input, output, inject, Renderer2, ElementRef, computed, signal, effect, HostListener, HostBinding, Directive, Injector, ChangeDetectionStrategy, ViewEncapsulation, Component, makeEnvironmentProviders, NgModule } from '@angular/core';
6
6
  import * as i1 from '@angular/forms';
@@ -17,18 +17,18 @@ const FORM_ROOT_KEY = "__root";
17
17
 
18
18
  function customizeFormField(...providers) {
19
19
  const factory = cachedFactory(providers);
20
- return async (property, schema, config, options, injector) => {
20
+ return async (field, options, injector, property, schema) => {
21
21
  const customizers = factory(injector);
22
- const configs = [config];
22
+ const fields = [field];
23
23
  for (const customizer of customizers) {
24
- const index = configs.findIndex(m => customizer.acceptField(m));
24
+ const index = fields.findIndex(m => customizer.acceptField(m, property, schema));
25
25
  if (index >= 0) {
26
- const custom = await customizer.customizeField(configs[index], property, schema, options);
26
+ const custom = await customizer.customizeField(fields[index], options, property, schema);
27
27
  const result = Array.isArray(custom) ? custom : [custom];
28
- configs.splice(index, 1, ...result);
28
+ fields.splice(index, 1, ...result);
29
29
  }
30
30
  }
31
- return configs;
31
+ return fields;
32
32
  };
33
33
  }
34
34
 
@@ -176,32 +176,9 @@ function maxValueValidation(max, each) {
176
176
  return validateEach(each, v => typeof v == "number" && v <= max, "maxValue");
177
177
  }
178
178
 
179
- function isStringWithVal(val) {
180
- return typeof val == "string" && val.length > 0;
181
- }
182
- function findRefs(property) {
183
- const refs = Array.isArray(property.allOf)
184
- ? property.allOf.map(o => o.$ref).filter(isStringWithVal)
185
- : [property.items?.$ref, property.$ref].filter(isStringWithVal);
186
- return refs.map(t => t.split("/").pop());
187
- }
188
179
  function replaceSpecialChars(str, to = "-") {
189
180
  return `${str}`.replace(/[&\/\\#, +()$~%.@'":*?<>{}]/g, to);
190
181
  }
191
- function mergeFormFields(formFields) {
192
- const res = [];
193
- for (const formModel of formFields) {
194
- for (const subModel of formModel) {
195
- const index = res.findIndex(t => t.key == subModel.key);
196
- if (index >= 0) {
197
- res[index] = subModel;
198
- continue;
199
- }
200
- res.push(subModel);
201
- }
202
- }
203
- return res;
204
- }
205
182
  function getFieldByPath(field, path) {
206
183
  if (field.path === path) {
207
184
  return field;
@@ -215,6 +192,18 @@ function getFieldByPath(field, path) {
215
192
  }
216
193
  return null;
217
194
  }
195
+ function getFieldsByKey(field, key) {
196
+ if (field.key === key) {
197
+ return [field];
198
+ }
199
+ if (!field.fieldGroup)
200
+ return [];
201
+ const results = [];
202
+ for (const sf of field.fieldGroup) {
203
+ results.push(...getFieldsByKey(sf, key));
204
+ }
205
+ return results;
206
+ }
218
207
  function setFieldHidden(field, hidden = true) {
219
208
  const hide = field.expressions?.hide;
220
209
  if (hide) {
@@ -245,6 +234,113 @@ const MIN_INPUT_NUM = -999999999;
245
234
  const MAX_INPUT_NUM = 999999999;
246
235
  const EDITOR_FORMATS = ["php", "json", "html", "css", "scss"];
247
236
 
237
+ class ConfigForSchemaWrap {
238
+ opts;
239
+ mode;
240
+ injector;
241
+ schema;
242
+ get labelPrefix() {
243
+ return this.opts.labelPrefix;
244
+ }
245
+ get labelCustomizer() {
246
+ return this.opts.labelCustomizer;
247
+ }
248
+ get testId() {
249
+ return this.opts.testId;
250
+ }
251
+ fieldCustomizer;
252
+ constructor(opts, mode, injector, schema) {
253
+ this.opts = opts;
254
+ this.mode = mode;
255
+ this.injector = injector;
256
+ this.schema = schema;
257
+ this.fieldCustomizer = this.mode !== "wrap" || !ObjectUtils.isFunction(this.opts.fieldCustomizer)
258
+ ? field => field
259
+ : this.opts.fieldCustomizer;
260
+ }
261
+ async customize(field, property, schema) {
262
+ field.defaultValue = `${field.props?.type}`.startsWith("date")
263
+ ? convertToDate(property.default) : property.default;
264
+ const res = await ForbiddenZone.run("customizer", () => this.fieldCustomizer(field, this.forCustomizer(), this.injector, property, schema));
265
+ return !res ? [field] : handleConfigs(res);
266
+ }
267
+ forCustomizer() {
268
+ return new ConfigForSchemaWrap(this.opts, "customizer", this.injector, this.schema);
269
+ }
270
+ forSchema(schema) {
271
+ return new ConfigForSchemaWrap(this.opts, this.mode, this.injector, schema);
272
+ }
273
+ }
274
+ async function toWrapOptions(customizeOrOptions, injector, schema, errorMsg) {
275
+ if (errorMsg && ForbiddenZone.isForbidden("customizer")) {
276
+ throw new Error(errorMsg);
277
+ }
278
+ if (customizeOrOptions instanceof ConfigForSchemaWrap) {
279
+ return customizeOrOptions;
280
+ }
281
+ let schemaOptions = customizeOrOptions;
282
+ if (!ObjectUtils.isObject(schemaOptions)) {
283
+ schemaOptions = {
284
+ fieldCustomizer: customizeOrOptions
285
+ };
286
+ }
287
+ return new ConfigForSchemaWrap(schemaOptions, "wrap", injector, schema);
288
+ }
289
+ function convertToDate(value) {
290
+ if (ObjectUtils.isNullOrUndefined(value))
291
+ return null;
292
+ const date = ObjectUtils.isDate(value)
293
+ ? value
294
+ : new Date(value);
295
+ return isNaN(date) ? new Date() : date;
296
+ }
297
+ function handleConfigs(configs) {
298
+ return Array.isArray(configs) ? configs : [configs];
299
+ }
300
+ function isStringWithVal(val) {
301
+ return typeof val == "string" && val.length > 0;
302
+ }
303
+ function findRefs(property) {
304
+ const refs = Array.isArray(property.allOf)
305
+ ? property.allOf.map(o => o.$ref).filter(isStringWithVal)
306
+ : [property.items?.$ref, property.$ref].filter(isStringWithVal);
307
+ return refs.map(t => t.split("/").pop());
308
+ }
309
+ function mergeFormFields(formFields) {
310
+ const res = [];
311
+ for (const formModel of formFields) {
312
+ for (const subModel of formModel) {
313
+ const index = res.findIndex(t => t.key == subModel.key);
314
+ if (index >= 0) {
315
+ res[index] = subModel;
316
+ continue;
317
+ }
318
+ res.push(subModel);
319
+ }
320
+ }
321
+ return res;
322
+ }
323
+ function getFormValidationErrors(controls, parentPath = "") {
324
+ const errors = [];
325
+ Object.entries(controls).forEach(([name, control], ix) => {
326
+ const path = !parentPath ? name : `${parentPath}.${name}`;
327
+ if (control instanceof FormGroup$1) {
328
+ getFormValidationErrors(control.controls, path).forEach(error => errors.push(error));
329
+ return;
330
+ }
331
+ if (control instanceof FormArray$1) {
332
+ control.controls.forEach((control, ix) => {
333
+ getFormValidationErrors(control.controls, `${path}.${ix}`).forEach(error => errors.push(error));
334
+ });
335
+ return;
336
+ }
337
+ Object.entries(control.errors || {}).forEach(([errorKey, errorValue]) => {
338
+ errors.push({ control, path, errorKey, errorValue });
339
+ });
340
+ });
341
+ return errors;
342
+ }
343
+
248
344
  class DynamicFormBuilderService {
249
345
  injector;
250
346
  api;
@@ -254,57 +350,6 @@ class DynamicFormBuilderService {
254
350
  this.api = api;
255
351
  this.language = language;
256
352
  }
257
- getLabel(key, label, parent, options) {
258
- const labelPrefix = !ObjectUtils.isString(options.labelPrefix) ? `` : options.labelPrefix;
259
- const pathPrefix = `${parent?.props?.label || labelPrefix}`;
260
- const labelItems = ObjectUtils.isString(label)
261
- ? (!label ? [] : [labelPrefix, label])
262
- : [pathPrefix, `${key || ""}`];
263
- return labelItems.filter(l => l.length > 0).join(".");
264
- }
265
- createFormField(key, type, data, props, parent, options) {
266
- const validators = Array.isArray(data.validators)
267
- ? data.validators.reduce((res, validator, ix) => {
268
- res[validator.validatorName || `validator_${ix}`] = validator;
269
- return res;
270
- }, {})
271
- : data.validators || {};
272
- const hide = new BehaviorSubject(data.hidden === true);
273
- const additional = new BehaviorSubject({});
274
- return {
275
- key,
276
- type,
277
- validators,
278
- parent,
279
- fieldSet: String(data.fieldSet || ""),
280
- resetOnHide: false,
281
- validation: {
282
- messages: Object.keys(validators).reduce((res, key) => {
283
- res[key] = validationMessage(this.injector, key, options.labelPrefix);
284
- return res;
285
- }, {})
286
- },
287
- props: {
288
- ...props,
289
- disabled: data.disabled === true,
290
- formCheck: "nolabel",
291
- required: !!validators.required,
292
- label: this.getLabel(key, data.label, parent, options),
293
- },
294
- modelOptions: {
295
- updateOn: "change"
296
- },
297
- fieldGroupClassName: "field-container",
298
- expressions: {
299
- hide,
300
- additional,
301
- className: (target) => {
302
- return target.hide ? `` : [`dynamic-form-field`, `dynamic-form-field-${target.key}`, `dynamic-form-${target.type || "group"}`].concat(Array.isArray(data.classes) ? data.classes : [data.classes || ""]).filter(c => c?.length > 0).join(" ");
303
- },
304
- ...this.getExpressions(options)
305
- }
306
- };
307
- }
308
353
  resolveFormFields(target, parent, options) {
309
354
  const prototype = target?.prototype || {};
310
355
  const fields = ReflectUtils.getMetadata("dynamicFormFields", target?.prototype || {}) || new Set();
@@ -344,7 +389,7 @@ class DynamicFormBuilderService {
344
389
  }
345
390
  // Create a field-set wrapper for each group and concat the other fields to the end
346
391
  return Object.keys(groups).map(group => {
347
- return {
392
+ const fieldSet = {
348
393
  fieldGroup: groups[group],
349
394
  wrappers: ["form-fieldset"],
350
395
  className: `dynamic-form-fieldset dynamic-form-fieldset-${group}`,
@@ -352,11 +397,10 @@ class DynamicFormBuilderService {
352
397
  props: {
353
398
  label: this.getLabel(group, group, parent, options),
354
399
  hidden: false
355
- },
356
- expressions: {
357
- ...this.getExpressions(options)
358
400
  }
359
401
  };
402
+ this.setExpressions(fieldSet, options);
403
+ return fieldSet;
360
404
  }).concat(others);
361
405
  }
362
406
  createFormInput(key, data, parent, options) {
@@ -499,8 +543,61 @@ class DynamicFormBuilderService {
499
543
  control.setValue(options[0].value);
500
544
  return options;
501
545
  }
502
- getExpressions(options) {
503
- return {
546
+ getLabel(key, label, parent, options) {
547
+ const labelPrefix = !ObjectUtils.isString(options.labelPrefix) ? `` : options.labelPrefix;
548
+ const pathPrefix = `${parent?.props?.label || labelPrefix}`;
549
+ const labelItems = ObjectUtils.isString(label)
550
+ ? (!label ? [] : [labelPrefix, label])
551
+ : [pathPrefix, `${key || ""}`];
552
+ return options.labelCustomizer?.(key, label, parent, options.labelPrefix)
553
+ ?? labelItems.filter(l => l.length > 0).join(".");
554
+ }
555
+ createFormField(key, type, data, props, parent, options) {
556
+ const validators = Array.isArray(data.validators)
557
+ ? data.validators.reduce((res, validator, ix) => {
558
+ res[validator.validatorName || `validator_${ix}`] = validator;
559
+ return res;
560
+ }, {})
561
+ : data.validators || {};
562
+ const hide = new BehaviorSubject(data.hidden === true);
563
+ const additional = new BehaviorSubject({});
564
+ const field = {
565
+ key,
566
+ type,
567
+ validators,
568
+ parent,
569
+ fieldSet: String(data.fieldSet || ""),
570
+ resetOnHide: false,
571
+ validation: {
572
+ messages: Object.keys(validators).reduce((res, key) => {
573
+ res[key] = validationMessage(this.injector, key, options.labelPrefix);
574
+ return res;
575
+ }, {})
576
+ },
577
+ props: {
578
+ ...props,
579
+ disabled: data.disabled === true,
580
+ formCheck: "nolabel",
581
+ required: !!validators.required,
582
+ label: this.getLabel(key, data.label, parent, options),
583
+ },
584
+ modelOptions: {
585
+ updateOn: "change"
586
+ },
587
+ fieldGroupClassName: "field-container",
588
+ expressions: {
589
+ hide,
590
+ additional,
591
+ className: (target) => {
592
+ return target.hide ? `` : [`dynamic-form-field`, `dynamic-form-field-${target.key}`, `dynamic-form-${target.type || "group"}`].concat(Array.isArray(data.classes) ? data.classes : [data.classes || ""]).filter(c => c?.length > 0).join(" ");
593
+ }
594
+ }
595
+ };
596
+ this.setExpressions(field, options);
597
+ return field;
598
+ }
599
+ setExpressions(field, options) {
600
+ const expressions = {
504
601
  path: target => {
505
602
  const tp = target.parent;
506
603
  const key = !target.key ? `` : `.${target.key}`;
@@ -513,6 +610,13 @@ class DynamicFormBuilderService {
513
610
  return !tp?.testId ? `${prefix}${target.key || key}` : `${tp.testId}${key}`;
514
611
  }
515
612
  };
613
+ Object.entries(expressions).forEach(([key, expression]) => {
614
+ field.expressions = field.expressions ?? {};
615
+ field.expressions[key] = expression;
616
+ if (ObjectUtils.isFunction(expression)) {
617
+ field[key] = expression(field);
618
+ }
619
+ });
516
620
  }
517
621
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DynamicFormBuilderService, deps: [{ token: i0.Injector }, { token: API_SERVICE }, { token: LANGUAGE_SERVICE }], target: i0.ɵɵFactoryTarget.Injectable });
518
622
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DynamicFormBuilderService });
@@ -527,28 +631,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
527
631
  args: [LANGUAGE_SERVICE]
528
632
  }] }] });
529
633
 
530
- function getFormValidationErrors(controls, parentPath = "") {
531
- const errors = [];
532
- Object.entries(controls).forEach(([name, control], ix) => {
533
- const path = !parentPath ? name : `${parentPath}.${name}`;
534
- if (control instanceof FormGroup$1) {
535
- getFormValidationErrors(control.controls, path).forEach(error => errors.push(error));
536
- return;
537
- }
538
- if (control instanceof FormArray$1) {
539
- control.controls.forEach((control, ix) => {
540
- getFormValidationErrors(control.controls, `${path}.${ix}`).forEach(error => errors.push(error));
541
- });
542
- return;
543
- }
544
- Object.entries(control.errors || {}).forEach(([errorKey, errorValue]) => {
545
- errors.push({ control, path, errorKey, errorValue });
546
- });
547
- });
548
- return errors;
549
- }
550
-
551
- class DynamicFormService {
634
+ class DynamicFormSchemaService {
552
635
  openApi;
553
636
  injector;
554
637
  builder;
@@ -558,149 +641,32 @@ class DynamicFormService {
558
641
  get language() {
559
642
  return this.api.language;
560
643
  }
561
- schemas;
562
644
  constructor(openApi, injector, builder) {
563
645
  this.openApi = openApi;
564
646
  this.injector = injector;
565
647
  this.builder = builder;
566
648
  }
567
- async validateForm(form, showErrors = true) {
568
- const group = form.group();
569
- if (!group)
570
- return Promise.resolve();
571
- return new Promise((resolve, reject) => {
572
- group.statusChanges
573
- .pipe(first(status => status == "VALID" || status == "INVALID"))
574
- .subscribe(status => {
575
- if (showErrors) {
576
- this.showErrorsForGroup(group);
577
- }
578
- if (status == "VALID") {
579
- resolve(null);
580
- return;
581
- }
582
- reject(getFormValidationErrors(group.controls));
583
- });
584
- group.updateValueAndValidity();
585
- });
586
- }
587
- async serializeForm(form, validate = true) {
588
- const fields = form.config();
589
- if (!fields)
590
- return null;
591
- if (validate) {
592
- await this.validateForm(form);
593
- }
594
- return this.serialize(fields);
649
+ async getSchema(name) {
650
+ return this.openApi.getSchema(name);
595
651
  }
596
- async serialize(fields) {
597
- const result = {};
598
- if (!fields)
599
- return result;
600
- for (const field of fields) {
601
- const serializer = field.serializer;
602
- const key = `${field.key}`;
603
- if (ObjectUtils.isFunction(serializer)) {
604
- result[key] = await serializer(field, this.injector);
605
- continue;
606
- }
607
- if (field.hide && !field.serialize) {
608
- continue;
609
- }
610
- const control = field.formControl;
611
- if (field.fieldGroup) {
612
- const group = await this.serialize(field.fieldGroup);
613
- if (field.key) {
614
- result[key] = !field.fieldArray ? group : Object.values(group);
615
- continue;
616
- }
617
- Object.assign(result, group);
618
- continue;
619
- }
620
- result[key] = control.value;
621
- }
622
- return result;
623
- }
624
- async getFormFieldsForSchema(name, customizeOrOptions) {
625
- const group = await this.getFormFieldGroupForSchema(name, customizeOrOptions);
626
- return group.fieldGroup;
627
- }
628
- async getFormFieldGroupForSchema(name, customizeOrOptions) {
629
- this.schemas = await this.openApi.getSchemas();
630
- const schemaOptions = ObjectUtils.isObject(customizeOrOptions) ? customizeOrOptions : {};
631
- const customizeConfig = ObjectUtils.isFunction(customizeOrOptions) ? customizeOrOptions : schemaOptions.customizer;
632
- const schema = this.schemas[name];
633
- const wrapOptions = {
634
- ...schemaOptions,
635
- schema,
636
- injector: this.injector,
637
- customizer: async (property, options, config) => {
638
- config.defaultValue = `${config.type}`.startsWith("date")
639
- ? this.convertToDate(property.default) : property.default;
640
- if (!ObjectUtils.isFunction(customizeConfig))
641
- return [config];
642
- let res = customizeConfig(property, schema, config, options, this.injector);
643
- if (!res)
644
- return [config];
645
- if (res instanceof Promise) {
646
- res = await res;
647
- }
648
- return Array.isArray(res) ? res : [res];
649
- }
650
- };
651
- const config = {
652
- key: FORM_ROOT_KEY,
653
- path: "",
654
- wrappers: ["form-group"]
655
- };
656
- const fields = await this.getFormFieldsForSchemaDef(schema, config, wrapOptions);
657
- const fieldGroup = [...fields];
658
- // Add id fields if necessary
659
- if (fields.length > 0) {
660
- const idFields = [
661
- { key: "id", props: { hidden: true } },
662
- { key: "_id", props: { hidden: true } },
663
- ];
664
- fieldGroup.unshift(...idFields
665
- .filter(t => !fields.some(c => c.key == t.key)));
666
- }
667
- config.fieldGroup = fieldGroup;
668
- const root = await wrapOptions.customizer({
669
- id: FORM_ROOT_KEY,
670
- type: "object",
671
- properties: schema?.properties || {}
672
- }, wrapOptions, config);
673
- // Check if the customized root wrapper returned an array
674
- fields.length = 0;
675
- for (const model of root) {
676
- if (model.key === FORM_ROOT_KEY) {
677
- return model;
678
- }
679
- else {
680
- fields.push(model);
681
- }
682
- }
683
- return {
684
- ...config,
685
- fieldGroup: fields
686
- };
687
- }
688
- async getFormFieldsForSchemaDef(schema, parent, options) {
652
+ async getFormFieldsForSchema(name, parent, customizeOrOptions) {
653
+ const schema = await this.getSchema(name);
689
654
  if (!schema)
690
655
  return [];
656
+ const options = await toWrapOptions(customizeOrOptions, this.injector, schema);
691
657
  const keys = Object.keys(schema.properties || {});
692
658
  const fields = [];
693
659
  // Collect all properties of this schema def
694
660
  for (const key of keys) {
695
661
  const property = schema.properties[key];
696
- const propFields = await this.getFormFieldsForProp(property, options, parent);
662
+ const propFields = await this.getFormFieldsForProp(property, schema, options, parent);
697
663
  fields.push(...propFields);
698
664
  }
699
665
  return this.builder.createFieldSets(fields.filter(f => null !== f), parent, options);
700
666
  }
701
- async getFormFieldsForProp(property, options, parent) {
667
+ async getFormFieldsForProp(property, schema, options, parent) {
702
668
  const field = await this.getFormFieldForProp(property, options, parent);
703
- return !field ? [] : options.customizer(property, options, field);
669
+ return !field ? [] : options.customize(field, property, schema);
704
670
  }
705
671
  async getFormFieldForProp(property, options, parent) {
706
672
  const $enum = property.items?.enum || property.enum;
@@ -754,9 +720,9 @@ class DynamicFormService {
754
720
  }
755
721
  async getFormArrayConfig(property, options, parent) {
756
722
  return this.builder.createFormArray(property.id, async (sp) => {
757
- const subSchemas = findRefs(property).map(ref => this.schemas[ref]);
723
+ const subSchemas = findRefs(property);
758
724
  if (subSchemas.length > 0) {
759
- const subModels = await Promise.all(subSchemas.map(s => this.getFormFieldsForSchemaDef(s, sp, options)));
725
+ const subModels = await Promise.all(subSchemas.map(s => this.getFormFieldsForSchema(s, sp, options)));
760
726
  return mergeFormFields(ObjectUtils.copy(subModels));
761
727
  }
762
728
  return this.getFormFieldForProp(property.items, options, null);
@@ -775,9 +741,9 @@ class DynamicFormService {
775
741
  }, parent, options);
776
742
  }
777
743
  async getFormGroupConfig(property, options, parent) {
778
- const subSchemas = findRefs(property).map(ref => this.schemas[ref]);
779
744
  return this.builder.createFormGroup(property.id, async (sp) => {
780
- const subModels = await Promise.all(subSchemas.map(s => this.getFormFieldsForSchemaDef(s, sp, options)));
745
+ const subSchemas = findRefs(property);
746
+ const subModels = await Promise.all(subSchemas.map(s => this.getFormFieldsForSchema(s, sp, options)));
781
747
  return mergeFormFields(subModels);
782
748
  }, {
783
749
  ...this.getFormFieldData(property, options),
@@ -827,7 +793,7 @@ class DynamicFormService {
827
793
  placeholder: property.placeholder || ""
828
794
  }, parent, options);
829
795
  }
830
- // getFormEditorConfig(property: IOpenApiSchemaProperty, options: ConfigForSchemaWrapOptions): DynamicEditorModelConfig {
796
+ // getFormEditorConfig(property: IOpenApiSchemaProperty, options: ConfigForSchemaWrap): DynamicEditorModelConfig {
831
797
  // const sub = property.type == "array" ? property.items || property : property;
832
798
  // return Object.assign(
833
799
  // this.getFormControlConfig(property, options),
@@ -851,8 +817,8 @@ class DynamicFormService {
851
817
  ...this.getFormFieldData(property, options),
852
818
  type: property.format == "date-time" ? "datetime-local" : "date",
853
819
  // format: property.dateFormat || "dd.MM.yyyy",
854
- min: this.convertToDate(property.min),
855
- max: this.convertToDate(property.max),
820
+ min: convertToDate(property.min),
821
+ max: convertToDate(property.max),
856
822
  }, parent, options);
857
823
  }
858
824
  getFormSelectConfig(property, options, parent) {
@@ -975,14 +941,6 @@ class DynamicFormService {
975
941
  }
976
942
  });
977
943
  }
978
- convertToDate(value) {
979
- if (ObjectUtils.isNullOrUndefined(value))
980
- return null;
981
- const date = ObjectUtils.isDate(value)
982
- ? value
983
- : new Date(value);
984
- return isNaN(date) ? new Date() : date;
985
- }
986
944
  addPropertyValidators(validators, property) {
987
945
  if (!property)
988
946
  return;
@@ -1023,12 +981,155 @@ class DynamicFormService {
1023
981
  validators.itemsMaxValue = maxValueValidation(items.maximum, true);
1024
982
  }
1025
983
  }
1026
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DynamicFormService, deps: [{ token: i2.OpenApiService }, { token: i0.Injector }, { token: DynamicFormBuilderService }], target: i0.ɵɵFactoryTarget.Injectable });
984
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DynamicFormSchemaService, deps: [{ token: i2.OpenApiService }, { token: i0.Injector }, { token: DynamicFormBuilderService }], target: i0.ɵɵFactoryTarget.Injectable });
985
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DynamicFormSchemaService });
986
+ }
987
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DynamicFormSchemaService, decorators: [{
988
+ type: Injectable
989
+ }], ctorParameters: () => [{ type: i2.OpenApiService }, { type: i0.Injector }, { type: DynamicFormBuilderService }] });
990
+
991
+ class DynamicFormService {
992
+ fs;
993
+ injector;
994
+ api;
995
+ constructor(fs, injector, api) {
996
+ this.fs = fs;
997
+ this.injector = injector;
998
+ this.api = api;
999
+ }
1000
+ async getFormFieldsForSchema(name, customizeOrOptions) {
1001
+ const group = await this.getFormFieldGroupBySchemaName(name, customizeOrOptions, "getFormFieldsForSchema");
1002
+ return group.fieldGroup;
1003
+ }
1004
+ async getFormFieldGroupForSchema(name, customizeOrOptions) {
1005
+ return this.getFormFieldGroupBySchemaName(name, customizeOrOptions, "getFormFieldsForSchema");
1006
+ }
1007
+ async getFormFieldGroupBySchemaName(name, customizeOrOptions, restrictedMethod) {
1008
+ const config = {
1009
+ key: FORM_ROOT_KEY,
1010
+ path: "",
1011
+ wrappers: ["form-group"]
1012
+ };
1013
+ const schema = await this.fs.getSchema(name);
1014
+ const wrapOptions = await toWrapOptions(customizeOrOptions, this.injector, schema, `"DynamicFormService.${restrictedMethod}" is called from a customizer, which is not allowed. Please use DynamicFormSchemaService instead!`);
1015
+ const fields = await this.fs.getFormFieldsForSchema(name, config, wrapOptions);
1016
+ const fieldGroup = [...fields];
1017
+ config.fieldGroup = fieldGroup;
1018
+ // There are no actual fields in the schema, or no schema exists
1019
+ if (fields.length === 0)
1020
+ return config;
1021
+ // Add id fields if necessary
1022
+ const idFields = [
1023
+ { key: "id", props: { hidden: true } },
1024
+ { key: "_id", props: { hidden: true } },
1025
+ ];
1026
+ fieldGroup.unshift(...idFields
1027
+ .filter(t => !fields.some(c => c.key == t.key)));
1028
+ const root = await wrapOptions.customize(config, {
1029
+ id: FORM_ROOT_KEY,
1030
+ type: "object",
1031
+ properties: schema?.properties || {}
1032
+ }, schema);
1033
+ // Check if the customized root wrapper returned an array
1034
+ fields.length = 0;
1035
+ for (const model of root) {
1036
+ if (model.key === FORM_ROOT_KEY) {
1037
+ return model;
1038
+ }
1039
+ else {
1040
+ fields.push(model);
1041
+ }
1042
+ }
1043
+ return {
1044
+ ...config,
1045
+ fieldGroup: fields
1046
+ };
1047
+ }
1048
+ async validateForm(form, showErrors = true) {
1049
+ const group = form.group();
1050
+ if (!group)
1051
+ return Promise.resolve();
1052
+ return new Promise((resolve, reject) => {
1053
+ group.statusChanges
1054
+ .pipe(first(status => status == "VALID" || status == "INVALID"))
1055
+ .subscribe(status => {
1056
+ if (showErrors) {
1057
+ this.showErrorsForGroup(group);
1058
+ }
1059
+ if (status == "VALID") {
1060
+ resolve(null);
1061
+ return;
1062
+ }
1063
+ reject(getFormValidationErrors(group.controls));
1064
+ });
1065
+ group.updateValueAndValidity();
1066
+ });
1067
+ }
1068
+ async serializeForm(form, validate = true) {
1069
+ const fields = form.config();
1070
+ if (!fields)
1071
+ return null;
1072
+ if (validate) {
1073
+ await this.validateForm(form);
1074
+ }
1075
+ return this.serialize(fields);
1076
+ }
1077
+ async serialize(fields) {
1078
+ const result = {};
1079
+ if (!fields)
1080
+ return result;
1081
+ for (const field of fields) {
1082
+ const serializer = field.serializer;
1083
+ const key = `${field.key}`;
1084
+ if (ObjectUtils.isFunction(serializer)) {
1085
+ result[key] = await serializer(field, this.injector);
1086
+ continue;
1087
+ }
1088
+ if (field.hide && !field.serialize) {
1089
+ continue;
1090
+ }
1091
+ const control = field.formControl;
1092
+ if (field.fieldGroup) {
1093
+ const group = await this.serialize(field.fieldGroup);
1094
+ if (field.key) {
1095
+ result[key] = !field.fieldArray ? group : Object.values(group);
1096
+ continue;
1097
+ }
1098
+ Object.assign(result, group);
1099
+ continue;
1100
+ }
1101
+ result[key] = control.value;
1102
+ }
1103
+ return result;
1104
+ }
1105
+ showErrorsForGroup(formGroup) {
1106
+ if (!formGroup)
1107
+ return;
1108
+ formGroup.markAsTouched({ onlySelf: true });
1109
+ const controls = Object.keys(formGroup.controls).map(id => formGroup.controls[id]);
1110
+ this.showErrorsForControls(controls);
1111
+ }
1112
+ showErrorsForControls(controls) {
1113
+ controls.forEach(control => {
1114
+ if (control instanceof FormGroup$1) {
1115
+ this.showErrorsForGroup(control);
1116
+ return;
1117
+ }
1118
+ control.markAsTouched({ onlySelf: true });
1119
+ if (control instanceof FormArray$1) {
1120
+ this.showErrorsForControls(control.controls);
1121
+ }
1122
+ });
1123
+ }
1124
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DynamicFormService, deps: [{ token: DynamicFormSchemaService }, { token: i0.Injector }, { token: API_SERVICE }], target: i0.ɵɵFactoryTarget.Injectable });
1027
1125
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DynamicFormService });
1028
1126
  }
1029
1127
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DynamicFormService, decorators: [{
1030
1128
  type: Injectable
1031
- }], ctorParameters: () => [{ type: i2.OpenApiService }, { type: i0.Injector }, { type: DynamicFormBuilderService }] });
1129
+ }], ctorParameters: () => [{ type: DynamicFormSchemaService }, { type: i0.Injector }, { type: undefined, decorators: [{
1130
+ type: Inject,
1131
+ args: [API_SERVICE]
1132
+ }] }] });
1032
1133
 
1033
1134
  class AsyncSubmitDirective {
1034
1135
  method = input(null, { alias: "async-submit" });
@@ -1132,6 +1233,7 @@ class DynamicFormComponent {
1132
1233
  events = inject(EventsService);
1133
1234
  languages = inject(LANGUAGE_SERVICE);
1134
1235
  labelPrefix = input("label");
1236
+ labelCustomizer = input(null);
1135
1237
  testId = input("");
1136
1238
  data = input({});
1137
1239
  fields = input(null);
@@ -1142,6 +1244,7 @@ class DynamicFormComponent {
1142
1244
  config = computed(() => {
1143
1245
  return this.fields() || this.builder.resolveFormFields(this.data()?.constructor, null, {
1144
1246
  labelPrefix: this.labelPrefix(),
1247
+ labelCustomizer: this.labelCustomizer(),
1145
1248
  testId: this.testId(),
1146
1249
  });
1147
1250
  });
@@ -1180,7 +1283,7 @@ class DynamicFormComponent {
1180
1283
  this.options?.resetModel?.();
1181
1284
  }
1182
1285
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DynamicFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1183
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: DynamicFormComponent, isStandalone: false, selector: "dynamic-form", inputs: { labelPrefix: { classPropertyName: "labelPrefix", publicName: "labelPrefix", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSubmit: "onSubmit" }, ngImport: i0, template: "@if (config() && group()) {\n <form [formGroup]=\"group()\" (ngSubmit)=\"submit()\" autocomplete=\"off\" role=\"presentation\">\n <input type=\"submit\" [hidden]=\"true\" />\n <formly-form [model]=\"data()\"\n [fields]=\"config()\"\n [form]=\"group()\"\n [options]=\"options\"></formly-form>\n <ng-content></ng-content>\n </form>\n}\n", dependencies: [{ kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i3.FormlyForm, selector: "formly-form", inputs: ["form", "model", "fields", "options"], outputs: ["modelChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1286
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: DynamicFormComponent, isStandalone: false, selector: "dynamic-form", inputs: { labelPrefix: { classPropertyName: "labelPrefix", publicName: "labelPrefix", isSignal: true, isRequired: false, transformFunction: null }, labelCustomizer: { classPropertyName: "labelCustomizer", publicName: "labelCustomizer", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSubmit: "onSubmit" }, ngImport: i0, template: "@if (config() && group()) {\n <form [formGroup]=\"group()\" (ngSubmit)=\"submit()\" autocomplete=\"off\" role=\"presentation\">\n <input type=\"submit\" [hidden]=\"true\" />\n <formly-form [model]=\"data()\"\n [fields]=\"config()\"\n [form]=\"group()\"\n [options]=\"options\"></formly-form>\n <ng-content></ng-content>\n </form>\n}\n", dependencies: [{ kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i3.FormlyForm, selector: "formly-form", inputs: ["form", "model", "fields", "options"], outputs: ["modelChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1184
1287
  }
1185
1288
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DynamicFormComponent, decorators: [{
1186
1289
  type: Component,
@@ -1294,7 +1397,8 @@ class NgxDynamicFormModule {
1294
1397
  return [
1295
1398
  ...providers,
1296
1399
  DynamicFormService,
1297
- DynamicFormBuilderService
1400
+ DynamicFormBuilderService,
1401
+ DynamicFormSchemaService
1298
1402
  ];
1299
1403
  }
1300
1404
  static forRoot(config) {
@@ -1360,5 +1464,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
1360
1464
  * Generated bundle index. Do not edit.
1361
1465
  */
1362
1466
 
1363
- export { AsyncSubmitDirective, DynamicFormArrayComponent, DynamicFormBuilderService, DynamicFormChipsComponent, DynamicFormComponent, DynamicFormFieldComponent, DynamicFormFieldsetComponent, DynamicFormGroupComponent, DynamicFormService, DynamicFormUploadComponent, EDITOR_FORMATS, FORM_ROOT_KEY, FormFile, FormGroup, FormInput, FormModel, FormSelect, FormSerializable, FormUpload, MAX_INPUT_NUM, MIN_INPUT_NUM, NgxDynamicFormModule, additionalFieldValues, customizeFormField, emailValidation, getFieldByPath, jsonValidation, maxLengthValidation, maxValueValidation, mergeFormFields, minLengthValidation, minValueValidation, phoneValidation, replaceSpecialChars, requiredValidation, setFieldDisabled, setFieldHidden, translationValidation, validationMessage };
1467
+ export { AsyncSubmitDirective, DynamicFormArrayComponent, DynamicFormBuilderService, DynamicFormChipsComponent, DynamicFormComponent, DynamicFormFieldComponent, DynamicFormFieldsetComponent, DynamicFormGroupComponent, DynamicFormSchemaService, DynamicFormService, DynamicFormUploadComponent, EDITOR_FORMATS, FORM_ROOT_KEY, FormFile, FormGroup, FormInput, FormModel, FormSelect, FormSerializable, FormUpload, MAX_INPUT_NUM, MIN_INPUT_NUM, NgxDynamicFormModule, additionalFieldValues, customizeFormField, emailValidation, getFieldByPath, getFieldsByKey, jsonValidation, maxLengthValidation, maxValueValidation, minLengthValidation, minValueValidation, phoneValidation, replaceSpecialChars, requiredValidation, setFieldDisabled, setFieldHidden, translationValidation, validationMessage };
1364
1468
  //# sourceMappingURL=stemy-ngx-dynamic-form.mjs.map