@decaf-ts/for-angular 0.0.4 → 0.0.6

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 (85) hide show
  1. package/LICENSE.md +646 -144
  2. package/README.md +37 -242
  3. package/dist/lib/README.md +92 -0
  4. package/dist/lib/assets/i18n/en.json +131 -0
  5. package/dist/lib/assets/images/angular-logo.svg +45 -0
  6. package/dist/lib/assets/images/decaf-logo-black.svg +22 -0
  7. package/dist/lib/assets/images/decaf-logo-lw.svg +50 -0
  8. package/dist/lib/assets/images/decaf-logo-white.svg +22 -0
  9. package/dist/lib/assets/images/decaf-logo.svg +54 -0
  10. package/dist/lib/components/component-renderer/component-renderer.component.d.ts +267 -0
  11. package/dist/lib/components/crud-field/crud-field.component.d.ts +445 -0
  12. package/dist/{for-angular/components/decaf-crud-form → lib/components/crud-form}/constants.d.ts +0 -0
  13. package/dist/lib/components/crud-form/crud-form.component.d.ts +102 -0
  14. package/dist/{for-angular/components/decaf-crud-form → lib/components/crud-form}/types.d.ts +0 -0
  15. package/dist/lib/components/model-renderer/model-renderer.component.d.ts +96 -0
  16. package/dist/lib/engine/DynamicModule.d.ts +17 -0
  17. package/dist/{for-angular → lib}/engine/NgxCrudFormField.d.ts +21 -17
  18. package/dist/lib/engine/NgxFormService.d.ts +167 -0
  19. package/dist/lib/engine/NgxRenderingEngine.d.ts +127 -0
  20. package/dist/lib/engine/NgxRenderingEngine2.d.ts +250 -0
  21. package/dist/lib/engine/ValidatorFactory.d.ts +15 -0
  22. package/dist/lib/engine/constants.d.ts +151 -0
  23. package/dist/lib/engine/decorators.d.ts +25 -0
  24. package/dist/lib/engine/index.d.ts +15 -0
  25. package/dist/lib/engine/types.d.ts +294 -0
  26. package/dist/lib/esm2022/components/component-renderer/component-renderer.component.mjs +312 -0
  27. package/dist/lib/esm2022/components/crud-field/crud-field.component.mjs +297 -0
  28. package/dist/lib/esm2022/components/crud-form/constants.mjs +14 -0
  29. package/dist/lib/esm2022/components/crud-form/crud-form.component.mjs +139 -0
  30. package/dist/lib/esm2022/components/crud-form/types.mjs +2 -0
  31. package/dist/lib/esm2022/components/model-renderer/model-renderer.component.mjs +138 -0
  32. package/dist/{for-angular → lib}/esm2022/decaf-ts-for-angular.mjs +0 -0
  33. package/dist/lib/esm2022/engine/DynamicModule.mjs +18 -0
  34. package/dist/lib/esm2022/engine/NgxCrudFormField.mjs +123 -0
  35. package/dist/lib/esm2022/engine/NgxFormService.mjs +315 -0
  36. package/dist/lib/esm2022/engine/NgxRenderingEngine.mjs +192 -0
  37. package/dist/lib/esm2022/engine/NgxRenderingEngine2.mjs +332 -0
  38. package/dist/lib/esm2022/engine/ValidatorFactory.mjs +102 -0
  39. package/dist/lib/esm2022/engine/constants.mjs +160 -0
  40. package/dist/lib/esm2022/engine/decorators.mjs +38 -0
  41. package/dist/lib/esm2022/engine/index.mjs +16 -0
  42. package/dist/lib/esm2022/engine/types.mjs +2 -0
  43. package/dist/lib/esm2022/for-angular.module.mjs +118 -0
  44. package/dist/lib/esm2022/interfaces.mjs +2 -0
  45. package/dist/lib/esm2022/public-apis.mjs +13 -0
  46. package/dist/lib/fesm2022/decaf-ts-for-angular.mjs +2152 -0
  47. package/dist/lib/fesm2022/decaf-ts-for-angular.mjs.map +1 -0
  48. package/dist/lib/for-angular.module.d.ts +45 -0
  49. package/dist/{for-angular → lib}/index.d.ts +0 -0
  50. package/dist/lib/interfaces.d.ts +28 -0
  51. package/dist/lib/public-apis.d.ts +12 -0
  52. package/package.json +71 -26
  53. package/dist/for-angular/README.md +0 -297
  54. package/dist/for-angular/assets/i18n/en.json +0 -21
  55. package/dist/for-angular/components/decaf-crud-field/decaf-crud-field.component.d.ts +0 -49
  56. package/dist/for-angular/components/decaf-crud-form/decaf-crud-form.component.d.ts +0 -28
  57. package/dist/for-angular/components/decaf-model-renderer/decaf-model-renderer.component.d.ts +0 -20
  58. package/dist/for-angular/engine/DynamicModule.d.ts +0 -2
  59. package/dist/for-angular/engine/NgxFormService.d.ts +0 -119
  60. package/dist/for-angular/engine/NgxRenderingEngine.d.ts +0 -18
  61. package/dist/for-angular/engine/ValidatorFactory.d.ts +0 -4
  62. package/dist/for-angular/engine/constants.d.ts +0 -13
  63. package/dist/for-angular/engine/decorators.d.ts +0 -1
  64. package/dist/for-angular/engine/index.d.ts +0 -5
  65. package/dist/for-angular/engine/types.d.ts +0 -44
  66. package/dist/for-angular/esm2022/components/decaf-crud-field/decaf-crud-field.component.mjs +0 -129
  67. package/dist/for-angular/esm2022/components/decaf-crud-form/constants.mjs +0 -14
  68. package/dist/for-angular/esm2022/components/decaf-crud-form/decaf-crud-form.component.mjs +0 -80
  69. package/dist/for-angular/esm2022/components/decaf-crud-form/types.mjs +0 -2
  70. package/dist/for-angular/esm2022/components/decaf-model-renderer/decaf-model-renderer.component.mjs +0 -47
  71. package/dist/for-angular/esm2022/engine/DynamicModule.mjs +0 -3
  72. package/dist/for-angular/esm2022/engine/NgxCrudFormField.mjs +0 -115
  73. package/dist/for-angular/esm2022/engine/NgxFormService.mjs +0 -235
  74. package/dist/for-angular/esm2022/engine/NgxRenderingEngine.mjs +0 -84
  75. package/dist/for-angular/esm2022/engine/ValidatorFactory.mjs +0 -48
  76. package/dist/for-angular/esm2022/engine/constants.mjs +0 -15
  77. package/dist/for-angular/esm2022/engine/decorators.mjs +0 -14
  78. package/dist/for-angular/esm2022/engine/index.mjs +0 -6
  79. package/dist/for-angular/esm2022/engine/types.mjs +0 -2
  80. package/dist/for-angular/esm2022/interfaces.mjs +0 -2
  81. package/dist/for-angular/esm2022/public-apis.mjs +0 -6
  82. package/dist/for-angular/fesm2022/decaf-ts-for-angular.mjs +0 -759
  83. package/dist/for-angular/fesm2022/decaf-ts-for-angular.mjs.map +0 -1
  84. package/dist/for-angular/interfaces.d.ts +0 -8
  85. package/dist/for-angular/public-apis.d.ts +0 -5
@@ -0,0 +1,2152 @@
1
+ import { UIKeys, parseValueByType, HTML5InputTypes, HTML5CheckTypes, escapeHtml, parseToNumber, RenderingEngine, RenderingError } from '@decaf-ts/ui-decorators';
2
+ import * as i0 from '@angular/core';
3
+ import { reflectComponentType, inject, EnvironmentInjector, EventEmitter, ViewContainerRef, TemplateRef, ViewChild, Input, Output, Component, Injector, ElementRef, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
4
+ import { VALIDATION_PARENT_KEY, ValidationKeys, DEFAULT_PATTERNS, Validation, ComparisonValidationKeys, PathProxyEngine, isValidDate, parseDate, Model, sf } from '@decaf-ts/decorator-validation';
5
+ import * as i1 from '@angular/common';
6
+ import { NgComponentOutlet, Location } from '@angular/common';
7
+ import { apply, metadata } from '@decaf-ts/reflection';
8
+ import { InternalError, OperationKeys } from '@decaf-ts/db-decorators';
9
+ import * as i2 from '@angular/forms';
10
+ import { FormGroup, FormControl, Validators } from '@angular/forms';
11
+ import { getLogger, ForAngularModule } from 'src/lib/for-angular.module';
12
+ import { NgxRenderingEngine2 as NgxRenderingEngine2$1 } from 'src/lib/engine/NgxRenderingEngine2';
13
+ import { __decorate } from 'tslib';
14
+ import * as i3 from '@ngx-translate/core';
15
+ import { TranslateService } from '@ngx-translate/core';
16
+ import * as i1$1 from '@ionic/angular/standalone';
17
+ import { IonInput, IonItem, IonCheckbox, IonRadioGroup, IonRadio, IonSelect, IonSelectOption, IonLabel, IonTextarea, IonText, IonIcon } from '@ionic/angular/standalone';
18
+
19
+ /**
20
+ * @description Angular engine key constants
21
+ * @summary Contains key strings used by the Angular rendering engine for reflection,
22
+ * dynamic component creation, and other engine operations.
23
+ * @typedef {Object} AngularEngineKeys
24
+ * @property {string} REFLECT - Prefix for reflection metadata keys
25
+ * @property {string} DYNAMIC - Key for dynamic component identification
26
+ * @property {string} ANNOTATIONS - Key for component annotations
27
+ * @property {string} ECMP - Key for embedded components
28
+ * @property {string} NG_REFLECT - Prefix for Angular reflection attributes
29
+ * @property {string} RENDERED - Prefix for rendered component markers
30
+ * @property {string} MAPPER - Key for property mappers
31
+ * @property {string} CHILDREN - Key for child components
32
+ * @property {string} LISTABLE - Key for listable components
33
+ * @property {string} RENDER - Key for renderable components
34
+ * @property {string} RENDERED_ID - Template for rendered component IDs
35
+ * @property {string} PARENT - Key for comparison decorators and validators
36
+ * @const AngularEngineKeys
37
+ * @memberOf module:engine
38
+ */
39
+ const AngularEngineKeys = {
40
+ REFLECT: `${UIKeys.REFLECT}.angular.`,
41
+ DYNAMIC: 'dynamic-component',
42
+ ANNOTATIONS: '__annotations__',
43
+ ECMP: 'ecmp',
44
+ NG_REFLECT: 'ng-reflect-',
45
+ RENDERED: 'rendered-as-',
46
+ MAPPER: 'mapper',
47
+ CHILDREN: 'children',
48
+ LISTABLE: 'listable',
49
+ RENDER: 'render',
50
+ RENDERED_ID: 'rendered-as-{0}',
51
+ PARENT: '_parent',
52
+ VALIDATION_PARENT_KEY: VALIDATION_PARENT_KEY
53
+ };
54
+ /**
55
+ * @description Form validation state constants
56
+ * @summary Contains constants representing the possible validation states of a form.
57
+ * These are used to check and handle form validation throughout the application.
58
+ * @typedef {Object} FormConstants
59
+ * @property {string} VALID - Constant representing a valid form state
60
+ * @property {string} INVALID - Constant representing an invalid form state
61
+ * @const FormConstants
62
+ * @memberOf module:engine
63
+ */
64
+ const FormConstants = {
65
+ VALID: 'VALID',
66
+ INVALID: 'INVALID',
67
+ };
68
+ /**
69
+ * @description Event name constants
70
+ * @summary Enum containing constants for event names used throughout the application.
71
+ * These are used to standardize event naming and handling.
72
+ * @enum {string}
73
+ * @readonly
74
+ * @property {string} BACK_BUTTON_NAVIGATION - Event fired when back button navigation ends
75
+ * @property {string} REFRESH_EVENT - Event fired when a refresh action occurs
76
+ * @property {string} CLICK_EVENT - Event fired when a click action occurs
77
+ * @property {string} SUBMIT_EVENT - Event fired when a form submission occurs
78
+ * @memberOf module:engine
79
+ */
80
+ var EventConstants;
81
+ (function (EventConstants) {
82
+ EventConstants["BACK_BUTTON_NAVIGATION"] = "backButtonNavigationEndEvent";
83
+ EventConstants["REFRESH_EVENT"] = "RefreshEvent";
84
+ EventConstants["CLICK_EVENT"] = "ClickEvent";
85
+ EventConstants["SUBMIT_EVENT"] = "SubmitEvent";
86
+ })(EventConstants || (EventConstants = {}));
87
+ /**
88
+ * @description Logger level constants
89
+ * @summary Enum defining the logging levels used in the application's logging system.
90
+ * Lower values represent more verbose logging, while higher values represent more critical logs.
91
+ * @enum {number}
92
+ * @readonly
93
+ * @property {number} ALL - Log everything (most verbose)
94
+ * @property {number} DEBUG - Log debug information
95
+ * @property {number} INFO - Log informational messages
96
+ * @property {number} WARN - Log warnings
97
+ * @property {number} ERROR - Log errors
98
+ * @property {number} CRITICAL - Log critical errors (least verbose)
99
+ * @memberOf module:engine
100
+ */
101
+ var LoggerLevels;
102
+ (function (LoggerLevels) {
103
+ LoggerLevels[LoggerLevels["ALL"] = 0] = "ALL";
104
+ LoggerLevels[LoggerLevels["DEBUG"] = 1] = "DEBUG";
105
+ LoggerLevels[LoggerLevels["INFO"] = 2] = "INFO";
106
+ LoggerLevels[LoggerLevels["WARN"] = 3] = "WARN";
107
+ LoggerLevels[LoggerLevels["ERROR"] = 4] = "ERROR";
108
+ LoggerLevels[LoggerLevels["CRITICAL"] = 5] = "CRITICAL";
109
+ })(LoggerLevels || (LoggerLevels = {}));
110
+ ;
111
+ /**
112
+ * @description Route direction constants
113
+ * @summary Enum defining the possible navigation directions in the application.
114
+ * Used for controlling navigation flow and animation directions.
115
+ * @enum {string}
116
+ * @readonly
117
+ * @property {string} BACK - Navigate back to the previous page
118
+ * @property {string} FORWARD - Navigate forward to the next page
119
+ * @property {string} ROOT - Navigate to the root/home page
120
+ * @memberOf module:engine
121
+ */
122
+ var RouteDirections;
123
+ (function (RouteDirections) {
124
+ RouteDirections["BACK"] = "back";
125
+ RouteDirections["FORWARD"] = "forward";
126
+ RouteDirections["ROOT"] = "root";
127
+ })(RouteDirections || (RouteDirections = {}));
128
+ /**
129
+ * @description Component tag name constants
130
+ * @summary Enum defining the tag names for custom components used in the application.
131
+ * These tag names are used for component registration and rendering.
132
+ * @enum {string}
133
+ * @readonly
134
+ * @property {string} LIST_ITEM - Tag name for list item component
135
+ * @property {string} LIST_INFINITE - Tag name for infinite scrolling list component
136
+ * @property {string} LIST_PAGINATED - Tag name for paginated list component
137
+ * @memberOf module:engine
138
+ */
139
+ var ComponentsTagNames;
140
+ (function (ComponentsTagNames) {
141
+ ComponentsTagNames["LIST_ITEM"] = "ngx-decaf-list-item";
142
+ ComponentsTagNames["LIST_INFINITE"] = "ngx-decaf-list-infinite";
143
+ ComponentsTagNames["LIST_PAGINATED"] = "ngx-decaf-list-paginated";
144
+ })(ComponentsTagNames || (ComponentsTagNames = {}));
145
+ /**
146
+ * @description Base component property name constants
147
+ * @summary Enum defining the standard property names used by base components in the application.
148
+ * These property names are used for consistent property access across components.
149
+ * @enum {string}
150
+ * @readonly
151
+ * @property {string} MODEL - Property name for the component's data model
152
+ * @property {string} LOCALE - Property name for localization settings
153
+ * @property {string} PK - Property name for primary key
154
+ * @property {string} ITEMS - Property name for collection items
155
+ * @property {string} ROUTE - Property name for routing information
156
+ * @property {string} OPERATIONS - Property name for available operations
157
+ * @property {string} UID - Property name for unique identifier
158
+ * @property {string} TRANSLATABLE - Property name for translation flag
159
+ * @property {string} MAPPER - Property name for property mapper
160
+ * @property {string} INITIALIZED - Property name for initialization state
161
+ * @memberOf module:engine
162
+ */
163
+ var BaseComponentProps;
164
+ (function (BaseComponentProps) {
165
+ BaseComponentProps["MODEL"] = "model";
166
+ BaseComponentProps["LOCALE"] = "locale";
167
+ BaseComponentProps["PK"] = "pk";
168
+ BaseComponentProps["ITEMS"] = "items";
169
+ BaseComponentProps["ROUTE"] = "route";
170
+ BaseComponentProps["OPERATIONS"] = "operations";
171
+ BaseComponentProps["UID"] = "uid";
172
+ BaseComponentProps["TRANSLATABLE"] = "translatable";
173
+ BaseComponentProps["MAPPER"] = "mapper";
174
+ BaseComponentProps["INITIALIZED"] = "initialized";
175
+ })(BaseComponentProps || (BaseComponentProps = {}));
176
+
177
+ /**
178
+ *
179
+ * Resolves the correct validator key and its associated properties based on the input key and type.
180
+ *
181
+ * When the validation key is TYPE, it's necessary to resolve the actual validator based on the
182
+ * field's type (e.g., 'password', 'email', 'url') instead of using the generic getValidator("type") logic.
183
+ * This allows directly invoking specific validators like getValidator('password'), ensuring the correct
184
+ * behavior for type-based validation.
185
+ *
186
+ * @param key - The validation key (e.g., 'type', 'required', etc.).
187
+ * @param value - The value that needs be provided to the validator.
188
+ * @param type - The field's declared type.
189
+ * @returns An object containing the resolved validator key and its corresponding props.
190
+ */
191
+ const resolveValidatorKeyProps = (key, value, type) => {
192
+ const patternValidators = {
193
+ [ValidationKeys.PASSWORD]: DEFAULT_PATTERNS.PASSWORD.CHAR8_ONE_OF_EACH,
194
+ [ValidationKeys.EMAIL]: DEFAULT_PATTERNS.EMAIL,
195
+ [ValidationKeys.URL]: DEFAULT_PATTERNS.URL,
196
+ };
197
+ const isTypeBased = key === ValidationKeys.TYPE && Object.keys(patternValidators).includes(type);
198
+ const validatorKey = isTypeBased ? type : key;
199
+ const props = {
200
+ [validatorKey]: value,
201
+ // Email, Password, and URL are validated using the "pattern" key
202
+ ...(isTypeBased && { [ValidationKeys.PATTERN]: patternValidators[type] }),
203
+ };
204
+ return { validatorKey, props };
205
+ };
206
+ class ValidatorFactory {
207
+ static spawn(fieldProps, key) {
208
+ if (!Validation.keys().includes(key))
209
+ throw new Error('Unsupported custom validation');
210
+ const validatorFn = (control) => {
211
+ const { name, type } = fieldProps;
212
+ const { validatorKey, props } = resolveValidatorKeyProps(key, fieldProps[key], type);
213
+ const validator = Validation.get(validatorKey);
214
+ // parseValueByType does not support undefined values
215
+ const value = typeof control.value !== 'undefined'
216
+ ? parseValueByType(type, type === HTML5InputTypes.CHECKBOX ? name : control.value, fieldProps)
217
+ : undefined;
218
+ // Create a proxy to enable access to parent and child values
219
+ let proxy = ValidatorFactory.createProxy({});
220
+ if (Object.values(ComparisonValidationKeys).includes(key)) {
221
+ const parent = control instanceof FormGroup ? control : control[AngularEngineKeys.PARENT];
222
+ proxy = ValidatorFactory.createProxy(parent);
223
+ }
224
+ let errs;
225
+ try {
226
+ errs = validator.hasErrors(value, props, proxy);
227
+ }
228
+ catch (e) {
229
+ errs = `${key} validator failed to validate: ${e}`;
230
+ console.warn(errs);
231
+ }
232
+ return errs ? { [validatorKey]: true } : null;
233
+ };
234
+ Object.defineProperty(validatorFn, 'name', {
235
+ value: `${key}Validator`,
236
+ });
237
+ return validatorFn;
238
+ }
239
+ /**
240
+ * @summary Creates a proxy wrapper for an Angular AbstractControl to assist with custom validation logic.
241
+ * @description Returns a structured proxy object that simulates a hierarchical tree of form values.
242
+ * Enables Validators handling method to access parent and child properties using consistent dot-notation in Angular forms.
243
+ *
244
+ * @param {AbstractControl} control - The control to wrap in a proxy.
245
+ * @returns {PathProxy<unknown>} A proxy object exposing form values and enabling recursive parent access.
246
+ */
247
+ static createProxy(control) {
248
+ return PathProxyEngine.create(control, {
249
+ getValue(target, prop) {
250
+ if (target instanceof FormControl)
251
+ return target.value;
252
+ if (target instanceof FormGroup) {
253
+ const control = target.controls[prop];
254
+ return control instanceof FormControl ? control.value : control;
255
+ }
256
+ // const value = target[prop];
257
+ // if (value instanceof FormControl)
258
+ // return value.value;
259
+ //
260
+ // if (value instanceof FormGroup) {
261
+ // const control = value.controls[prop];
262
+ // return control instanceof FormControl ? control.value : control;
263
+ // }
264
+ return target?.[prop];
265
+ },
266
+ getParent: function (target) {
267
+ return target?.['_parent'];
268
+ },
269
+ ignoreUndefined: true,
270
+ ignoreNull: true,
271
+ });
272
+ }
273
+ }
274
+
275
+ /**
276
+ * @description Service for managing Angular forms and form controls.
277
+ * @summary The NgxFormService provides utility methods for creating, managing, and validating Angular forms and form controls. It includes functionality for registering forms, adding controls, validating fields, and handling form data.
278
+ *
279
+ * @class
280
+ * @param {WeakMap<AbstractControl, FieldProperties>} controls - A WeakMap to store control properties.
281
+ * @param {Map<string, FormGroup>} formRegistry - A Map to store registered forms.
282
+ *
283
+ * @example
284
+ * // Creating a form from components
285
+ * const components = [
286
+ * { inputs: { name: 'username', type: 'text', required: true } },
287
+ * { inputs: { name: 'password', type: 'password', minLength: 8 } }
288
+ * ];
289
+ * const form = NgxFormService.createFormFromComponents('loginForm', components, true);
290
+ *
291
+ * // Validating fields
292
+ * NgxFormService.validateFields(form);
293
+ *
294
+ * // Getting form data
295
+ * const formData = NgxFormService.getFormData(form);
296
+ *
297
+ * @mermaid
298
+ * sequenceDiagram
299
+ * participant C as Component
300
+ * participant NFS as NgxFormService
301
+ * participant AF as Angular Forms
302
+ * C->>NFS: createFormFromComponents()
303
+ * NFS->>AF: new FormGroup()
304
+ * NFS->>NFS: addFormControl()
305
+ * NFS->>AF: addControl()
306
+ * NFS-->>C: Return FormGroup
307
+ * C->>NFS: validateFields()
308
+ * NFS->>AF: markAsTouched(), markAsDirty(), updateValueAndValidity()
309
+ * C->>NFS: getFormData()
310
+ * NFS->>AF: Get control values
311
+ * NFS-->>C: Return form data
312
+ */
313
+ class NgxFormService {
314
+ static { this.controls = new WeakMap(); }
315
+ static { this.formRegistry = new Map(); }
316
+ /**
317
+ * @description Adds a form to the registry.
318
+ * @summary Registers a FormGroup with a unique identifier. Throws an error if the identifier is already in use.
319
+ * @param {string} formId - The unique identifier for the form.
320
+ * @param {FormGroup} formGroup - The FormGroup to be registered.
321
+ * @throws {Error} If a FormGroup with the given id is already registered.
322
+ */
323
+ static addRegistry(formId, formGroup) {
324
+ if (this.formRegistry.has(formId))
325
+ throw new Error(`A FormGroup with id '${formId}' is already registered.`);
326
+ this.formRegistry.set(formId, formGroup);
327
+ }
328
+ /**
329
+ * @description Removes a form from the registry.
330
+ * @summary Deletes a FormGroup from the registry using its unique identifier.
331
+ * @param {string} formId - The unique identifier of the form to be removed.
332
+ */
333
+ static removeRegistry(formId) {
334
+ this.formRegistry.delete(formId);
335
+ }
336
+ /**
337
+ * @description Resolves the parent group and control name from a path.
338
+ * @summary Traverses the form group structure to find the parent group and control name for a given path.
339
+ * @param {FormGroup} formGroup - The root FormGroup.
340
+ * @param {string} path - The path to the control.
341
+ * @return {FormParentGroup} A tuple containing the parent FormGroup and the control name.
342
+ */
343
+ static resolveParentGroup(formGroup, path) {
344
+ const parts = path.split('.');
345
+ const controlName = parts.pop();
346
+ let currentGroup = formGroup;
347
+ for (const part of parts) {
348
+ if (!currentGroup.get(part)) {
349
+ currentGroup.addControl(part, new FormGroup({}));
350
+ }
351
+ currentGroup = currentGroup.get(part);
352
+ }
353
+ return [currentGroup, controlName];
354
+ }
355
+ /**
356
+ * @description Adds a form control to a form group.
357
+ * @summary Creates and adds a form control to the specified form group based on the provided component properties.
358
+ * @param {FormGroup} formGroup - The form group to add the control to.
359
+ * @param {ComponentInput} componentProps - The properties of the component to create the control from.
360
+ */
361
+ static addFormControl(formGroup, componentProps) {
362
+ const { name, childOf } = componentProps;
363
+ const fullPath = childOf ? `${childOf}.${name}` : name;
364
+ const [parentGroup, controlName] = this.resolveParentGroup(formGroup, fullPath);
365
+ if (!parentGroup.get(controlName)) {
366
+ const control = NgxFormService.fromProps(componentProps, componentProps.updateMode || 'change');
367
+ NgxFormService.register(control, componentProps);
368
+ parentGroup.addControl(controlName, control);
369
+ }
370
+ componentProps['formGroup'] = parentGroup;
371
+ componentProps['formControl'] = parentGroup.get(controlName);
372
+ }
373
+ /**
374
+ * @description Retrieves a control from a registered form.
375
+ * @summary Finds and returns an AbstractControl from a registered form using the form id and optional path.
376
+ * @param {string} formId - The unique identifier of the form.
377
+ * @param {string} [path] - The path to the control within the form.
378
+ * @return {AbstractControl} The requested AbstractControl.
379
+ * @throws {Error} If the form is not found in the registry or the control is not found in the form.
380
+ */
381
+ static getControlFromForm(formId, path) {
382
+ const form = this.formRegistry.get(formId);
383
+ if (!form)
384
+ throw new Error(`Form with id '${formId}' not found in the registry.`);
385
+ if (!path)
386
+ return form;
387
+ const control = form.get(path);
388
+ if (!control)
389
+ throw new Error(`Control with path '${path}' not found in form '${formId}'.`);
390
+ return control;
391
+ }
392
+ /**
393
+ * @description Creates a form from component configurations.
394
+ * @summary Generates a FormGroup based on an array of component configurations and optionally registers it.
395
+ * @param {string} id - The unique identifier for the form.
396
+ * @param {ComponentConfig[]} components - An array of component configurations.
397
+ * @param {boolean} [registry=false] - Whether to register the created form.
398
+ * @return {FormGroup} The created FormGroup.
399
+ */
400
+ static createFormFromComponents(id, components, registry = false) {
401
+ const form = new FormGroup({});
402
+ components.forEach(component => {
403
+ this.addFormControl(form, component.inputs);
404
+ });
405
+ if (registry)
406
+ this.addRegistry(id, form);
407
+ return form;
408
+ }
409
+ /**
410
+ * @description Adds a control to a form based on component properties.
411
+ * @summary Creates and adds a form control to a form (existing or new) based on the provided component properties.
412
+ * @param {string} id - The unique identifier of the form.
413
+ * @param {FieldProperties} componentProperties - The properties of the component to create the control from.
414
+ * @return {AbstractControl} The form or created control.
415
+ */
416
+ static addControlFromProps(id, componentProperties) {
417
+ const form = this.formRegistry.get(id) ?? new FormGroup({});
418
+ if (!this.formRegistry.has(id))
419
+ this.addRegistry(id, form);
420
+ if (componentProperties.path)
421
+ this.addFormControl(form, componentProperties);
422
+ return form;
423
+ }
424
+ /**
425
+ * @description Retrieves form data from a FormGroup.
426
+ * @summary Extracts and processes the data from a FormGroup, handling different input types and nested form groups.
427
+ * @param {FormGroup} formGroup - The FormGroup to extract data from.
428
+ * @return {Record<string, unknown>} An object containing the form data.
429
+ */
430
+ static getFormData(formGroup) {
431
+ const data = {};
432
+ for (const key in formGroup.controls) {
433
+ const control = formGroup.controls[key];
434
+ if (!(control instanceof FormControl)) {
435
+ data[key] = NgxFormService.getFormData(control);
436
+ continue;
437
+ }
438
+ const props = NgxFormService.getPropsFromControl(control);
439
+ let value = control.value;
440
+ if (!HTML5CheckTypes.includes(props['type'])) {
441
+ switch (props['type']) {
442
+ case HTML5InputTypes.NUMBER:
443
+ value = parseToNumber(value);
444
+ break;
445
+ case HTML5InputTypes.DATE:
446
+ case HTML5InputTypes.DATETIME_LOCAL:
447
+ value = new Date(value);
448
+ break;
449
+ default:
450
+ value = escapeHtml(value);
451
+ }
452
+ }
453
+ data[key] = value;
454
+ }
455
+ return data;
456
+ }
457
+ /**
458
+ * @description Validates fields in a form control or form group.
459
+ * @summary Recursively validates all fields in a form control or form group, marking them as touched and dirty.
460
+ * @param {AbstractControl} control - The control or form group to validate.
461
+ * @param {string} [path] - The path to the control within the form.
462
+ * @return {boolean} True if all fields are valid, false otherwise.
463
+ * @throws {Error} If no control is found at the specified path or if the control type is unknown.
464
+ */
465
+ static validateFields(control, path) {
466
+ control = path ? control.get(path) : control;
467
+ if (!control)
468
+ throw new Error(`No control found at path: ${path || 'root'}.`);
469
+ const isAllowed = [FormGroup, FormControl].some(type => control instanceof type);
470
+ if (!isAllowed)
471
+ throw new Error(`Unknown control type at: ${path || 'root'}`);
472
+ control.markAsTouched();
473
+ control.markAsDirty();
474
+ control.updateValueAndValidity({ emitEvent: true });
475
+ if (control instanceof FormGroup) {
476
+ Object.values(control.controls).forEach((childControl) => {
477
+ this.validateFields(childControl);
478
+ });
479
+ }
480
+ return control.valid;
481
+ }
482
+ /**
483
+ * @description Generates validators from component properties.
484
+ * @summary Creates an array of ValidatorFn based on the supported validation keys in the component properties.
485
+ * @param {FieldProperties} props - The component properties.
486
+ * @return {ValidatorFn[]} An array of validator functions.
487
+ */
488
+ static validatorsFromProps(props) {
489
+ const supportedValidationKeys = Validation.keys();
490
+ return Object.keys(props)
491
+ .filter((k) => supportedValidationKeys.includes(k))
492
+ .map((k) => {
493
+ return ValidatorFactory.spawn(props, k);
494
+ });
495
+ }
496
+ /**
497
+ * @description Creates a FormControl from component properties.
498
+ * @summary Generates a FormControl with validators based on the provided component properties.
499
+ * @param {FieldProperties} props - The component properties.
500
+ * @param {FieldUpdateMode} [updateMode='change'] - The update mode for the control.
501
+ * @return {FormControl} The created FormControl.
502
+ */
503
+ static fromProps(props, updateMode = 'change') {
504
+ const validators = this.validatorsFromProps(props);
505
+ const composed = validators.length ? Validators.compose(validators) : null;
506
+ return new FormControl({
507
+ value: props.value && props.type !== HTML5InputTypes.CHECKBOX
508
+ ? props.type === HTML5InputTypes.DATE
509
+ ? !isValidDate(parseDate(props.format, props.value))
510
+ ? undefined : props.value :
511
+ props.value : undefined,
512
+ disabled: props.disabled,
513
+ }, {
514
+ validators: composed,
515
+ updateOn: updateMode,
516
+ });
517
+ }
518
+ /**
519
+ * @description Retrieves properties from a FormControl.
520
+ * @summary Gets the FieldProperties associated with a FormControl from the internal WeakMap.
521
+ * @param {FormControl} control - The FormControl to get properties for.
522
+ * @return {FieldProperties} The properties associated with the control.
523
+ */
524
+ static getPropsFromControl(control) {
525
+ return this.controls.get(control) || {};
526
+ }
527
+ /**
528
+ * @description Finds a parent element with a specific tag.
529
+ * @summary Traverses up the DOM tree to find the nearest parent element with the specified tag.
530
+ * @param {HTMLElement} el - The starting element.
531
+ * @param {string} tag - The tag name to search for.
532
+ * @return {HTMLElement} The found parent element.
533
+ * @throws {Error} If no parent with the specified tag is found.
534
+ */
535
+ static getParentEl(el, tag) {
536
+ let parent;
537
+ while ((parent = el.parentElement) !== null) {
538
+ if (parent.tagName.toLowerCase() === tag.toLowerCase()) {
539
+ return parent;
540
+ }
541
+ el = parent;
542
+ }
543
+ throw new Error(`No parent with the tag ${tag} was found for provided element`);
544
+ }
545
+ /**
546
+ * @description Registers a control with its properties.
547
+ * @summary Associates a control with its properties in the internal WeakMap.
548
+ * @param {AbstractControl} control - The control to register.
549
+ * @param {FieldProperties} props - The properties to associate with the control.
550
+ */
551
+ static register(control, props) {
552
+ this.controls.set(control, props);
553
+ }
554
+ /**
555
+ * @description Unregisters a control.
556
+ * @summary Removes a control and its associated properties from the internal WeakMap.
557
+ * @param {AbstractControl} control - The control to unregister.
558
+ * @return {boolean} True if the control was successfully unregistered, false otherwise.
559
+ */
560
+ static unregister(control) {
561
+ return this.controls.delete(control);
562
+ }
563
+ /**
564
+ * @description Resets a form group.
565
+ * @summary Recursively resets all controls in a form group, clearing values, errors, and marking them as pristine and untouched.
566
+ * @param {FormGroup} formGroup - The form group to reset.
567
+ */
568
+ static reset(formGroup) {
569
+ for (const key in formGroup.controls) {
570
+ const control = formGroup.controls[key];
571
+ if (!(control instanceof FormControl)) {
572
+ NgxFormService.reset(control);
573
+ continue;
574
+ }
575
+ const { type } = NgxFormService.getPropsFromControl(control);
576
+ if (!HTML5CheckTypes.includes(type))
577
+ control.setValue(undefined);
578
+ control.markAsPristine();
579
+ control.markAsUntouched();
580
+ control.setErrors(null);
581
+ control.updateValueAndValidity();
582
+ }
583
+ }
584
+ }
585
+
586
+ /**
587
+ * @description Angular implementation of the RenderingEngine with enhanced features
588
+ * @summary This class extends the base RenderingEngine to provide Angular-specific rendering capabilities
589
+ * with additional features compared to NgxRenderingEngine. It handles the conversion of field definitions
590
+ * to Angular components, manages component registration, and provides utilities for component creation
591
+ * and input handling. This implementation uses Angular's newer component APIs.
592
+ *
593
+ * @template AngularFieldDefinition - Type for Angular-specific field definitions
594
+ * @template AngularDynamicOutput - Type for Angular-specific component output
595
+ *
596
+ * @class NgxRenderingEngine2
597
+ * @example
598
+ * ```typescript
599
+ * const engine = NgxRenderingEngine2.get();
600
+ * engine.initialize();
601
+ * const output = engine.render(myModel, {}, viewContainerRef, injector, templateRef);
602
+ * ```
603
+ *
604
+ * @mermaid
605
+ * sequenceDiagram
606
+ * participant Client
607
+ * participant Engine as NgxRenderingEngine2
608
+ * participant Components as RegisteredComponents
609
+ *
610
+ * Client->>Engine: get()
611
+ * Client->>Engine: initialize()
612
+ * Client->>Engine: render(model, props, vcr, injector, tpl)
613
+ * Engine->>Engine: toFieldDefinition(model, props)
614
+ * Engine->>Engine: fromFieldDefinition(fieldDef, vcr, injector, tpl)
615
+ * Engine->>Components: components(fieldDef.tag)
616
+ * Components-->>Engine: component constructor
617
+ * Engine->>Engine: createComponent(component, inputs, metadata, vcr, injector, template)
618
+ * Engine-->>Client: return AngularDynamicOutput
619
+ */
620
+ class NgxRenderingEngine2 extends RenderingEngine {
621
+ /**
622
+ * @description Creates a new instance of NgxRenderingEngine2
623
+ * @summary Initializes the rendering engine with the 'angular' engine type.
624
+ * This constructor sets up the base configuration needed for Angular-specific rendering.
625
+ */
626
+ constructor() {
627
+ super('angular');
628
+ }
629
+ /**
630
+ * @description Converts a field definition to an Angular component output
631
+ * @summary This private method takes a field definition and creates the corresponding Angular component.
632
+ * It handles component instantiation, input property mapping, and child component rendering.
633
+ * The method validates input properties against the component's metadata and processes
634
+ * child components recursively.
635
+ *
636
+ * @param {FieldDefinition<AngularFieldDefinition>} fieldDef - The field definition to convert
637
+ * @param {ViewContainerRef} vcr - The view container reference for component creation
638
+ * @param {Injector} injector - The Angular injector for dependency injection
639
+ * @param {TemplateRef<any>} tpl - The template reference for content projection
640
+ * @param {string} registryFormId - Form identifier for the component renderer
641
+ * @return {AngularDynamicOutput} The Angular component output with component reference and inputs
642
+ *
643
+ * @mermaid
644
+ * sequenceDiagram
645
+ * participant Method as fromFieldDefinition
646
+ * participant Components as NgxRenderingEngine2.components
647
+ * participant Angular as Angular Core
648
+ * participant Process as processChild
649
+ *
650
+ * Method->>Components: components(fieldDef.tag)
651
+ * Components-->>Method: component constructor
652
+ * Method->>Angular: reflectComponentType(component)
653
+ * Angular-->>Method: componentMetadata
654
+ * Method->>Method: Validate input properties
655
+ * Method->>Method: Create result object
656
+ * alt Has children
657
+ * Method->>Process: Process children recursively
658
+ * Process->>Method: Return processed children
659
+ * Method->>Angular: Create embedded view
660
+ * Method->>Method: Create component instance
661
+ * end
662
+ * Method-->>Caller: return AngularDynamicOutput
663
+ */
664
+ fromFieldDefinition(fieldDef, vcr, injector, tpl, registryFormId = Date.now().toString(36).toUpperCase()) {
665
+ const cmp = fieldDef?.['component'] || NgxRenderingEngine2.components(fieldDef.tag);
666
+ const component = cmp.constructor;
667
+ const componentMetadata = reflectComponentType(component);
668
+ if (!componentMetadata) {
669
+ throw new InternalError(`Metadata for component ${fieldDef.tag} not found.`);
670
+ }
671
+ const { inputs: possibleInputs } = componentMetadata;
672
+ const inputs = { ...fieldDef.props };
673
+ const unmappedKeys = Object.keys(inputs).filter(input => {
674
+ const isMapped = possibleInputs.find(({ propName }) => propName === input);
675
+ if (!isMapped)
676
+ delete inputs[input];
677
+ return !isMapped;
678
+ });
679
+ if (unmappedKeys.length > 0)
680
+ console.warn(`Unmapped input properties for component ${fieldDef.tag}: ${unmappedKeys.join(', ')}`);
681
+ const result = {
682
+ component,
683
+ inputs,
684
+ injector,
685
+ };
686
+ if (fieldDef.rendererId)
687
+ result.inputs['rendererId'] = fieldDef.rendererId;
688
+ // process children
689
+ if (fieldDef.children?.length) {
690
+ result.children = fieldDef.children.map((child) => {
691
+ // create a child form and add its controls as properties of child.props
692
+ NgxFormService.addControlFromProps(registryFormId, child.props);
693
+ return this.fromFieldDefinition(child, vcr, injector, tpl, registryFormId);
694
+ });
695
+ }
696
+ // generating DOM
697
+ vcr.clear();
698
+ const template = vcr.createEmbeddedView(tpl, injector).rootNodes;
699
+ const componentInstance = NgxRenderingEngine2.createComponent(component, { ...inputs, model: this._model }, componentMetadata, vcr, injector, template);
700
+ result.instance = NgxRenderingEngine2._instance = componentInstance.instance;
701
+ return result;
702
+ }
703
+ /**
704
+ * @description Creates an Angular component instance
705
+ * @summary This static utility method creates an Angular component instance with the specified
706
+ * inputs and template. It uses Angular's component creation API to instantiate the component
707
+ * and then sets the input properties using the provided metadata.
708
+ *
709
+ * @param {Type<unknown>} component - The component type to create
710
+ * @param {KeyValue} [inputs={}] - The input properties to set on the component
711
+ * @param {ComponentMirror<unknown>} metadata - The component metadata for input validation
712
+ * @param {ViewContainerRef} vcr - The view container reference for component creation
713
+ * @param {Injector} injector - The Angular injector for dependency injection
714
+ * @param {Node[]} [template=[]] - The template nodes to project into the component
715
+ * @return {ComponentRef<unknown>} The created component reference
716
+ */
717
+ static createComponent(component, inputs = {}, metadata, vcr, injector, template = []) {
718
+ const componentInstance = vcr.createComponent(component, {
719
+ environmentInjector: injector,
720
+ projectableNodes: [template],
721
+ });
722
+ this.setInputs(componentInstance, inputs, metadata);
723
+ return componentInstance;
724
+ }
725
+ /**
726
+ * @description Extracts decorator metadata from a model
727
+ * @summary This method provides access to the field definition generated from a model's
728
+ * decorators. It's a convenience wrapper around the toFieldDefinition method that
729
+ * converts a model to a field definition based on its decorators and the provided
730
+ * global properties.
731
+ *
732
+ * @param {Model} model - The model to extract decorators from
733
+ * @param {Record<string, unknown>} globalProps - Global properties to include in the field definition
734
+ * @return {FieldDefinition<AngularFieldDefinition>} The field definition generated from the model
735
+ */
736
+ getDecorators(model, globalProps) {
737
+ return this.toFieldDefinition(model, globalProps);
738
+ }
739
+ /**
740
+ * @description Destroys the current engine instance
741
+ * @summary This static method clears the current instance reference, effectively
742
+ * destroying the singleton instance of the rendering engine. This can be used
743
+ * to reset the engine state or to prepare for a new instance creation.
744
+ *
745
+ * @return {Promise<void>} A promise that resolves when the instance is destroyed
746
+ */
747
+ static async destroy() {
748
+ NgxRenderingEngine2._instance = undefined;
749
+ }
750
+ /**
751
+ * @description Renders a model into an Angular component output
752
+ * @summary This method takes a model and converts it to an Angular component output.
753
+ * It first stores a reference to the model, then converts it to a field definition
754
+ * using the base RenderingEngine's toFieldDefinition method, and finally converts
755
+ * that field definition to an Angular component output using fromFieldDefinition.
756
+ *
757
+ * @template M - Type extending Model
758
+ * @param {M} model - The model to render
759
+ * @param {Record<string, unknown>} globalProps - Global properties to pass to the component
760
+ * @param {ViewContainerRef} vcr - The view container reference for component creation
761
+ * @param {Injector} injector - The Angular injector for dependency injection
762
+ * @param {TemplateRef<any>} tpl - The template reference for content projection
763
+ * @return {AngularDynamicOutput} The Angular component output with component reference and inputs
764
+ *
765
+ * @mermaid
766
+ * sequenceDiagram
767
+ * participant Client as Client Code
768
+ * participant Render as render method
769
+ * participant ToField as toFieldDefinition
770
+ * participant FromField as fromFieldDefinition
771
+ *
772
+ * Client->>Render: render(model, globalProps, vcr, injector, tpl)
773
+ * Render->>Render: Store model reference
774
+ * Render->>ToField: toFieldDefinition(model, globalProps)
775
+ * ToField-->>Render: fieldDef
776
+ * Render->>FromField: fromFieldDefinition(fieldDef, vcr, injector, tpl)
777
+ * FromField-->>Render: AngularDynamicOutput
778
+ * Render-->>Client: return AngularDynamicOutput
779
+ */
780
+ render(model, globalProps, vcr, injector, tpl) {
781
+ let result;
782
+ try {
783
+ this._model = model;
784
+ const formId = Date.now().toString(36).toUpperCase();
785
+ const fieldDef = this.toFieldDefinition(model, globalProps);
786
+ result = this.fromFieldDefinition(fieldDef, vcr, injector, tpl, formId);
787
+ result.instance['formGroup'] = NgxFormService.getControlFromForm(formId);
788
+ NgxFormService.removeRegistry(formId);
789
+ }
790
+ catch (e) {
791
+ throw new InternalError(`Failed to render Model ${model.constructor.name}: ${e}`);
792
+ }
793
+ return result;
794
+ }
795
+ /**
796
+ * @description Initializes the rendering engine
797
+ * @summary This method initializes the rendering engine. It checks if the engine is already initialized
798
+ * and sets the initialized flag to true. This method is called before the engine is used
799
+ * to ensure it's properly set up for rendering operations.
800
+ *
801
+ * @return {Promise<void>} A promise that resolves when initialization is complete
802
+ */
803
+ async initialize() {
804
+ if (this.initialized)
805
+ return;
806
+ // ValidatableByType[]
807
+ this.initialized = true;
808
+ }
809
+ /**
810
+ * @description Registers a component with the rendering engine
811
+ * @summary This static method registers a component constructor with the rendering engine
812
+ * under a specific name. It initializes the components registry if needed and throws
813
+ * an error if a component is already registered under the same name to prevent
814
+ * accidental overrides.
815
+ *
816
+ * @param {string} name - The name to register the component under
817
+ * @param {Constructor<unknown>} constructor - The component constructor
818
+ * @return {void}
819
+ */
820
+ static registerComponent(name, constructor) {
821
+ if (!this._components)
822
+ this._components = {};
823
+ if (name in this._components)
824
+ throw new InternalError(`Component already registered under ${name}`);
825
+ this._components[name] = {
826
+ constructor: constructor,
827
+ };
828
+ }
829
+ /**
830
+ * @description Retrieves registered components from the rendering engine
831
+ * @summary This static method retrieves either all registered components or a specific component
832
+ * by its selector. When called without a selector, it returns an array of all registered
833
+ * components. When called with a selector, it returns the specific component if found,
834
+ * or throws an error if the component is not registered.
835
+ *
836
+ * @param {string} [selector] - Optional selector to retrieve a specific component
837
+ * @return {Object|Array} Either a specific component or an array of all components
838
+ */
839
+ static components(selector) {
840
+ if (!selector)
841
+ return Object.values(this._components);
842
+ if (!(selector in this._components))
843
+ throw new InternalError(`No Component registered under ${selector}`);
844
+ return this._components[selector];
845
+ }
846
+ /**
847
+ * @description Generates a key for reflection metadata
848
+ * @summary This static method generates a key for reflection metadata by prefixing the input key
849
+ * with the Angular engine's reflection prefix. This is used for storing and retrieving
850
+ * metadata in a namespaced way to avoid conflicts with other metadata.
851
+ *
852
+ * @param {string} key - The base key to prefix
853
+ * @return {string} The prefixed key for reflection metadata
854
+ */
855
+ static key(key) {
856
+ return `${AngularEngineKeys.REFLECT}${key}`;
857
+ }
858
+ /**
859
+ * @description Sets input properties on a component instance
860
+ * @summary This static utility method sets input properties on a component instance
861
+ * based on the provided inputs object and component metadata. It handles both simple
862
+ * values and nested objects, recursively processing object properties. The method
863
+ * validates each input against the component's metadata to ensure only valid inputs
864
+ * are set.
865
+ *
866
+ * @param {ComponentRef<unknown>} component - The component reference to set inputs on
867
+ * @param {KeyValue} inputs - The input properties to set
868
+ * @param {ComponentMirror<unknown>} metadata - The component metadata for input validation
869
+ * @return {void}
870
+ *
871
+ * @mermaid
872
+ * sequenceDiagram
873
+ * participant Caller
874
+ * participant SetInputs as setInputs
875
+ * participant Parse as parseInputValue
876
+ * participant Component as ComponentRef
877
+ *
878
+ * Caller->>SetInputs: setInputs(component, inputs, metadata)
879
+ * SetInputs->>SetInputs: Iterate through inputs
880
+ * loop For each input
881
+ * SetInputs->>SetInputs: Check if input exists in metadata
882
+ * alt Input is 'props'
883
+ * SetInputs->>Parse: parseInputValue(component, value)
884
+ * Parse->>Parse: Recursively process nested objects
885
+ * Parse->>Component: setInput(key, value)
886
+ * else Input is valid
887
+ * SetInputs->>Component: setInput(key, value)
888
+ * end
889
+ * end
890
+ */
891
+ static setInputs(component, inputs, metadata) {
892
+ function parseInputValue(component, input) {
893
+ Object.keys(input).forEach(key => {
894
+ const value = input[key];
895
+ if (typeof value === 'object' && !!value)
896
+ return parseInputValue(component, value);
897
+ component.setInput(key, value);
898
+ });
899
+ }
900
+ Object.entries(inputs).forEach(([key, value]) => {
901
+ const prop = metadata.inputs.find((item) => item.propName === key);
902
+ if (prop) {
903
+ if (key === 'props')
904
+ parseInputValue(component, value);
905
+ // if(key === 'locale' && !value)
906
+ // value = getLocaleFromClassName(this._componentName);
907
+ component.setInput(key, value);
908
+ }
909
+ });
910
+ }
911
+ }
912
+
913
+ /**
914
+ * @description Marks an Angular component as dynamically loadable
915
+ * @summary Decorator that registers an Angular component with the NgxRenderingEngine2 for dynamic loading.
916
+ * This decorator must be applied before the @Component decorator to properly extract component metadata.
917
+ * It adds metadata to the component class and registers it with the rendering engine using its selector.
918
+ * @function Dynamic
919
+ * @return {Function} A decorator function that can be applied to Angular component classes
920
+ * @mermaid
921
+ * sequenceDiagram
922
+ * participant C as Component Class
923
+ * participant D as Dynamic Decorator
924
+ * participant R as NgxRenderingEngine2
925
+ * participant M as Angular Metadata
926
+ * C->>D: Apply decorator
927
+ * D->>M: reflectComponentType()
928
+ * M-->>D: Return component metadata
929
+ * alt No metadata found
930
+ * D->>D: Throw InternalError
931
+ * else Metadata found
932
+ * D->>R: registerComponent(selector, constructor)
933
+ * D->>C: Apply metadata
934
+ * end
935
+ * @category Decorators
936
+ */
937
+ function Dynamic() {
938
+ return apply((original) => {
939
+ const metadata = reflectComponentType(original);
940
+ if (!metadata)
941
+ throw new InternalError(`Could not find Component metadata. @Dynamic decorator must come above @Component`);
942
+ NgxRenderingEngine2.registerComponent(metadata.selector, original);
943
+ }, metadata(NgxRenderingEngine2.key(AngularEngineKeys.DYNAMIC), true));
944
+ }
945
+
946
+ /**
947
+ * @description Abstract base class for dynamic Angular modules
948
+ * @summary The DynamicModule serves as a base class for Angular modules that need to be
949
+ * dynamically loaded or configured at runtime. It provides a common type for the rendering
950
+ * engine to identify and work with dynamic modules.
951
+ * @class DynamicModule
952
+ * @example
953
+ * ```typescript
954
+ * @NgModule({
955
+ * declarations: [MyComponent],
956
+ * imports: [CommonModule]
957
+ * })
958
+ * export class MyDynamicModule extends DynamicModule {}
959
+ * ```
960
+ */
961
+ class DynamicModule {
962
+ }
963
+
964
+ /**
965
+ * @description Angular implementation of the RenderingEngine
966
+ * @summary This class extends the base RenderingEngine to provide Angular-specific rendering capabilities.
967
+ * It handles the conversion of field definitions to Angular components and manages component registration.
968
+ * @template AngularFieldDefinition - Type for Angular-specific field definitions
969
+ * @template AngularDynamicOutput - Type for Angular-specific component output
970
+ * @param {Injector} injector - Angular injector for dependency injection
971
+ * @param {ViewContainerRef} vcr - View container reference for component creation
972
+ * @param {TemplateRef<any>} tpl - Template reference for content projection
973
+ * @class NgxRenderingEngine
974
+ * @example
975
+ * ```typescript
976
+ * const engine = new NgxRenderingEngine();
977
+ * engine.initialize();
978
+ * const output = engine.render(myModel, {}, viewContainerRef, injector, templateRef);
979
+ * ```
980
+ * @mermaid
981
+ * sequenceDiagram
982
+ * participant Client
983
+ * participant Engine as NgxRenderingEngine
984
+ * participant Components as RegisteredComponents
985
+ *
986
+ * Client->>Engine: new NgxRenderingEngine()
987
+ * Client->>Engine: initialize()
988
+ * Client->>Engine: render(model, props, vcr, injector, tpl)
989
+ * Engine->>Engine: toFieldDefinition(model, props)
990
+ * Engine->>Engine: fromFieldDefinition(fieldDef, vcr, injector, tpl)
991
+ * Engine->>Components: components(fieldDef.tag)
992
+ * Components-->>Engine: component constructor
993
+ * Engine->>Client: return AngularDynamicOutput
994
+ */
995
+ class NgxRenderingEngine extends RenderingEngine {
996
+ constructor() {
997
+ super('angular');
998
+ }
999
+ /**
1000
+ * @description Converts a field definition to an Angular component output
1001
+ * @summary This private method takes a field definition and creates the corresponding Angular component.
1002
+ * It handles component instantiation, input property mapping, and child component rendering.
1003
+ * @param {FieldDefinition<AngularFieldDefinition>} fieldDef - The field definition to convert
1004
+ * @param {ViewContainerRef} vcr - The view container reference for component creation
1005
+ * @param {Injector} injector - The Angular injector for dependency injection
1006
+ * @param {TemplateRef<any>} tpl - The template reference for content projection
1007
+ * @return {AngularDynamicOutput} The Angular component output with component reference and inputs
1008
+ * @mermaid
1009
+ * sequenceDiagram
1010
+ * participant Method as fromFieldDefinition
1011
+ * participant Components as NgxRenderingEngine.components
1012
+ * participant Angular as Angular Core
1013
+ *
1014
+ * Method->>Components: components(fieldDef.tag)
1015
+ * Components-->>Method: component constructor
1016
+ * Method->>Angular: reflectComponentType(component)
1017
+ * Angular-->>Method: componentMetadata
1018
+ * Method->>Method: Check input properties
1019
+ * Method->>Method: Create result object
1020
+ * Method->>Method: Process children if any
1021
+ * Method-->>Caller: return AngularDynamicOutput
1022
+ */
1023
+ fromFieldDefinition(fieldDef, vcr, injector, tpl) {
1024
+ const component = NgxRenderingEngine.components(fieldDef.tag)
1025
+ .constructor;
1026
+ const componentMetadata = reflectComponentType(component);
1027
+ if (!componentMetadata) {
1028
+ throw new InternalError(`Metadata for component ${fieldDef.tag} not found.`);
1029
+ }
1030
+ const inputs = fieldDef.props;
1031
+ const possibleInputs = componentMetadata.inputs;
1032
+ const inputKeys = Object.keys(inputs);
1033
+ for (const input of possibleInputs) {
1034
+ const index = inputKeys.indexOf(input.propName);
1035
+ if (index !== -1) {
1036
+ inputKeys.splice(index, 1);
1037
+ }
1038
+ if (!inputKeys.length)
1039
+ break;
1040
+ }
1041
+ if (inputKeys.length)
1042
+ console.warn(`Unmapped input properties for component ${fieldDef.tag}: ${inputKeys.join(', ')}`);
1043
+ const result = {
1044
+ component: component,
1045
+ inputs: inputs || {},
1046
+ injector: injector,
1047
+ };
1048
+ if (fieldDef.rendererId) {
1049
+ result.inputs['rendererId'] =
1050
+ fieldDef.rendererId;
1051
+ }
1052
+ if (fieldDef.children && fieldDef.children.length) {
1053
+ result.children = fieldDef.children.map((child) => {
1054
+ return this.fromFieldDefinition(child, vcr, injector, tpl);
1055
+ });
1056
+ const template = vcr.createEmbeddedView(tpl, injector).rootNodes;
1057
+ result.content = [template];
1058
+ }
1059
+ return result;
1060
+ }
1061
+ /**
1062
+ * @description Renders a model into an Angular component output
1063
+ * @summary This method takes a model and converts it to an Angular component output.
1064
+ * It first converts the model to a field definition using the base RenderingEngine's
1065
+ * toFieldDefinition method, then converts that field definition to an Angular component output.
1066
+ * @template M - Type extending Model
1067
+ * @param {M} model - The model to render
1068
+ * @param {Record<string, unknown>} globalProps - Global properties to pass to the component
1069
+ * @param {ViewContainerRef} vcr - The view container reference for component creation
1070
+ * @param {Injector} injector - The Angular injector for dependency injection
1071
+ * @param {TemplateRef<any>} tpl - The template reference for content projection
1072
+ * @return {AngularDynamicOutput} The Angular component output with component reference and inputs
1073
+ * @mermaid
1074
+ * sequenceDiagram
1075
+ * participant Client as Client Code
1076
+ * participant Render as render method
1077
+ * participant ToField as toFieldDefinition
1078
+ * participant FromField as fromFieldDefinition
1079
+ *
1080
+ * Client->>Render: render(model, globalProps, vcr, injector, tpl)
1081
+ * Render->>ToField: toFieldDefinition(model, globalProps)
1082
+ * ToField-->>Render: fieldDef
1083
+ * Render->>FromField: fromFieldDefinition(fieldDef, vcr, injector, tpl)
1084
+ * FromField-->>Render: AngularDynamicOutput
1085
+ * Render-->>Client: return AngularDynamicOutput
1086
+ */
1087
+ render(model, globalProps, vcr, injector, tpl) {
1088
+ let result;
1089
+ try {
1090
+ const fieldDef = this.toFieldDefinition(model, globalProps);
1091
+ result = this.fromFieldDefinition(fieldDef, vcr, injector, tpl);
1092
+ }
1093
+ catch (e) {
1094
+ throw new InternalError(`Failed to render Model ${model.constructor.name}: ${e}`);
1095
+ }
1096
+ return result;
1097
+ }
1098
+ /**
1099
+ * @description Initializes the rendering engine
1100
+ * @summary This method initializes the rendering engine. It checks if the engine is already initialized
1101
+ * and sets the initialized flag to true. This method is called before the engine is used.
1102
+ * @return {Promise<void>} A promise that resolves when initialization is complete
1103
+ */
1104
+ async initialize() {
1105
+ if (this.initialized)
1106
+ return;
1107
+ this.initialized = true;
1108
+ }
1109
+ /**
1110
+ * @description Registers a component with the rendering engine
1111
+ * @summary This static method registers a component constructor with the rendering engine
1112
+ * under a specific name. It throws an error if a component is already registered under the same name.
1113
+ * @param {string} name - The name to register the component under
1114
+ * @param {Constructor<unknown>} constructor - The component constructor
1115
+ * @return {void}
1116
+ */
1117
+ static registerComponent(name, constructor) {
1118
+ if (!this._components)
1119
+ this._components = {};
1120
+ if (name in this._components)
1121
+ throw new InternalError(`Component already registered under ${name}`);
1122
+ this._components[name] = {
1123
+ constructor: constructor,
1124
+ };
1125
+ }
1126
+ /**
1127
+ * @description Retrieves registered components from the rendering engine
1128
+ * @summary This static method retrieves either all registered components or a specific component
1129
+ * by its selector. It throws an error if the requested component is not registered.
1130
+ * @param {string} [selector] - Optional selector to retrieve a specific component
1131
+ * @return {Object|Array} Either a specific component or an array of all components
1132
+ */
1133
+ static components(selector) {
1134
+ if (!selector)
1135
+ return Object.values(this._components);
1136
+ if (!(selector in this._components))
1137
+ throw new InternalError(`No Component registered under ${selector}`);
1138
+ return this._components[selector];
1139
+ }
1140
+ /**
1141
+ * @description Generates a key for reflection metadata
1142
+ * @summary This static method generates a key for reflection metadata by prefixing the input key
1143
+ * with the Angular engine's reflection prefix. This is used for storing and retrieving metadata.
1144
+ * @param {string} key - The base key to prefix
1145
+ * @return {string} The prefixed key for reflection metadata
1146
+ */
1147
+ static key(key) {
1148
+ return `${AngularEngineKeys.REFLECT}${key}`;
1149
+ }
1150
+ }
1151
+
1152
+ /**
1153
+ * @module engine
1154
+ * @description Angular rendering engine for Decaf applications
1155
+ * @summary The engine module provides core functionality for rendering Angular components
1156
+ * in Decaf applications. It includes constants, decorators, rendering engines, and utility types
1157
+ * that enable dynamic component creation, property mapping, and component lifecycle management.
1158
+ * Key exports include {@link NgxRenderingEngine}, {@link DynamicModule}, and various decorators
1159
+ * for component configuration.
1160
+ */
1161
+
1162
+ /**
1163
+ * @description Dynamic component renderer for Decaf Angular applications.
1164
+ * @summary This component provides a flexible way to dynamically render Angular components
1165
+ * at runtime based on a tag name. It handles the creation, property binding, and event
1166
+ * subscription for dynamically loaded components. This is particularly useful for
1167
+ * building configurable UIs where components need to be determined at runtime.
1168
+ *
1169
+ * @component {ComponentRendererComponent}
1170
+ * @example
1171
+ * <ngx-decaf-component-renderer
1172
+ * [tag]="tag"
1173
+ * [globals]="globals"
1174
+ * (listenEvent)="listenEvent($event)">
1175
+ * </ngx-decaf-component-renderer>
1176
+ *
1177
+ * @mermaid
1178
+ * classDiagram
1179
+ * class ComponentRendererComponent {
1180
+ * +ViewContainerRef vcr
1181
+ * +string tag
1182
+ * +Record~string, unknown~ globals
1183
+ * +EnvironmentInjector injector
1184
+ * +ComponentRef~unknown~ component
1185
+ * +EventEmitter~ModelRenderCustomEvent~ listenEvent
1186
+ * +ngOnInit()
1187
+ * +ngOnDestroy()
1188
+ * +ngOnChanges(changes)
1189
+ * -createComponent(tag, globals)
1190
+ * -subscribeEvents()
1191
+ * -unsubscribeEvents()
1192
+ * }
1193
+ * ComponentRendererComponent --|> OnInit
1194
+ * ComponentRendererComponent --|> OnChanges
1195
+ * ComponentRendererComponent --|> OnDestroy
1196
+ *
1197
+ * @implements {OnInit}
1198
+ * @implements {OnChanges}
1199
+ * @implements {OnDestroy}
1200
+ */
1201
+ class ComponentRendererComponent {
1202
+ /**
1203
+ * @description Creates an instance of ComponentRendererComponent.
1204
+ * @summary Initializes a new ComponentRendererComponent. This component doesn't require
1205
+ * any dependencies to be injected in its constructor as it uses the inject function to
1206
+ * obtain the EnvironmentInjector.
1207
+ *
1208
+ * @memberOf ComponentRendererComponent
1209
+ */
1210
+ constructor() {
1211
+ /**
1212
+ * @description Global properties to pass to the rendered component.
1213
+ * @summary This input property allows passing a set of properties to the dynamically
1214
+ * rendered component. These properties will be mapped to the component's inputs if they
1215
+ * match. Properties that don't match any input on the target component will be filtered out
1216
+ * with a warning.
1217
+ *
1218
+ * @type {Record<string, unknown>}
1219
+ * @default {}
1220
+ * @memberOf ComponentRendererComponent
1221
+ */
1222
+ this.globals = {};
1223
+ /**
1224
+ * @description Injector used for dependency injection in the dynamic component.
1225
+ * @summary This injector is used when creating the dynamic component to provide it with
1226
+ * access to the application's dependency injection system. It ensures that the dynamically
1227
+ * created component can access the same services and dependencies as statically created
1228
+ * components.
1229
+ *
1230
+ * @type {EnvironmentInjector}
1231
+ * @memberOf ComponentRendererComponent
1232
+ */
1233
+ this.injector = inject(EnvironmentInjector);
1234
+ /**
1235
+ * @description Event emitter for events from the rendered component.
1236
+ * @summary This output property emits events that originate from the dynamically rendered
1237
+ * component. It allows the parent component to listen for and respond to events from the
1238
+ * dynamic component, creating a communication channel between the parent and the dynamically
1239
+ * rendered child.
1240
+ *
1241
+ * @type {EventEmitter<ModelRenderCustomEvent>}
1242
+ * @memberOf ComponentRendererComponent
1243
+ */
1244
+ this.listenEvent = new EventEmitter();
1245
+ this.parent = undefined;
1246
+ this.logger = getLogger(this);
1247
+ }
1248
+ /**
1249
+ * @description Initializes the component after Angular first displays the data-bound properties.
1250
+ * @summary Sets up the component by creating the dynamic component specified by the tag input.
1251
+ * This method is called once when the component is initialized and triggers the dynamic
1252
+ * component creation process with the provided tag name and global properties.
1253
+ *
1254
+ * @mermaid
1255
+ * sequenceDiagram
1256
+ * participant A as Angular Lifecycle
1257
+ * participant C as ComponentRendererComponent
1258
+ * participant R as NgxRenderingEngine2
1259
+ *
1260
+ * A->>C: ngOnInit()
1261
+ * C->>C: createComponent(tag, globals)
1262
+ * C->>R: components(tag)
1263
+ * R-->>C: Return component constructor
1264
+ * C->>C: Process component inputs
1265
+ * C->>C: Create component instance
1266
+ * C->>C: subscribeEvents()
1267
+ *
1268
+ * @return {void}
1269
+ * @memberOf ComponentRendererComponent
1270
+ */
1271
+ ngOnInit() {
1272
+ if (!this.parent) {
1273
+ this.createComponent(this.tag, this.globals);
1274
+ }
1275
+ else {
1276
+ this.createParentComponent();
1277
+ }
1278
+ }
1279
+ /**
1280
+ * @description Cleans up resources when the component is destroyed.
1281
+ * @summary Performs cleanup operations when the component is being destroyed by Angular.
1282
+ * This includes unsubscribing from all event emitters of the dynamic component and
1283
+ * destroying the rendering engine instance to prevent memory leaks.
1284
+ *
1285
+ * @mermaid
1286
+ * sequenceDiagram
1287
+ * participant A as Angular Lifecycle
1288
+ * participant C as ComponentRendererComponent
1289
+ * participant R as NgxRenderingEngine2
1290
+ *
1291
+ * A->>C: ngOnDestroy()
1292
+ * alt component exists
1293
+ * C->>C: unsubscribeEvents()
1294
+ * C->>R: destroy()
1295
+ * end
1296
+ *
1297
+ * @return {Promise<void>} A promise that resolves when cleanup is complete
1298
+ * @memberOf ComponentRendererComponent
1299
+ */
1300
+ async ngOnDestroy() {
1301
+ if (this.component) {
1302
+ this.unsubscribeEvents();
1303
+ NgxRenderingEngine2$1.destroy();
1304
+ }
1305
+ }
1306
+ /**
1307
+ * @description Creates and renders a dynamic component.
1308
+ * @summary This method handles the creation of a dynamic component based on the provided tag.
1309
+ * It retrieves the component constructor from the rendering engine, processes its inputs,
1310
+ * filters out unmapped properties, creates the component instance, and sets up event subscriptions.
1311
+ *
1312
+ * @param {string} tag - The tag name of the component to create
1313
+ * @param {KeyValue} globals - Global properties to pass to the component
1314
+ * @return {void}
1315
+ *
1316
+ * @mermaid
1317
+ * sequenceDiagram
1318
+ * participant C as ComponentRendererComponent
1319
+ * participant R as NgxRenderingEngine2
1320
+ * participant V as ViewContainerRef
1321
+ *
1322
+ * C->>R: components(tag)
1323
+ * R-->>C: Return component constructor
1324
+ * C->>C: reflectComponentType(component)
1325
+ * C->>C: Process input properties
1326
+ * C->>C: Filter unmapped properties
1327
+ * C->>V: clear()
1328
+ * C->>R: createComponent(component, props, metadata, vcr, injector, [])
1329
+ * R-->>C: Return component reference
1330
+ * C->>C: subscribeEvents()
1331
+ *
1332
+ * @private
1333
+ * @memberOf ComponentRendererComponent
1334
+ */
1335
+ createComponent(tag, globals = {}) {
1336
+ const component = NgxRenderingEngine2$1.components(tag)
1337
+ ?.constructor;
1338
+ const metadata = reflectComponentType(component);
1339
+ const componentInputs = metadata.inputs;
1340
+ const props = globals?.['item'];
1341
+ delete props['tag'];
1342
+ const inputKeys = Object.keys(props);
1343
+ const unmappedKeys = [];
1344
+ for (const input of inputKeys) {
1345
+ if (!inputKeys.length)
1346
+ break;
1347
+ const prop = componentInputs.find((item) => item.propName === input);
1348
+ if (!prop) {
1349
+ delete props[input];
1350
+ unmappedKeys.push(input);
1351
+ }
1352
+ }
1353
+ if (unmappedKeys.length)
1354
+ this.logger.info(`Unmapped input properties for component ${tag}: ${unmappedKeys.join(', ')}`);
1355
+ this.vcr.clear();
1356
+ this.component = NgxRenderingEngine2$1.createComponent(component, props, metadata, this.vcr, this.injector, []);
1357
+ this.subscribeEvents();
1358
+ }
1359
+ createParentComponent() {
1360
+ const { component, inputs } = this.parent;
1361
+ const metadata = reflectComponentType(component);
1362
+ const template = this.vcr.createEmbeddedView(this.inner, this.injector).rootNodes;
1363
+ this.component = NgxRenderingEngine2$1.createComponent(component, inputs, metadata, this.vcr, this.injector, template);
1364
+ this.subscribeEvents();
1365
+ }
1366
+ /**
1367
+ * @description Subscribes to events emitted by the dynamic component.
1368
+ * @summary This method sets up subscriptions to all EventEmitter properties of the
1369
+ * dynamically created component. When an event is emitted by the dynamic component,
1370
+ * it is captured and re-emitted through the listenEvent output property with additional
1371
+ * metadata about the event source.
1372
+ *
1373
+ * @mermaid
1374
+ * sequenceDiagram
1375
+ * participant C as ComponentRendererComponent
1376
+ * participant D as Dynamic Component
1377
+ * participant P as Parent Component
1378
+ *
1379
+ * C->>C: subscribeEvents()
1380
+ * C->>D: Get instance properties
1381
+ * loop For each property
1382
+ * C->>C: Check if property is EventEmitter
1383
+ * alt is EventEmitter
1384
+ * C->>D: Subscribe to event
1385
+ * D-->>C: Event emitted
1386
+ * C->>P: Re-emit event with metadata
1387
+ * end
1388
+ * end
1389
+ *
1390
+ * @private
1391
+ * @return {void}
1392
+ * @memberOf ComponentRendererComponent
1393
+ */
1394
+ subscribeEvents() {
1395
+ if (this.component) {
1396
+ const instance = this.component?.instance;
1397
+ const componentKeys = Object.keys(instance);
1398
+ for (const key of componentKeys) {
1399
+ const value = instance[key];
1400
+ if (value instanceof EventEmitter)
1401
+ instance[key].subscribe((event) => {
1402
+ this.listenEvent.emit({
1403
+ name: key,
1404
+ ...event,
1405
+ });
1406
+ });
1407
+ }
1408
+ }
1409
+ }
1410
+ /**
1411
+ * @description Unsubscribes from all events of the dynamic component.
1412
+ * @summary This method cleans up event subscriptions when the component is being destroyed.
1413
+ * It iterates through all properties of the dynamic component instance and unsubscribes
1414
+ * from any EventEmitter properties to prevent memory leaks and unexpected behavior after
1415
+ * the component is destroyed.
1416
+ *
1417
+ * @mermaid
1418
+ * sequenceDiagram
1419
+ * participant C as ComponentRendererComponent
1420
+ * participant D as Dynamic Component
1421
+ *
1422
+ * C->>C: unsubscribeEvents()
1423
+ * C->>D: Get instance properties
1424
+ * loop For each property
1425
+ * C->>C: Check if property is EventEmitter
1426
+ * alt is EventEmitter
1427
+ * C->>D: Unsubscribe from event
1428
+ * end
1429
+ * end
1430
+ *
1431
+ * @private
1432
+ * @return {void}
1433
+ * @memberOf ComponentRendererComponent
1434
+ */
1435
+ unsubscribeEvents() {
1436
+ if (this.component) {
1437
+ const instance = this.component?.instance;
1438
+ const componentKeys = Object.keys(instance);
1439
+ for (const key of componentKeys) {
1440
+ const value = instance[key];
1441
+ if (value instanceof EventEmitter)
1442
+ instance[key].unsubscribe();
1443
+ }
1444
+ }
1445
+ }
1446
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ComponentRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1447
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: ComponentRendererComponent, isStandalone: true, selector: "ngx-decaf-component-renderer", inputs: { tag: "tag", globals: "globals", parent: "parent" }, outputs: { listenEvent: "listenEvent" }, 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: "<ng-template #componentViewContainer></ng-template>\n\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", styles: [""], dependencies: [{ kind: "component", type: ComponentRendererComponent, selector: "ngx-decaf-component-renderer", inputs: ["tag", "globals", "parent"], outputs: ["listenEvent"] }, { kind: "ngmodule", type: ForAngularModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }] }); }
1448
+ }
1449
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ComponentRendererComponent, decorators: [{
1450
+ type: Component,
1451
+ args: [{ selector: 'ngx-decaf-component-renderer', imports: [ForAngularModule], standalone: true, template: "<ng-template #componentViewContainer></ng-template>\n\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" }]
1452
+ }], ctorParameters: () => [], propDecorators: { vcr: [{
1453
+ type: ViewChild,
1454
+ args: ['componentViewContainer', { static: true, read: ViewContainerRef }]
1455
+ }], tag: [{
1456
+ type: Input,
1457
+ args: [{ required: true }]
1458
+ }], globals: [{
1459
+ type: Input
1460
+ }], listenEvent: [{
1461
+ type: Output
1462
+ }], parent: [{
1463
+ type: Input
1464
+ }], inner: [{
1465
+ type: ViewChild,
1466
+ args: ['inner', { read: TemplateRef, static: true }]
1467
+ }] } });
1468
+
1469
+ /**
1470
+ * @description Component for rendering dynamic models
1471
+ * @summary This component is responsible for dynamically rendering models,
1472
+ * handling model changes, and managing event subscriptions for the rendered components.
1473
+ * It uses the NgxRenderingEngine2 to render the models and supports both string and Model inputs.
1474
+ * @class
1475
+ * @template M - Type extending Model
1476
+ * @param {Injector} injector - Angular Injector for dependency injection
1477
+ * @example
1478
+ * <ngx-decaf-model-renderer
1479
+ * [model]="myModel"
1480
+ * [globals]="globalVariables"
1481
+ * (listenEvent)="handleEvent($event)">
1482
+ * </ngx-decaf-model-renderer>
1483
+ * @mermaid
1484
+ * sequenceDiagram
1485
+ * participant App
1486
+ * participant ModelRenderer
1487
+ * participant RenderingEngine
1488
+ * participant Model
1489
+ * App->>ModelRenderer: Input model
1490
+ * ModelRenderer->>Model: Parse if string
1491
+ * Model-->>ModelRenderer: Parsed model
1492
+ * ModelRenderer->>RenderingEngine: Render model
1493
+ * RenderingEngine-->>ModelRenderer: Rendered output
1494
+ * ModelRenderer->>ModelRenderer: Subscribe to events
1495
+ * ModelRenderer-->>App: Emit events
1496
+ */
1497
+ class ModelRendererComponent {
1498
+ constructor() {
1499
+ /**
1500
+ * @description Global variables to be passed to the rendered component
1501
+ */
1502
+ this.globals = {};
1503
+ /**
1504
+ * @description Event emitter for custom events from the rendered component
1505
+ */
1506
+ this.listenEvent = new EventEmitter();
1507
+ this.injector = inject(Injector);
1508
+ this.JSON = JSON;
1509
+ }
1510
+ // constructor() {}
1511
+ /**
1512
+ * @description Refreshes the rendered model
1513
+ * @param {string | M} model - The model to be rendered
1514
+ */
1515
+ refresh(model) {
1516
+ model =
1517
+ typeof model === 'string'
1518
+ ? Model.build({}, JSON.parse(model))
1519
+ : model;
1520
+ this.output = model.render(this.globals || {}, this.vcr, this.injector, this.inner);
1521
+ if (this.output?.inputs)
1522
+ this.rendererId = sf(AngularEngineKeys.RENDERED_ID, this.output.inputs['rendererId']);
1523
+ this.instance = this.output?.instance;
1524
+ this.subscribeEvents();
1525
+ }
1526
+ /**
1527
+ * @description Lifecycle hook that is called when data-bound properties of a directive change
1528
+ * @param {SimpleChanges} changes - Object containing changes
1529
+ */
1530
+ ngOnChanges(changes) {
1531
+ if (changes[BaseComponentProps.MODEL]) {
1532
+ const { currentValue } = changes[BaseComponentProps.MODEL];
1533
+ this.refresh(currentValue);
1534
+ }
1535
+ }
1536
+ /**
1537
+ * @description Lifecycle hook that is called when a directive, pipe, or service is destroyed
1538
+ * @return {Promise<void>}
1539
+ */
1540
+ async ngOnDestroy() {
1541
+ if (this.instance) {
1542
+ this.unsubscribeEvents();
1543
+ await NgxRenderingEngine2.destroy();
1544
+ }
1545
+ this.output = undefined;
1546
+ }
1547
+ subscribeEvents() {
1548
+ if (this.instance) {
1549
+ const componentKeys = Object.keys(this.instance);
1550
+ for (const key of componentKeys) {
1551
+ const value = this.instance[key];
1552
+ if (value instanceof EventEmitter)
1553
+ this.instance[key].subscribe((event) => {
1554
+ this.listenEvent.emit({
1555
+ component: this.output?.component.name || '',
1556
+ name: key,
1557
+ ...event,
1558
+ });
1559
+ });
1560
+ }
1561
+ }
1562
+ }
1563
+ /**
1564
+ * @description Unsubscribes from events emitted by the rendered component
1565
+ */
1566
+ unsubscribeEvents() {
1567
+ if (this.instance) {
1568
+ const componentKeys = Object.keys(this.instance);
1569
+ for (const key of componentKeys) {
1570
+ const value = this.instance[key];
1571
+ if (value instanceof EventEmitter)
1572
+ this.instance[key].unsubscribe();
1573
+ }
1574
+ }
1575
+ }
1576
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ModelRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1577
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: ModelRendererComponent, isStandalone: true, selector: "ngx-decaf-model-renderer", inputs: { model: "model", globals: "globals", rendererId: "rendererId" }, outputs: { listenEvent: "listenEvent" }, 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: " <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: "ngmodule", type: ForAngularModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "component", type: ComponentRendererComponent, selector: "ngx-decaf-component-renderer", inputs: ["tag", "globals", "parent"], outputs: ["listenEvent"] }] }); }
1578
+ }
1579
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ModelRendererComponent, decorators: [{
1580
+ type: Component,
1581
+ args: [{ standalone: true, imports: [ForAngularModule, NgComponentOutlet, ComponentRendererComponent], selector: 'ngx-decaf-model-renderer', template: " <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" }]
1582
+ }], propDecorators: { model: [{
1583
+ type: Input,
1584
+ args: [{ required: true }]
1585
+ }], globals: [{
1586
+ type: Input
1587
+ }], inner: [{
1588
+ type: ViewChild,
1589
+ args: ['inner', { read: TemplateRef, static: true }]
1590
+ }], rendererId: [{
1591
+ type: Input
1592
+ }], vcr: [{
1593
+ type: ViewChild,
1594
+ args: ['componentOuter', { static: true, read: ViewContainerRef }]
1595
+ }], listenEvent: [{
1596
+ type: Output
1597
+ }] } });
1598
+
1599
+ /**
1600
+ * @class NgxCrudFormField
1601
+ * @implements {FieldProperties}
1602
+ * @implements {ControlValueAccessor}
1603
+ * @summary Abstract class representing a CRUD form field for Angular applications
1604
+ * @description This class provides the base implementation for CRUD form fields in Angular,
1605
+ * implementing both CrudFormField and ControlValueAccessor interfaces.
1606
+ */
1607
+ class NgxCrudFormField {
1608
+ constructor() {
1609
+ this.translateService = inject(TranslateService);
1610
+ // protected constructor() {}
1611
+ /**
1612
+ * @summary String formatting function
1613
+ * @description Provides access to the sf function for error message formatting
1614
+ * @prop {function(string, ...string): string} sf - String formatting function
1615
+ */
1616
+ this.sf = sf;
1617
+ /**
1618
+ * @summary Change callback function
1619
+ * @description Function called when the field value changes
1620
+ * @property {function(): unknown} onChange - onChange event handler
1621
+ */
1622
+ this.onChange = () => { };
1623
+ /**
1624
+ * @summary Touch callback function
1625
+ * @description Function called when the field is touched
1626
+ * @property {function(): unknown} onTouch - onTouch event handler
1627
+ */
1628
+ this.onTouch = () => { };
1629
+ }
1630
+ /**
1631
+ * @summary Write value to the field
1632
+ * @description Sets the value of the field
1633
+ * @param {string} obj - The value to be set
1634
+ */
1635
+ writeValue(obj) {
1636
+ this.value = obj;
1637
+ }
1638
+ /**
1639
+ * @summary Register change callback
1640
+ * @description Registers a function to be called when the field value changes
1641
+ * @param {function(): unknown} fn - The function to be called on change
1642
+ */
1643
+ registerOnChange(fn) {
1644
+ this.onChange = fn;
1645
+ }
1646
+ /**
1647
+ * @summary Register touch callback
1648
+ * @description Registers a function to be called when the field is touched
1649
+ * @param {function(): unknown} fn - The function to be called on touch
1650
+ */
1651
+ registerOnTouched(fn) {
1652
+ this.onTouch = fn;
1653
+ }
1654
+ /**
1655
+ * @summary Set disabled state
1656
+ * @description Sets the disabled state of the field
1657
+ * @param {boolean} isDisabled - Whether the field should be disabled
1658
+ */
1659
+ setDisabledState(isDisabled) {
1660
+ this.disabled = isDisabled;
1661
+ }
1662
+ /**
1663
+ * @summary After view initialization logic
1664
+ * @description Performs necessary setup after the view has been initialized
1665
+ * @returns {HTMLElement} The parent element of the field
1666
+ */
1667
+ afterViewInit() {
1668
+ let parent;
1669
+ switch (this.operation) {
1670
+ case OperationKeys.READ:
1671
+ case OperationKeys.DELETE:
1672
+ return this.component.nativeElement.parentElement;
1673
+ case OperationKeys.CREATE:
1674
+ case OperationKeys.UPDATE:
1675
+ try {
1676
+ parent = NgxFormService.getParentEl(this.component.nativeElement, 'div');
1677
+ }
1678
+ catch (e) {
1679
+ throw new RenderingError(`Unable to retrieve parent form element for the ${this.operation}: ${e instanceof Error ? e.message : e}`);
1680
+ }
1681
+ // NgxFormService.register(parent.id, this.formGroup, this as AngularFieldDefinition);
1682
+ return parent;
1683
+ default:
1684
+ throw new InternalError(`Invalid operation: ${this.operation}`);
1685
+ }
1686
+ }
1687
+ /**
1688
+ * @summary Cleanup on component destruction
1689
+ * @description Unregisters the field when the component is destroyed
1690
+ */
1691
+ onDestroy() {
1692
+ if (this.formGroup)
1693
+ NgxFormService.unregister(this.formGroup);
1694
+ }
1695
+ /**
1696
+ * @summary Get field errors
1697
+ * @description Retrieves all errors associated with the field
1698
+ * @returns {string|void} An array of error objects
1699
+ */
1700
+ getErrors(parent) {
1701
+ const formControl = this.formControl;
1702
+ if ((!formControl.pristine || formControl.touched) && !formControl.valid) {
1703
+ const collapsableContainer = parent.closest('ion-accordion-group');
1704
+ if (collapsableContainer)
1705
+ collapsableContainer.setAttribute('value', 'open');
1706
+ const errors = Object.keys(formControl.errors ?? {}).map(key => ({
1707
+ key: key,
1708
+ message: key,
1709
+ }));
1710
+ for (const error of errors)
1711
+ return `* ${this.sf(this.translateService.instant(`errors.${error?.['message']}`), this[error?.['key']] ?? "")}`;
1712
+ }
1713
+ }
1714
+ }
1715
+
1716
+ /**
1717
+ * @description A dynamic form field component for CRUD operations.
1718
+ * @summary The CrudFieldComponent is a versatile form field component that adapts to different
1719
+ * input types and CRUD operations. It extends NgxCrudFormField to inherit form handling capabilities
1720
+ * and implements lifecycle hooks to properly initialize, render, and clean up. This component
1721
+ * supports various input types (text, number, date, select, etc.), validation rules, and styling
1722
+ * options, making it suitable for building dynamic forms for create, read, update, and delete
1723
+ * operations.
1724
+ *
1725
+ * @param {CrudOperations} operation - The CRUD operation being performed (create, read, update, delete)
1726
+ * @param {string} name - The field name, used as form control identifier
1727
+ * @param {PossibleInputTypes} type - The input type (text, number, date, select, etc.)
1728
+ * @param {string|number|Date} value - The initial value of the field
1729
+ * @param {boolean} disabled - Whether the field is disabled
1730
+ * @param {string} label - The display label for the field
1731
+ * @param {string} placeholder - Placeholder text when field is empty
1732
+ * @param {string} format - Format pattern for the field value
1733
+ * @param {boolean} hidden - Whether the field should be hidden
1734
+ * @param {number|Date} max - Maximum allowed value
1735
+ * @param {number} maxlength - Maximum allowed length
1736
+ * @param {number|Date} min - Minimum allowed value
1737
+ * @param {number} minlength - Minimum allowed length
1738
+ * @param {string} pattern - Validation pattern
1739
+ * @param {boolean} readonly - Whether the field is read-only
1740
+ * @param {boolean} required - Whether the field is required
1741
+ * @param {number} step - Step value for number inputs
1742
+ * @param {FormGroup} formGroup - The parent form group
1743
+ * @param {StringOrBoolean} translatable - Whether field labels should be translated
1744
+ *
1745
+ * @component CrudFieldComponent
1746
+ * @example
1747
+ * <ngx-decaf-crud-field
1748
+ * operation="create"
1749
+ * name="firstName"
1750
+ * type="text"
1751
+ * label="<NAME>"
1752
+ * placeholder="<NAME>"
1753
+ * [value]="model.firstName"
1754
+ * [disabled]="model.readOnly">
1755
+ *
1756
+ *
1757
+ * @memberOf module:for-angular
1758
+ */
1759
+ let CrudFieldComponent = class CrudFieldComponent extends NgxCrudFormField {
1760
+ constructor() {
1761
+ super(...arguments);
1762
+ /**
1763
+ * @description The parent field path, if this field is nested.
1764
+ * @summary Specifies the full dot-delimited path of the parent field. This is only set when the field is nested.
1765
+ *
1766
+ * @type {string}
1767
+ * @memberOf CrudFieldComponent
1768
+ */
1769
+ this.childOf = '';
1770
+ /**
1771
+ * @description The initial value of the field.
1772
+ * @summary Sets the initial value of the form field. This can be a string, number, or Date
1773
+ * depending on the field type. For select fields, this should match one of the option values.
1774
+ *
1775
+ * @type {string | number | Date}
1776
+ * @default ''
1777
+ * @memberOf CrudFieldComponent
1778
+ */
1779
+ this.value = '';
1780
+ /**
1781
+ * @description Interface style for select inputs.
1782
+ * @summary Specifies the interface style for select inputs, such as 'alert', 'action-sheet', or 'popover'.
1783
+ * This determines how the select options are presented to the user.
1784
+ *
1785
+ * @type {SelectInterface}
1786
+ * @memberOf CrudFieldComponent
1787
+ */
1788
+ this.interface = 'popover';
1789
+ /**
1790
+ * @description Spellcheck attribute for text inputs.
1791
+ * @summary Enables or disables spellchecking for text inputs.
1792
+ * When true, the browser will check the spelling of the input text.
1793
+ *
1794
+ * @type {boolean}
1795
+ * @default false
1796
+ * @memberOf CrudFieldComponent
1797
+ */
1798
+ this.spellcheck = false;
1799
+ /**
1800
+ * @description Input mode for text inputs.
1801
+ * @summary Hints at the type of data that might be entered by the user while editing the element.
1802
+ * This can affect the virtual keyboard layout on mobile devices.
1803
+ *
1804
+ * @type {'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'}
1805
+ * @default 'none'
1806
+ * @memberOf CrudFieldComponent
1807
+ */
1808
+ this.inputmode = 'none';
1809
+ /**
1810
+ * @description Autocomplete behavior for the field.
1811
+ * @summary Specifies whether and how the browser should automatically complete the input.
1812
+ * This can improve user experience by suggesting previously entered values.
1813
+ *
1814
+ * @type {AutocompleteTypes}
1815
+ * @default 'off'
1816
+ * @memberOf CrudFieldComponent
1817
+ */
1818
+ this.autocomplete = 'off';
1819
+ /**
1820
+ * @description Fill style for the field.
1821
+ * @summary Determines the fill style of the field, such as 'outline' or 'solid'.
1822
+ * This affects the border and background of the field.
1823
+ *
1824
+ * @type {'outline' | 'solid'}
1825
+ * @default 'outline'
1826
+ * @memberOf CrudFieldComponent
1827
+ */
1828
+ this.fill = 'outline';
1829
+ /**
1830
+ * @description Placement of the label relative to the field.
1831
+ * @summary Specifies where the label should be placed relative to the field.
1832
+ * Options include 'start', 'end', 'floating', 'stacked', and 'fixed'.
1833
+ *
1834
+ * @type {'start' | 'end' | 'floating' | 'stacked' | 'fixed'}
1835
+ * @default 'floating'
1836
+ * @memberOf CrudFieldComponent
1837
+ */
1838
+ this.labelPlacement = 'floating';
1839
+ /**
1840
+ * @description Update mode for the field.
1841
+ * @summary Determines when the field value should be updated in the form model.
1842
+ * Options include 'change', 'blur', and 'submit'.
1843
+ *
1844
+ * @type {FieldUpdateMode}
1845
+ * @default 'change'
1846
+ * @memberOf CrudFieldComponent
1847
+ */
1848
+ this.updateOn = 'change';
1849
+ /**
1850
+ * @description Translatability of field labels.
1851
+ * @summary Indicates whether the field labels should be translated based on the current language settings.
1852
+ * This is useful for applications supporting multiple languages.
1853
+ *
1854
+ * @type {StringOrBoolean}
1855
+ * @default true
1856
+ * @memberOf CrudFieldComponent
1857
+ */
1858
+ this.translatable = true;
1859
+ }
1860
+ // constructor() {
1861
+ // }
1862
+ ngOnInit() {
1863
+ // super.onInit(this.updateOn);
1864
+ if ([OperationKeys.READ, OperationKeys.DELETE].includes(this.operation)) {
1865
+ this.formGroup = undefined;
1866
+ }
1867
+ else {
1868
+ if (this.type === HTML5InputTypes.RADIO && !this.value)
1869
+ this.formGroup?.get(this.name)?.setValue(this.options[0].value); // TODO: migrate to RenderingEngine
1870
+ }
1871
+ }
1872
+ ngAfterViewInit() {
1873
+ if ([OperationKeys.READ, OperationKeys.DELETE].includes(this.operation))
1874
+ super.afterViewInit();
1875
+ }
1876
+ ngOnDestroy() {
1877
+ if ([OperationKeys.READ, OperationKeys.DELETE].includes(this.operation))
1878
+ this.onDestroy();
1879
+ }
1880
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: CrudFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1881
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", 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", translatable: "translatable", uid: "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 #component [formGroup]=\"formGroup\">\n <div #container [class]=\"'dcf-input-item ' + (operation || 'create')\">\n @if(type === 'textarea') {\n <ion-textarea\n [mode]=\"mode\"\n [hidden]=\"hidden\"\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 >\n </ion-textarea>\n }\n @else if(type === 'checkbox') {\n <ion-item>\n <ion-checkbox\n #checkboxElement\n [name]=\"path\"\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 >\n <span [innerHTML]=\"label | translate\"></span>\n </ion-checkbox>\n </ion-item>\n }\n @else if(type === 'radio') {\n <ion-radio-group [formControlName]=\"name\" [name]=\"path\" [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 [name]=\"path\"\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\">\n @for(option of options; track option.value) {\n <ion-select-option [value]=\"option.value\">\n {{ translatable ? (option.text | translate) : options.text }}\n </ion-select-option>\n }\n </ion-select>\n }\n @else {\n <ion-input\n [name]=\"path\"\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\">\n </ion-input>\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}.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-hover: var(--ion-color-primary);--background-activated-opacity: .15;--background-focused: var(--ion-color-primary);--background-focused-opacity: .15}.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(--ion-color-primary);--checkmark-width: 2px}ion-item{--inner-padding-start: .75rem}ion-checkbox::part(container){border-radius:50%;border:2px solid var(--ion-color-primary);padding:3px}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(--ion-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(--ion-color-gray-8)!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)}}\n"], dependencies: [{ kind: "ngmodule", type: ForAngularModule }, { kind: "component", type: i1$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i2.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "pipe", type: i3.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: 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"] }] }); }
1882
+ };
1883
+ CrudFieldComponent = __decorate([
1884
+ Dynamic()
1885
+ ], CrudFieldComponent);
1886
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: CrudFieldComponent, decorators: [{
1887
+ type: Component,
1888
+ args: [{ standalone: true, imports: [
1889
+ ForAngularModule,
1890
+ IonInput,
1891
+ IonItem,
1892
+ IonCheckbox,
1893
+ IonRadioGroup,
1894
+ IonRadio,
1895
+ IonSelect,
1896
+ IonSelectOption,
1897
+ IonLabel,
1898
+ IonText,
1899
+ IonTextarea,
1900
+ ], selector: 'ngx-decaf-crud-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], 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 #component [formGroup]=\"formGroup\">\n <div #container [class]=\"'dcf-input-item ' + (operation || 'create')\">\n @if(type === 'textarea') {\n <ion-textarea\n [mode]=\"mode\"\n [hidden]=\"hidden\"\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 >\n </ion-textarea>\n }\n @else if(type === 'checkbox') {\n <ion-item>\n <ion-checkbox\n #checkboxElement\n [name]=\"path\"\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 >\n <span [innerHTML]=\"label | translate\"></span>\n </ion-checkbox>\n </ion-item>\n }\n @else if(type === 'radio') {\n <ion-radio-group [formControlName]=\"name\" [name]=\"path\" [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 [name]=\"path\"\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\">\n @for(option of options; track option.value) {\n <ion-select-option [value]=\"option.value\">\n {{ translatable ? (option.text | translate) : options.text }}\n </ion-select-option>\n }\n </ion-select>\n }\n @else {\n <ion-input\n [name]=\"path\"\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\">\n </ion-input>\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}.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-hover: var(--ion-color-primary);--background-activated-opacity: .15;--background-focused: var(--ion-color-primary);--background-focused-opacity: .15}.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(--ion-color-primary);--checkmark-width: 2px}ion-item{--inner-padding-start: .75rem}ion-checkbox::part(container){border-radius:50%;border:2px solid var(--ion-color-primary);padding:3px}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(--ion-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(--ion-color-gray-8)!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)}}\n"] }]
1901
+ }], propDecorators: { operation: [{
1902
+ type: Input,
1903
+ args: [{ required: true }]
1904
+ }], name: [{
1905
+ type: Input,
1906
+ args: [{ required: true }]
1907
+ }], path: [{
1908
+ type: Input,
1909
+ args: [{ required: true }]
1910
+ }], childOf: [{
1911
+ type: Input
1912
+ }], type: [{
1913
+ type: Input,
1914
+ args: [{ required: true }]
1915
+ }], value: [{
1916
+ type: Input
1917
+ }], disabled: [{
1918
+ type: Input
1919
+ }], label: [{
1920
+ type: Input,
1921
+ args: [{ required: true }]
1922
+ }], placeholder: [{
1923
+ type: Input
1924
+ }], format: [{
1925
+ type: Input
1926
+ }], hidden: [{
1927
+ type: Input
1928
+ }], max: [{
1929
+ type: Input
1930
+ }], maxlength: [{
1931
+ type: Input
1932
+ }], min: [{
1933
+ type: Input
1934
+ }], minlength: [{
1935
+ type: Input
1936
+ }], pattern: [{
1937
+ type: Input
1938
+ }], readonly: [{
1939
+ type: Input
1940
+ }], required: [{
1941
+ type: Input
1942
+ }], step: [{
1943
+ type: Input
1944
+ }], equals: [{
1945
+ type: Input
1946
+ }], different: [{
1947
+ type: Input
1948
+ }], lessThan: [{
1949
+ type: Input
1950
+ }], lessThanOrEqual: [{
1951
+ type: Input
1952
+ }], greaterThan: [{
1953
+ type: Input
1954
+ }], greaterThanOrEqual: [{
1955
+ type: Input
1956
+ }], cols: [{
1957
+ type: Input
1958
+ }], rows: [{
1959
+ type: Input
1960
+ }], alignment: [{
1961
+ type: Input
1962
+ }], checked: [{
1963
+ type: Input
1964
+ }], justify: [{
1965
+ type: Input
1966
+ }], cancelText: [{
1967
+ type: Input
1968
+ }], interface: [{
1969
+ type: Input
1970
+ }], options: [{
1971
+ type: Input
1972
+ }], mode: [{
1973
+ type: Input
1974
+ }], spellcheck: [{
1975
+ type: Input
1976
+ }], inputmode: [{
1977
+ type: Input
1978
+ }], autocomplete: [{
1979
+ type: Input
1980
+ }], fill: [{
1981
+ type: Input
1982
+ }], labelPlacement: [{
1983
+ type: Input
1984
+ }], updateOn: [{
1985
+ type: Input
1986
+ }], component: [{
1987
+ type: ViewChild,
1988
+ args: ['component', { read: ElementRef }]
1989
+ }], formGroup: [{
1990
+ type: Input
1991
+ }], formControl: [{
1992
+ type: Input
1993
+ }], translatable: [{
1994
+ type: Input
1995
+ }], uid: [{
1996
+ type: Input
1997
+ }] } });
1998
+
1999
+ const CssClasses = {
2000
+ BUTTONS_CONTAINER: 'buttons-container',
2001
+ };
2002
+ const DefaultFormReactiveOptions = {
2003
+ buttons: {
2004
+ submit: {
2005
+ text: 'Submit',
2006
+ },
2007
+ clear: {
2008
+ text: 'Clear',
2009
+ },
2010
+ },
2011
+ };
2012
+
2013
+ /**
2014
+ * @component CrudFormComponent
2015
+ * @example <ngx-decaf-crud-form
2016
+ * action="create"
2017
+ * operation="create"
2018
+ * formGroup="formGroup"
2019
+ * rendererId="rendererId"
2020
+ * submitEvent="submitEvent"
2021
+ * target="_self"
2022
+ * method="event">
2023
+ * </ngx-decaf-crud-form>
2024
+ *
2025
+ * @param {string} action - The action to be performed (create, read, update, delete)
2026
+ * @param {CrudOperations} operation - The CRUD operation being performed (create, read, update, delete)
2027
+ * @param {FormGroup} formGroup - The form group
2028
+ * @param {string} rendererId - The renderer id
2029
+ * @param {SubmitEvent} submitEvent - The submit event
2030
+ * @param {string} target - The target
2031
+ * @param {string} method - The method
2032
+ */
2033
+ let CrudFormComponent = class CrudFormComponent {
2034
+ constructor() {
2035
+ this.updateOn = 'change';
2036
+ this.target = '_self';
2037
+ this.method = 'event';
2038
+ this.submitEvent = new EventEmitter();
2039
+ /**
2040
+ * @description Angular Location service.
2041
+ * @summary Injected service that provides access to the browser's URL and history.
2042
+ * This service is used for interacting with the browser's history API, allowing
2043
+ * for back navigation and URL manipulation outside of Angular's router.
2044
+ *
2045
+ * @private
2046
+ * @type {Location}
2047
+ * @memberOf CrudFormComponent
2048
+ */
2049
+ this.location = inject(Location);
2050
+ this.OperationKeys = OperationKeys;
2051
+ }
2052
+ // ngAfterViewInit() {
2053
+ // if (![OperationKeys.READ, OperationKeys.DELETE].includes(this.operation))
2054
+ // NgxFormService.formAfterViewInit(this, this.rendererId);
2055
+ // }
2056
+ async ngOnInit() {
2057
+ if (!this.logger)
2058
+ this.logger = getLogger(this);
2059
+ if (this.operation === OperationKeys.READ || this.operation === OperationKeys.DELETE)
2060
+ this.formGroup = undefined;
2061
+ this.options = Object.assign({}, DefaultFormReactiveOptions, this.options || {});
2062
+ }
2063
+ ngOnDestroy() {
2064
+ if (this.formGroup)
2065
+ NgxFormService.unregister(this.formGroup);
2066
+ }
2067
+ /**
2068
+ * @param {SubmitEvent} event
2069
+ */
2070
+ async submit(event) {
2071
+ event.preventDefault();
2072
+ event.stopImmediatePropagation();
2073
+ event.stopPropagation();
2074
+ if (!NgxFormService.validateFields(this.formGroup))
2075
+ return false;
2076
+ const data = NgxFormService.getFormData(this.formGroup);
2077
+ this.submitEvent.emit({
2078
+ data,
2079
+ component: 'FormReactiveComponent',
2080
+ name: this.action || EventConstants.SUBMIT_EVENT,
2081
+ handlers: this.handlers,
2082
+ });
2083
+ }
2084
+ handleReset() {
2085
+ this.location.back();
2086
+ // if(OperationKeys.DELETE !== this.operation)
2087
+ // NgxFormService.reset(this.formGroup);
2088
+ // else
2089
+ // this.location.back();
2090
+ }
2091
+ handleDelete() {
2092
+ this.submitEvent.emit({
2093
+ data: this.uid,
2094
+ component: 'FormReactiveComponent',
2095
+ name: EventConstants.SUBMIT_EVENT,
2096
+ });
2097
+ }
2098
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: CrudFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2099
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: CrudFormComponent, isStandalone: true, selector: "ngx-decaf-crud-form", inputs: { model: "model", updateOn: "updateOn", target: "target", method: "method", options: "options", action: "action", operation: "operation", handlers: "handlers", formGroup: "formGroup", childOf: "childOf", rendererId: "rendererId", uid: "uid" }, outputs: { submitEvent: "submitEvent" }, 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-flex dcf-flex-right\">\n <div>\n @if(options.buttons.clear) {\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 {{operation === 'update' ? 'Back' : options.buttons.clear?.text}}\n </ion-button>\n }\n\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 </div>\n </form>\n} @else {\n <div [class]=\"'dcf-flex dcf-flex-right ' + operation\" id=\"dcf-buttons-container\">\n <div>\n @if(options.buttons.clear) {\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 {{ ['delete', 'read', 'update'].includes(operation) ? 'Back' : options.buttons.clear?.text}}\n </ion-button>\n }\n @if(operation === 'delete' && uid) {\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 }\n @if(operation === OperationKeys.CREATE || operation === OperationKeys.UPDATE) {\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 }\n\n </div>\n </div>\n}\n\n", styles: ["#dcf-buttons-container{margin-top:1.8rem;margin-bottom:0}\n"], dependencies: [{ kind: "ngmodule", type: ForAngularModule }, { kind: "component", type: i1$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
2100
+ };
2101
+ CrudFormComponent = __decorate([
2102
+ Dynamic()
2103
+ ], CrudFormComponent);
2104
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: CrudFormComponent, decorators: [{
2105
+ type: Component,
2106
+ args: [{ standalone: true, selector: 'ngx-decaf-crud-form', imports: [ForAngularModule, IonIcon], 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-flex dcf-flex-right\">\n <div>\n @if(options.buttons.clear) {\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 {{operation === 'update' ? 'Back' : options.buttons.clear?.text}}\n </ion-button>\n }\n\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 </div>\n </form>\n} @else {\n <div [class]=\"'dcf-flex dcf-flex-right ' + operation\" id=\"dcf-buttons-container\">\n <div>\n @if(options.buttons.clear) {\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 {{ ['delete', 'read', 'update'].includes(operation) ? 'Back' : options.buttons.clear?.text}}\n </ion-button>\n }\n @if(operation === 'delete' && uid) {\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 }\n @if(operation === OperationKeys.CREATE || operation === OperationKeys.UPDATE) {\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 }\n\n </div>\n </div>\n}\n\n", styles: ["#dcf-buttons-container{margin-top:1.8rem;margin-bottom:0}\n"] }]
2107
+ }], propDecorators: { model: [{
2108
+ type: Input
2109
+ }], updateOn: [{
2110
+ type: Input
2111
+ }], component: [{
2112
+ type: ViewChild,
2113
+ args: ['reactiveForm', { static: false, read: ElementRef }]
2114
+ }], target: [{
2115
+ type: Input
2116
+ }], method: [{
2117
+ type: Input
2118
+ }], options: [{
2119
+ type: Input
2120
+ }], action: [{
2121
+ type: Input
2122
+ }], operation: [{
2123
+ type: Input,
2124
+ args: [{ required: true }]
2125
+ }], handlers: [{
2126
+ type: Input
2127
+ }], formGroup: [{
2128
+ type: Input
2129
+ }], childOf: [{
2130
+ type: Input
2131
+ }], rendererId: [{
2132
+ type: Input
2133
+ }], uid: [{
2134
+ type: Input
2135
+ }], submitEvent: [{
2136
+ type: Output
2137
+ }] } });
2138
+
2139
+ /**
2140
+ * @description Angular integration for the Decaf framework
2141
+ * @summary This module provides Angular components and services for integrating with the Decaf framework.
2142
+ * It includes components for rendering models, CRUD operations, and form handling, as well as
2143
+ * rendering engines and utility functions to facilitate Angular application development with Decaf.
2144
+ * @module for-angular
2145
+ */
2146
+
2147
+ /**
2148
+ * Generated bundle index. Do not edit.
2149
+ */
2150
+
2151
+ export { AngularEngineKeys, BaseComponentProps, ComponentsTagNames, CrudFieldComponent, CrudFormComponent, Dynamic, DynamicModule, EventConstants, FormConstants, LoggerLevels, ModelRendererComponent, NgxRenderingEngine, NgxRenderingEngine2, RouteDirections };
2152
+ //# sourceMappingURL=decaf-ts-for-angular.mjs.map