@decaf-ts/for-angular 0.0.22 → 0.0.24

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.
Files changed (46) hide show
  1. package/components/component-renderer/component-renderer.component.d.ts +3 -2
  2. package/components/crud-field/crud-field.component.d.ts +4 -2
  3. package/components/fieldset/fieldset.component.d.ts +10 -1
  4. package/components/for-angular-components.module.d.ts +3 -2
  5. package/components/index.d.ts +1 -1
  6. package/components/layout/layout.component.d.ts +1 -24
  7. package/components/list/list.component.d.ts +1 -2
  8. package/components/model-renderer/model-renderer.component.d.ts +6 -1
  9. package/components/steped-form/steped-form.component.d.ts +243 -0
  10. package/engine/NgxBaseComponent.d.ts +3 -3
  11. package/engine/NgxCrudFormField.d.ts +1 -0
  12. package/engine/NgxFormService.d.ts +381 -48
  13. package/engine/NgxRenderingEngine.d.ts +4 -2
  14. package/engine/constants.d.ts +12 -0
  15. package/engine/interfaces.d.ts +1 -1
  16. package/engine/types.d.ts +17 -3
  17. package/esm2022/components/component-renderer/component-renderer.component.mjs +10 -4
  18. package/esm2022/components/crud-field/crud-field.component.mjs +14 -3
  19. package/esm2022/components/crud-form/crud-form.component.mjs +3 -3
  20. package/esm2022/components/empty-state/empty-state.component.mjs +2 -2
  21. package/esm2022/components/fieldset/fieldset.component.mjs +6 -4
  22. package/esm2022/components/for-angular-components.module.mjs +10 -5
  23. package/esm2022/components/index.mjs +2 -2
  24. package/esm2022/components/layout/layout.component.mjs +4 -29
  25. package/esm2022/components/list/list.component.mjs +5 -4
  26. package/esm2022/components/model-renderer/model-renderer.component.mjs +10 -3
  27. package/esm2022/components/steped-form/steped-form.component.mjs +291 -0
  28. package/esm2022/engine/NgxBaseComponent.mjs +12 -12
  29. package/esm2022/engine/NgxCrudFormField.mjs +19 -17
  30. package/esm2022/engine/NgxFormService.mjs +438 -57
  31. package/esm2022/engine/NgxRenderingEngine.mjs +21 -10
  32. package/esm2022/engine/ValidatorFactory.mjs +4 -4
  33. package/esm2022/engine/constants.mjs +6 -1
  34. package/esm2022/engine/interfaces.mjs +1 -1
  35. package/esm2022/engine/types.mjs +1 -1
  36. package/esm2022/for-angular-common.module.mjs +4 -2
  37. package/esm2022/i18n/Loader.mjs +4 -4
  38. package/fesm2022/decaf-ts-for-angular.mjs +830 -153
  39. package/fesm2022/decaf-ts-for-angular.mjs.map +1 -1
  40. package/for-angular-common.module.d.ts +8 -2
  41. package/i18n/Loader.d.ts +2 -7
  42. package/package.json +9 -9
  43. package/assets/i18n/en.json +0 -80
  44. package/assets/i18n/pt.json +0 -80
  45. package/components/list/constants.d.ts +0 -25
  46. package/esm2022/components/list/constants.mjs +0 -6
@@ -1,6 +1,6 @@
1
- import { UIKeys, parseValueByType, HTML5InputTypes, HTML5CheckTypes, escapeHtml, parseToNumber, RenderingEngine, RenderingError } from '@decaf-ts/ui-decorators';
1
+ import { UIKeys, parseValueByType, HTML5CheckTypes, escapeHtml, HTML5InputTypes, parseToNumber, RenderingEngine, RenderingError } from '@decaf-ts/ui-decorators';
2
2
  import * as i0 from '@angular/core';
3
- import { NgModule, isDevMode, reflectComponentType, inject, EnvironmentInjector, EventEmitter, ViewContainerRef, TemplateRef, ViewChild, Input, Output, Component, InjectionToken, ElementRef, HostListener, CUSTOM_ELEMENTS_SCHEMA, Inject, ChangeDetectorRef, Renderer2, Injector, Directive } from '@angular/core';
3
+ import { InjectionToken, NgModule, isDevMode, reflectComponentType, inject, EnvironmentInjector, EventEmitter, ViewContainerRef, TemplateRef, ViewChild, Input, Output, Component, ElementRef, HostListener, CUSTOM_ELEMENTS_SCHEMA, Inject, ChangeDetectorRef, Renderer2, Injector, Directive } from '@angular/core';
4
4
  import { CommonModule, NgComponentOutlet, Location } from '@angular/common';
5
5
  import { VALIDATION_PARENT_KEY, ValidationKeys, DEFAULT_PATTERNS, Validation, ComparisonValidationKeys, PathProxyEngine, Primitives, ModelKeys, isValidDate as isValidDate$1, parseDate, sf, Model } from '@decaf-ts/decorator-validation';
6
6
  import { OperationKeys, InternalError } from '@decaf-ts/db-decorators';
@@ -14,12 +14,11 @@ import { apply, metadata } from '@decaf-ts/reflection';
14
14
  import { IonInput, IonItem, IonCheckbox, IonRadioGroup, IonRadio, IonSelect, IonSelectOption, IonLabel, IonText, IonTextarea, IonIcon, IonButton, IonCard, IonCardContent, IonAccordionGroup, IonAccordion, IonList, IonReorder, IonReorderGroup, IonSearchbar, IonChip, IonListHeader, IonItemSliding, IonItemOptions, IonItemOption, IonContent, IonPopover, IonRefresher, IonThumbnail, IonSkeletonText, IonRefresherContent, IonInfiniteScroll, IonInfiniteScrollContent, IonLoading } from '@ionic/angular/standalone';
15
15
  import { addIcons } from 'ionicons';
16
16
  import * as allIcons from 'ionicons/icons';
17
- import { chevronUpOutline, chevronDownOutline, createOutline, alertCircleOutline, chevronForwardOutline, chevronBackOutline } from 'ionicons/icons';
17
+ import { chevronUpOutline, chevronDownOutline, createOutline, alertCircleOutline, chevronForwardOutline, chevronBackOutline, arrowBackOutline, arrowForwardOutline } from 'ionicons/icons';
18
18
  import { TranslateHttpLoader } from '@ngx-translate/http-loader';
19
- import { forkJoin, fromEvent, debounceTime, Subject } from 'rxjs';
19
+ import { forkJoin, fromEvent, debounceTime, Subject, timer } from 'rxjs';
20
20
  import { map } from 'rxjs/operators';
21
21
  import { Repository, OrderDirection, Condition } from '@decaf-ts/core';
22
- import { RamAdapter } from '@decaf-ts/core/ram';
23
22
  import { DomSanitizer } from '@angular/platform-browser';
24
23
  import { Router } from '@angular/router';
25
24
  import { NavController } from '@ionic/angular';
@@ -186,6 +185,11 @@ var BaseComponentProps;
186
185
  BaseComponentProps["MAPPER"] = "mapper";
187
186
  BaseComponentProps["INITIALIZED"] = "initialized";
188
187
  })(BaseComponentProps || (BaseComponentProps = {}));
188
+ var ListComponentsTypes;
189
+ (function (ListComponentsTypes) {
190
+ ListComponentsTypes["INFINITE"] = "infinite";
191
+ ListComponentsTypes["PAGINATED"] = "paginated";
192
+ })(ListComponentsTypes || (ListComponentsTypes = {}));
189
193
 
190
194
  /**
191
195
  *
@@ -222,12 +226,12 @@ class ValidatorFactory {
222
226
  if (!Validation.keys().includes(key))
223
227
  throw new Error('Unsupported custom validation');
224
228
  const validatorFn = (control) => {
225
- const { name, type } = fieldProps;
229
+ const { type } = fieldProps;
226
230
  const { validatorKey, props } = resolveValidatorKeyProps(key, fieldProps[key], type);
227
231
  const validator = Validation.get(validatorKey);
228
232
  // parseValueByType does not support undefined values
229
233
  const value = typeof control.value !== 'undefined'
230
- ? parseValueByType(type, type === HTML5InputTypes.CHECKBOX ? name : control.value, fieldProps)
234
+ ? parseValueByType(type, control.value, fieldProps)
231
235
  : undefined;
232
236
  // Create a proxy to enable access to parent and child values
233
237
  let proxy = ValidatorFactory.createProxy({});
@@ -288,6 +292,8 @@ class ValidatorFactory {
288
292
  }
289
293
  }
290
294
 
295
+ const DB_ADAPTER_PROVIDER_TOKEN = new InjectionToken('DB_ADAPTER_PROVIDER');
296
+ const I18N_CONFIG_TOKEN = new InjectionToken('I18N_CONFIG_TOKEN');
291
297
  const ComponentsAndModules = [
292
298
  CommonModule,
293
299
  FormsModule,
@@ -869,12 +875,61 @@ class NgxFormService {
869
875
  * @memberOf NgxFormService
870
876
  */
871
877
  static { this.formRegistry = new Map(); }
878
+ static { this.pageMapper = {}; }
879
+ /**
880
+ * @description Creates a new form group or form array with the specified identifier.
881
+ * @summary Generates a FormGroup or FormArray based on the provided properties. If pages are specified
882
+ * and greater than 1, creates a FormArray; otherwise creates a FormGroup. The form can optionally
883
+ * be registered in the global form registry for later access throughout the application.
884
+ *
885
+ * @param {string} id - Unique identifier for the form
886
+ * @param {Partial<IComponentInput>} [props={}] - Configuration properties for the form
887
+ * @param {boolean} [registry=true] - Whether to register the form in the global registry
888
+ * @return {FormGroup | FormArray} The created form instance
889
+ *
890
+ * @mermaid
891
+ * sequenceDiagram
892
+ * participant C as Component
893
+ * participant NFS as NgxFormService
894
+ * participant FR as Form Registry
895
+ * participant AF as Angular Forms
896
+ *
897
+ * C->>NFS: createForm(id, props, registry)
898
+ * NFS->>FR: Check if form exists
899
+ * alt Form doesn't exist
900
+ * alt props.pages > 1
901
+ * NFS->>AF: new FormArray([])
902
+ * else
903
+ * NFS->>AF: new FormGroup({})
904
+ * end
905
+ * alt registry is true
906
+ * NFS->>FR: addRegistry(id, form)
907
+ * end
908
+ * end
909
+ * NFS-->>C: Return FormGroup | FormArray
910
+ *
911
+ * @static
912
+ * @memberOf NgxFormService
913
+ */
914
+ static createForm(id, props = {}, registry = true) {
915
+ const form = this.formRegistry.get(id) ?? (props?.pages && props?.pages > 1 ? new FormArray([]) : new FormGroup({}));
916
+ if (!this.formRegistry.has(id) && registry)
917
+ this.addRegistry(id, form);
918
+ return form;
919
+ }
872
920
  /**
873
921
  * @description Adds a form to the registry.
874
- * @summary Registers a FormGroup with a unique identifier. Throws an error if the identifier is already in use.
875
- * @param {string} formId - The unique identifier for the form.
876
- * @param {FormGroup} formGroup - The FormGroup to be registered.
877
- * @throws {Error} If a FormGroup with the given id is already registered.
922
+ * @summary Registers a FormGroup or FormArray with a unique identifier for global access throughout
923
+ * the application. This allows forms to be retrieved and managed centrally. Throws an error if
924
+ * the identifier is already in use to prevent conflicts.
925
+ *
926
+ * @param {string} formId - The unique identifier for the form
927
+ * @param {FormParent} formGroup - The FormGroup or FormArray to be registered
928
+ * @return {void}
929
+ * @throws {Error} If a FormGroup with the given id is already registered
930
+ *
931
+ * @static
932
+ * @memberOf NgxFormService
878
933
  */
879
934
  static addRegistry(formId, formGroup) {
880
935
  if (this.formRegistry.has(formId))
@@ -883,8 +938,15 @@ class NgxFormService {
883
938
  }
884
939
  /**
885
940
  * @description Removes a form from the registry.
886
- * @summary Deletes a FormGroup from the registry using its unique identifier.
887
- * @param {string} formId - The unique identifier of the form to be removed.
941
+ * @summary Deletes a FormGroup or FormArray from the registry using its unique identifier.
942
+ * This cleans up the registry and allows the identifier to be reused. The form itself
943
+ * is not destroyed, only removed from the central registry.
944
+ *
945
+ * @param {string} formId - The unique identifier of the form to be removed
946
+ * @return {void}
947
+ *
948
+ * @static
949
+ * @memberOf NgxFormService
888
950
  */
889
951
  static removeRegistry(formId) {
890
952
  this.formRegistry.delete(formId);
@@ -892,9 +954,38 @@ class NgxFormService {
892
954
  /**
893
955
  * @description Resolves the parent group and control name from a path.
894
956
  * @summary Traverses the form group structure to find the parent group and control name for a given path.
895
- * @param {FormGroup} formGroup - The root FormGroup.
896
- * @param {string} path - The path to the control.
897
- * @return {FormParentGroup} A tuple containing the parent FormGroup and the control name.
957
+ * Handles complex nested structures including arrays and sub-groups. Creates missing intermediate
958
+ * groups as needed and properly configures FormArray controls for multiple value scenarios.
959
+ *
960
+ * @param {FormGroup} formGroup - The root FormGroup to traverse
961
+ * @param {string} path - The dot-separated path to the control (e.g., 'user.address.street')
962
+ * @param {IComponentInput} componentProps - Properties defining the component configuration
963
+ * @param {KeyValue} parentProps - Properties from the parent component for context
964
+ * @return {FormParentGroup} A tuple containing the parent FormGroup and the control name
965
+ *
966
+ * @private
967
+ * @mermaid
968
+ * sequenceDiagram
969
+ * participant NFS as NgxFormService
970
+ * participant FG as FormGroup
971
+ * participant FA as FormArray
972
+ *
973
+ * NFS->>NFS: Split path into parts
974
+ * loop For each path part
975
+ * alt Control doesn't exist
976
+ * alt isMultiple and part is childOf
977
+ * NFS->>FA: new FormArray([new FormGroup({})])
978
+ * else
979
+ * NFS->>FG: new FormGroup({})
980
+ * end
981
+ * NFS->>FG: addControl(part, newControl)
982
+ * end
983
+ * NFS->>NFS: Navigate to next level
984
+ * end
985
+ * NFS-->>NFS: Return [parentGroup, controlName]
986
+ *
987
+ * @static
988
+ * @memberOf NgxFormService
898
989
  */
899
990
  static resolveParentGroup(formGroup, path, componentProps, parentProps) {
900
991
  const isMultiple = parentProps?.['multiple'] || parentProps?.['type'] === 'Array' || false;
@@ -1073,19 +1164,62 @@ class NgxFormService {
1073
1164
  if (!parentGroup.get(controlName)) {
1074
1165
  const control = NgxFormService.fromProps(componentProps, componentProps.updateMode || 'change');
1075
1166
  NgxFormService.register(control, componentProps);
1076
- parentGroup.addControl(controlName, control);
1167
+ if (parentGroup instanceof FormGroup) {
1168
+ parentGroup.addControl(controlName, control);
1169
+ }
1170
+ if (parentGroup instanceof FormArray) {
1171
+ const root = parentGroup.controls[componentProps?.['page'] - 1];
1172
+ if (root) {
1173
+ root.addControl(controlName, control);
1174
+ }
1175
+ else {
1176
+ parentGroup.push({ [controlName]: control });
1177
+ }
1178
+ }
1077
1179
  }
1078
- componentProps['formGroup'] = parentGroup;
1180
+ const root = parentGroup instanceof FormArray ? parentGroup.controls[componentProps?.['page'] - 1] : parentGroup;
1181
+ componentProps['formGroup'] = root;
1079
1182
  componentProps['formControl'] = parentGroup.get(controlName);
1080
1183
  componentProps['multiple'] = isMultiple;
1081
1184
  }
1082
1185
  /**
1083
1186
  * @description Retrieves a control from a registered form.
1084
1187
  * @summary Finds and returns an AbstractControl from a registered form using the form id and optional path.
1085
- * @param {string} formId - The unique identifier of the form.
1086
- * @param {string} [path] - The path to the control within the form.
1087
- * @return {AbstractControl} The requested AbstractControl.
1088
- * @throws {Error} If the form is not found in the registry or the control is not found in the form.
1188
+ * This method provides centralized access to form controls across the application by leveraging
1189
+ * the form registry system.
1190
+ *
1191
+ * @param {string} formId - The unique identifier of the form in the registry
1192
+ * @param {string} [path] - The optional dot-separated path to a specific control within the form
1193
+ * @return {AbstractControl} The requested AbstractControl (FormGroup, FormArray, or FormControl)
1194
+ * @throws {Error} If the form is not found in the registry or the control is not found in the form
1195
+ *
1196
+ * @mermaid
1197
+ * sequenceDiagram
1198
+ * participant C as Component
1199
+ * participant NFS as NgxFormService
1200
+ * participant FR as Form Registry
1201
+ *
1202
+ * C->>NFS: getControlFromForm(formId, path?)
1203
+ * NFS->>FR: Get form by formId
1204
+ * alt Form not found
1205
+ * FR-->>NFS: null
1206
+ * NFS-->>C: Throw Error
1207
+ * else Form found
1208
+ * FR-->>NFS: Return form
1209
+ * alt path provided
1210
+ * NFS->>NFS: form.get(path)
1211
+ * alt Control not found
1212
+ * NFS-->>C: Throw Error
1213
+ * else
1214
+ * NFS-->>C: Return control
1215
+ * end
1216
+ * else
1217
+ * NFS-->>C: Return form
1218
+ * end
1219
+ * end
1220
+ *
1221
+ * @static
1222
+ * @memberOf NgxFormService
1089
1223
  */
1090
1224
  static getControlFromForm(formId, path) {
1091
1225
  const form = this.formRegistry.get(formId);
@@ -1098,13 +1232,77 @@ class NgxFormService {
1098
1232
  throw new Error(`Control with path '${path}' not found in form '${formId}'.`);
1099
1233
  return control;
1100
1234
  }
1235
+ /**
1236
+ * @description Creates a form from UI model metadata children.
1237
+ * @summary Generates a FormGroup from an array of UIModelMetadata objects, extracting component
1238
+ * properties and creating appropriate form controls. This method is specifically designed to work
1239
+ * with the UI decorator system and provides automatic form generation from metadata.
1240
+ *
1241
+ * @param {string} id - Unique identifier for the form
1242
+ * @param {boolean} [registry=false] - Whether to register the created form in the global registry
1243
+ * @param {UIModelMetadata[]} [children] - Array of UI model metadata objects to create controls from
1244
+ * @return {FormGroup} The created FormGroup with controls for each child metadata
1245
+ *
1246
+ * @mermaid
1247
+ * sequenceDiagram
1248
+ * participant C as Component
1249
+ * participant NFS as NgxFormService
1250
+ * participant AF as Angular Forms
1251
+ *
1252
+ * C->>NFS: createFormFromChildren(id, registry, children)
1253
+ * NFS->>AF: new FormGroup({})
1254
+ * loop For each child metadata
1255
+ * NFS->>NFS: addFormControl(form, child.props)
1256
+ * NFS->>AF: Create and add FormControl
1257
+ * end
1258
+ * alt registry is true
1259
+ * NFS->>NFS: addRegistry(id, form)
1260
+ * end
1261
+ * NFS-->>C: Return FormGroup
1262
+ *
1263
+ * @static
1264
+ * @memberOf NgxFormService
1265
+ */
1266
+ static createFormFromChildren(id, registry = false, children) {
1267
+ const form = new FormGroup({});
1268
+ if (children?.length)
1269
+ children.forEach(child => {
1270
+ this.addFormControl(form, child.props);
1271
+ });
1272
+ if (registry)
1273
+ this.addRegistry(id, form);
1274
+ return form;
1275
+ }
1101
1276
  /**
1102
1277
  * @description Creates a form from component configurations.
1103
1278
  * @summary Generates a FormGroup based on an array of component configurations and optionally registers it.
1104
- * @param {string} id - The unique identifier for the form.
1105
- * @param {IComponentConfig[]} components - An array of component configurations.
1106
- * @param {boolean} [registry=false] - Whether to register the created form.
1107
- * @return {FormGroup} The created FormGroup.
1279
+ * This method processes component input configurations to create appropriate form controls with
1280
+ * validation and initial values.
1281
+ *
1282
+ * @param {string} id - The unique identifier for the form
1283
+ * @param {IComponentConfig[]} components - An array of component configurations defining the form structure
1284
+ * @param {boolean} [registry=false] - Whether to register the created form in the global registry
1285
+ * @return {FormGroup} The created FormGroup with controls for each component configuration
1286
+ *
1287
+ * @mermaid
1288
+ * sequenceDiagram
1289
+ * participant C as Component
1290
+ * participant NFS as NgxFormService
1291
+ * participant AF as Angular Forms
1292
+ *
1293
+ * C->>NFS: createFormFromComponents(id, components, registry)
1294
+ * NFS->>AF: new FormGroup({})
1295
+ * loop For each component config
1296
+ * NFS->>NFS: addFormControl(form, component.inputs)
1297
+ * NFS->>AF: Create and add FormControl
1298
+ * end
1299
+ * alt registry is true
1300
+ * NFS->>NFS: addRegistry(id, form)
1301
+ * end
1302
+ * NFS-->>C: Return FormGroup
1303
+ *
1304
+ * @static
1305
+ * @memberOf NgxFormService
1108
1306
  */
1109
1307
  static createFormFromComponents(id, components, registry = false) {
1110
1308
  const form = new FormGroup({});
@@ -1118,14 +1316,61 @@ class NgxFormService {
1118
1316
  /**
1119
1317
  * @description Adds a control to a form based on component properties.
1120
1318
  * @summary Creates and adds a form control to a form (existing or new) based on the provided component properties.
1121
- * @param {string} id - The unique identifier of the form.
1122
- * @param {FieldProperties} componentProperties - The properties of the component to create the control from.
1123
- * @return {AbstractControl} The form or created control.
1319
+ * Handles multi-page forms by managing FormArray structures and proper indexing. This method supports
1320
+ * complex form scenarios including nested controls and page-based form organization.
1321
+ *
1322
+ * @param {string} id - The unique identifier of the form
1323
+ * @param {FieldProperties} componentProperties - The properties of the component to create the control from
1324
+ * @param {FieldProperties} [parentProps] - Optional parent properties for context and configuration
1325
+ * @return {AbstractControl} The form or created control
1326
+ *
1327
+ * @mermaid
1328
+ * sequenceDiagram
1329
+ * participant C as Component
1330
+ * participant NFS as NgxFormService
1331
+ * participant F as Form
1332
+ *
1333
+ * C->>NFS: addControlFromProps(id, componentProps, parentProps?)
1334
+ * NFS->>NFS: createForm(id, parentProps, true)
1335
+ * alt Multi-page form (parentProps.pages > 1)
1336
+ * NFS->>NFS: Calculate page index
1337
+ * NFS->>F: Get or create FormGroup at index
1338
+ * NFS->>NFS: Set form to page FormGroup
1339
+ * end
1340
+ * alt componentProperties has path
1341
+ * NFS->>NFS: addFormControl(form, componentProperties, parentProps)
1342
+ * end
1343
+ * NFS-->>C: Return form/control
1344
+ *
1345
+ * @static
1346
+ * @memberOf NgxFormService
1124
1347
  */
1125
1348
  static addControlFromProps(id, componentProperties, parentProps) {
1126
- const form = this.formRegistry.get(id) ?? new FormGroup({});
1127
- if (!this.formRegistry.has(id))
1128
- this.addRegistry(id, form);
1349
+ let form = this.createForm(id, parentProps, true);
1350
+ const formLength = form.length;
1351
+ if (parentProps?.pages && parentProps?.pages > 1) {
1352
+ let index = componentProperties.page || parentProps.page;
1353
+ if (!(typeof index === 'number') || index === 0)
1354
+ throw Error(`Property 'page' is required and greather than 0 on ${componentProperties.name}`);
1355
+ if (index > formLength) {
1356
+ if (form?.['lastIndex'] && index === form['lastIndex']['page']) {
1357
+ index = form['lastIndex']['index'];
1358
+ }
1359
+ else {
1360
+ form['lastIndex'] = {
1361
+ page: index,
1362
+ index: formLength + 1
1363
+ };
1364
+ index = formLength + 1;
1365
+ }
1366
+ }
1367
+ let group = form.controls[index - 1];
1368
+ if (!group) {
1369
+ group = new FormGroup({});
1370
+ form.insert(index, group);
1371
+ }
1372
+ form = group;
1373
+ }
1129
1374
  if (componentProperties.path)
1130
1375
  this.addFormControl(form, componentProperties, parentProps);
1131
1376
  return form;
@@ -1133,8 +1378,45 @@ class NgxFormService {
1133
1378
  /**
1134
1379
  * @description Retrieves form data from a FormGroup.
1135
1380
  * @summary Extracts and processes the data from a FormGroup, handling different input types and nested form groups.
1136
- * @param {FormGroup} formGroup - The FormGroup to extract data from.
1137
- * @return {Record<string, unknown>} An object containing the form data.
1381
+ * Performs type conversion for various HTML5 input types, validates nested controls, and manages
1382
+ * multiple control scenarios. Automatically enables all group controls after data extraction.
1383
+ *
1384
+ * @param {FormGroup} formGroup - The FormGroup to extract data from
1385
+ * @return {Record<string, unknown>} An object containing the processed form data with proper type conversions
1386
+ *
1387
+ * @mermaid
1388
+ * sequenceDiagram
1389
+ * participant C as Component
1390
+ * participant NFS as NgxFormService
1391
+ * participant FG as FormGroup
1392
+ * participant FC as FormControl
1393
+ *
1394
+ * C->>NFS: getFormData(formGroup)
1395
+ * loop For each control in formGroup
1396
+ * alt Control is not FormControl
1397
+ * NFS->>NFS: Recursive getFormData(control)
1398
+ * alt parentProps.multiple and !isValid
1399
+ * NFS->>NFS: reset(control)
1400
+ * end
1401
+ * else Control is FormControl
1402
+ * NFS->>FC: Get control value
1403
+ * NFS->>NFS: Apply type conversion based on props.type
1404
+ * alt HTML5CheckTypes
1405
+ * NFS->>NFS: Keep boolean value
1406
+ * else NUMBER type
1407
+ * NFS->>NFS: parseToNumber(value)
1408
+ * else DATE/DATETIME types
1409
+ * NFS->>NFS: new Date(value)
1410
+ * else Other types
1411
+ * NFS->>NFS: escapeHtml(value)
1412
+ * end
1413
+ * end
1414
+ * end
1415
+ * NFS->>NFS: enableAllGroupControls(formGroup)
1416
+ * NFS-->>C: Return processed data object
1417
+ *
1418
+ * @static
1419
+ * @memberOf NgxFormService
1138
1420
  */
1139
1421
  static getFormData(formGroup) {
1140
1422
  const data = {};
@@ -1179,10 +1461,51 @@ class NgxFormService {
1179
1461
  /**
1180
1462
  * @description Validates fields in a form control or form group.
1181
1463
  * @summary Recursively validates all fields in a form control or form group, marking them as touched and dirty.
1182
- * @param {AbstractControl} control - The control or form group to validate.
1183
- * @param {string} [path] - The path to the control within the form.
1184
- * @return {boolean} True if all fields are valid, false otherwise.
1185
- * @throws {Error} If no control is found at the specified path or if the control type is unknown.
1464
+ * Performs comprehensive validation including uniqueness checks for primary keys in FormArray scenarios.
1465
+ * This method ensures all validation rules are applied and form state is properly updated.
1466
+ *
1467
+ * @param {AbstractControl} control - The control or form group to validate
1468
+ * @param {string} [pk] - Optional primary key field name for uniqueness validation
1469
+ * @param {string} [path] - The path to the control within the form for error reporting
1470
+ * @return {boolean} True if all fields are valid, false otherwise
1471
+ * @throws {Error} If no control is found at the specified path or if the control type is unknown
1472
+ *
1473
+ * @mermaid
1474
+ * sequenceDiagram
1475
+ * participant C as Component
1476
+ * participant NFS as NgxFormService
1477
+ * participant FC as FormControl
1478
+ * participant FG as FormGroup
1479
+ * participant FA as FormArray
1480
+ *
1481
+ * C->>NFS: validateFields(control, pk?, path?)
1482
+ * alt Control is FormControl
1483
+ * NFS->>FC: markAsTouched()
1484
+ * NFS->>FC: markAsDirty()
1485
+ * NFS->>FC: updateValueAndValidity()
1486
+ * alt Is in FormArray group
1487
+ * NFS->>NFS: Check uniqueness in group
1488
+ * alt Not unique
1489
+ * NFS->>FC: setErrors({notUnique: true})
1490
+ * end
1491
+ * end
1492
+ * NFS-->>C: Return control.valid
1493
+ * else Control is FormGroup
1494
+ * loop For each child control
1495
+ * NFS->>NFS: Recursive validateFields(child)
1496
+ * end
1497
+ * NFS-->>C: Return allValid
1498
+ * else Control is FormArray
1499
+ * loop For each array control
1500
+ * NFS->>NFS: Recursive validateFields(child)
1501
+ * end
1502
+ * NFS-->>C: Return allValid
1503
+ * else Unknown control type
1504
+ * NFS-->>C: Throw Error
1505
+ * end
1506
+ *
1507
+ * @static
1508
+ * @memberOf NgxFormService
1186
1509
  */
1187
1510
  static validateFields(control, pk, path) {
1188
1511
  control = path ? control.get(path) : control;
@@ -1222,13 +1545,13 @@ class NgxFormService {
1222
1545
  });
1223
1546
  }
1224
1547
  }
1225
- function getControlName(control) {
1226
- const group = control.parent;
1227
- if (!group)
1228
- return null;
1229
- return Object.keys(group.controls).find(name => control === group.get(name)) || null;
1230
- }
1231
- return !getControlName(control) ? true : control.valid;
1548
+ // function getControlName(control: AbstractControl): string | null {
1549
+ // const group = control.parent as FormGroup;
1550
+ // if (!group)
1551
+ // return null;
1552
+ // return Object.keys(group.controls).find(name => control === group.get(name)) || null;
1553
+ // }
1554
+ return control.valid;
1232
1555
  }
1233
1556
  /**
1234
1557
  * @description Generates validators from component properties.
@@ -1246,10 +1569,38 @@ class NgxFormService {
1246
1569
  }
1247
1570
  /**
1248
1571
  * @description Creates a FormControl from component properties.
1249
- * @summary Generates a FormControl with validators based on the provided component properties.
1250
- * @param {FieldProperties} props - The component properties.
1251
- * @param {FieldUpdateMode} [updateMode='change'] - The update mode for the control.
1252
- * @return {FormControl} The created FormControl.
1572
+ * @summary Generates a FormControl with validators and initial configuration based on the provided
1573
+ * component properties. Handles different input types, sets initial values, and configures
1574
+ * validation rules and update modes.
1575
+ *
1576
+ * @param {FieldProperties} props - The component properties defining the control configuration
1577
+ * @param {FieldUpdateMode} [updateMode='change'] - The update mode for the control ('change', 'blur', 'submit')
1578
+ * @return {FormControl} The created FormControl with proper configuration and validators
1579
+ *
1580
+ * @mermaid
1581
+ * sequenceDiagram
1582
+ * participant C as Component
1583
+ * participant NFS as NgxFormService
1584
+ * participant VF as ValidatorFactory
1585
+ * participant AF as Angular Forms
1586
+ *
1587
+ * C->>NFS: fromProps(props, updateMode?)
1588
+ * NFS->>NFS: validatorsFromProps(props)
1589
+ * NFS->>VF: Create validators from props
1590
+ * VF-->>NFS: Return validator array
1591
+ * NFS->>NFS: Compose validators
1592
+ * alt props.value exists and not checkbox
1593
+ * alt props.type is DATE
1594
+ * NFS->>NFS: Validate date format
1595
+ * end
1596
+ * NFS->>NFS: Set initial value
1597
+ * end
1598
+ * NFS->>AF: new FormControl(config)
1599
+ * AF-->>NFS: Return FormControl
1600
+ * NFS-->>C: Return configured FormControl
1601
+ *
1602
+ * @static
1603
+ * @memberOf NgxFormService
1253
1604
  */
1254
1605
  static fromProps(props, updateMode = 'change') {
1255
1606
  const validators = this.validatorsFromProps(props);
@@ -1267,21 +1618,50 @@ class NgxFormService {
1267
1618
  });
1268
1619
  }
1269
1620
  /**
1270
- * @description Retrieves properties from a FormControl.
1271
- * @summary Gets the FieldProperties associated with a FormControl from the internal WeakMap.
1272
- * @param {FormControl} control - The FormControl to get properties for.
1273
- * @return {FieldProperties} The properties associated with the control.
1621
+ * @description Retrieves properties from a FormControl, FormArray, or FormGroup.
1622
+ * @summary Gets the FieldProperties associated with a form control from the internal WeakMap.
1623
+ * This method provides access to the original component properties that were used to create
1624
+ * the control, enabling validation, rendering, and behavior configuration.
1625
+ *
1626
+ * @param {FormControl | FormArray | FormGroup} control - The form control to get properties for
1627
+ * @return {FieldProperties} The properties associated with the control, or empty object if not found
1628
+ *
1629
+ * @static
1630
+ * @memberOf NgxFormService
1274
1631
  */
1275
1632
  static getPropsFromControl(control) {
1276
1633
  return this.controls.get(control) || {};
1277
1634
  }
1278
1635
  /**
1279
- * @description Finds a parent element with a specific tag.
1280
- * @summary Traverses up the DOM tree to find the nearest parent element with the specified tag.
1281
- * @param {HTMLElement} el - The starting element.
1282
- * @param {string} tag - The tag name to search for.
1283
- * @return {HTMLElement} The found parent element.
1284
- * @throws {Error} If no parent with the specified tag is found.
1636
+ * @description Finds a parent element with a specific tag in the DOM tree.
1637
+ * @summary Traverses up the DOM tree to find the nearest parent element with the specified tag name.
1638
+ * This is useful for finding container elements or specific parent components in the DOM hierarchy.
1639
+ * The search is case-insensitive for tag name matching.
1640
+ *
1641
+ * @param {HTMLElement} el - The starting element to traverse from
1642
+ * @param {string} tag - The tag name to search for (case-insensitive)
1643
+ * @return {HTMLElement} The found parent element with the specified tag
1644
+ * @throws {Error} If no parent with the specified tag is found in the DOM tree
1645
+ *
1646
+ * @mermaid
1647
+ * sequenceDiagram
1648
+ * participant C as Component
1649
+ * participant NFS as NgxFormService
1650
+ * participant DOM as DOM Tree
1651
+ *
1652
+ * C->>NFS: getParentEl(element, tagName)
1653
+ * loop Traverse up DOM tree
1654
+ * NFS->>DOM: Get parentElement
1655
+ * DOM-->>NFS: Return parent or null
1656
+ * alt Parent exists and tag matches
1657
+ * NFS-->>C: Return parent element
1658
+ * else Parent is null
1659
+ * NFS-->>C: Throw Error
1660
+ * end
1661
+ * end
1662
+ *
1663
+ * @static
1664
+ * @memberOf NgxFormService
1285
1665
  */
1286
1666
  static getParentEl(el, tag) {
1287
1667
  let parent;
@@ -1294,10 +1674,17 @@ class NgxFormService {
1294
1674
  throw new Error(`No parent with the tag ${tag} was found for provided element`);
1295
1675
  }
1296
1676
  /**
1297
- * @description Registers a control with its properties.
1298
- * @summary Associates a control with its properties in the internal WeakMap.
1299
- * @param {AbstractControl} control - The control to register.
1300
- * @param {FieldProperties} props - The properties to associate with the control.
1677
+ * @description Registers a control with its properties in the internal WeakMap.
1678
+ * @summary Associates a form control with its component properties for later retrieval.
1679
+ * This enables the service to maintain metadata about controls without creating memory leaks,
1680
+ * as WeakMap automatically cleans up references when controls are garbage collected.
1681
+ *
1682
+ * @param {AbstractControl} control - The control to register (FormControl, FormGroup, or FormArray)
1683
+ * @param {FieldProperties} props - The properties to associate with the control
1684
+ * @return {void}
1685
+ *
1686
+ * @static
1687
+ * @memberOf NgxFormService
1301
1688
  */
1302
1689
  static register(control, props) {
1303
1690
  this.controls.set(control, props);
@@ -1385,6 +1772,8 @@ class NgxRenderingEngine extends RenderingEngine {
1385
1772
  * @type {string | undefined}
1386
1773
  */
1387
1774
  static { this._operation = undefined; }
1775
+ static { this._projectable = true; }
1776
+ static { this._parentProps = undefined; }
1388
1777
  /**
1389
1778
  * @description Constructs a new NgxRenderingEngine instance
1390
1779
  * @summary Initializes a new instance of the Angular rendering engine by calling the parent
@@ -1453,7 +1842,6 @@ class NgxRenderingEngine extends RenderingEngine {
1453
1842
  const hiddenOn = inputs?.hidden || [];
1454
1843
  if (hiddenOn.includes(operation))
1455
1844
  return { inputs, injector };
1456
- // const hiddenOn = inputs?.hidden || [];
1457
1845
  const result = {
1458
1846
  component,
1459
1847
  inputs,
@@ -1463,6 +1851,8 @@ class NgxRenderingEngine extends RenderingEngine {
1463
1851
  result.inputs['rendererId'] = fieldDef.rendererId;
1464
1852
  // process children
1465
1853
  if (fieldDef.children?.length) {
1854
+ if (!NgxRenderingEngine._parentProps && inputs?.pages)
1855
+ NgxRenderingEngine._parentProps = { pages: inputs?.pages };
1466
1856
  result.children = fieldDef.children.map((child) => {
1467
1857
  if (child?.children?.length) {
1468
1858
  child.children = child.children.filter(c => {
@@ -1471,15 +1861,18 @@ class NgxRenderingEngine extends RenderingEngine {
1471
1861
  return c;
1472
1862
  });
1473
1863
  }
1474
- // create a child form and add its controls as properties of child.props
1475
- NgxFormService.addControlFromProps(registryFormId, child.props, inputs);
1864
+ NgxFormService.addControlFromProps(registryFormId, child.props, { ...inputs, ...NgxRenderingEngine._parentProps || {} });
1476
1865
  return this.fromFieldDefinition(child, vcr, injector, tpl, registryFormId);
1477
1866
  });
1478
1867
  }
1479
1868
  // generating DOM
1869
+ const projectable = NgxRenderingEngine._projectable;
1480
1870
  vcr.clear();
1481
- const template = vcr.createEmbeddedView(tpl, injector).rootNodes;
1482
- const componentInstance = NgxRenderingEngine.createComponent(component, { ...inputs, model: this._model }, componentMetadata, vcr, injector, template);
1871
+ const template = !projectable ? [] : vcr.createEmbeddedView(tpl, injector).rootNodes;
1872
+ const hasChildren = Object.values(possibleInputs).some(({ propName }) => propName === 'children');
1873
+ const hasModel = Object.values(possibleInputs).some(({ propName }) => propName === 'children');
1874
+ const componentInputs = Object.assign(inputs, (hasModel ? { model: this._model } : {}), (hasChildren ? { children: fieldDef?.['children'] || [] } : {}));
1875
+ const componentInstance = NgxRenderingEngine.createComponent(component, componentInputs, componentMetadata, vcr, injector, template);
1483
1876
  result.instance = NgxRenderingEngine._instance = componentInstance.instance;
1484
1877
  return result;
1485
1878
  }
@@ -1527,8 +1920,11 @@ class NgxRenderingEngine extends RenderingEngine {
1527
1920
  *
1528
1921
  * @return {Promise<void>} A promise that resolves when the instance is destroyed
1529
1922
  */
1530
- static async destroy() {
1923
+ static async destroy(formId) {
1924
+ if (formId)
1925
+ NgxFormService.removeRegistry(formId);
1531
1926
  NgxRenderingEngine._instance = undefined;
1927
+ NgxRenderingEngine._parentProps = undefined;
1532
1928
  }
1533
1929
  /**
1534
1930
  * @description Renders a model into an Angular component output
@@ -1560,18 +1956,20 @@ class NgxRenderingEngine extends RenderingEngine {
1560
1956
  * FromField-->>Render: AngularDynamicOutput
1561
1957
  * Render-->>Client: return AngularDynamicOutput
1562
1958
  */
1563
- render(model, globalProps, vcr, injector, tpl) {
1959
+ render(model, globalProps, vcr, injector, tpl, projectable = true) {
1564
1960
  let result;
1565
1961
  try {
1566
1962
  this._model = model;
1963
+ NgxRenderingEngine._projectable = projectable;
1567
1964
  const formId = Date.now().toString(36).toUpperCase();
1568
1965
  const fieldDef = this.toFieldDefinition(model, globalProps);
1569
1966
  const props = fieldDef.props;
1570
1967
  if (!NgxRenderingEngine._operation)
1571
1968
  NgxRenderingEngine._operation = props?.['operation'] || undefined;
1969
+ const formGroup = NgxFormService.createForm(formId, props);
1572
1970
  result = this.fromFieldDefinition(fieldDef, vcr, injector, tpl, formId);
1573
- result.instance['formGroup'] = NgxFormService.getControlFromForm(formId);
1574
- NgxFormService.removeRegistry(formId);
1971
+ result.instance['formGroup'] = formGroup;
1972
+ NgxRenderingEngine.destroy(formId);
1575
1973
  }
1576
1974
  catch (e) {
1577
1975
  throw new InternalError(`Failed to render Model ${model.constructor.name}: ${e}`);
@@ -1768,6 +2166,7 @@ class ComponentRendererComponent {
1768
2166
  * @memberOf ComponentRendererComponent
1769
2167
  */
1770
2168
  this.injector = inject(EnvironmentInjector);
2169
+ this.children = [];
1771
2170
  /**
1772
2171
  * @description Event emitter for events from the rendered component.
1773
2172
  * @summary This output property emits events that originate from the dynamically rendered
@@ -1878,6 +2277,8 @@ class ComponentRendererComponent {
1878
2277
  const props = globals?.['item'] || globals?.['props'] || {};
1879
2278
  if (props?.['tag'])
1880
2279
  delete props['tag'];
2280
+ if (props?.['children'] && !this.children.length)
2281
+ this.children = props['children'];
1881
2282
  const inputKeys = Object.keys(props);
1882
2283
  const unmappedKeys = [];
1883
2284
  for (const input of inputKeys) {
@@ -1890,7 +2291,8 @@ class ComponentRendererComponent {
1890
2291
  }
1891
2292
  }
1892
2293
  this.vcr.clear();
1893
- this.component = NgxRenderingEngine.createComponent(component, props, metadata, this.vcr, this.injector, []);
2294
+ const template = this.children?.length ? this.vcr.createEmbeddedView(this.inner, this.injector).rootNodes : [];
2295
+ this.component = NgxRenderingEngine.createComponent(component, props, metadata, this.vcr, this.injector, template);
1894
2296
  this.subscribeEvents();
1895
2297
  }
1896
2298
  createParentComponent() {
@@ -1981,11 +2383,11 @@ class ComponentRendererComponent {
1981
2383
  }
1982
2384
  }
1983
2385
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1984
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ComponentRendererComponent, isStandalone: true, selector: "ngx-decaf-component-renderer", inputs: { tag: "tag", globals: "globals", model: "model", parent: "parent" }, outputs: { listenEvent: "listenEvent" }, host: { properties: { "attr.id": "rendererId" } }, viewQueries: [{ propertyName: "vcr", first: true, predicate: ["componentViewContainer"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "inner", first: true, predicate: ["inner"], descendants: true, read: TemplateRef, static: true }], ngImport: i0, template: "<!-- Keep to avoid id conflicts -->\n<div [id]=\"uid\"></div>\n\n<ng-template #componentViewContainer></ng-template>\n<ng-template #inner>\n @if(parent?.children?.length) {\n @for(child of parent.children; track child) {\n @if(!child.children?.length) {\n <ng-container\n *ngComponentOutlet=\"\n child.component;\n injector: child.injector;\n inputs: child.inputs;\n content:child.content;\n \"\n />\n } @else {\n <ngx-decaf-component-renderer [parent]=\"child\"> </ngx-decaf-component-renderer>\n }\n }\n }\n</ng-template>\n\n\n\n", styles: [""], dependencies: [{ kind: "component", type: ComponentRendererComponent, selector: "ngx-decaf-component-renderer", inputs: ["tag", "globals", "model", "parent"], outputs: ["listenEvent"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }] }); }
2386
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ComponentRendererComponent, isStandalone: true, selector: "ngx-decaf-component-renderer", inputs: { tag: "tag", globals: "globals", children: "children", model: "model", parent: "parent" }, outputs: { listenEvent: "listenEvent" }, host: { properties: { "attr.id": "uid" } }, viewQueries: [{ propertyName: "vcr", first: true, predicate: ["componentViewContainer"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "inner", first: true, predicate: ["inner"], descendants: true, read: TemplateRef, static: true }], ngImport: i0, template: "<!-- Keep to avoid id conflicts -->\n<div [id]=\"uid\"></div>\n\n<ng-template #componentViewContainer></ng-template>\n<ng-template #inner>\n @if(parent?.children?.length) {\n @for(child of parent.children; track child) {\n @if(!child.children?.length) {\n <ng-container\n *ngComponentOutlet=\"\n child.component;\n injector: child.injector;\n inputs: child.inputs;\n content:child.content;\n \"\n />\n } @else {\n <ngx-decaf-component-renderer [parent]=\"child\"> </ngx-decaf-component-renderer>\n }\n }\n }\n @if(children?.length) {\n @for(child of children; track child) {\n @if(child.children?.length) {\n <ngx-decaf-component-renderer [parent]=\"child\"> </ngx-decaf-component-renderer>\n } @else {\n <ngx-decaf-component-renderer\n [tag]=\"child?.tag\"\n (listenEvent)=\"handleEvent($event)\"\n [globals]=\"{props: child.props}\" />\n }\n }\n }\n</ng-template>\n\n\n", styles: [""], dependencies: [{ kind: "component", type: ComponentRendererComponent, selector: "ngx-decaf-component-renderer", inputs: ["tag", "globals", "children", "model", "parent"], outputs: ["listenEvent"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }] }); }
1985
2387
  }
1986
2388
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentRendererComponent, decorators: [{
1987
2389
  type: Component,
1988
- args: [{ selector: 'ngx-decaf-component-renderer', imports: [NgComponentOutlet], standalone: true, host: { '[attr.id]': 'rendererId' }, template: "<!-- Keep to avoid id conflicts -->\n<div [id]=\"uid\"></div>\n\n<ng-template #componentViewContainer></ng-template>\n<ng-template #inner>\n @if(parent?.children?.length) {\n @for(child of parent.children; track child) {\n @if(!child.children?.length) {\n <ng-container\n *ngComponentOutlet=\"\n child.component;\n injector: child.injector;\n inputs: child.inputs;\n content:child.content;\n \"\n />\n } @else {\n <ngx-decaf-component-renderer [parent]=\"child\"> </ngx-decaf-component-renderer>\n }\n }\n }\n</ng-template>\n\n\n\n" }]
2390
+ args: [{ selector: 'ngx-decaf-component-renderer', imports: [NgComponentOutlet], standalone: true, host: { '[attr.id]': 'uid' }, template: "<!-- Keep to avoid id conflicts -->\n<div [id]=\"uid\"></div>\n\n<ng-template #componentViewContainer></ng-template>\n<ng-template #inner>\n @if(parent?.children?.length) {\n @for(child of parent.children; track child) {\n @if(!child.children?.length) {\n <ng-container\n *ngComponentOutlet=\"\n child.component;\n injector: child.injector;\n inputs: child.inputs;\n content:child.content;\n \"\n />\n } @else {\n <ngx-decaf-component-renderer [parent]=\"child\"> </ngx-decaf-component-renderer>\n }\n }\n }\n @if(children?.length) {\n @for(child of children; track child) {\n @if(child.children?.length) {\n <ngx-decaf-component-renderer [parent]=\"child\"> </ngx-decaf-component-renderer>\n } @else {\n <ngx-decaf-component-renderer\n [tag]=\"child?.tag\"\n (listenEvent)=\"handleEvent($event)\"\n [globals]=\"{props: child.props}\" />\n }\n }\n }\n</ng-template>\n\n\n" }]
1989
2391
  }], ctorParameters: () => [], propDecorators: { vcr: [{
1990
2392
  type: ViewChild,
1991
2393
  args: ['componentViewContainer', { static: true, read: ViewContainerRef }]
@@ -1994,6 +2396,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
1994
2396
  args: [{ required: true }]
1995
2397
  }], globals: [{
1996
2398
  type: Input
2399
+ }], children: [{
2400
+ type: Input
1997
2401
  }], listenEvent: [{
1998
2402
  type: Output
1999
2403
  }], model: [{
@@ -2111,24 +2515,26 @@ class NgxCrudFormField {
2111
2515
  */
2112
2516
  getErrors(parent) {
2113
2517
  const formControl = this.formControl;
2114
- const accordionComponent = parent.closest('ngx-decaf-fieldset')?.querySelector('ion-accordion-group');
2115
- if ((!formControl.pristine || formControl.touched) && !formControl.valid) {
2116
- const errors = Object.keys(formControl.errors ?? {}).map(key => ({
2117
- key: key,
2118
- message: key,
2119
- }));
2120
- if (errors.length) {
2121
- if (accordionComponent && !this.validationErrorEventDispateched) {
2122
- const validationErrorEvent = new CustomEvent(EventConstants.VALIDATION_ERROR, {
2123
- detail: { fieldName: this.name, hasErrors: true },
2124
- bubbles: true
2125
- });
2126
- accordionComponent.dispatchEvent(validationErrorEvent);
2127
- this.validationErrorEventDispateched = true;
2518
+ if (formControl) {
2519
+ const accordionComponent = parent.closest('ngx-decaf-fieldset')?.querySelector('ion-accordion-group');
2520
+ if ((!formControl.pristine || formControl.touched) && !formControl.valid) {
2521
+ const errors = Object.keys(formControl.errors ?? {}).map(key => ({
2522
+ key: key,
2523
+ message: key,
2524
+ }));
2525
+ if (errors.length) {
2526
+ if (accordionComponent && !this.validationErrorEventDispateched) {
2527
+ const validationErrorEvent = new CustomEvent(EventConstants.VALIDATION_ERROR, {
2528
+ detail: { fieldName: this.name, hasErrors: true },
2529
+ bubbles: true
2530
+ });
2531
+ accordionComponent.dispatchEvent(validationErrorEvent);
2532
+ this.validationErrorEventDispateched = true;
2533
+ }
2128
2534
  }
2535
+ for (const error of errors)
2536
+ return `* ${this.sf(this.translateService.instant(`errors.${error?.['message']}`), this[error?.['key']] ?? "")}`;
2129
2537
  }
2130
- for (const error of errors)
2131
- return `* ${this.sf(this.translateService.instant(`errors.${error?.['message']}`), this[error?.['key']] ?? "")}`;
2132
2538
  }
2133
2539
  }
2134
2540
  }
@@ -2288,7 +2694,6 @@ function getLocaleContextByKey(locale, phrase) {
2288
2694
  const parts = phrase.split(' ');
2289
2695
  return `${locale}.${cleanSpaces(parts.join('.'), true)}`;
2290
2696
  }
2291
- const I18N_CONFIG_TOKEN = new InjectionToken('I18N_CONFIG_TOKEN');
2292
2697
  function I18nLoaderFactory(http) {
2293
2698
  const { resources, versionedSuffix } = inject(I18N_CONFIG_TOKEN, { optional: true }) ?? getI18nLoaderFactoryProviderConfig().useValue;
2294
2699
  return new MultiI18nLoader(http, resources, versionedSuffix);
@@ -2299,7 +2704,7 @@ function getI18nLoaderFactoryProviderConfig(resources = [], versionedSuffix = fa
2299
2704
  return {
2300
2705
  provide: I18N_CONFIG_TOKEN,
2301
2706
  useValue: { resources: [
2302
- { prefix: './assets/i18n/', suffix: '.json' },
2707
+ // { prefix: './assets/i18n/', suffix: '.json' },
2303
2708
  ...resources
2304
2709
  ], versionedSuffix }
2305
2710
  };
@@ -2403,6 +2808,15 @@ let CrudFieldComponent = class CrudFieldComponent extends NgxCrudFormField {
2403
2808
  * @memberOf CrudFieldComponent
2404
2809
  */
2405
2810
  this.value = '';
2811
+ /**
2812
+ * @description Whether the field should be hidden.
2813
+ * @summary When true, the field will not be visible in the UI but will still be part of the form model.
2814
+ * This is useful for fields that need to be included in form submission but should not be displayed to the user.
2815
+ *
2816
+ * @type {boolean}
2817
+ * @memberOf CrudFieldComponent
2818
+ */
2819
+ this.hidden = false;
2406
2820
  /**
2407
2821
  * @description Interface style for select inputs.
2408
2822
  * @summary Specifies the interface style for select inputs, such as 'alert', 'action-sheet', or 'popover'.
@@ -2683,7 +3097,7 @@ let CrudFieldComponent = class CrudFieldComponent extends NgxCrudFormField {
2683
3097
  component.dispatchEvent(event);
2684
3098
  }
2685
3099
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CrudFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
2686
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: CrudFieldComponent, isStandalone: true, selector: "ngx-decaf-crud-field", inputs: { operation: "operation", name: "name", path: "path", childOf: "childOf", type: "type", value: "value", disabled: "disabled", label: "label", placeholder: "placeholder", format: "format", hidden: "hidden", max: "max", maxlength: "maxlength", min: "min", minlength: "minlength", pattern: "pattern", readonly: "readonly", required: "required", step: "step", equals: "equals", different: "different", lessThan: "lessThan", lessThanOrEqual: "lessThanOrEqual", greaterThan: "greaterThan", greaterThanOrEqual: "greaterThanOrEqual", cols: "cols", rows: "rows", alignment: "alignment", checked: "checked", justify: "justify", cancelText: "cancelText", interface: "interface", options: "options", mode: "mode", spellcheck: "spellcheck", inputmode: "inputmode", autocomplete: "autocomplete", fill: "fill", labelPlacement: "labelPlacement", updateOn: "updateOn", formGroup: "formGroup", formControl: "formControl", multiple: "multiple", uid: "uid", translatable: "translatable", activeFormGroup: "activeFormGroup", pk: "pk" }, host: { listeners: { "window:fieldsetAddGroupEvent": "handleFieldsetCreateGroupEvent($event)", "window:fieldsetUpdateGroupEvent": "handleFieldsetUpdateGroupEvent($event)", "window:fieldsetRemoveGroupEvent": "handleFieldsetRemoveGroupEvent($event)" }, properties: { "attr.id": "uid" } }, viewQueries: [{ propertyName: "component", first: true, predicate: ["component"], descendants: true, read: ElementRef }], usesInheritance: true, ngImport: i0, template: "@if(operation === 'read' || operation === 'delete') {\n <ng-container #component>\n <div [class]=\"'dcf-input-item ' + operation\">\n <ion-item>\n <ion-label>\n {{ label | translate }}<br />\n @if(value) {\n <ion-text [innerHTML]=\"type === 'password' ? '********' : value\"></ion-text>\n } @else {\n <br />\n }\n </ion-label>\n </ion-item>\n </div>\n </ng-container>\n} @else {\n <ng-container [formGroup]=\"getActiveFormGroup\">\n <div #container [class]=\"'dcf-input-item ' + (operation || 'create')\" (createGroupEvent)=\"multiple ? handleFieldsetCreateGroupEvent($event) : ''\">\n @if(type === 'textarea') {\n <ion-textarea\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [autoGrow]=\"true\"\n [required]=\"required !== undefined ? required : null\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [readonly]=\"readonly !== undefined ? readonly : null\"\n [inputmode]=\"inputmode\"\n [spellcheck]=\"spellcheck\"\n [rows]=\"rows\"\n [labelPlacement]=\"labelPlacement\"\n [value]=\"value\"\n [fill]=\"fill\"\n [errorText]=\"getErrors(container)\"\n [placeholder]=\"translatable ? (placeholder | translate) : placeholder\"\n [formControlName]=\"name\"\n [label]=\"translatable ? (label | translate) : label\"\n #component>\n </ion-textarea>\n }\n @else if(type === 'checkbox') {\n <ion-item>\n <ion-checkbox\n #checkboxElement\n\n [mode]=\"mode\"\n [errorText]=\"getErrors(container)\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify\"\n [value]=\"value\"\n [checked]=\"checked\"\n [readonly]=\"readonly\"\n (ionChange)=\"checked = !checked\"\n [formControlName]=\"name\"\n #component>\n <span [innerHTML]=\"label | translate\"></span>\n </ion-checkbox>\n </ion-item>\n }\n @else if(type === 'radio') {\n <ion-radio-group [formControlName]=\"name\" [value]=\"value\" #component>\n <label class=\"dcf-radio-group-label\" [for]=\"path\">{{label | translate}}</label>\n @for(option of options; track option) {\n <ion-item>\n <ion-radio\n [errorText]=\"getErrors(container)\"\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [alignment]=\"alignment\"\n [justify]=\"justify\"\n [readonly]=\"readonly\"\n [value]=\"option.value\"\n >{{ translatable ? (option?.text | translate) : option?.text }}</ion-radio>\n </ion-item>\n }\n </ion-radio-group>\n }\n @else if(type === 'select') {\n <ion-select\n toggleIcon=\"chevron-down-outline\"\n expandedIcon=\"chevron-up-outline\"\n\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [label]=\"translatable ? (label | translate) : label\"\n [value]=\"value\"\n [fill]=\"fill\"\n [placeholder]=\"translatable ? (placeholder | translate) : placeholder\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [interface]=\"interface\" #component>\n @for(option of options; track option.value) {\n <ion-select-option [value]=\"option.value\">\n {{ option.text | translate }}\n </ion-select-option>\n }\n </ion-select>\n }\n @else {\n <ion-input\n [type]=\"type\"\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [inputmode]=\"inputmode\"\n [labelPlacement]=\"labelPlacement\"\n [required]=\"required !== undefined ? required : false\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [readonly]=\"readonly !== undefined ? readonly : null\"\n [max]=\"max !== undefined ? max : null\"\n [min]=\"min !== undefined ? min : null\"\n [pattern]=\"pattern !== undefined ? pattern : null\"\n [step]=\"step !== undefined ? step : null\"\n [fill]=\"fill\"\n [placeholder]=\"translatable ? (placeholder | translate) : placeholder\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [label]=\"translatable ? (label | translate) : label\" #component />\n }\n </div>\n </ng-container>\n}\n\n", styles: [".dcf-input-item.create,.dcf-input-item.update{margin-bottom:1.8rem;margin-top:0!important}.dcf-input-item.create.checkbox+.checkbox,.dcf-input-item.update.checkbox+.checkbox{margin-top:-.25rem!important}.dcf-input-item.create ion-item,.dcf-input-item.update ion-item{--border-color: transparent}.dcf-input-item.create ion-item.dcf-text-wrap ion-label>*,.dcf-input-item.update ion-item.dcf-text-wrap ion-label>*{white-space:wrap!important;word-break:break-all!important}.dcf-input-item.read ion-label,.dcf-input-item.delete ion-label{font-weight:600}@media (prefers-color-scheme: light){.dcf-input-item.read ion-label,.dcf-input-item.delete ion-label{color:var(--dcf-color-gray-7)}}.dcf-input-item.read ion-text,.dcf-input-item.delete ion-text{display:block;margin-top:.5rem!important}.dcf-input-item ion-item{--padding-end: 0rem;--padding-start: 0px !important;--padding-top: 0px !important;--background: transparent;--background-hover-opacity: .1;--background-activated-opacity: .15;--background-focused-opacity: .15}@media (prefers-color-scheme: dark){.dcf-input-item ion-item{--border-color: var(--dcf-color-gray-6)}}@media (prefers-color-scheme: light){.dcf-input-item ion-item{--background-hover: var(--dcf-color-primary);--background-focused: var(--dcf-color-primary);--border-color: var(--dcf-color-gray-2)}}.dcf-input-item ion-item span,.dcf-input-item ion-item ion-text{font-weight:400!important;font-size:.925rem;min-height:.5rem!important}.dcf-input-item ion-item span:not(.dcf-display-block),.dcf-input-item ion-item ion-text:not(.dcf-display-block){display:inline-block}.dcf-input-item ion-item span.dcf-display-block,.dcf-input-item ion-item ion-text.dcf-display-block{display:block!important}ion-textarea textarea{scrollbar-width:thin!important;margin-bottom:.5rem!important}ion-select.dcf-select-label-placement-floating::part(label){line-height:1.2rem!important}.dcf-proccessing,.dcf-proccessing *{pointer-events:none;touch-action:none;cursor:text}ion-checkbox{--size: 1.5rem;--checkbox-background-checked: var(--dcf-color-primary);--checkmark-width: 2px}ion-item{--inner-padding-start: .75rem}ion-checkbox::part(container){border-radius:50%;padding:3px}@media (prefers-color-scheme: light){ion-checkbox::part(container){border:2px solid var(--dcf-color-primary)}}ion-item .dcf-radio-group-label,ion-radio-group .dcf-radio-group-label{font-weight:600}ion-item .dcf-radio-group-label~ion-item,ion-radio-group .dcf-radio-group-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}ion-item+.dcf-helper,ion-radio-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-error{position:absolute;color:var(--dcf-color-danger)!important;font-size:.8rem!important;font-weight:600!important;line-height:1.1rem;box-sizing:border-box;z-index:9999;margin-top:0;animation-duration:.1s;animation-timing-function:ease-out;animation-fill-mode:both;animation-name:fadeTopSmallAnimation;display:flex;align-items:flex-start;gap:.25rem}.dcf-error .ti,.dcf-error ion-icon{position:relative;top:2px!important;min-width:20px;font-size:1rem!important;text-align:left}.dcf-helper{font-size:.875rem!important;font-weight:500;margin-top:.25rem;margin-bottom:-.75rem}.dcf-helper.dcf-has-action{cursor:pointer;color:var(--dcf-color-gray-7)!important;text-decoration:underline}.dcf-error+.dcf-helper{padding-top:1rem}@keyframes fadeTopSmallAnimation{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeBottomSmallAnimation{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeTopMediumAnimation{0%{opacity:0;transform:translateY(-50px)}to{opacity:1;transform:translateY(0)}}::ng-deep ion-textarea{min-height:80px!important;scrollbar-color:#888 #f0f0f0;scrollbar-width:thin}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonRadioGroup, selector: "ion-radio-group", inputs: ["allowEmptySelection", "compareWith", "errorText", "helperText", "name", "value"] }, { kind: "component", type: IonRadio, selector: "ion-radio", inputs: ["alignment", "color", "disabled", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }] }); }
3100
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: CrudFieldComponent, isStandalone: true, selector: "ngx-decaf-crud-field", inputs: { operation: "operation", name: "name", path: "path", childOf: "childOf", type: "type", value: "value", disabled: "disabled", label: "label", placeholder: "placeholder", format: "format", hidden: "hidden", max: "max", maxlength: "maxlength", min: "min", minlength: "minlength", pattern: "pattern", readonly: "readonly", required: "required", step: "step", equals: "equals", different: "different", lessThan: "lessThan", lessThanOrEqual: "lessThanOrEqual", greaterThan: "greaterThan", greaterThanOrEqual: "greaterThanOrEqual", cols: "cols", rows: "rows", alignment: "alignment", checked: "checked", justify: "justify", cancelText: "cancelText", interface: "interface", options: "options", mode: "mode", spellcheck: "spellcheck", inputmode: "inputmode", autocomplete: "autocomplete", fill: "fill", labelPlacement: "labelPlacement", updateOn: "updateOn", formGroup: "formGroup", formControl: "formControl", multiple: "multiple", uid: "uid", page: "page", translatable: "translatable", activeFormGroup: "activeFormGroup", pk: "pk" }, host: { listeners: { "window:fieldsetAddGroupEvent": "handleFieldsetCreateGroupEvent($event)", "window:fieldsetUpdateGroupEvent": "handleFieldsetUpdateGroupEvent($event)", "window:fieldsetRemoveGroupEvent": "handleFieldsetRemoveGroupEvent($event)" }, properties: { "attr.id": "uid" } }, viewQueries: [{ propertyName: "component", first: true, predicate: ["component"], descendants: true, read: ElementRef }], usesInheritance: true, ngImport: i0, template: "@if(operation === 'read' || operation === 'delete') {\n <ng-container #component>\n <div [class]=\"'dcf-input-item ' + operation\">\n <ion-item>\n <ion-label>\n {{ label | translate }}<br />\n @if(value) {\n <ion-text [innerHTML]=\"type === 'password' ? '********' : value\"></ion-text>\n } @else {\n <br />\n }\n </ion-label>\n </ion-item>\n </div>\n </ng-container>\n} @else {\n <ng-container [formGroup]=\"getActiveFormGroup\">\n <div #container [class]=\"'dcf-input-item ' + (operation || 'create')\" (createGroupEvent)=\"multiple ? handleFieldsetCreateGroupEvent($event) : ''\">\n @if(type === 'textarea') {\n <ion-textarea\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [autoGrow]=\"true\"\n [required]=\"required !== undefined ? required : null\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [readonly]=\"readonly !== undefined ? readonly : null\"\n [inputmode]=\"inputmode\"\n [spellcheck]=\"spellcheck\"\n [rows]=\"rows\"\n [labelPlacement]=\"labelPlacement\"\n [value]=\"value\"\n [fill]=\"fill\"\n [errorText]=\"getErrors(container)\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [label]=\"label | translate\"\n #component>\n </ion-textarea>\n }\n @else if(type === 'checkbox') {\n <ion-item>\n <ion-checkbox\n #checkboxElement\n [mode]=\"mode\"\n [errorText]=\"getErrors(container)\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify\"\n [value]=\"value\"\n [checked]=\"checked\"\n [readonly]=\"readonly\"\n (ionChange)=\"checked = !checked\"\n [formControlName]=\"name\"\n #component>\n <span [innerHTML]=\"label | translate\"></span>\n </ion-checkbox>\n </ion-item>\n }\n @else if(type === 'radio') {\n <ion-radio-group [formControlName]=\"name\" [value]=\"value\" #component>\n <label class=\"dcf-radio-group-label\" [for]=\"path\">{{label | translate}}</label>\n @for(option of options; track option) {\n <ion-item>\n <ion-radio\n [errorText]=\"getErrors(container)\"\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [alignment]=\"alignment\"\n [justify]=\"justify\"\n [readonly]=\"readonly\"\n [value]=\"option.value\"\n >{{ translatable ? (option?.text | translate) : option?.text }}</ion-radio>\n </ion-item>\n }\n </ion-radio-group>\n }\n @else if(type === 'select') {\n <ion-select\n toggleIcon=\"chevron-down-outline\"\n expandedIcon=\"chevron-up-outline\"\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [label]=\"translatable ? (label | translate) : label\"\n [value]=\"value\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [interface]=\"interface\" #component>\n @for(option of options; track option.value) {\n <ion-select-option [value]=\"option.value\">\n {{ option.text | translate }}\n </ion-select-option>\n }\n </ion-select>\n }\n @else {\n <ion-input\n [type]=\"type\"\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [inputmode]=\"inputmode\"\n [labelPlacement]=\"labelPlacement\"\n [required]=\"required !== undefined ? required : false\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [readonly]=\"readonly !== undefined ? readonly : null\"\n [max]=\"max !== undefined ? max : null\"\n [min]=\"min !== undefined ? min : null\"\n [pattern]=\"pattern !== undefined ? pattern : null\"\n [step]=\"step !== undefined ? step : null\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [label]=\"label | translate\" #component />\n }\n </div>\n </ng-container>\n}\n\n", styles: [".dcf-input-item.create,.dcf-input-item.update{margin-bottom:1.8rem;margin-top:0!important}.dcf-input-item.create.checkbox+.checkbox,.dcf-input-item.update.checkbox+.checkbox{margin-top:-.25rem!important}.dcf-input-item.create ion-item,.dcf-input-item.update ion-item{--border-color: transparent}.dcf-input-item.create ion-item.dcf-text-wrap ion-label>*,.dcf-input-item.update ion-item.dcf-text-wrap ion-label>*{white-space:wrap!important;word-break:break-all!important}.dcf-input-item.read ion-label,.dcf-input-item.delete ion-label{font-weight:600}@media (prefers-color-scheme: light){.dcf-input-item.read ion-label,.dcf-input-item.delete ion-label{color:var(--dcf-color-gray-7)}}.dcf-input-item.read ion-text,.dcf-input-item.delete ion-text{display:block;margin-top:.5rem!important}.dcf-input-item ion-item{--padding-end: 0rem;--padding-start: 0px !important;--padding-top: 0px !important;--background: transparent;--background-hover-opacity: .1;--background-activated-opacity: .15;--background-focused-opacity: .15}@media (prefers-color-scheme: dark){.dcf-input-item ion-item{--border-color: var(--dcf-color-gray-6)}}@media (prefers-color-scheme: light){.dcf-input-item ion-item{--background-hover: var(--dcf-color-primary);--background-focused: var(--dcf-color-primary);--border-color: var(--dcf-color-gray-2)}}.dcf-input-item ion-item span,.dcf-input-item ion-item ion-text{font-weight:400!important;font-size:.925rem;min-height:.5rem!important}.dcf-input-item ion-item span:not(.dcf-display-block),.dcf-input-item ion-item ion-text:not(.dcf-display-block){display:inline-block}.dcf-input-item ion-item span.dcf-display-block,.dcf-input-item ion-item ion-text.dcf-display-block{display:block!important}ion-textarea textarea{scrollbar-width:thin!important;margin-bottom:.5rem!important}ion-select.dcf-select-label-placement-floating::part(label){line-height:1.2rem!important}.dcf-proccessing,.dcf-proccessing *{pointer-events:none;touch-action:none;cursor:text}ion-checkbox{--size: 1.5rem;--checkbox-background-checked: var(--dcf-color-primary);--checkmark-width: 2px}ion-item{--inner-padding-start: .75rem}ion-checkbox::part(container){border-radius:50%;padding:3px}@media (prefers-color-scheme: light){ion-checkbox::part(container){border:2px solid var(--dcf-color-primary)}}ion-item .dcf-radio-group-label,ion-radio-group .dcf-radio-group-label{font-weight:600}ion-item .dcf-radio-group-label~ion-item,ion-radio-group .dcf-radio-group-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}ion-item+.dcf-helper,ion-radio-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-error{position:absolute;color:var(--dcf-color-danger)!important;font-size:.8rem!important;font-weight:600!important;line-height:1.1rem;box-sizing:border-box;z-index:9999;margin-top:0;animation-duration:.1s;animation-timing-function:ease-out;animation-fill-mode:both;animation-name:fadeTopSmallAnimation;display:flex;align-items:flex-start;gap:.25rem}.dcf-error .ti,.dcf-error ion-icon{position:relative;top:2px!important;min-width:20px;font-size:1rem!important;text-align:left}.dcf-helper{font-size:.875rem!important;font-weight:500;margin-top:.25rem;margin-bottom:-.75rem}.dcf-helper.dcf-has-action{cursor:pointer;color:var(--dcf-color-gray-7)!important;text-decoration:underline}.dcf-error+.dcf-helper{padding-top:1rem}@keyframes fadeTopSmallAnimation{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeBottomSmallAnimation{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeTopMediumAnimation{0%{opacity:0;transform:translateY(-50px)}to{opacity:1;transform:translateY(0)}}::ng-deep ion-textarea{min-height:80px!important;scrollbar-color:#888 #f0f0f0;scrollbar-width:thin}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonRadioGroup, selector: "ion-radio-group", inputs: ["allowEmptySelection", "compareWith", "errorText", "helperText", "name", "value"] }, { kind: "component", type: IonRadio, selector: "ion-radio", inputs: ["alignment", "color", "disabled", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }] }); }
2687
3101
  };
2688
3102
  CrudFieldComponent = __decorate([
2689
3103
  Dynamic()
@@ -2704,7 +3118,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2704
3118
  IonText,
2705
3119
  IonTextarea,
2706
3120
  IonIcon
2707
- ], selector: 'ngx-decaf-crud-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], host: { '[attr.id]': 'uid' }, template: "@if(operation === 'read' || operation === 'delete') {\n <ng-container #component>\n <div [class]=\"'dcf-input-item ' + operation\">\n <ion-item>\n <ion-label>\n {{ label | translate }}<br />\n @if(value) {\n <ion-text [innerHTML]=\"type === 'password' ? '********' : value\"></ion-text>\n } @else {\n <br />\n }\n </ion-label>\n </ion-item>\n </div>\n </ng-container>\n} @else {\n <ng-container [formGroup]=\"getActiveFormGroup\">\n <div #container [class]=\"'dcf-input-item ' + (operation || 'create')\" (createGroupEvent)=\"multiple ? handleFieldsetCreateGroupEvent($event) : ''\">\n @if(type === 'textarea') {\n <ion-textarea\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [autoGrow]=\"true\"\n [required]=\"required !== undefined ? required : null\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [readonly]=\"readonly !== undefined ? readonly : null\"\n [inputmode]=\"inputmode\"\n [spellcheck]=\"spellcheck\"\n [rows]=\"rows\"\n [labelPlacement]=\"labelPlacement\"\n [value]=\"value\"\n [fill]=\"fill\"\n [errorText]=\"getErrors(container)\"\n [placeholder]=\"translatable ? (placeholder | translate) : placeholder\"\n [formControlName]=\"name\"\n [label]=\"translatable ? (label | translate) : label\"\n #component>\n </ion-textarea>\n }\n @else if(type === 'checkbox') {\n <ion-item>\n <ion-checkbox\n #checkboxElement\n\n [mode]=\"mode\"\n [errorText]=\"getErrors(container)\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify\"\n [value]=\"value\"\n [checked]=\"checked\"\n [readonly]=\"readonly\"\n (ionChange)=\"checked = !checked\"\n [formControlName]=\"name\"\n #component>\n <span [innerHTML]=\"label | translate\"></span>\n </ion-checkbox>\n </ion-item>\n }\n @else if(type === 'radio') {\n <ion-radio-group [formControlName]=\"name\" [value]=\"value\" #component>\n <label class=\"dcf-radio-group-label\" [for]=\"path\">{{label | translate}}</label>\n @for(option of options; track option) {\n <ion-item>\n <ion-radio\n [errorText]=\"getErrors(container)\"\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [alignment]=\"alignment\"\n [justify]=\"justify\"\n [readonly]=\"readonly\"\n [value]=\"option.value\"\n >{{ translatable ? (option?.text | translate) : option?.text }}</ion-radio>\n </ion-item>\n }\n </ion-radio-group>\n }\n @else if(type === 'select') {\n <ion-select\n toggleIcon=\"chevron-down-outline\"\n expandedIcon=\"chevron-up-outline\"\n\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [label]=\"translatable ? (label | translate) : label\"\n [value]=\"value\"\n [fill]=\"fill\"\n [placeholder]=\"translatable ? (placeholder | translate) : placeholder\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [interface]=\"interface\" #component>\n @for(option of options; track option.value) {\n <ion-select-option [value]=\"option.value\">\n {{ option.text | translate }}\n </ion-select-option>\n }\n </ion-select>\n }\n @else {\n <ion-input\n [type]=\"type\"\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [inputmode]=\"inputmode\"\n [labelPlacement]=\"labelPlacement\"\n [required]=\"required !== undefined ? required : false\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [readonly]=\"readonly !== undefined ? readonly : null\"\n [max]=\"max !== undefined ? max : null\"\n [min]=\"min !== undefined ? min : null\"\n [pattern]=\"pattern !== undefined ? pattern : null\"\n [step]=\"step !== undefined ? step : null\"\n [fill]=\"fill\"\n [placeholder]=\"translatable ? (placeholder | translate) : placeholder\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [label]=\"translatable ? (label | translate) : label\" #component />\n }\n </div>\n </ng-container>\n}\n\n", styles: [".dcf-input-item.create,.dcf-input-item.update{margin-bottom:1.8rem;margin-top:0!important}.dcf-input-item.create.checkbox+.checkbox,.dcf-input-item.update.checkbox+.checkbox{margin-top:-.25rem!important}.dcf-input-item.create ion-item,.dcf-input-item.update ion-item{--border-color: transparent}.dcf-input-item.create ion-item.dcf-text-wrap ion-label>*,.dcf-input-item.update ion-item.dcf-text-wrap ion-label>*{white-space:wrap!important;word-break:break-all!important}.dcf-input-item.read ion-label,.dcf-input-item.delete ion-label{font-weight:600}@media (prefers-color-scheme: light){.dcf-input-item.read ion-label,.dcf-input-item.delete ion-label{color:var(--dcf-color-gray-7)}}.dcf-input-item.read ion-text,.dcf-input-item.delete ion-text{display:block;margin-top:.5rem!important}.dcf-input-item ion-item{--padding-end: 0rem;--padding-start: 0px !important;--padding-top: 0px !important;--background: transparent;--background-hover-opacity: .1;--background-activated-opacity: .15;--background-focused-opacity: .15}@media (prefers-color-scheme: dark){.dcf-input-item ion-item{--border-color: var(--dcf-color-gray-6)}}@media (prefers-color-scheme: light){.dcf-input-item ion-item{--background-hover: var(--dcf-color-primary);--background-focused: var(--dcf-color-primary);--border-color: var(--dcf-color-gray-2)}}.dcf-input-item ion-item span,.dcf-input-item ion-item ion-text{font-weight:400!important;font-size:.925rem;min-height:.5rem!important}.dcf-input-item ion-item span:not(.dcf-display-block),.dcf-input-item ion-item ion-text:not(.dcf-display-block){display:inline-block}.dcf-input-item ion-item span.dcf-display-block,.dcf-input-item ion-item ion-text.dcf-display-block{display:block!important}ion-textarea textarea{scrollbar-width:thin!important;margin-bottom:.5rem!important}ion-select.dcf-select-label-placement-floating::part(label){line-height:1.2rem!important}.dcf-proccessing,.dcf-proccessing *{pointer-events:none;touch-action:none;cursor:text}ion-checkbox{--size: 1.5rem;--checkbox-background-checked: var(--dcf-color-primary);--checkmark-width: 2px}ion-item{--inner-padding-start: .75rem}ion-checkbox::part(container){border-radius:50%;padding:3px}@media (prefers-color-scheme: light){ion-checkbox::part(container){border:2px solid var(--dcf-color-primary)}}ion-item .dcf-radio-group-label,ion-radio-group .dcf-radio-group-label{font-weight:600}ion-item .dcf-radio-group-label~ion-item,ion-radio-group .dcf-radio-group-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}ion-item+.dcf-helper,ion-radio-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-error{position:absolute;color:var(--dcf-color-danger)!important;font-size:.8rem!important;font-weight:600!important;line-height:1.1rem;box-sizing:border-box;z-index:9999;margin-top:0;animation-duration:.1s;animation-timing-function:ease-out;animation-fill-mode:both;animation-name:fadeTopSmallAnimation;display:flex;align-items:flex-start;gap:.25rem}.dcf-error .ti,.dcf-error ion-icon{position:relative;top:2px!important;min-width:20px;font-size:1rem!important;text-align:left}.dcf-helper{font-size:.875rem!important;font-weight:500;margin-top:.25rem;margin-bottom:-.75rem}.dcf-helper.dcf-has-action{cursor:pointer;color:var(--dcf-color-gray-7)!important;text-decoration:underline}.dcf-error+.dcf-helper{padding-top:1rem}@keyframes fadeTopSmallAnimation{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeBottomSmallAnimation{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeTopMediumAnimation{0%{opacity:0;transform:translateY(-50px)}to{opacity:1;transform:translateY(0)}}::ng-deep ion-textarea{min-height:80px!important;scrollbar-color:#888 #f0f0f0;scrollbar-width:thin}\n"] }]
3121
+ ], selector: 'ngx-decaf-crud-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], host: { '[attr.id]': 'uid' }, template: "@if(operation === 'read' || operation === 'delete') {\n <ng-container #component>\n <div [class]=\"'dcf-input-item ' + operation\">\n <ion-item>\n <ion-label>\n {{ label | translate }}<br />\n @if(value) {\n <ion-text [innerHTML]=\"type === 'password' ? '********' : value\"></ion-text>\n } @else {\n <br />\n }\n </ion-label>\n </ion-item>\n </div>\n </ng-container>\n} @else {\n <ng-container [formGroup]=\"getActiveFormGroup\">\n <div #container [class]=\"'dcf-input-item ' + (operation || 'create')\" (createGroupEvent)=\"multiple ? handleFieldsetCreateGroupEvent($event) : ''\">\n @if(type === 'textarea') {\n <ion-textarea\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [autoGrow]=\"true\"\n [required]=\"required !== undefined ? required : null\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [readonly]=\"readonly !== undefined ? readonly : null\"\n [inputmode]=\"inputmode\"\n [spellcheck]=\"spellcheck\"\n [rows]=\"rows\"\n [labelPlacement]=\"labelPlacement\"\n [value]=\"value\"\n [fill]=\"fill\"\n [errorText]=\"getErrors(container)\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [label]=\"label | translate\"\n #component>\n </ion-textarea>\n }\n @else if(type === 'checkbox') {\n <ion-item>\n <ion-checkbox\n #checkboxElement\n [mode]=\"mode\"\n [errorText]=\"getErrors(container)\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify\"\n [value]=\"value\"\n [checked]=\"checked\"\n [readonly]=\"readonly\"\n (ionChange)=\"checked = !checked\"\n [formControlName]=\"name\"\n #component>\n <span [innerHTML]=\"label | translate\"></span>\n </ion-checkbox>\n </ion-item>\n }\n @else if(type === 'radio') {\n <ion-radio-group [formControlName]=\"name\" [value]=\"value\" #component>\n <label class=\"dcf-radio-group-label\" [for]=\"path\">{{label | translate}}</label>\n @for(option of options; track option) {\n <ion-item>\n <ion-radio\n [errorText]=\"getErrors(container)\"\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [alignment]=\"alignment\"\n [justify]=\"justify\"\n [readonly]=\"readonly\"\n [value]=\"option.value\"\n >{{ translatable ? (option?.text | translate) : option?.text }}</ion-radio>\n </ion-item>\n }\n </ion-radio-group>\n }\n @else if(type === 'select') {\n <ion-select\n toggleIcon=\"chevron-down-outline\"\n expandedIcon=\"chevron-up-outline\"\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [label]=\"translatable ? (label | translate) : label\"\n [value]=\"value\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [interface]=\"interface\" #component>\n @for(option of options; track option.value) {\n <ion-select-option [value]=\"option.value\">\n {{ option.text | translate }}\n </ion-select-option>\n }\n </ion-select>\n }\n @else {\n <ion-input\n [type]=\"type\"\n [mode]=\"mode\"\n [hidden]=\"hidden\"\n [inputmode]=\"inputmode\"\n [labelPlacement]=\"labelPlacement\"\n [required]=\"required !== undefined ? required : false\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [readonly]=\"readonly !== undefined ? readonly : null\"\n [max]=\"max !== undefined ? max : null\"\n [min]=\"min !== undefined ? min : null\"\n [pattern]=\"pattern !== undefined ? pattern : null\"\n [step]=\"step !== undefined ? step : null\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [label]=\"label | translate\" #component />\n }\n </div>\n </ng-container>\n}\n\n", styles: [".dcf-input-item.create,.dcf-input-item.update{margin-bottom:1.8rem;margin-top:0!important}.dcf-input-item.create.checkbox+.checkbox,.dcf-input-item.update.checkbox+.checkbox{margin-top:-.25rem!important}.dcf-input-item.create ion-item,.dcf-input-item.update ion-item{--border-color: transparent}.dcf-input-item.create ion-item.dcf-text-wrap ion-label>*,.dcf-input-item.update ion-item.dcf-text-wrap ion-label>*{white-space:wrap!important;word-break:break-all!important}.dcf-input-item.read ion-label,.dcf-input-item.delete ion-label{font-weight:600}@media (prefers-color-scheme: light){.dcf-input-item.read ion-label,.dcf-input-item.delete ion-label{color:var(--dcf-color-gray-7)}}.dcf-input-item.read ion-text,.dcf-input-item.delete ion-text{display:block;margin-top:.5rem!important}.dcf-input-item ion-item{--padding-end: 0rem;--padding-start: 0px !important;--padding-top: 0px !important;--background: transparent;--background-hover-opacity: .1;--background-activated-opacity: .15;--background-focused-opacity: .15}@media (prefers-color-scheme: dark){.dcf-input-item ion-item{--border-color: var(--dcf-color-gray-6)}}@media (prefers-color-scheme: light){.dcf-input-item ion-item{--background-hover: var(--dcf-color-primary);--background-focused: var(--dcf-color-primary);--border-color: var(--dcf-color-gray-2)}}.dcf-input-item ion-item span,.dcf-input-item ion-item ion-text{font-weight:400!important;font-size:.925rem;min-height:.5rem!important}.dcf-input-item ion-item span:not(.dcf-display-block),.dcf-input-item ion-item ion-text:not(.dcf-display-block){display:inline-block}.dcf-input-item ion-item span.dcf-display-block,.dcf-input-item ion-item ion-text.dcf-display-block{display:block!important}ion-textarea textarea{scrollbar-width:thin!important;margin-bottom:.5rem!important}ion-select.dcf-select-label-placement-floating::part(label){line-height:1.2rem!important}.dcf-proccessing,.dcf-proccessing *{pointer-events:none;touch-action:none;cursor:text}ion-checkbox{--size: 1.5rem;--checkbox-background-checked: var(--dcf-color-primary);--checkmark-width: 2px}ion-item{--inner-padding-start: .75rem}ion-checkbox::part(container){border-radius:50%;padding:3px}@media (prefers-color-scheme: light){ion-checkbox::part(container){border:2px solid var(--dcf-color-primary)}}ion-item .dcf-radio-group-label,ion-radio-group .dcf-radio-group-label{font-weight:600}ion-item .dcf-radio-group-label~ion-item,ion-radio-group .dcf-radio-group-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}ion-item+.dcf-helper,ion-radio-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-error{position:absolute;color:var(--dcf-color-danger)!important;font-size:.8rem!important;font-weight:600!important;line-height:1.1rem;box-sizing:border-box;z-index:9999;margin-top:0;animation-duration:.1s;animation-timing-function:ease-out;animation-fill-mode:both;animation-name:fadeTopSmallAnimation;display:flex;align-items:flex-start;gap:.25rem}.dcf-error .ti,.dcf-error ion-icon{position:relative;top:2px!important;min-width:20px;font-size:1rem!important;text-align:left}.dcf-helper{font-size:.875rem!important;font-weight:500;margin-top:.25rem;margin-bottom:-.75rem}.dcf-helper.dcf-has-action{cursor:pointer;color:var(--dcf-color-gray-7)!important;text-decoration:underline}.dcf-error+.dcf-helper{padding-top:1rem}@keyframes fadeTopSmallAnimation{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeBottomSmallAnimation{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeTopMediumAnimation{0%{opacity:0;transform:translateY(-50px)}to{opacity:1;transform:translateY(0)}}::ng-deep ion-textarea{min-height:80px!important;scrollbar-color:#888 #f0f0f0;scrollbar-width:thin}\n"] }]
2708
3122
  }], propDecorators: { operation: [{
2709
3123
  type: Input,
2710
3124
  args: [{ required: true }]
@@ -2801,6 +3215,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2801
3215
  type: Input
2802
3216
  }], uid: [{
2803
3217
  type: Input
3218
+ }], page: [{
3219
+ type: Input
2804
3220
  }], translatable: [{
2805
3221
  type: Input
2806
3222
  }], activeFormGroup: [{
@@ -3089,12 +3505,7 @@ class NgxBaseComponent {
3089
3505
  const constructor = Model.get(modelName);
3090
3506
  if (!constructor)
3091
3507
  throw new InternalError('Cannot find model. was it registered with @model?');
3092
- let dbAdapterFlavour = getOnWindow('dbAdapterFlavour');
3093
- if (!dbAdapterFlavour && isDevelopmentMode()) {
3094
- const adapter = new RamAdapter({ user: 'user' });
3095
- dbAdapterFlavour = adapter.flavour;
3096
- }
3097
- this._repository = Repository.forModel(constructor, dbAdapterFlavour);
3508
+ this._repository = Repository.forModel(constructor);
3098
3509
  this.model = new constructor();
3099
3510
  if (this.model && !this.pk)
3100
3511
  this.pk =
@@ -3235,7 +3646,9 @@ class NgxBaseComponent {
3235
3646
  * an initialization message with the component name. This method is typically called
3236
3647
  * during the component's lifecycle setup.
3237
3648
  */
3238
- initialize() {
3649
+ async initialize(parseProps = true, skip) {
3650
+ if (!this.initialized && parseProps)
3651
+ return this.parseProps(this, skip || []);
3239
3652
  this.initialized = true;
3240
3653
  }
3241
3654
  /**
@@ -3315,11 +3728,15 @@ class NgxBaseComponent {
3315
3728
  * @protected
3316
3729
  * @memberOf NgxBaseComponent
3317
3730
  */
3318
- parseProps(instance) {
3731
+ parseProps(instance, skip) {
3319
3732
  Object.keys(instance).forEach((key) => {
3320
- if (Object.keys(this.props).includes(key))
3733
+ if (Object.keys(this.props).includes(key) && !skip.includes(key)) {
3321
3734
  this[key] = this.props[key];
3735
+ delete this.props[key];
3736
+ }
3322
3737
  });
3738
+ if (!this.initialized)
3739
+ this.initialized = true;
3323
3740
  }
3324
3741
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NgxBaseComponent, deps: [{ token: 'instanceToken' }], target: i0.ɵɵFactoryTarget.Component }); }
3325
3742
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: NgxBaseComponent, isStandalone: true, selector: "ng-component", inputs: { rendererId: "rendererId", model: "model", props: "props", item: "item", pk: "pk", route: "route", operations: "operations", uid: "uid", mapper: "mapper", locale: "locale", translatable: "translatable", className: "className", mode: "mode", renderChild: "renderChild" }, outputs: { listenEvent: "listenEvent" }, host: { properties: { "attr.id": "uid" } }, viewQueries: [{ propertyName: "component", first: true, predicate: ["component"], descendants: true, read: ElementRef, static: true }], usesOnChanges: true, ngImport: i0, template: '<div></div>', isInline: true }); }
@@ -3594,14 +4011,14 @@ let CrudFormComponent = class CrudFormComponent {
3594
4011
  });
3595
4012
  }
3596
4013
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CrudFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3597
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: CrudFormComponent, isStandalone: true, selector: "ngx-decaf-crud-form", inputs: { model: "model", modelId: "modelId", updateOn: "updateOn", target: "target", method: "method", options: "options", action: "action", operation: "operation", handlers: "handlers", formGroup: "formGroup", childOf: "childOf", rendererId: "rendererId", uid: "uid", allowClear: "allowClear" }, outputs: { submitEvent: "submitEvent" }, host: { properties: { "attr.id": "uid" } }, viewQueries: [{ propertyName: "component", first: true, predicate: ["reactiveForm"], descendants: true, read: ElementRef }], ngImport: i0, template: "@if(operation !== 'read' && operation !== 'delete') {\n <form #reactiveForm [id]=\"rendererId\" [formGroup]=\"formGroup\" (submit)=\"submit($event)\" novalidate [target]=\"target\">\n <ng-content #formContent></ng-content>\n <div class=\"dcf-buttons-container dcf-grid dcf-grid-collapse dcf-flex dcf-flex-left\">\n <div>\n <ion-button\n type=\"submit\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n {{ action ? action : options.buttons.submit.text}}\n </ion-button>\n </div>\n @if(!action) {\n <div>\n <ion-button fill=\"clear\" (click)=\"handleReset()\">\n @if(options.buttons.clear?.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.clear?.iconSlot\" [name]=\"options.buttons.clear?.icon\"></ion-icon>\n }\n Back\n </ion-button>\n </div>\n }\n </div>\n </form>\n} @else {\n <div [class]=\"'dcf-buttons-container dcf-grid dcf-grid-collapse dcf-flex dcf-flex-left ' + operation\" [id]=\"uid\">\n @if(operation === OperationKeys.READ && modelId) {\n <div>\n <ion-button\n (click)=\"handleDelete()\"\n color=\"danger\"\n type=\"button\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n Delete\n </ion-button>\n </div>\n\n }\n @if(operation === OperationKeys.CREATE || operation === OperationKeys.UPDATE) {\n\n <div>\n <ion-button\n type=\"submit\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n {{options.buttons.submit.text}}\n </ion-button>\n </div>\n }\n\n @if(options.buttons.clear) {\n <div>\n <ion-button fill=\"clear\" (click)=\"handleReset()\">\n @if(options.buttons.clear?.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.clear?.iconSlot\" [name]=\"options.buttons.clear?.icon\"></ion-icon>\n }\n {{ [OperationKeys.DELETE, OperationKeys.READ, OperationKeys.UPDATE].includes(operation) ? 'Back' : options.buttons.clear?.text}}\n </ion-button>\n </div>\n\n }\n </div>\n}\n\n", styles: [".dcf-buttons-container{margin-top:1.8rem;margin-bottom:0}@media (min-width: 768px){.dcf-buttons-container.dcf-flex{flex-direction:row-reverse}}@media (max-width: 767px){.dcf-buttons-container.dcf-flex div{width:100%}.dcf-buttons-container.dcf-flex ion-button{width:100%;margin-bottom:1rem}}form{padding:2rem 1rem}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
4014
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: CrudFormComponent, isStandalone: true, selector: "ngx-decaf-crud-form", inputs: { model: "model", modelId: "modelId", updateOn: "updateOn", target: "target", method: "method", options: "options", action: "action", operation: "operation", handlers: "handlers", formGroup: "formGroup", childOf: "childOf", rendererId: "rendererId", uid: "uid", allowClear: "allowClear" }, outputs: { submitEvent: "submitEvent" }, host: { properties: { "attr.id": "uid" } }, viewQueries: [{ propertyName: "component", first: true, predicate: ["reactiveForm"], descendants: true, read: ElementRef }], ngImport: i0, template: "@if(operation !== 'read' && operation !== 'delete') {\n <form #reactiveForm [id]=\"rendererId\" [formGroup]=\"formGroup\" (submit)=\"submit($event)\" novalidate [target]=\"target\">\n <ng-content #formContent></ng-content>\n <div class=\"dcf-buttons-container dcf-grid dcf-grid-collapse dcf-flex dcf-flex-left\">\n <div>\n <ion-button type=\"submit\" [expand]=\"action ? 'block' : 'default'\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n {{ action ? action : options.buttons.submit.text}}\n </ion-button>\n </div>\n @if(!action) {\n <div>\n <ion-button fill=\"clear\" (click)=\"handleReset()\">\n @if(options.buttons.clear?.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.clear?.iconSlot\" [name]=\"options.buttons.clear?.icon\"></ion-icon>\n }\n Back\n </ion-button>\n </div>\n }\n </div>\n </form>\n} @else {\n <div [class]=\"'dcf-buttons-container dcf-grid dcf-grid-collapse dcf-flex dcf-flex-left ' + operation\" [id]=\"uid\">\n @if(operation === OperationKeys.READ && modelId) {\n <div>\n <ion-button\n (click)=\"handleDelete()\"\n color=\"danger\"\n type=\"button\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n Delete\n </ion-button>\n </div>\n\n }\n @if(operation === OperationKeys.CREATE || operation === OperationKeys.UPDATE) {\n\n <div>\n <ion-button\n type=\"submit\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n {{options.buttons.submit.text}}\n </ion-button>\n </div>\n }\n\n @if(options.buttons.clear) {\n <div>\n <ion-button fill=\"clear\" (click)=\"handleReset()\">\n @if(options.buttons.clear?.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.clear?.iconSlot\" [name]=\"options.buttons.clear?.icon\"></ion-icon>\n }\n {{ [OperationKeys.DELETE, OperationKeys.READ, OperationKeys.UPDATE].includes(operation) ? 'Back' : options.buttons.clear?.text}}\n </ion-button>\n </div>\n\n }\n </div>\n}\n\n", styles: [".dcf-buttons-container{margin-top:1.8rem;margin-bottom:0}@media (min-width: 768px){.dcf-buttons-container.dcf-flex{flex-direction:row-reverse}}@media (max-width: 767px){.dcf-buttons-container.dcf-flex div{width:100%}.dcf-buttons-container.dcf-flex ion-button{width:100%;margin-bottom:1rem}}form{padding:2rem 1rem}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
3598
4015
  };
3599
4016
  CrudFormComponent = __decorate([
3600
4017
  Dynamic()
3601
4018
  ], CrudFormComponent);
3602
4019
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CrudFormComponent, decorators: [{
3603
4020
  type: Component,
3604
- args: [{ standalone: true, selector: 'ngx-decaf-crud-form', imports: [ReactiveFormsModule, IonButton, IonIcon], host: { '[attr.id]': 'uid' }, template: "@if(operation !== 'read' && operation !== 'delete') {\n <form #reactiveForm [id]=\"rendererId\" [formGroup]=\"formGroup\" (submit)=\"submit($event)\" novalidate [target]=\"target\">\n <ng-content #formContent></ng-content>\n <div class=\"dcf-buttons-container dcf-grid dcf-grid-collapse dcf-flex dcf-flex-left\">\n <div>\n <ion-button\n type=\"submit\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n {{ action ? action : options.buttons.submit.text}}\n </ion-button>\n </div>\n @if(!action) {\n <div>\n <ion-button fill=\"clear\" (click)=\"handleReset()\">\n @if(options.buttons.clear?.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.clear?.iconSlot\" [name]=\"options.buttons.clear?.icon\"></ion-icon>\n }\n Back\n </ion-button>\n </div>\n }\n </div>\n </form>\n} @else {\n <div [class]=\"'dcf-buttons-container dcf-grid dcf-grid-collapse dcf-flex dcf-flex-left ' + operation\" [id]=\"uid\">\n @if(operation === OperationKeys.READ && modelId) {\n <div>\n <ion-button\n (click)=\"handleDelete()\"\n color=\"danger\"\n type=\"button\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n Delete\n </ion-button>\n </div>\n\n }\n @if(operation === OperationKeys.CREATE || operation === OperationKeys.UPDATE) {\n\n <div>\n <ion-button\n type=\"submit\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n {{options.buttons.submit.text}}\n </ion-button>\n </div>\n }\n\n @if(options.buttons.clear) {\n <div>\n <ion-button fill=\"clear\" (click)=\"handleReset()\">\n @if(options.buttons.clear?.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.clear?.iconSlot\" [name]=\"options.buttons.clear?.icon\"></ion-icon>\n }\n {{ [OperationKeys.DELETE, OperationKeys.READ, OperationKeys.UPDATE].includes(operation) ? 'Back' : options.buttons.clear?.text}}\n </ion-button>\n </div>\n\n }\n </div>\n}\n\n", styles: [".dcf-buttons-container{margin-top:1.8rem;margin-bottom:0}@media (min-width: 768px){.dcf-buttons-container.dcf-flex{flex-direction:row-reverse}}@media (max-width: 767px){.dcf-buttons-container.dcf-flex div{width:100%}.dcf-buttons-container.dcf-flex ion-button{width:100%;margin-bottom:1rem}}form{padding:2rem 1rem}\n"] }]
4021
+ args: [{ standalone: true, selector: 'ngx-decaf-crud-form', imports: [ReactiveFormsModule, IonButton, IonIcon], host: { '[attr.id]': 'uid' }, template: "@if(operation !== 'read' && operation !== 'delete') {\n <form #reactiveForm [id]=\"rendererId\" [formGroup]=\"formGroup\" (submit)=\"submit($event)\" novalidate [target]=\"target\">\n <ng-content #formContent></ng-content>\n <div class=\"dcf-buttons-container dcf-grid dcf-grid-collapse dcf-flex dcf-flex-left\">\n <div>\n <ion-button type=\"submit\" [expand]=\"action ? 'block' : 'default'\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n {{ action ? action : options.buttons.submit.text}}\n </ion-button>\n </div>\n @if(!action) {\n <div>\n <ion-button fill=\"clear\" (click)=\"handleReset()\">\n @if(options.buttons.clear?.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.clear?.iconSlot\" [name]=\"options.buttons.clear?.icon\"></ion-icon>\n }\n Back\n </ion-button>\n </div>\n }\n </div>\n </form>\n} @else {\n <div [class]=\"'dcf-buttons-container dcf-grid dcf-grid-collapse dcf-flex dcf-flex-left ' + operation\" [id]=\"uid\">\n @if(operation === OperationKeys.READ && modelId) {\n <div>\n <ion-button\n (click)=\"handleDelete()\"\n color=\"danger\"\n type=\"button\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n Delete\n </ion-button>\n </div>\n\n }\n @if(operation === OperationKeys.CREATE || operation === OperationKeys.UPDATE) {\n\n <div>\n <ion-button\n type=\"submit\">\n @if(options.buttons.submit.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.submit.iconSlot\" [name]=\"options.buttons.submit.icon\"></ion-icon>\n }\n {{options.buttons.submit.text}}\n </ion-button>\n </div>\n }\n\n @if(options.buttons.clear) {\n <div>\n <ion-button fill=\"clear\" (click)=\"handleReset()\">\n @if(options.buttons.clear?.icon) {\n <ion-icon aria-hidden=\"true\" [slot]=\"options.buttons.clear?.iconSlot\" [name]=\"options.buttons.clear?.icon\"></ion-icon>\n }\n {{ [OperationKeys.DELETE, OperationKeys.READ, OperationKeys.UPDATE].includes(operation) ? 'Back' : options.buttons.clear?.text}}\n </ion-button>\n </div>\n\n }\n </div>\n}\n\n", styles: [".dcf-buttons-container{margin-top:1.8rem;margin-bottom:0}@media (min-width: 768px){.dcf-buttons-container.dcf-flex{flex-direction:row-reverse}}@media (max-width: 767px){.dcf-buttons-container.dcf-flex div{width:100%}.dcf-buttons-container.dcf-flex ion-button{width:100%;margin-bottom:1rem}}form{padding:2rem 1rem}\n"] }]
3605
4022
  }], propDecorators: { model: [{
3606
4023
  type: Input
3607
4024
  }], modelId: [{
@@ -3842,7 +4259,7 @@ let EmptyStateComponent = class EmptyStateComponent extends NgxBaseComponent {
3842
4259
  * @memberOf EmptyStateComponent
3843
4260
  */
3844
4261
  async ngOnInit() {
3845
- this.parseProps(this);
4262
+ this.initialize();
3846
4263
  this.translatable = stringToBoolean(this.translatable);
3847
4264
  this.showIcon = stringToBoolean(this.showIcon);
3848
4265
  this.locale = this.getLocale(this.translatable);
@@ -4580,7 +4997,7 @@ let FieldsetComponent = class FieldsetComponent extends NgxBaseComponent {
4580
4997
  return this.mapper;
4581
4998
  }
4582
4999
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FieldsetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4583
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FieldsetComponent, isStandalone: true, selector: "ngx-decaf-fieldset", inputs: { name: "name", childOf: "childOf", uid: "uid", customTypes: "customTypes", operation: "operation", formGroup: "formGroup", title: "title", description: "description", target: "target", multiple: "multiple", value: "value", handlers: "handlers" }, host: { properties: { "attr.id": "overriode " } }, viewQueries: [{ propertyName: "accordionComponent", first: true, predicate: ["accordionComponent"], descendants: true }], usesInheritance: true, ngImport: i0, template: "\n\n<fieldset\n (fieldsetAddGroupEvent)=\"handleCreateItem($event)\"\n (fieldsetRemoveGroupEvent)=\"handleRemoveItem(undefined, $event)\"\n [class]=\"'dcf-fieldset ' + operation\"\n #component>\n <ion-accordion-group [class.open]=\"isOpen\" [class.hasValidationErrors]=\"hasValidationErrors\" (validationErrorEvent)=\"handleValidationError($event)\" #accordionComponent>\n <ion-accordion value=\"open\">\n <ion-item slot=\"header\" (click)=\"handleAccordionToggle($event)\">\n <div class=\"dcf-grid dcf-grid-collapse dcf-flex dcf-flex-middle dcf-width-1-1\">\n <div class=\"dcf-width-expand\">\n <legend>{{ name | translate }}</legend>\n </div>\n @if(!isRequired && ['create', 'update'].includes(operation)) {\n <div class=\"dcf-width-auto dcf-delete\">\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleRemoveComponent($event)\">\n <ion-icon name=\"trash-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n </div>\n }\n </div>\n </ion-item>\n <div slot=\"content\" [attr.aria-hidden]=\"!isOpen\">\n @if(multiple && items.length) {\n <ion-list class=\"dcf-fields-list\">\n <ion-reorder-group [formGroup]=\"formGroup.parent\" [disabled]=\"updatingItem\" (ionItemReorder)=\"handleReorderItems($any($event))\" #accordionComponent>\n @for(item of items; track item.index) {\n <ion-item [class.not-unique]=\"item.title === isUniqueError\" [class.updating]=\"updatingItem?.[pk] === item.title\" lines=\"full\" [button]=\"false\">\n @if(items?.length > 1 && !updatingItem) {\n <ion-reorder slot=\"start\">\n <ion-icon name=\"swap-vertical-outline\"></ion-icon>\n </ion-reorder>\n } @else {\n <div slot=\"start\">\n <ion-icon class=\"dcf-reorder-disabled\" size=\"small\" name=\"swap-vertical-outline\" disabled></ion-icon>\n </div>\n }\n <ion-label [color]=\"(item.title === isUniqueError && !updatingItem?.[pk] === item.title) ? 'danger' : ''\">{{ item.index }}. {{ item.title }}\n @if(item.description?.length > 0) {\n <br />\n <ion-text class=\"dcf-subtitle\">{{item.description}}</ion-text>\n }\n </ion-label>\n @if(!updatingItem || updatingItem?.[pk] !== item.title) {\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleUpdateItem(item.title, $index)\">\n <ion-icon name=\"create-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n }\n\n @if(!updatingItem) {\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleRemoveItem(item.title)\">\n <ion-icon name=\"trash-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n }\n </ion-item>\n }\n </ion-reorder-group>\n </ion-list>\n }\n\n <ng-content></ng-content>\n\n @if(multiple && ['create', 'update'].includes(operation)) {\n @if(isUniqueError) {\n <div class=\"dcf-not-unique-container dcf-animation dcf-animation-bottom-small dcf-animation-fast\">\n <div class=\" dcf-grid dcf-grid-collapse dcf-width-1-1 \">\n <div class=\"dcf-auto\" [attr.style]=\"'max-width: 50px'\">\n <ion-icon name=\"alert-circle-outline\"></ion-icon>\n </div>\n <div class=\"dcf-width-expand\">\n <ion-text color=\"danger\" class=\"dcf-text-small\">{{ locale + '.not_unique' | translate : { value: isUniqueError } }}</ion-text>\n </div>\n </div>\n </div>\n }\n <div class=\"dcf-margin-bottom dcf-grid dcf-grid-collapse dcf-flex\">\n @if(updatingItem) {\n <ion-button size=\"small\" fill=\"clear\" color=\"danger\" (click)=\"handleCancelUpdateItem()\">\n {{ buttonCancelLabel }}\n </ion-button>\n }\n <ion-button size=\"small\" fill=\"clear\" class=\"dcf-button-add\" (click)=\"handleCreateItem()\">\n <ion-icon name=\"add-outline\" slot=\"start\"></ion-icon>\n {{buttonLabel}}\n </ion-button>\n\n </div>\n }\n\n </div>\n </ion-accordion>\n </ion-accordion-group>\n</fieldset>\n\n", styles: ["ion-accordion-group ion-item[slot=header] .dcf-delete{width:30px}ion-accordion-group ion-item[slot=header] .dcf-delete ion-button{transform:translateY(-2px)}ion-accordion-group ion-item[slot=header] .dcf-delete ion-icon{font-size:1.15rem}::ng-deep ion-accordion ngx-decaf-crud-field:last-child ion-item{--inner-border-width: 0px !important;--border-width: 0px !important}.dcf-fieldset{margin-bottom:1.8rem;margin-top:1rem;padding-bottom:0;padding-top:1rem;background:var(--dcf-card-background);border-radius:6px;height:100%}@media (prefers-color-scheme: light){.dcf-fieldset{border:1px solid var(--dcf-color-gray-3)}.dcf-fieldset .dcf-button-add{color:var(--ion-color-dark)!important}}@media (prefers-color-scheme: dark){.dcf-fieldset{border:1px solid var(--dcf-color-step-400)}.dcf-fieldset .dcf-button-add{color:var(--ion-color-gray-2)}}.dcf-fieldset.read,.dcf-fieldset.delete{margin-top:1.25rem;padding-bottom:1rem}.dcf-fieldset.read [slot=content],.dcf-fieldset.delete [slot=content]{padding-top:0!important}.dcf-fieldset.read ion-accordion,.dcf-fieldset.delete ion-accordion{margin-bottom:0rem!important}@media (prefers-color-scheme: dark){.dcf-fieldset.read,.dcf-fieldset.delete{border:1px solid var(--dcf-color-gray-6)}}.dcf-fieldset ion-accordion{background:var(--dcf-card-background);margin-bottom:1rem}.dcf-fieldset ion-accordion.accordion-collapsing,.dcf-fieldset ion-accordion.accordion-collapsed{margin-bottom:1rem}.dcf-fieldset ion-accordion ion-item[slot=header]{--border-color: transparent;--border-radius: 6px;--inner-border-width: 0;--padding-start: 12px}.dcf-fieldset ion-accordion ion-item[slot=header] legend{font-weight:600;font-size:1rem;margin:0}@media (prefers-color-scheme: light){.dcf-fieldset ion-accordion ion-item[slot=header] legend{color:var(--dcf-color-gray-7)}}.dcf-fieldset ion-accordion [slot=content]{padding-top:2rem!important;padding-inline:.75rem}.dcf-not-unique-container{display:flex;justify-content:center;align-items:center;margin-bottom:1rem;flex:1 1 auto}.dcf-not-unique-container>div{display:flex;justify-content:center;align-items:center}.dcf-not-unique-container ion-icon{transform:translatey(2px);margin-right:5px}.dcf-fields-list{margin-top:-1rem;margin-bottom:1rem;padding:0!important}.dcf-fields-list ion-item{--min-height: 50px;--padding-top: .25rem;--padding-bottom: .25rem;--padding-start: .75rem;--padding-end: .75rem;--inner-padding-start: 0px !important;--inner-padding-end: 0px !important;--border-color: var(--dcf-color-gray-2) !important;border:1px solid transparent;box-sizing:border-box}.dcf-fields-list ion-item ion-icon.dcf-reorder-disabled{width:1rem;transform:translatey(2px);color:var(--dcf-color-gray-4)}.dcf-fields-list ion-item.updating{--background: rgba(var(--dcf-color-primary-rgb), .1) !important}.dcf-fields-list ion-item.not-unique{--background: rgba(var(--dcf-color-danger-rgb), .05) !important}.dcf-fields-list ion-item .dcf-subtitle{font-size:.8rem;color:var(--dcf-color-gray-7)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "ngmodule", type: ReactiveFormsModule }, { 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: IonAccordionGroup, selector: "ion-accordion-group", inputs: ["animated", "disabled", "expand", "mode", "multiple", "readonly", "value"] }, { kind: "component", type: IonAccordion, selector: "ion-accordion", inputs: ["disabled", "mode", "readonly", "toggleIcon", "toggleIconSlot", "value"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonReorder, selector: "ion-reorder" }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonReorderGroup, selector: "ion-reorder-group", inputs: ["disabled"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
5000
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FieldsetComponent, isStandalone: true, selector: "ngx-decaf-fieldset", inputs: { name: "name", childOf: "childOf", page: "page", uid: "uid", customTypes: "customTypes", operation: "operation", formGroup: "formGroup", title: "title", description: "description", target: "target", multiple: "multiple", value: "value", handlers: "handlers" }, host: { properties: { "attr.id": "overriode " } }, viewQueries: [{ propertyName: "accordionComponent", first: true, predicate: ["accordionComponent"], descendants: true }], usesInheritance: true, ngImport: i0, template: "\n\n<fieldset\n (fieldsetAddGroupEvent)=\"handleCreateItem($event)\"\n (fieldsetRemoveGroupEvent)=\"handleRemoveItem(undefined, $event)\"\n [class]=\"'dcf-fieldset ' + operation\"\n #component>\n <ion-accordion-group [class.open]=\"isOpen\" [class.hasValidationErrors]=\"hasValidationErrors\" (validationErrorEvent)=\"handleValidationError($event)\" #accordionComponent>\n <ion-accordion value=\"open\">\n <ion-item slot=\"header\" (click)=\"handleAccordionToggle($event)\">\n <div class=\"dcf-grid dcf-grid-collapse dcf-flex dcf-flex-middle dcf-width-1-1\">\n <div class=\"dcf-width-expand\">\n <legend>{{ name | translate }}</legend>\n </div>\n @if(!isRequired && ['create', 'update'].includes(operation)) {\n <div class=\"dcf-width-auto dcf-delete\">\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleRemoveComponent($event)\">\n <ion-icon name=\"trash-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n </div>\n }\n </div>\n </ion-item>\n <div slot=\"content\" [attr.aria-hidden]=\"!isOpen\">\n @if(multiple && items.length) {\n <ion-list class=\"dcf-fields-list\">\n <ion-reorder-group [formGroup]=\"formGroup.parent\" [disabled]=\"updatingItem\" (ionItemReorder)=\"handleReorderItems($any($event))\" #accordionComponent>\n @for(item of items; track item.index) {\n <ion-item [class.not-unique]=\"item.title === isUniqueError\" [class.updating]=\"updatingItem?.[pk] === item.title\" lines=\"full\" [button]=\"false\">\n @if(items?.length > 1 && !updatingItem) {\n <ion-reorder slot=\"start\">\n <ion-icon name=\"swap-vertical-outline\"></ion-icon>\n </ion-reorder>\n } @else {\n <div slot=\"start\">\n <ion-icon class=\"dcf-reorder-disabled\" size=\"small\" name=\"swap-vertical-outline\" disabled></ion-icon>\n </div>\n }\n <ion-label [color]=\"(item.title === isUniqueError && !updatingItem?.[pk] === item.title) ? 'danger' : ''\">{{ item.index }}. {{ item.title }}\n @if(item.description?.length > 0) {\n <br />\n <ion-text class=\"dcf-subtitle\">{{item.description}}</ion-text>\n }\n </ion-label>\n @if(!updatingItem || updatingItem?.[pk] !== item.title) {\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleUpdateItem(item.title, $index)\">\n <ion-icon name=\"create-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n }\n\n @if(!updatingItem) {\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleRemoveItem(item.title)\">\n <ion-icon name=\"trash-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n }\n </ion-item>\n }\n </ion-reorder-group>\n </ion-list>\n }\n\n <ng-content select=\"[slot=content]\"></ng-content>\n\n @if(multiple && ['create', 'update'].includes(operation)) {\n @if(isUniqueError) {\n <div class=\"dcf-not-unique-container dcf-animation dcf-animation-bottom-small dcf-animation-fast\">\n <div class=\" dcf-grid dcf-grid-collapse dcf-width-1-1 \">\n <div class=\"dcf-auto\" [attr.style]=\"'max-width: 50px'\">\n <ion-icon name=\"alert-circle-outline\"></ion-icon>\n </div>\n <div class=\"dcf-width-expand\">\n <ion-text color=\"danger\" class=\"dcf-text-small\">{{ locale + '.not_unique' | translate : { value: isUniqueError } }}</ion-text>\n </div>\n </div>\n </div>\n }\n <div class=\"dcf-margin-bottom dcf-grid dcf-grid-collapse dcf-flex\">\n @if(updatingItem) {\n <ion-button size=\"small\" fill=\"clear\" color=\"danger\" (click)=\"handleCancelUpdateItem()\">\n {{ buttonCancelLabel }}\n </ion-button>\n }\n <ion-button size=\"small\" fill=\"clear\" class=\"dcf-button-add\" (click)=\"handleCreateItem()\">\n <ion-icon name=\"add-outline\" slot=\"start\"></ion-icon>\n {{buttonLabel}}\n </ion-button>\n\n </div>\n }\n\n </div>\n </ion-accordion>\n </ion-accordion-group>\n</fieldset>\n\n", styles: ["ion-accordion-group ion-item[slot=header] .dcf-delete{width:30px}ion-accordion-group ion-item[slot=header] .dcf-delete ion-button{transform:translateY(-2px)}ion-accordion-group ion-item[slot=header] .dcf-delete ion-icon{font-size:1.15rem}::ng-deep ion-accordion ngx-decaf-crud-field:last-child ion-item{--inner-border-width: 0px !important;--border-width: 0px !important}.dcf-fieldset{margin-bottom:1.8rem;margin-top:1rem;padding-bottom:0;padding-top:1rem;background:var(--dcf-card-background);border-radius:6px;height:100%}@media (prefers-color-scheme: light){.dcf-fieldset{border:1px solid var(--dcf-color-gray-3)}.dcf-fieldset .dcf-button-add{color:var(--ion-color-dark)!important}}@media (prefers-color-scheme: dark){.dcf-fieldset{border:1px solid var(--dcf-color-step-400)}.dcf-fieldset .dcf-button-add{color:var(--ion-color-gray-2)}}.dcf-fieldset.read,.dcf-fieldset.delete{margin-top:1.25rem;padding-bottom:1rem}.dcf-fieldset.read [slot=content],.dcf-fieldset.delete [slot=content]{padding-top:0!important}.dcf-fieldset.read ion-accordion,.dcf-fieldset.delete ion-accordion{margin-bottom:0rem!important}@media (prefers-color-scheme: dark){.dcf-fieldset.read,.dcf-fieldset.delete{border:1px solid var(--dcf-color-gray-6)}}.dcf-fieldset ion-accordion{background:var(--dcf-card-background);margin-bottom:1rem}.dcf-fieldset ion-accordion.accordion-collapsing,.dcf-fieldset ion-accordion.accordion-collapsed{margin-bottom:1rem}.dcf-fieldset ion-accordion ion-item[slot=header]{--border-color: transparent;--border-radius: 6px;--inner-border-width: 0;--padding-start: 12px}.dcf-fieldset ion-accordion ion-item[slot=header] legend{font-weight:600;font-size:1rem;margin:0}@media (prefers-color-scheme: light){.dcf-fieldset ion-accordion ion-item[slot=header] legend{color:var(--dcf-color-gray-7)}}.dcf-fieldset ion-accordion [slot=content]{padding-top:2rem!important;padding-inline:.75rem}.dcf-not-unique-container{display:flex;justify-content:center;align-items:center;margin-bottom:1rem;flex:1 1 auto}.dcf-not-unique-container>div{display:flex;justify-content:center;align-items:center}.dcf-not-unique-container ion-icon{transform:translatey(2px);margin-right:5px}.dcf-fields-list{margin-top:-1rem;margin-bottom:1rem;padding:0!important}.dcf-fields-list ion-item{--min-height: 50px;--padding-top: .25rem;--padding-bottom: .25rem;--padding-start: .75rem;--padding-end: .75rem;--inner-padding-start: 0px !important;--inner-padding-end: 0px !important;--border-color: var(--dcf-color-gray-2) !important;border:1px solid transparent;box-sizing:border-box}.dcf-fields-list ion-item ion-icon.dcf-reorder-disabled{width:1rem;transform:translatey(2px);color:var(--dcf-color-gray-4)}.dcf-fields-list ion-item.updating{--background: rgba(var(--dcf-color-primary-rgb), .1) !important}.dcf-fields-list ion-item.not-unique{--background: rgba(var(--dcf-color-danger-rgb), .05) !important}.dcf-fields-list ion-item .dcf-subtitle{font-size:.8rem;color:var(--dcf-color-gray-7)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "ngmodule", type: ReactiveFormsModule }, { 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: IonAccordionGroup, selector: "ion-accordion-group", inputs: ["animated", "disabled", "expand", "mode", "multiple", "readonly", "value"] }, { kind: "component", type: IonAccordion, selector: "ion-accordion", inputs: ["disabled", "mode", "readonly", "toggleIcon", "toggleIconSlot", "value"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonReorder, selector: "ion-reorder" }, { kind: "component", type: IonReorderGroup, selector: "ion-reorder-group", inputs: ["disabled"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
4584
5001
  };
4585
5002
  FieldsetComponent = __decorate([
4586
5003
  Dynamic(),
@@ -4596,12 +5013,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4596
5013
  IonList,
4597
5014
  IonItem,
4598
5015
  IonLabel,
4599
- IonReorder,
4600
5016
  IonText,
5017
+ IonReorder,
4601
5018
  IonReorderGroup,
4602
5019
  IonButton,
4603
5020
  IonIcon,
4604
- ], host: { '[attr.id]': 'overriode ' }, template: "\n\n<fieldset\n (fieldsetAddGroupEvent)=\"handleCreateItem($event)\"\n (fieldsetRemoveGroupEvent)=\"handleRemoveItem(undefined, $event)\"\n [class]=\"'dcf-fieldset ' + operation\"\n #component>\n <ion-accordion-group [class.open]=\"isOpen\" [class.hasValidationErrors]=\"hasValidationErrors\" (validationErrorEvent)=\"handleValidationError($event)\" #accordionComponent>\n <ion-accordion value=\"open\">\n <ion-item slot=\"header\" (click)=\"handleAccordionToggle($event)\">\n <div class=\"dcf-grid dcf-grid-collapse dcf-flex dcf-flex-middle dcf-width-1-1\">\n <div class=\"dcf-width-expand\">\n <legend>{{ name | translate }}</legend>\n </div>\n @if(!isRequired && ['create', 'update'].includes(operation)) {\n <div class=\"dcf-width-auto dcf-delete\">\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleRemoveComponent($event)\">\n <ion-icon name=\"trash-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n </div>\n }\n </div>\n </ion-item>\n <div slot=\"content\" [attr.aria-hidden]=\"!isOpen\">\n @if(multiple && items.length) {\n <ion-list class=\"dcf-fields-list\">\n <ion-reorder-group [formGroup]=\"formGroup.parent\" [disabled]=\"updatingItem\" (ionItemReorder)=\"handleReorderItems($any($event))\" #accordionComponent>\n @for(item of items; track item.index) {\n <ion-item [class.not-unique]=\"item.title === isUniqueError\" [class.updating]=\"updatingItem?.[pk] === item.title\" lines=\"full\" [button]=\"false\">\n @if(items?.length > 1 && !updatingItem) {\n <ion-reorder slot=\"start\">\n <ion-icon name=\"swap-vertical-outline\"></ion-icon>\n </ion-reorder>\n } @else {\n <div slot=\"start\">\n <ion-icon class=\"dcf-reorder-disabled\" size=\"small\" name=\"swap-vertical-outline\" disabled></ion-icon>\n </div>\n }\n <ion-label [color]=\"(item.title === isUniqueError && !updatingItem?.[pk] === item.title) ? 'danger' : ''\">{{ item.index }}. {{ item.title }}\n @if(item.description?.length > 0) {\n <br />\n <ion-text class=\"dcf-subtitle\">{{item.description}}</ion-text>\n }\n </ion-label>\n @if(!updatingItem || updatingItem?.[pk] !== item.title) {\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleUpdateItem(item.title, $index)\">\n <ion-icon name=\"create-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n }\n\n @if(!updatingItem) {\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleRemoveItem(item.title)\">\n <ion-icon name=\"trash-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n }\n </ion-item>\n }\n </ion-reorder-group>\n </ion-list>\n }\n\n <ng-content></ng-content>\n\n @if(multiple && ['create', 'update'].includes(operation)) {\n @if(isUniqueError) {\n <div class=\"dcf-not-unique-container dcf-animation dcf-animation-bottom-small dcf-animation-fast\">\n <div class=\" dcf-grid dcf-grid-collapse dcf-width-1-1 \">\n <div class=\"dcf-auto\" [attr.style]=\"'max-width: 50px'\">\n <ion-icon name=\"alert-circle-outline\"></ion-icon>\n </div>\n <div class=\"dcf-width-expand\">\n <ion-text color=\"danger\" class=\"dcf-text-small\">{{ locale + '.not_unique' | translate : { value: isUniqueError } }}</ion-text>\n </div>\n </div>\n </div>\n }\n <div class=\"dcf-margin-bottom dcf-grid dcf-grid-collapse dcf-flex\">\n @if(updatingItem) {\n <ion-button size=\"small\" fill=\"clear\" color=\"danger\" (click)=\"handleCancelUpdateItem()\">\n {{ buttonCancelLabel }}\n </ion-button>\n }\n <ion-button size=\"small\" fill=\"clear\" class=\"dcf-button-add\" (click)=\"handleCreateItem()\">\n <ion-icon name=\"add-outline\" slot=\"start\"></ion-icon>\n {{buttonLabel}}\n </ion-button>\n\n </div>\n }\n\n </div>\n </ion-accordion>\n </ion-accordion-group>\n</fieldset>\n\n", styles: ["ion-accordion-group ion-item[slot=header] .dcf-delete{width:30px}ion-accordion-group ion-item[slot=header] .dcf-delete ion-button{transform:translateY(-2px)}ion-accordion-group ion-item[slot=header] .dcf-delete ion-icon{font-size:1.15rem}::ng-deep ion-accordion ngx-decaf-crud-field:last-child ion-item{--inner-border-width: 0px !important;--border-width: 0px !important}.dcf-fieldset{margin-bottom:1.8rem;margin-top:1rem;padding-bottom:0;padding-top:1rem;background:var(--dcf-card-background);border-radius:6px;height:100%}@media (prefers-color-scheme: light){.dcf-fieldset{border:1px solid var(--dcf-color-gray-3)}.dcf-fieldset .dcf-button-add{color:var(--ion-color-dark)!important}}@media (prefers-color-scheme: dark){.dcf-fieldset{border:1px solid var(--dcf-color-step-400)}.dcf-fieldset .dcf-button-add{color:var(--ion-color-gray-2)}}.dcf-fieldset.read,.dcf-fieldset.delete{margin-top:1.25rem;padding-bottom:1rem}.dcf-fieldset.read [slot=content],.dcf-fieldset.delete [slot=content]{padding-top:0!important}.dcf-fieldset.read ion-accordion,.dcf-fieldset.delete ion-accordion{margin-bottom:0rem!important}@media (prefers-color-scheme: dark){.dcf-fieldset.read,.dcf-fieldset.delete{border:1px solid var(--dcf-color-gray-6)}}.dcf-fieldset ion-accordion{background:var(--dcf-card-background);margin-bottom:1rem}.dcf-fieldset ion-accordion.accordion-collapsing,.dcf-fieldset ion-accordion.accordion-collapsed{margin-bottom:1rem}.dcf-fieldset ion-accordion ion-item[slot=header]{--border-color: transparent;--border-radius: 6px;--inner-border-width: 0;--padding-start: 12px}.dcf-fieldset ion-accordion ion-item[slot=header] legend{font-weight:600;font-size:1rem;margin:0}@media (prefers-color-scheme: light){.dcf-fieldset ion-accordion ion-item[slot=header] legend{color:var(--dcf-color-gray-7)}}.dcf-fieldset ion-accordion [slot=content]{padding-top:2rem!important;padding-inline:.75rem}.dcf-not-unique-container{display:flex;justify-content:center;align-items:center;margin-bottom:1rem;flex:1 1 auto}.dcf-not-unique-container>div{display:flex;justify-content:center;align-items:center}.dcf-not-unique-container ion-icon{transform:translatey(2px);margin-right:5px}.dcf-fields-list{margin-top:-1rem;margin-bottom:1rem;padding:0!important}.dcf-fields-list ion-item{--min-height: 50px;--padding-top: .25rem;--padding-bottom: .25rem;--padding-start: .75rem;--padding-end: .75rem;--inner-padding-start: 0px !important;--inner-padding-end: 0px !important;--border-color: var(--dcf-color-gray-2) !important;border:1px solid transparent;box-sizing:border-box}.dcf-fields-list ion-item ion-icon.dcf-reorder-disabled{width:1rem;transform:translatey(2px);color:var(--dcf-color-gray-4)}.dcf-fields-list ion-item.updating{--background: rgba(var(--dcf-color-primary-rgb), .1) !important}.dcf-fields-list ion-item.not-unique{--background: rgba(var(--dcf-color-danger-rgb), .05) !important}.dcf-fields-list ion-item .dcf-subtitle{font-size:.8rem;color:var(--dcf-color-gray-7)}\n"] }]
5021
+ ], host: { '[attr.id]': 'overriode ' }, template: "\n\n<fieldset\n (fieldsetAddGroupEvent)=\"handleCreateItem($event)\"\n (fieldsetRemoveGroupEvent)=\"handleRemoveItem(undefined, $event)\"\n [class]=\"'dcf-fieldset ' + operation\"\n #component>\n <ion-accordion-group [class.open]=\"isOpen\" [class.hasValidationErrors]=\"hasValidationErrors\" (validationErrorEvent)=\"handleValidationError($event)\" #accordionComponent>\n <ion-accordion value=\"open\">\n <ion-item slot=\"header\" (click)=\"handleAccordionToggle($event)\">\n <div class=\"dcf-grid dcf-grid-collapse dcf-flex dcf-flex-middle dcf-width-1-1\">\n <div class=\"dcf-width-expand\">\n <legend>{{ name | translate }}</legend>\n </div>\n @if(!isRequired && ['create', 'update'].includes(operation)) {\n <div class=\"dcf-width-auto dcf-delete\">\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleRemoveComponent($event)\">\n <ion-icon name=\"trash-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n </div>\n }\n </div>\n </ion-item>\n <div slot=\"content\" [attr.aria-hidden]=\"!isOpen\">\n @if(multiple && items.length) {\n <ion-list class=\"dcf-fields-list\">\n <ion-reorder-group [formGroup]=\"formGroup.parent\" [disabled]=\"updatingItem\" (ionItemReorder)=\"handleReorderItems($any($event))\" #accordionComponent>\n @for(item of items; track item.index) {\n <ion-item [class.not-unique]=\"item.title === isUniqueError\" [class.updating]=\"updatingItem?.[pk] === item.title\" lines=\"full\" [button]=\"false\">\n @if(items?.length > 1 && !updatingItem) {\n <ion-reorder slot=\"start\">\n <ion-icon name=\"swap-vertical-outline\"></ion-icon>\n </ion-reorder>\n } @else {\n <div slot=\"start\">\n <ion-icon class=\"dcf-reorder-disabled\" size=\"small\" name=\"swap-vertical-outline\" disabled></ion-icon>\n </div>\n }\n <ion-label [color]=\"(item.title === isUniqueError && !updatingItem?.[pk] === item.title) ? 'danger' : ''\">{{ item.index }}. {{ item.title }}\n @if(item.description?.length > 0) {\n <br />\n <ion-text class=\"dcf-subtitle\">{{item.description}}</ion-text>\n }\n </ion-label>\n @if(!updatingItem || updatingItem?.[pk] !== item.title) {\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleUpdateItem(item.title, $index)\">\n <ion-icon name=\"create-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n }\n\n @if(!updatingItem) {\n <ion-button fill=\"clear\" size=\"small\" (click)=\"handleRemoveItem(item.title)\">\n <ion-icon name=\"trash-outline\" color=\"dark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n }\n </ion-item>\n }\n </ion-reorder-group>\n </ion-list>\n }\n\n <ng-content select=\"[slot=content]\"></ng-content>\n\n @if(multiple && ['create', 'update'].includes(operation)) {\n @if(isUniqueError) {\n <div class=\"dcf-not-unique-container dcf-animation dcf-animation-bottom-small dcf-animation-fast\">\n <div class=\" dcf-grid dcf-grid-collapse dcf-width-1-1 \">\n <div class=\"dcf-auto\" [attr.style]=\"'max-width: 50px'\">\n <ion-icon name=\"alert-circle-outline\"></ion-icon>\n </div>\n <div class=\"dcf-width-expand\">\n <ion-text color=\"danger\" class=\"dcf-text-small\">{{ locale + '.not_unique' | translate : { value: isUniqueError } }}</ion-text>\n </div>\n </div>\n </div>\n }\n <div class=\"dcf-margin-bottom dcf-grid dcf-grid-collapse dcf-flex\">\n @if(updatingItem) {\n <ion-button size=\"small\" fill=\"clear\" color=\"danger\" (click)=\"handleCancelUpdateItem()\">\n {{ buttonCancelLabel }}\n </ion-button>\n }\n <ion-button size=\"small\" fill=\"clear\" class=\"dcf-button-add\" (click)=\"handleCreateItem()\">\n <ion-icon name=\"add-outline\" slot=\"start\"></ion-icon>\n {{buttonLabel}}\n </ion-button>\n\n </div>\n }\n\n </div>\n </ion-accordion>\n </ion-accordion-group>\n</fieldset>\n\n", styles: ["ion-accordion-group ion-item[slot=header] .dcf-delete{width:30px}ion-accordion-group ion-item[slot=header] .dcf-delete ion-button{transform:translateY(-2px)}ion-accordion-group ion-item[slot=header] .dcf-delete ion-icon{font-size:1.15rem}::ng-deep ion-accordion ngx-decaf-crud-field:last-child ion-item{--inner-border-width: 0px !important;--border-width: 0px !important}.dcf-fieldset{margin-bottom:1.8rem;margin-top:1rem;padding-bottom:0;padding-top:1rem;background:var(--dcf-card-background);border-radius:6px;height:100%}@media (prefers-color-scheme: light){.dcf-fieldset{border:1px solid var(--dcf-color-gray-3)}.dcf-fieldset .dcf-button-add{color:var(--ion-color-dark)!important}}@media (prefers-color-scheme: dark){.dcf-fieldset{border:1px solid var(--dcf-color-step-400)}.dcf-fieldset .dcf-button-add{color:var(--ion-color-gray-2)}}.dcf-fieldset.read,.dcf-fieldset.delete{margin-top:1.25rem;padding-bottom:1rem}.dcf-fieldset.read [slot=content],.dcf-fieldset.delete [slot=content]{padding-top:0!important}.dcf-fieldset.read ion-accordion,.dcf-fieldset.delete ion-accordion{margin-bottom:0rem!important}@media (prefers-color-scheme: dark){.dcf-fieldset.read,.dcf-fieldset.delete{border:1px solid var(--dcf-color-gray-6)}}.dcf-fieldset ion-accordion{background:var(--dcf-card-background);margin-bottom:1rem}.dcf-fieldset ion-accordion.accordion-collapsing,.dcf-fieldset ion-accordion.accordion-collapsed{margin-bottom:1rem}.dcf-fieldset ion-accordion ion-item[slot=header]{--border-color: transparent;--border-radius: 6px;--inner-border-width: 0;--padding-start: 12px}.dcf-fieldset ion-accordion ion-item[slot=header] legend{font-weight:600;font-size:1rem;margin:0}@media (prefers-color-scheme: light){.dcf-fieldset ion-accordion ion-item[slot=header] legend{color:var(--dcf-color-gray-7)}}.dcf-fieldset ion-accordion [slot=content]{padding-top:2rem!important;padding-inline:.75rem}.dcf-not-unique-container{display:flex;justify-content:center;align-items:center;margin-bottom:1rem;flex:1 1 auto}.dcf-not-unique-container>div{display:flex;justify-content:center;align-items:center}.dcf-not-unique-container ion-icon{transform:translatey(2px);margin-right:5px}.dcf-fields-list{margin-top:-1rem;margin-bottom:1rem;padding:0!important}.dcf-fields-list ion-item{--min-height: 50px;--padding-top: .25rem;--padding-bottom: .25rem;--padding-start: .75rem;--padding-end: .75rem;--inner-padding-start: 0px !important;--inner-padding-end: 0px !important;--border-color: var(--dcf-color-gray-2) !important;border:1px solid transparent;box-sizing:border-box}.dcf-fields-list ion-item ion-icon.dcf-reorder-disabled{width:1rem;transform:translatey(2px);color:var(--dcf-color-gray-4)}.dcf-fields-list ion-item.updating{--background: rgba(var(--dcf-color-primary-rgb), .1) !important}.dcf-fields-list ion-item.not-unique{--background: rgba(var(--dcf-color-danger-rgb), .05) !important}.dcf-fields-list ion-item .dcf-subtitle{font-size:.8rem;color:var(--dcf-color-gray-7)}\n"] }]
4605
5022
  }], ctorParameters: () => [], propDecorators: { accordionComponent: [{
4606
5023
  type: ViewChild,
4607
5024
  args: ['accordionComponent', { static: false }]
@@ -4609,6 +5026,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4609
5026
  type: Input
4610
5027
  }], childOf: [{
4611
5028
  type: Input
5029
+ }], page: [{
5030
+ type: Input
4612
5031
  }], uid: [{
4613
5032
  type: Input
4614
5033
  }], customTypes: [{
@@ -5832,6 +6251,11 @@ class ModelRendererComponent {
5832
6251
  * @description Global variables to be passed to the rendered component
5833
6252
  */
5834
6253
  this.globals = {};
6254
+ /**
6255
+ * @description Set if render content projection is allowed
6256
+ * @default true
6257
+ */
6258
+ this.projectable = true;
5835
6259
  /**
5836
6260
  * @description Event emitter for custom events from the rendered component
5837
6261
  */
@@ -5849,7 +6273,7 @@ class ModelRendererComponent {
5849
6273
  typeof model === 'string'
5850
6274
  ? Model.build({}, model)
5851
6275
  : model;
5852
- this.output = model.render(this.globals || {}, this.vcr, this.injector, this.inner);
6276
+ this.output = model.render(this.globals || {}, this.vcr, this.injector, this.inner, this.projectable);
5853
6277
  if (this.output?.inputs)
5854
6278
  this.rendererId = sf(AngularEngineKeys.RENDERED_ID, this.output.inputs['rendererId']);
5855
6279
  this.instance = this.output?.instance;
@@ -5907,7 +6331,7 @@ class ModelRendererComponent {
5907
6331
  }
5908
6332
  }
5909
6333
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModelRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5910
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ModelRendererComponent, isStandalone: true, selector: "ngx-decaf-model-renderer", inputs: { model: "model", globals: "globals", rendererId: "rendererId" }, outputs: { listenEvent: "listenEvent" }, host: { properties: { "attr.id": "rendererId" } }, viewQueries: [{ propertyName: "inner", first: true, predicate: ["inner"], descendants: true, read: TemplateRef, static: true }, { propertyName: "vcr", first: true, predicate: ["componentOuter"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: " <!-- Keep to avoid id conflicts -->\n <div [id]=\"rendererId\"></div>\n\n <ng-template #componentOuter></ng-template>\n <ng-template #inner>\n <div [id]=\"rendererId || null\">\n @for (child of output?.children; track child) {\n @if(child?.children?.length) {\n <ngx-decaf-component-renderer [parent]=\"child\" />\n } @else {\n <ng-container\n #childComponents\n *ngComponentOutlet=\"\n child.component;\n injector: child.injector;\n inputs: child.inputs;\n content:child.content;\n \"\n />\n }\n }\n </div>\n </ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "component", type: ComponentRendererComponent, selector: "ngx-decaf-component-renderer", inputs: ["tag", "globals", "model", "parent"], outputs: ["listenEvent"] }] }); }
6334
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ModelRendererComponent, isStandalone: true, selector: "ngx-decaf-model-renderer", inputs: { model: "model", globals: "globals", projectable: "projectable", rendererId: "rendererId" }, outputs: { listenEvent: "listenEvent" }, host: { properties: { "attr.id": "rendererId" } }, viewQueries: [{ propertyName: "inner", first: true, predicate: ["inner"], descendants: true, read: TemplateRef, static: true }, { propertyName: "vcr", first: true, predicate: ["componentOuter"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: " <!-- Keep to avoid id conflicts -->\n <div [id]=\"rendererId\"></div>\n\n <ng-template #componentOuter></ng-template>\n <ng-template #inner>\n <div [id]=\"rendererId || null\">\n @for (child of output?.children; track child) {\n @if(child?.children?.length) {\n <ngx-decaf-component-renderer [parent]=\"child\" />\n } @else {\n <ng-container\n #childComponents\n *ngComponentOutlet=\"\n child.component;\n injector: child.injector;\n inputs: child.inputs;\n content:child.content;\n \"\n />\n }\n }\n </div>\n </ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "component", type: ComponentRendererComponent, selector: "ngx-decaf-component-renderer", inputs: ["tag", "globals", "children", "model", "parent"], outputs: ["listenEvent"] }] }); }
5911
6335
  }
5912
6336
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModelRendererComponent, decorators: [{
5913
6337
  type: Component,
@@ -5917,6 +6341,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5917
6341
  args: [{ required: true }]
5918
6342
  }], globals: [{
5919
6343
  type: Input
6344
+ }], projectable: [{
6345
+ type: Input
5920
6346
  }], inner: [{
5921
6347
  type: ViewChild,
5922
6348
  args: ['inner', { read: TemplateRef, static: true }]
@@ -6050,40 +6476,15 @@ class LayoutComponent extends NgxBaseComponent {
6050
6476
  *
6051
6477
  * @memberOf LayoutComponent
6052
6478
  */
6053
- ngOnInit() {
6054
- this.initialize();
6055
- }
6056
- /**
6057
- * @description Initializes the layout component with processed properties.
6058
- * @summary Overrides the base component's initialize method to set up the grid layout.
6059
- * This method processes input properties, normalizes the breakpoint value, converts
6060
- * rows and columns to their array representations, and marks the component as initialized.
6061
- * The initialization ensures all properties are in the correct format for rendering.
6062
- *
6063
- * @mermaid
6064
- * sequenceDiagram
6065
- * participant L as LayoutComponent
6066
- * participant B as NgxBaseComponent
6067
- *
6068
- * L->>B: parseProps(this)
6069
- * Note over L: Process component properties
6070
- * L->>L: Normalize breakpoint to lowercase
6071
- * L->>L: Convert rows to array format
6072
- * L->>L: Convert cols to array format
6073
- * L->>L: Set initialized = true
6074
- *
6075
- * @override
6076
- * @memberOf LayoutComponent
6077
- */
6078
- initialize() {
6079
- this.parseProps(this);
6479
+ async ngOnInit() {
6480
+ await this.initialize();
6080
6481
  this.breakpoint = this.breakpoint.slice(0, 2).toLowerCase();
6081
6482
  this.cols = this._cols;
6082
6483
  this.rows = this._rows;
6083
6484
  this.initialized = true;
6084
6485
  }
6085
6486
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6086
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: LayoutComponent, isStandalone: true, selector: "ngx-decaf-layout", inputs: { cols: "cols", rows: "rows", breakpoint: "breakpoint", children: "children" }, usesInheritance: true, ngImport: i0, template: "\n@if(initialized) {\n @for (row of rows; track trackItemFn($index, row); let rowIndex = $index) {\n <div [id]=\"uid\" class=\"dcf-grid dcf-grid-collapse dcf-grid-match\">\n @if(row) {\n <div class=\"dcf-width-1-1 dcf-grid-title\">\n <ion-card class=\"dcf-grid-title\">\n {{row.title | translate}}\n </ion-card>\n </div>\n }\n @for (child of row.cols; track trackItemFn($index, child.col); let colIndex = $index) {\n <div [class]=\"(child.col === cols.length ? 'dcf-width-1-1' : 'dcf-width-'+child.col+'-'+cols.length+'@'+breakpoint)\">\n <div [class]=\"'dcf-grid-child '+child.col \">\n @if(child.tag === 'ngx-decaf-crud-form') {\n <ion-card [class]=\"'dcf-height-1-1 ' + className\">\n <ion-card-content>\n <ngx-decaf-model-renderer\n [model]=\"child.props.name\"\n (listenEvent)=\"handleEvent($event)\"\n />\n </ion-card-content>\n </ion-card>\n } @else {\n <ngx-decaf-component-renderer\n [tag]=\"child.tag\"\n (listenEvent)=\"handleEvent($event)\"\n [globals]=\"{props: child.props}\"\n />\n }\n </div>\n </div>\n }\n </div>\n }\n}\n", styles: [".dcf-grid>div:not(.dcf-grid-title) ::ng-deep ngx-decaf-component-renderer>*>*{height:100%;display:flex;justify-content:center!important;align-items:center!important}.dcf-grid ion-card.dcf-height-1-1>ion-card-content{margin-top:2rem}.dcf-grid.dcf-grid-small .dcf-grid-child{margin-bottom:2rem}.dcf-grid.dcf-grid-collapse .dcf-grid-child{margin-bottom:1.25rem}.dcf-grid.dcf-grid-collapse .dcf-grid-child ion-card{margin-bottom:1.25rem}.dcf-grid-title{font-size:1.05rem!important;background:none;box-shadow:none;margin-bottom:0;padding-bottom:0;font-weight:600;color:var(--dcf-color-dark);display:flex;align-items:center;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: ModelRendererComponent, selector: "ngx-decaf-model-renderer", inputs: ["model", "globals", "rendererId"], outputs: ["listenEvent"] }, { kind: "component", type: ComponentRendererComponent, selector: "ngx-decaf-component-renderer", inputs: ["tag", "globals", "model", "parent"], outputs: ["listenEvent"] }] }); }
6487
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: LayoutComponent, isStandalone: true, selector: "ngx-decaf-layout", inputs: { cols: "cols", rows: "rows", breakpoint: "breakpoint", children: "children" }, usesInheritance: true, ngImport: i0, template: "\n@if(initialized) {\n @for (row of rows; track trackItemFn($index, row); let rowIndex = $index) {\n <div [id]=\"uid\" class=\"dcf-grid dcf-grid-collapse dcf-grid-match\">\n @if(row) {\n <div class=\"dcf-width-1-1 dcf-grid-title\">\n <ion-card class=\"dcf-grid-title\">\n {{row.title | translate}}\n </ion-card>\n </div>\n }\n @for (child of row.cols; track trackItemFn($index, child.col); let colIndex = $index) {\n <div [class]=\"(child.col === cols.length ? 'dcf-width-1-1' : 'dcf-width-'+child.col+'-'+cols.length+'@'+breakpoint)\">\n <div [class]=\"'dcf-grid-child '+child.col \">\n @if(child.tag === 'ngx-decaf-crud-form') {\n <ion-card [class]=\"'dcf-height-1-1 ' + className\">\n <ion-card-content>\n <ngx-decaf-model-renderer\n [model]=\"child.props.name\"\n (listenEvent)=\"handleEvent($event)\"\n />\n </ion-card-content>\n </ion-card>\n } @else {\n <ngx-decaf-component-renderer\n [tag]=\"child.tag\"\n (listenEvent)=\"handleEvent($event)\"\n [globals]=\"{props: child.props}\"\n />\n }\n </div>\n </div>\n }\n </div>\n }\n}\n", styles: [".dcf-grid>div:not(.dcf-grid-title) ::ng-deep ngx-decaf-component-renderer>*>*{height:100%;display:flex;justify-content:center!important;align-items:center!important}.dcf-grid ion-card.dcf-height-1-1>ion-card-content{margin-top:2rem}.dcf-grid.dcf-grid-small .dcf-grid-child{margin-bottom:2rem}.dcf-grid.dcf-grid-collapse .dcf-grid-child{margin-bottom:1.25rem}.dcf-grid.dcf-grid-collapse .dcf-grid-child ion-card{margin-bottom:1.25rem}.dcf-grid-title{font-size:1.05rem!important;background:none;box-shadow:none;margin-bottom:0;padding-bottom:0;font-weight:600;color:var(--dcf-color-dark);display:flex;align-items:center;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: ModelRendererComponent, selector: "ngx-decaf-model-renderer", inputs: ["model", "globals", "projectable", "rendererId"], outputs: ["listenEvent"] }, { kind: "component", type: ComponentRendererComponent, selector: "ngx-decaf-component-renderer", inputs: ["tag", "globals", "children", "model", "parent"], outputs: ["listenEvent"] }] }); }
6087
6488
  }
6088
6489
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LayoutComponent, decorators: [{
6089
6490
  type: Component,
@@ -6802,12 +7203,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
6802
7203
  type: Output
6803
7204
  }] } });
6804
7205
 
6805
- var ListComponentsTypes;
6806
- (function (ListComponentsTypes) {
6807
- ListComponentsTypes["INFINITE"] = "infinite";
6808
- ListComponentsTypes["PAGINATED"] = "paginated";
6809
- })(ListComponentsTypes || (ListComponentsTypes = {}));
6810
-
6811
7206
  /**
6812
7207
  * @description A versatile list component that supports various data display modes.
6813
7208
  * @summary This component provides a flexible way to display lists of data with support
@@ -7243,7 +7638,7 @@ let ListComponent = class ListComponent extends NgxBaseComponent {
7243
7638
  await this.refresh();
7244
7639
  if (this.operations.includes(OperationKeys.CREATE) && this.route)
7245
7640
  this.empty.link = `${this.route}/${OperationKeys.CREATE}`;
7246
- this.initialize();
7641
+ await this.initialize();
7247
7642
  if (this.model instanceof Model && this._repository)
7248
7643
  this._repository.observe(this.observer);
7249
7644
  }
@@ -7822,6 +8217,7 @@ let ListComponent = class ListComponent extends NgxBaseComponent {
7822
8217
  this.getMoreData(paginator.total);
7823
8218
  }
7824
8219
  catch (error) {
8220
+ this.logger.info(error?.message || 'Unable to get page from paginator. Return empty array from component');
7825
8221
  result = [];
7826
8222
  }
7827
8223
  }
@@ -7938,7 +8334,7 @@ let ListComponent = class ListComponent extends NgxBaseComponent {
7938
8334
  }, []);
7939
8335
  }
7940
8336
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7941
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ListComponent, isStandalone: true, selector: "ngx-decaf-list", inputs: { type: "type", translatable: "translatable", showSearchbar: "showSearchbar", data: "data", source: "source", start: "start", limit: "limit", loadMoreData: "loadMoreData", lines: "lines", inset: "inset", scrollThreshold: "scrollThreshold", scrollPosition: "scrollPosition", loadingText: "loadingText", showRefresher: "showRefresher", loadingSpinner: "loadingSpinner", enableFilter: "enableFilter", sortDirection: "sortDirection", sortBy: "sortBy", disableSort: "disableSort", emptyIcon: "emptyIcon", empty: "empty" }, outputs: { refreshEvent: "refreshEvent", clickEvent: "clickEvent" }, host: { listeners: { "window:ListItemClickEvent": "handleClick($event)", "window:searchbarEvent": "handleSearch($event)", "window:BackButtonNavigationEndEvent": "refresh($event)" } }, usesInheritance: true, ngImport: i0, template: "\n@if(showRefresher) {\n <ion-refresher slot=\"fixed\" [pullFactor]=\"1\" [pullMin]=\"100\" [pullMax]=\"200\" (ionRefresh)=\"handleRefresh($event)\">\n <ion-refresher-content />\n </ion-refresher>\n}\n\n@if(showSearchbar && data?.length) {\n @if(model && enableFilter) {\n <ngx-decaf-filter\n [model]=\"model\"\n [sortDirection]=\"sortDirection\"\n [disableSort]=\"disableSort\"\n (filterEvent)=\"handleFilter($event)\"\n (searchEvent)=\"handleSearch($event)\"\n />\n } @else {\n <ngx-decaf-searchbar [emitEventToWindow]=\"false\" [debounce]=\"500\" (searchEvent)=\"handleSearch($event)\" />\n }\n}\n\n@if(data?.length) {\n <ion-list [id]=\"uid\" [inset]=\"inset\" [lines]=\"lines\" #component>\n @if(item?.tag) {\n @for(child of items; track trackItemFn($index, child)) {\n <ngx-decaf-component-renderer\n [tag]=\"item.tag\"\n (listenEvent)=\"handleEvent($event)\"\n [globals]='{\n item: child,\n mapper: mapper,\n route: route\n }'>\n </ngx-decaf-component-renderer>\n }\n } @else {\n <ng-content></ng-content>\n }\n </ion-list>\n\n @if(loadMoreData) {\n @if(pages > 0 && type === 'paginated' && !searchValue?.length) {\n <ngx-decaf-pagination\n [totalPages]=\"pages\"\n [current]=\"page\"\n (clickEvent)=\"handlePaginate($event)\"\n />\n\n } @else {\n <ion-infinite-scroll\n [class]=\"searchValue?.length ? 'dcf-hidden' : ''\"\n\n [position]=\"scrollPosition\"\n [threshold]=\"scrollThreshold\"\n (ionInfinite)=\"handleRefresh($event)\">\n <ion-infinite-scroll-content [loadingSpinner]=\"loadingSpinner\" [loadingText]=\"loadingText\" />\n </ion-infinite-scroll>\n }\n }\n} @else {\n @if(refreshing) {\n @for(skl of skeletonData; track $index) {\n <ion-item>\n <ion-thumbnail slot=\"start\">\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n </ion-thumbnail>\n <ion-label>\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n <ion-text class=\"date\" style=\"width: 20%;\"><ion-skeleton-text [animated]=\"true\"></ion-skeleton-text></ion-text>\n </ion-label>\n </ion-item>\n }\n\n } @else {\n @if(!searchValue?.length) {\n <ngx-decaf-empty-state\n [title]=\"(locale + '.'+ empty.title) | translate\"\n [subtitle]=\"(locale + '.'+ empty.subtitle) | translate\"\n [buttonText]=\"empty.showButton ? (locale + '.'+ empty.button | translate) : ''\"\n [buttonLink]=\"empty.showButton ? empty.route : ''\"\n />\n } @else {\n <ngx-decaf-empty-state\n icon=\"search-outline\"\n ngClass=\"empty-search\"\n [translatable]=\"true\"\n title=\"search.title\"\n subtitle=\"search.subtitle\"\n [searchValue]=\"searchValue\"\n />\n }\n }\n}\n\n", styles: ["ion-infinite-scroll{max-height:50px}ion-infinite-scroll:not(.infinite-scroll-loading) ::ng-deep{max-height:1.5rem}ion-infinite-scroll ::ng-deep ion-spinner{--color: var(--dcf-color-primary);padding-top:1rem}@media (max-width: 768px){#end,[slot=end]{display:none!important}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: PaginationComponent, selector: "ngx-decaf-pagination", inputs: ["totalPages", "current"], outputs: ["clickEvent"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonThumbnail, selector: "ion-thumbnail" }, { kind: "component", type: IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: IonInfiniteScroll, selector: "ion-infinite-scroll", inputs: ["disabled", "position", "threshold"] }, { kind: "component", type: IonInfiniteScrollContent, selector: "ion-infinite-scroll-content", inputs: ["loadingSpinner", "loadingText"] }, { kind: "component", type: SearchbarComponent, selector: "ngx-decaf-searchbar", inputs: ["autocomplete", "autocorrect", "animated", "buttonCancelText", "clearIcon", "color", "debounce", "disabled", "enterkeyhint", "inputmode", "placeholder", "searchIcon", "showCancelButton", "showClearButton", "spellcheck", "type", "value", "queryKeys", "isVisible", "wrapper", "wrapperColor", "emitEventToWindow"], outputs: ["searchEvent"] }, { kind: "component", type: EmptyStateComponent, selector: "ngx-decaf-empty-state", inputs: ["title", "titleColor", "subtitle", "subtitleColor", "showIcon", "icon", "iconSize", "iconColor", "buttonLink", "buttonText", "buttonFill", "buttonColor", "buttonSize", "searchValue"] }, { kind: "component", type: FilterComponent, selector: "ngx-decaf-filter", inputs: ["indexes", "conditions", "sortBy", "disableSort"], outputs: ["filterEvent", "searchEvent"] }, { kind: "component", type: ComponentRendererComponent, selector: "ngx-decaf-component-renderer", inputs: ["tag", "globals", "model", "parent"], outputs: ["listenEvent"] }] }); }
8337
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ListComponent, isStandalone: true, selector: "ngx-decaf-list", inputs: { type: "type", translatable: "translatable", showSearchbar: "showSearchbar", data: "data", source: "source", start: "start", limit: "limit", loadMoreData: "loadMoreData", lines: "lines", inset: "inset", scrollThreshold: "scrollThreshold", scrollPosition: "scrollPosition", loadingText: "loadingText", showRefresher: "showRefresher", loadingSpinner: "loadingSpinner", enableFilter: "enableFilter", sortDirection: "sortDirection", sortBy: "sortBy", disableSort: "disableSort", emptyIcon: "emptyIcon", empty: "empty" }, outputs: { refreshEvent: "refreshEvent", clickEvent: "clickEvent" }, host: { listeners: { "window:ListItemClickEvent": "handleClick($event)", "window:searchbarEvent": "handleSearch($event)", "window:BackButtonNavigationEndEvent": "refresh($event)" } }, usesInheritance: true, ngImport: i0, template: "\n@if(showRefresher) {\n <ion-refresher slot=\"fixed\" [pullFactor]=\"1\" [pullMin]=\"100\" [pullMax]=\"200\" (ionRefresh)=\"handleRefresh($event)\">\n <ion-refresher-content />\n </ion-refresher>\n}\n\n@if(showSearchbar && data?.length) {\n @if(model && enableFilter) {\n <ngx-decaf-filter\n [model]=\"model\"\n [sortDirection]=\"sortDirection\"\n [disableSort]=\"disableSort\"\n (filterEvent)=\"handleFilter($event)\"\n (searchEvent)=\"handleSearch($event)\"\n />\n } @else {\n <ngx-decaf-searchbar [emitEventToWindow]=\"false\" [debounce]=\"500\" (searchEvent)=\"handleSearch($event)\" />\n }\n}\n\n@if(data?.length) {\n <ion-list [id]=\"uid\" [inset]=\"inset\" [lines]=\"lines\" #component>\n @if(item?.tag) {\n @for(child of items; track trackItemFn($index, child)) {\n <ngx-decaf-component-renderer\n [tag]=\"item.tag\"\n (listenEvent)=\"handleEvent($event)\"\n [globals]='{\n item: child,\n mapper: mapper,\n route: route\n }'>\n </ngx-decaf-component-renderer>\n }\n } @else {\n <ng-content></ng-content>\n }\n </ion-list>\n\n @if(loadMoreData) {\n @if(pages > 0 && type === 'paginated' && !searchValue?.length) {\n <ngx-decaf-pagination\n [totalPages]=\"pages\"\n [current]=\"page\"\n (clickEvent)=\"handlePaginate($event)\"\n />\n\n } @else {\n <ion-infinite-scroll\n [class]=\"searchValue?.length ? 'dcf-hidden' : ''\"\n\n [position]=\"scrollPosition\"\n [threshold]=\"scrollThreshold\"\n (ionInfinite)=\"handleRefresh($event)\">\n <ion-infinite-scroll-content [loadingSpinner]=\"loadingSpinner\" [loadingText]=\"loadingText\" />\n </ion-infinite-scroll>\n }\n }\n} @else {\n @if(refreshing) {\n @for(skl of skeletonData; track $index) {\n <ion-item>\n <ion-thumbnail slot=\"start\">\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n </ion-thumbnail>\n <ion-label>\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n <ion-text class=\"date\" style=\"width: 20%;\"><ion-skeleton-text [animated]=\"true\"></ion-skeleton-text></ion-text>\n </ion-label>\n </ion-item>\n }\n\n } @else {\n @if(!searchValue?.length) {\n <ngx-decaf-empty-state\n [title]=\"(locale + '.'+ empty.title) | translate\"\n [subtitle]=\"(locale + '.'+ empty.subtitle) | translate\"\n [buttonText]=\"empty.showButton ? (locale + '.'+ empty.button | translate) : ''\"\n [buttonLink]=\"empty.showButton ? empty.route : ''\"\n />\n } @else {\n <ngx-decaf-empty-state\n icon=\"search-outline\"\n ngClass=\"empty-search\"\n [translatable]=\"true\"\n title=\"search.title\"\n subtitle=\"search.subtitle\"\n [searchValue]=\"searchValue\"\n />\n }\n }\n}\n\n", styles: ["ion-infinite-scroll{max-height:50px}ion-infinite-scroll:not(.infinite-scroll-loading) ::ng-deep{max-height:1.5rem}ion-infinite-scroll ::ng-deep ion-spinner{--color: var(--dcf-color-primary);padding-top:1rem}@media (max-width: 768px){#end,[slot=end]{display:none!important}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: PaginationComponent, selector: "ngx-decaf-pagination", inputs: ["totalPages", "current"], outputs: ["clickEvent"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonThumbnail, selector: "ion-thumbnail" }, { kind: "component", type: IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: IonInfiniteScroll, selector: "ion-infinite-scroll", inputs: ["disabled", "position", "threshold"] }, { kind: "component", type: IonInfiniteScrollContent, selector: "ion-infinite-scroll-content", inputs: ["loadingSpinner", "loadingText"] }, { kind: "component", type: SearchbarComponent, selector: "ngx-decaf-searchbar", inputs: ["autocomplete", "autocorrect", "animated", "buttonCancelText", "clearIcon", "color", "debounce", "disabled", "enterkeyhint", "inputmode", "placeholder", "searchIcon", "showCancelButton", "showClearButton", "spellcheck", "type", "value", "queryKeys", "isVisible", "wrapper", "wrapperColor", "emitEventToWindow"], outputs: ["searchEvent"] }, { kind: "component", type: EmptyStateComponent, selector: "ngx-decaf-empty-state", inputs: ["title", "titleColor", "subtitle", "subtitleColor", "showIcon", "icon", "iconSize", "iconColor", "buttonLink", "buttonText", "buttonFill", "buttonColor", "buttonSize", "searchValue"] }, { kind: "component", type: FilterComponent, selector: "ngx-decaf-filter", inputs: ["indexes", "conditions", "sortBy", "disableSort"], outputs: ["filterEvent", "searchEvent"] }, { kind: "component", type: ComponentRendererComponent, selector: "ngx-decaf-component-renderer", inputs: ["tag", "globals", "children", "model", "parent"], outputs: ["listenEvent"] }] }); }
7942
8338
  };
7943
8339
  ListComponent = __decorate([
7944
8340
  Dynamic(),
@@ -8025,6 +8421,283 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8025
8421
  args: ['window:BackButtonNavigationEndEvent', ['$event']]
8026
8422
  }] } });
8027
8423
 
8424
+ let StepedFormComponent = class StepedFormComponent {
8425
+ /**
8426
+ * @description Creates an instance of StepedFormComponent.
8427
+ * @summary Initializes a new StepedFormComponent instance and registers the required
8428
+ * Ionic icons for navigation buttons (forward and back arrows).
8429
+ *
8430
+ * @memberOf StepedFormComponent
8431
+ */
8432
+ constructor() {
8433
+ /**
8434
+ * @description Number of pages in the stepped form.
8435
+ * @summary Represents the total number of steps/pages in the multi-step form.
8436
+ * This value is automatically calculated based on the page properties of the children
8437
+ * or can be explicitly set. Each page represents a logical group of form fields.
8438
+ *
8439
+ * @type {number}
8440
+ * @default 1
8441
+ * @memberOf StepedFormComponent
8442
+ */
8443
+ this.pages = 1;
8444
+ /**
8445
+ * @description The CRUD operation type for this form.
8446
+ * @summary Defines the type of operation being performed (CREATE, READ, UPDATE, DELETE).
8447
+ * This affects form behavior, validation rules, and field accessibility. For example,
8448
+ * READ operations might disable form fields, while CREATE operations enable all fields.
8449
+ *
8450
+ * @type {CrudOperations}
8451
+ * @default OperationKeys.CREATE
8452
+ * @memberOf StepedFormComponent
8453
+ */
8454
+ this.operation = OperationKeys.CREATE;
8455
+ /**
8456
+ * @description The initial page to display when the form loads.
8457
+ * @summary Specifies which page of the multi-step form should be shown first.
8458
+ * This allows starting the form at any step, useful for scenarios like editing
8459
+ * existing data where you might want to jump to a specific section.
8460
+ *
8461
+ * @type {number}
8462
+ * @default 1
8463
+ * @memberOf StepedFormComponent
8464
+ */
8465
+ this.startPage = 1;
8466
+ /**
8467
+ * @description Array of UI model metadata for the currently active page.
8468
+ * @summary Contains only the UI model metadata for fields that should be displayed
8469
+ * on the currently active page. This is a filtered subset of the children array,
8470
+ * updated whenever the user navigates between pages.
8471
+ *
8472
+ * @type {UIModelMetadata[] | undefined}
8473
+ * @memberOf StepedFormComponent
8474
+ */
8475
+ this.activeChildren = undefined;
8476
+ /**
8477
+ * @description FormGroup for the currently active page.
8478
+ * @summary The FormGroup instance that manages form controls and validation
8479
+ * for the current page only. This is extracted from the main formGroup
8480
+ * when using FormArray structure.
8481
+ *
8482
+ * @type {FormGroup | undefined}
8483
+ * @memberOf StepedFormComponent
8484
+ */
8485
+ this.activeFormGroup = undefined;
8486
+ /**
8487
+ * @description The currently active page number.
8488
+ * @summary Tracks which page of the multi-step form is currently being displayed.
8489
+ * This property is updated as users navigate through the form steps using
8490
+ * the next/back buttons or programmatic navigation.
8491
+ *
8492
+ * @type {number}
8493
+ * @memberOf StepedFormComponent
8494
+ */
8495
+ this.activePage = 1;
8496
+ /**
8497
+ * @description Array representing the structure of pages.
8498
+ * @summary Contains metadata about each page, including page numbers and indices.
8499
+ * This array is built during initialization to organize the form fields into
8500
+ * logical pages and provide navigation structure.
8501
+ *
8502
+ * @type {UIModelMetadata[]}
8503
+ * @memberOf StepedFormComponent
8504
+ */
8505
+ this.pagesArray = [];
8506
+ /**
8507
+ * @description Event emitter for form submission.
8508
+ * @summary Emits events when the form is submitted, typically on the last page
8509
+ * when all validation passes. The emitted event contains the form data and
8510
+ * event type information for parent components to handle.
8511
+ *
8512
+ * @type {EventEmitter<BaseCustomEvent>}
8513
+ * @memberOf StepedFormComponent
8514
+ */
8515
+ this.submitEvent = new EventEmitter();
8516
+ addIcons({ arrowForwardOutline, arrowBackOutline });
8517
+ }
8518
+ /**
8519
+ * @description Initializes the component after Angular first displays the data-bound properties.
8520
+ * @summary Sets up the stepped form by organizing children into pages, calculating the total
8521
+ * number of pages, and initializing the active page. This method processes the UI model metadata
8522
+ * to create a logical page structure and ensures proper page assignments for all form fields.
8523
+ *
8524
+ * @mermaid
8525
+ * sequenceDiagram
8526
+ * participant A as Angular Lifecycle
8527
+ * participant S as StepedFormComponent
8528
+ * participant F as Form Service
8529
+ *
8530
+ * A->>S: ngOnInit()
8531
+ * S->>S: Set activePage = startPage
8532
+ * S->>S: Process children into pagesArray
8533
+ * S->>S: Calculate total pages
8534
+ * S->>S: Assign page props to children
8535
+ * S->>S: getCurrentFormGroup(activePage)
8536
+ * S->>F: Extract FormGroup for active page
8537
+ * F-->>S: Return activeFormGroup
8538
+ *
8539
+ * @memberOf StepedFormComponent
8540
+ */
8541
+ ngOnInit() {
8542
+ this.activePage = this.startPage;
8543
+ this.pagesArray = this.children.reduce((acc, curr, index) => {
8544
+ const page = curr.props?.['page'] || index + 1;
8545
+ if (!acc[page])
8546
+ acc[page] = [];
8547
+ acc[page].push({ index: page });
8548
+ return acc;
8549
+ }, []).filter(Boolean);
8550
+ this.pages = this.pagesArray.length;
8551
+ this.children = [...this.children.map((c) => {
8552
+ if (!c.props)
8553
+ c.props = {};
8554
+ const page = c.props['page'] || 1;
8555
+ // prevent page overflow
8556
+ c.props['page'] = page > this.pages ? this.pages : page;
8557
+ return c;
8558
+ })];
8559
+ this.getCurrentFormGroup(this.activePage);
8560
+ }
8561
+ /**
8562
+ * @description Cleanup method called when the component is destroyed.
8563
+ * @summary Unsubscribes from any active timer subscriptions to prevent memory leaks.
8564
+ * This is part of Angular's component lifecycle and ensures proper resource cleanup.
8565
+ *
8566
+ * @memberOf StepedFormComponent
8567
+ */
8568
+ ngOnDestroy() {
8569
+ if (this.timerSubscription)
8570
+ this.timerSubscription.unsubscribe();
8571
+ }
8572
+ /**
8573
+ * @description Handles navigation to the next page or form submission.
8574
+ * @summary Validates the current page's form fields and either navigates to the next page
8575
+ * or submits the entire form if on the last page. Form validation must pass before
8576
+ * proceeding. On successful submission, emits a submit event with form data.
8577
+ *
8578
+ * @param {boolean} lastPage - Whether this is the last page of the form
8579
+ * @return {void}
8580
+ *
8581
+ * @mermaid
8582
+ * sequenceDiagram
8583
+ * participant U as User
8584
+ * participant S as StepedFormComponent
8585
+ * participant F as Form Service
8586
+ * participant P as Parent Component
8587
+ *
8588
+ * U->>S: Click Next/Submit
8589
+ * S->>F: validateFields(activeFormGroup)
8590
+ * F-->>S: Return validation result
8591
+ * alt Not last page and valid
8592
+ * S->>S: activePage++
8593
+ * S->>S: getCurrentFormGroup(activePage)
8594
+ * else Last page and valid
8595
+ * S->>F: getFormData(formGroup)
8596
+ * F-->>S: Return form data
8597
+ * S->>P: submitEvent.emit({data, name: SUBMIT})
8598
+ * end
8599
+ *
8600
+ * @memberOf StepedFormComponent
8601
+ */
8602
+ handleNext(lastPage = false) {
8603
+ const isValid = NgxFormService.validateFields(this.activeFormGroup);
8604
+ if (!lastPage) {
8605
+ if (isValid) {
8606
+ this.activePage = this.activePage + 1;
8607
+ this.getCurrentFormGroup(this.activePage);
8608
+ }
8609
+ }
8610
+ else {
8611
+ if (isValid) {
8612
+ const data = Object.assign({}, ...Object.values(NgxFormService.getFormData(this.formGroup)));
8613
+ this.submitEvent.emit({
8614
+ data,
8615
+ name: EventConstants.SUBMIT,
8616
+ });
8617
+ }
8618
+ }
8619
+ }
8620
+ /**
8621
+ * @description Handles navigation to the previous page.
8622
+ * @summary Moves the user back to the previous page in the stepped form.
8623
+ * This method decrements the active page number and updates the form
8624
+ * group and children to display the previous page's content.
8625
+ *
8626
+ * @return {void}
8627
+ *
8628
+ * @mermaid
8629
+ * sequenceDiagram
8630
+ * participant U as User
8631
+ * participant S as StepedFormComponent
8632
+ *
8633
+ * U->>S: Click Back
8634
+ * S->>S: activePage--
8635
+ * S->>S: getCurrentFormGroup(activePage)
8636
+ * Note over S: Update active form and children
8637
+ *
8638
+ * @memberOf StepedFormComponent
8639
+ */
8640
+ handleBack() {
8641
+ this.activePage = this.activePage - 1;
8642
+ this.getCurrentFormGroup(this.activePage);
8643
+ }
8644
+ /**
8645
+ * @description Updates the active form group and children for the specified page.
8646
+ * @summary Extracts the FormGroup for the given page from the FormArray and filters
8647
+ * the children to show only fields belonging to that page. Uses a timer to ensure
8648
+ * proper Angular change detection when updating the activeChildren.
8649
+ *
8650
+ * @param {number} page - The page number to activate
8651
+ * @return {void}
8652
+ *
8653
+ * @private
8654
+ * @mermaid
8655
+ * sequenceDiagram
8656
+ * participant S as StepedFormComponent
8657
+ * participant F as FormArray
8658
+ * participant T as Timer
8659
+ *
8660
+ * S->>F: Extract FormGroup at index (page - 1)
8661
+ * F-->>S: Return page FormGroup
8662
+ * S->>S: Set activeChildren = undefined
8663
+ * S->>T: timer(10).subscribe()
8664
+ * T-->>S: Filter children for active page
8665
+ * S->>S: Set activeChildren
8666
+ *
8667
+ * @memberOf StepedFormComponent
8668
+ */
8669
+ getCurrentFormGroup(page) {
8670
+ this.activeFormGroup = this.formGroup.at(page - 1);
8671
+ this.activeChildren = undefined;
8672
+ this.timerSubscription = timer(10).subscribe(() => {
8673
+ this.activeChildren = this.children.filter(c => c.props?.['page'] === page);
8674
+ console.log(this.activeChildren);
8675
+ });
8676
+ }
8677
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StepedFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8678
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: StepedFormComponent, isStandalone: true, selector: "ngx-decaf-steped-form", inputs: { pages: "pages", operation: "operation", startPage: "startPage", children: "children", formGroup: "formGroup" }, outputs: { submitEvent: "submitEvent" }, ngImport: i0, template: " <!-- @for(child of children; track trackItemFn($index, child)) {\n {{child.tag }}\n <ngx-decaf-component-renderer\n [tag]=\"child.tag\"\n (listenEvent)=\"handleEvent($event)\"\n [globals]='{props: child.props}'>\n </ngx-decaf-component-renderer>\n } -->\n <form class=\"dcf-steped-form\" novalidate>\n <div class=\"dcf-page-steps\">\n <div>\n @for(page of pagesArray; track $index;) {\n <div [class.dcf-active]=\"activePage === $index + 1\" [class.dcf-passed]=\"($index + 1) < activePage\">{{ $index + 1 }}</div>\n }\n </div>\n </div>\n @if(formGroup) {\n @for(child of activeChildren; track $index) {\n <ngx-decaf-component-renderer\n [tag]=\"child?.tag\"\n (listenEvent)=\"handleEvent($event)\"\n [children]=\"child?.children || []\"\n [globals]=\"{props: child.props}\"\n />\n }\n } @else {\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n <ion-text class=\"date\" style=\"width: 20%;\"><ion-skeleton-text [animated]=\"true\"></ion-skeleton-text></ion-text>\n <br />\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n <ion-text class=\"date\" style=\"width: 20%;\"><ion-skeleton-text [animated]=\"true\"></ion-skeleton-text></ion-text>\n }\n\n <div class=\"dcf-buttons-container dcf-grid dcf-grid-collapse dcf-flex dcf-flex-left\">\n <div class=\"dcf-width-1-2@s\">\n <ion-button fill=\"clear\" (click)=\"handleBack()\" [disabled]=\"activePage <= 1\">\n <ion-icon aria-hidden=\"true\" name=\"arrow-back-outline\"></ion-icon>\n Previous\n </ion-button>\n </div>\n\n <div class=\"dcf-width-1-2@s\">\n <ion-button [fill]=\"activePage === pages ? 'solid' : 'outline'\" (click)=\"handleNext(activePage === pages ? true : false)\">\n @if(activePage === pages) {\n Submit\n } @else {\n Next\n <ion-icon aria-hidden=\"true\" name=\"arrow-forward-outline\"></ion-icon>\n }\n </ion-button>\n </div>\n </div>\n </form>\n", styles: [".dcf-buttons-container{margin-top:1.8rem;margin-bottom:0}@media (min-width: 639px){.dcf-buttons-container.dcf-flex div:nth-child(2){display:flex;justify-content:flex-end}}@media (max-width: 638px){.dcf-buttons-container.dcf-flex div{width:100%}.dcf-buttons-container.dcf-flex ion-button{width:100%;margin-bottom:1rem}}.dcf-steped-form{padding:2rem 1rem}.dcf-page-steps{display:flex;justify-content:center;margin-bottom:2rem}.dcf-page-steps>div{justify-content:center;min-width:200px;max-width:200px;column-gap:.25em;display:flex}.dcf-page-steps>div>div{width:30px;text-align:center;border-bottom:solid var(--dcf-color-gray-3);box-sizing:border-box;border-width:3px;font-size:0px;font-weight:600}.dcf-page-steps>div>div.dcf-active{border-width:5px;font-size:1rem;color:var(--ion-color-gray-7);border-color:var(--ion-color-primary);line-height:1rem}.dcf-page-steps>div>div.dcf-passed{border-width:4px;font-size:.5rem;line-height:1.2rem;border-color:var(--dcf-color-gray-5);color:var(--ion-color-primary)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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: "component", type: IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: ComponentRendererComponent, selector: "ngx-decaf-component-renderer", inputs: ["tag", "globals", "children", "model", "parent"], outputs: ["listenEvent"] }] }); }
8679
+ };
8680
+ StepedFormComponent = __decorate([
8681
+ Dynamic(),
8682
+ __metadata("design:paramtypes", [])
8683
+ ], StepedFormComponent);
8684
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StepedFormComponent, decorators: [{
8685
+ type: Component,
8686
+ args: [{ selector: 'ngx-decaf-steped-form', imports: [ReactiveFormsModule, IonSkeletonText, IonText, IonButton, IonIcon, ModelRendererComponent, ComponentRendererComponent], standalone: true, template: " <!-- @for(child of children; track trackItemFn($index, child)) {\n {{child.tag }}\n <ngx-decaf-component-renderer\n [tag]=\"child.tag\"\n (listenEvent)=\"handleEvent($event)\"\n [globals]='{props: child.props}'>\n </ngx-decaf-component-renderer>\n } -->\n <form class=\"dcf-steped-form\" novalidate>\n <div class=\"dcf-page-steps\">\n <div>\n @for(page of pagesArray; track $index;) {\n <div [class.dcf-active]=\"activePage === $index + 1\" [class.dcf-passed]=\"($index + 1) < activePage\">{{ $index + 1 }}</div>\n }\n </div>\n </div>\n @if(formGroup) {\n @for(child of activeChildren; track $index) {\n <ngx-decaf-component-renderer\n [tag]=\"child?.tag\"\n (listenEvent)=\"handleEvent($event)\"\n [children]=\"child?.children || []\"\n [globals]=\"{props: child.props}\"\n />\n }\n } @else {\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n <ion-text class=\"date\" style=\"width: 20%;\"><ion-skeleton-text [animated]=\"true\"></ion-skeleton-text></ion-text>\n <br />\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n <ion-text class=\"date\" style=\"width: 20%;\"><ion-skeleton-text [animated]=\"true\"></ion-skeleton-text></ion-text>\n }\n\n <div class=\"dcf-buttons-container dcf-grid dcf-grid-collapse dcf-flex dcf-flex-left\">\n <div class=\"dcf-width-1-2@s\">\n <ion-button fill=\"clear\" (click)=\"handleBack()\" [disabled]=\"activePage <= 1\">\n <ion-icon aria-hidden=\"true\" name=\"arrow-back-outline\"></ion-icon>\n Previous\n </ion-button>\n </div>\n\n <div class=\"dcf-width-1-2@s\">\n <ion-button [fill]=\"activePage === pages ? 'solid' : 'outline'\" (click)=\"handleNext(activePage === pages ? true : false)\">\n @if(activePage === pages) {\n Submit\n } @else {\n Next\n <ion-icon aria-hidden=\"true\" name=\"arrow-forward-outline\"></ion-icon>\n }\n </ion-button>\n </div>\n </div>\n </form>\n", styles: [".dcf-buttons-container{margin-top:1.8rem;margin-bottom:0}@media (min-width: 639px){.dcf-buttons-container.dcf-flex div:nth-child(2){display:flex;justify-content:flex-end}}@media (max-width: 638px){.dcf-buttons-container.dcf-flex div{width:100%}.dcf-buttons-container.dcf-flex ion-button{width:100%;margin-bottom:1rem}}.dcf-steped-form{padding:2rem 1rem}.dcf-page-steps{display:flex;justify-content:center;margin-bottom:2rem}.dcf-page-steps>div{justify-content:center;min-width:200px;max-width:200px;column-gap:.25em;display:flex}.dcf-page-steps>div>div{width:30px;text-align:center;border-bottom:solid var(--dcf-color-gray-3);box-sizing:border-box;border-width:3px;font-size:0px;font-weight:600}.dcf-page-steps>div>div.dcf-active{border-width:5px;font-size:1rem;color:var(--ion-color-gray-7);border-color:var(--ion-color-primary);line-height:1rem}.dcf-page-steps>div>div.dcf-passed{border-width:4px;font-size:.5rem;line-height:1.2rem;border-color:var(--dcf-color-gray-5);color:var(--ion-color-primary)}\n"] }]
8687
+ }], ctorParameters: () => [], propDecorators: { pages: [{
8688
+ type: Input
8689
+ }], operation: [{
8690
+ type: Input
8691
+ }], startPage: [{
8692
+ type: Input
8693
+ }], children: [{
8694
+ type: Input
8695
+ }], formGroup: [{
8696
+ type: Input
8697
+ }], submitEvent: [{
8698
+ type: Output
8699
+ }] } });
8700
+
8028
8701
  class CollapsableDirective {
8029
8702
  constructor() {
8030
8703
  this.element = inject(ElementRef);
@@ -8066,7 +8739,8 @@ const Components = [
8066
8739
  CrudFormComponent,
8067
8740
  FieldsetComponent,
8068
8741
  LayoutComponent,
8069
- FilterComponent
8742
+ FilterComponent,
8743
+ StepedFormComponent
8070
8744
  ];
8071
8745
  class ForAngularComponentsModule {
8072
8746
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ForAngularComponentsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
@@ -8082,7 +8756,8 @@ class ForAngularComponentsModule {
8082
8756
  CrudFormComponent,
8083
8757
  FieldsetComponent,
8084
8758
  LayoutComponent,
8085
- FilterComponent, CollapsableDirective], exports: [ModelRendererComponent,
8759
+ FilterComponent,
8760
+ StepedFormComponent, CollapsableDirective], exports: [ModelRendererComponent,
8086
8761
  ComponentRendererComponent,
8087
8762
  CrudFieldComponent,
8088
8763
  CrudFormComponent,
@@ -8094,7 +8769,8 @@ class ForAngularComponentsModule {
8094
8769
  CrudFormComponent,
8095
8770
  FieldsetComponent,
8096
8771
  LayoutComponent,
8097
- FilterComponent, CollapsableDirective] }); }
8772
+ FilterComponent,
8773
+ StepedFormComponent, CollapsableDirective] }); }
8098
8774
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ForAngularComponentsModule, imports: [CrudFieldComponent,
8099
8775
  CrudFormComponent,
8100
8776
  EmptyStateComponent,
@@ -8104,7 +8780,8 @@ class ForAngularComponentsModule {
8104
8780
  PaginationComponent,
8105
8781
  CrudFormComponent,
8106
8782
  FieldsetComponent,
8107
- FilterComponent] }); }
8783
+ FilterComponent,
8784
+ StepedFormComponent] }); }
8108
8785
  }
8109
8786
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ForAngularComponentsModule, decorators: [{
8110
8787
  type: NgModule,
@@ -8130,5 +8807,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8130
8807
  * Generated bundle index. Do not edit.
8131
8808
  */
8132
8809
 
8133
- export { AngularEngineKeys, BaseComponentProps, CollapsableDirective, ComponentRendererComponent, ComponentsTagNames, CrudFieldComponent, CrudFormComponent, CssClasses, DefaultFormReactiveOptions, Dynamic, DynamicModule, EmptyStateComponent, EventConstants, FieldsetComponent, FilterComponent, ForAngularCommonModule, ForAngularComponentsModule, FormConstants, I18N_CONFIG_TOKEN, I18nLoader, I18nLoaderFactory, LayoutComponent, ListComponent, ListComponentsTypes, ListItemComponent, LoggerLevels, ModelRendererComponent, MultiI18nLoader, NgxBaseComponent, NgxCrudFormField, NgxFormService, NgxRenderingEngine, PaginationComponent, RouteDirections, SearchbarComponent, cleanSpaces, dataMapper, formatDate, generateRandomValue, getI18nLoaderFactoryProviderConfig, getInjectablesRegistry, getLocaleContext, getLocaleContextByKey, getLocaleFromClassName, getLocaleLanguage, getLogger, getOnWindow, getOnWindowDocument, getWindow, getWindowDocument, getWindowWidth, isDarkMode, isDevelopmentMode, isNotUndefined, isValidDate, itemMapper, parseToValidDate, removeFocusTrap, setOnWindow, stringToBoolean, windowEventEmitter };
8810
+ export { AngularEngineKeys, BaseComponentProps, CollapsableDirective, ComponentRendererComponent, ComponentsTagNames, CrudFieldComponent, CrudFormComponent, CssClasses, DB_ADAPTER_PROVIDER_TOKEN, DefaultFormReactiveOptions, Dynamic, DynamicModule, EmptyStateComponent, EventConstants, FieldsetComponent, FilterComponent, ForAngularCommonModule, ForAngularComponentsModule, FormConstants, I18N_CONFIG_TOKEN, I18nLoader, I18nLoaderFactory, LayoutComponent, ListComponent, ListComponentsTypes, ListItemComponent, LoggerLevels, ModelRendererComponent, MultiI18nLoader, NgxBaseComponent, NgxCrudFormField, NgxFormService, NgxRenderingEngine, PaginationComponent, RouteDirections, SearchbarComponent, StepedFormComponent, cleanSpaces, dataMapper, formatDate, generateRandomValue, getI18nLoaderFactoryProviderConfig, getInjectablesRegistry, getLocaleContext, getLocaleContextByKey, getLocaleFromClassName, getLocaleLanguage, getLogger, getOnWindow, getOnWindowDocument, getWindow, getWindowDocument, getWindowWidth, isDarkMode, isDevelopmentMode, isNotUndefined, isValidDate, itemMapper, parseToValidDate, removeFocusTrap, setOnWindow, stringToBoolean, windowEventEmitter };
8134
8811
  //# sourceMappingURL=decaf-ts-for-angular.mjs.map