@decaf-ts/for-angular 0.0.3 → 0.0.5

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