@a.nemreen/dga-dynamic-form 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ahmed Nemreen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Schema-driven dynamic forms for Angular, rendered with [`@a.nemreen/dga-ui`](https://www.npmjs.com/package/@a.nemreen/dga-ui) controls.
4
4
 
5
- Version **0.1.0** — API may move until freeze.
5
+ Version **0.1.1** — API may move until freeze.
6
6
 
7
7
  ## Install
8
8
 
@@ -14,8 +14,6 @@ npm install @a.nemreen/dga-dynamic-form @a.nemreen/dga-ui @a.nemreen/dga-tokens
14
14
 
15
15
  ## Setup
16
16
 
17
- Provide adapters once in `app.config.ts`:
18
-
19
17
  ```ts
20
18
  import { provideHttpClient } from '@angular/common/http';
21
19
  import { provideDgaDynamicForm } from '@a.nemreen/dga-dynamic-form';
@@ -23,25 +21,33 @@ import { provideDgaDynamicForm } from '@a.nemreen/dga-dynamic-form';
23
21
  export const appConfig = {
24
22
  providers: [
25
23
  provideHttpClient(),
26
- provideDgaDynamicForm({ httpSubmit: true, dgaToast: true }),
24
+ provideDgaDynamicForm({
25
+ httpSubmit: true,
26
+ httpLookup: true,
27
+ dgaToast: true,
28
+ // Recommended when schemas/endpoints come from CMS or untrusted config:
29
+ // allowedOrigins: ['https://api.example.com'],
30
+ }),
27
31
  ],
28
32
  };
29
33
  ```
30
34
 
31
- Ensure Tailwind scans the package:
32
-
35
+ **Security note:** Treat form schemas as trusted app config unless you set `allowedOrigins`.
36
+ HTTP adapters reject non-`http(s)` URLs; path-absolute URLs (`/api/...`) stay same-origin.
37
+ Always validate uploads and payloads on the server.
33
38
  ```css
34
39
  @source "../node_modules/@a.nemreen/dga-dynamic-form/**/*.{mjs,js}";
35
40
  @source "../node_modules/@a.nemreen/dga-ui/**/*.{mjs,js}";
36
41
  ```
37
42
 
38
- ## Usage
43
+ ## Quick usage
39
44
 
40
45
  ```ts
41
46
  import { DgaDynamicForm, type DgaDynamicFormConfig } from '@a.nemreen/dga-dynamic-form';
42
47
 
43
48
  readonly config: DgaDynamicFormConfig = {
44
- emitOnly: true,
49
+ endpoint: 'https://api.example.com/contact',
50
+ method: 'POST',
45
51
  submitButtonLabel: { en: 'Send', ar: 'إرسال' },
46
52
  fields: [
47
53
  {
@@ -51,13 +57,6 @@ readonly config: DgaDynamicFormConfig = {
51
57
  required: true,
52
58
  columns: { md: 6 },
53
59
  },
54
- {
55
- name: 'email',
56
- type: 'email',
57
- label: { en: 'Email', ar: 'البريد' },
58
- required: true,
59
- columns: { md: 6 },
60
- },
61
60
  ],
62
61
  };
63
62
  ```
@@ -66,20 +65,204 @@ readonly config: DgaDynamicFormConfig = {
66
65
  <dga-dynamic-form [config]="config" (formSubmitted)="onSubmit($event)" />
67
66
  ```
68
67
 
69
- ## Adapters
68
+ ---
69
+
70
+ ## Config reference (`DgaDynamicFormConfig`)
71
+
72
+ Passed as `[config]` on `<dga-dynamic-form>`.
73
+
74
+ | Key | Type | Default | Description |
75
+ |-----|------|---------|-------------|
76
+ | `endpoint` | `string?` | — | Submit URL. Omit (or use `emitOnly`) to only emit `formSubmitted`. |
77
+ | `method` | `'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'` | `'POST'` | HTTP method for the submit adapter. |
78
+ | `emitOnly` | `boolean?` | `false` | Skip HTTP; emit the payload on `formSubmitted`. |
79
+ | `fields` | `DgaFormField[]` | **required** | Field list. Can be `[]` when using `wizard.steps` only. |
80
+ | `description` | `DgaFormLabel?` | — | Bilingual text above the form. |
81
+ | `submitButtonLabel` | `DgaFormLabel?` | Submit / إرسال | Primary button label. |
82
+ | `clearButtonLabel` | `DgaFormLabel?` | — | Shows Clear when set. |
83
+ | `saveButtonLabel` | `DgaFormLabel?` | — | Draft save button label. |
84
+ | `successMessage` | `DgaFormLabel?` | — | Toast on success. |
85
+ | `errorMessage` | `DgaFormLabel?` | — | Toast on failure. |
86
+ | `fieldMapping` | `Record<string, string>?` | — | Rename field keys in the submit payload. |
87
+ | `payloadTransformer` | `(data) => object \| FormData` | — | Final reshape before submit. |
88
+ | `formId` | `string?` | — | localStorage namespace for drafts. |
89
+ | `enableLocalStorageSave` | `boolean?` | `false` | Enable Save draft + restore on init. |
90
+ | `idempotencyKey` | `string?` | — | Sent as `Idempotency-Key` header. |
91
+ | `wizard` | `DgaWizardConfig?` | — | Multi-step mode (see below). |
92
+
93
+ `DgaFormLabel` is always `{ en: string; ar: string }`.
94
+
95
+ ### Example with endpoint
96
+
97
+ ```ts
98
+ const EDIT_STUDY: DgaDynamicFormConfig = {
99
+ endpoint: `${apiUrl}/consulting-studies/{id}`,
100
+ method: 'PUT',
101
+ successMessage: { en: 'Saved', ar: 'تم الحفظ' },
102
+ errorMessage: { en: 'Failed', ar: 'فشل' },
103
+ fields: [ /* ... */ ],
104
+ };
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Wizard (`config.wizard` → `DgaWizardConfig`)
110
+
111
+ When `wizard.enabled` is true, fields are taken from `wizard.steps[].fields` (root `fields` can be empty).
112
+
113
+ | Key | Type | Default | Description |
114
+ |-----|------|---------|-------------|
115
+ | `enabled` | `boolean` | — | Turn on stepper UI. |
116
+ | `steps` | `DgaFormStep[]` | — | Ordered steps. |
117
+ | `validateOnStepChange` | `boolean?` | `true` | Block Next if current step invalid. |
118
+ | `allowSkipSteps` | `boolean?` | `false` | Allow jumping ahead (reserved). |
119
+ | `showSaveButton` | `boolean?` | — | Show draft save in wizard chrome. |
120
+ | `autoSave` | `boolean?` | — | Auto-save drafts (host / reserved). |
121
+ | `saveEndpoint` | `string?` | — | Optional draft HTTP URL. |
122
+ | `saveMethod` | `DgaHttpMethod?` | — | Method for `saveEndpoint`. |
123
+ | `nextButtonText` | `DgaFormLabel?` | Next / التالي | |
124
+ | `previousButtonText` | `DgaFormLabel?` | Previous / السابق | |
125
+ | `submitButtonText` | `DgaFormLabel?` | Finish / إنهاء | Final step. |
126
+ | `saveButtonText` | `DgaFormLabel?` | Save / حفظ | |
127
+
128
+ ### Step (`DgaFormStep`)
70
129
 
71
- Override via InjectionTokens:
130
+ | Key | Type | Description |
131
+ |-----|------|-------------|
132
+ | `id` | `string` | Stable step id. |
133
+ | `title` | `DgaFormLabel` | Stepper title. |
134
+ | `description` | `DgaFormLabel?` | Stepper subtitle. |
135
+ | `fields` | `DgaFormField[]` | Fields in this step. |
136
+ | `optional` | `boolean?` | Mark step optional. |
137
+
138
+ ---
139
+
140
+ ## Field (`DgaFormField`)
141
+
142
+ ### Core
143
+
144
+ | Key | Type | Description |
145
+ |-----|------|-------------|
146
+ | `name` | `string` | FormControl name / payload key. |
147
+ | `type` | `DgaFormFieldType` | See built-in types below. |
148
+ | `label` | `DgaFormLabel` | Bilingual label. |
149
+ | `required` | `boolean?` | Always-required. |
150
+ | `placeholder` | `DgaFormLabel?` | |
151
+ | `hint` | `DgaFormLabel?` | Helper under the field. |
152
+ | `value` | `unknown?` | Initial value. |
153
+ | `disabled` | `boolean?` | |
154
+ | `readonly` | `boolean?` | Text-like inputs. |
155
+ | `hidden` | `boolean?` | Hide from layout. |
156
+ | `excludeFromPayload` | `boolean?` | Keep in UI; drop from submit body. |
157
+ | `columns` | `{ sm?, md?, lg?, xl? }` | 12-column grid spans (default 12). |
158
+ | `maxLength` | `number?` | |
159
+ | `rows` | `number?` | Textarea rows. |
160
+ | `inputRestriction` | `'numbers' \| 'english' \| 'arabic'` | Filter keystrokes. |
161
+
162
+ ### Built-in `type` values
163
+
164
+ `text` · `email` · `textarea` · `number` · `otp` · `phone` · `select` · `multiselect` · `checkbox` · `radio` · `date` · `chips` · `toggle` · `file` · `hidden`
165
+
166
+ ### Options (select / radio / checkbox)
167
+
168
+ | Key | Type | Description |
169
+ |-----|------|-------------|
170
+ | `options` | `DgaFormFieldOption[]?` | Static options. |
171
+ | `groupedOptions` | `{ groupLabel, options }[]?` | **Planned** — flattened for visibility only today. |
172
+ | `optionsLayout` | `'stack' \| 'grid'` | Radio/checkbox layout (default `stack`). |
173
+
174
+ **Option object:** `value`, `label`, optional `requiresTextInput`, `description`, `disabled`.
175
+
176
+ ### Visibility & conditional required
177
+
178
+ | Key | Type | Description |
179
+ |-----|------|-------------|
180
+ | `dependsOn` | `string?` | Other field that controls visibility. |
181
+ | `showOnValues` | `unknown[]?` | Show when `dependsOn` value is in list. |
182
+ | `showWhenOptionProperty` | `{ property, value }?` | Show when selected option has `property === value`. |
183
+ | `requiredWhen` | object? | Conditional required (see below). |
184
+
185
+ ```ts
186
+ requiredWhen: {
187
+ field: 'topic',
188
+ value?: 'other', // single match
189
+ values?: ['a', 'b'], // any-of match
190
+ optionProperty?: 'requiresTextInput',
191
+ optionPropertyValue?: true,
192
+ }
193
+ ```
194
+
195
+ ### Lookup (remote options)
196
+
197
+ | Key | Type | Description |
198
+ |-----|------|-------------|
199
+ | `lookupDomain` | `string?` | Base URL for lookup adapter. |
200
+ | `lookupName` | `string?` | Resource → `{domain}/lookup/{name}`. |
201
+ | `lookupFilter` | `{ property, value }?` | Client-side filter on loaded options. |
202
+ | `useParentId` | `boolean?` | Pass `dependsOn` value as `parentId` for cascading lookups. |
203
+ | `searchLookup` | `boolean?` | **Planned** — searchable UI only; remote search adapter not wired yet. |
204
+ | `searchParamName` | `string?` | Query param (default `q`) when search is implemented. |
205
+ | `minSearchLength` | `number?` | Min chars before search (default 3) — **planned**. |
206
+
207
+ ### Type-specific
208
+
209
+ | Key | Types | Description |
210
+ |-----|-------|-------------|
211
+ | `minDate` / `maxDate` | `date` | ISO date bounds. |
212
+ | `monthPicker` | `date` | Month-only picker. |
213
+ | `acceptedFileTypes` | `file` | e.g. `.pdf,.png`. |
214
+ | `maxFileSize` | `file` | Max bytes. |
215
+ | `maxFiles` | `file` | Max files (`>1` → multiple; enforced client-side). |
216
+ | `maxChips` | `chips` | Max chip count. |
217
+ | `allowDuplicates` | `chips` | **Planned** — not yet bound to `dga-chip-input`. |
218
+
219
+ ### Validation (`field.validation`)
220
+
221
+ | Key | Description |
222
+ |-----|-------------|
223
+ | `pattern` | Regex string, or `'email'`. |
224
+ | `minLength` / `maxLength` | Length validators. |
225
+ | `min` / `max` | Numeric bounds. |
226
+ | `required` | Same as `field.required`. |
227
+ | `errorMessages.*` | Bilingual messages for `required`, `pattern`, `minLength`, `maxLength`, `email`, `min`, `max`, `minDate`, `maxDate`, `maxChips`, `invalidFormat`. |
228
+
229
+ ---
230
+
231
+ ## Component API
232
+
233
+ | Member | Kind | Description |
234
+ |--------|------|-------------|
235
+ | `config` | input | `DgaDynamicFormConfig` (required). |
236
+ | `locale` | input | `'ar' \| 'en' \| null` (default from `<html lang>`). |
237
+ | `formSubmitted` | output | HTTP response, or payload when `emitOnly` / no endpoint. |
238
+ | `formError` | output | Submit failure. |
239
+ | `clearButtonClick` | output | After Clear. |
240
+ | `draftSaved` | output | After localStorage save. |
241
+ | `patchFormValues(values)` | method | Patch controls. |
242
+ | `getForm()` | method | Returns `FormGroup`. |
243
+
244
+ ---
245
+
246
+ ## Adapters
72
247
 
73
248
  | Token | Role |
74
249
  |-------|------|
75
- | `DGA_LOOKUP_ADAPTER` | Lookup / search options |
76
- | `DGA_SUBMIT_ADAPTER` | HTTP submit |
77
- | `DGA_FORM_TOAST_ADAPTER` | Success / error feedback |
78
- | `DGA_CAPTCHA_ADAPTER` | Optional captcha token |
79
- | `DGA_FORM_I18N_ADAPTER` | Label resolution |
80
- | `DGA_DYNAMIC_FORM_FIELD_REGISTRY` | Custom field type component map |
250
+ | `DGA_SUBMIT_ADAPTER` | `submit({ endpoint, method, body, headers })` |
251
+ | `DGA_LOOKUP_ADAPTER` | `lookup({ domain, name, parentId?, search? })` |
252
+ | `DGA_FORM_TOAST_ADAPTER` | `show({ title?, message?, variant? })` |
253
+ | `DGA_CAPTCHA_ADAPTER` | `getToken(action?) Observable<string \| null>` |
254
+ | `DGA_FORM_I18N_ADAPTER` | `resolveLabel(label, locale)` |
255
+ | `DGA_DYNAMIC_FORM_FIELD_REGISTRY` | `{ type, component }[]` custom field renderers |
81
256
 
82
- ## Custom field types
257
+ ```ts
258
+ provideDgaDynamicForm({
259
+ httpSubmit: true,
260
+ httpLookup: true,
261
+ dgaToast: true,
262
+ });
263
+ ```
264
+
265
+ ### Custom field type
83
266
 
84
267
  ```ts
85
268
  {
@@ -87,7 +270,3 @@ Override via InjectionTokens:
87
270
  useValue: [{ type: 'nationalId', component: NationalIdField }],
88
271
  }
89
272
  ```
90
-
91
- ## Built-in field types
92
-
93
- `text` · `email` · `textarea` · `number` · `otp` · `phone` · `select` · `multiselect` · `checkbox` · `radio` · `date` · `chips` · `toggle` · `file` · `hidden`
@@ -1,4 +1,4 @@
1
- import*as i from"@angular/core";import{InjectionToken as g,inject as s,Injectable as P,input as u,booleanAttribute as ye,output as S,DestroyRef as E,signal as C,computed as d,ChangeDetectionStrategy as q,Component as z,effect as xe,makeEnvironmentProviders as Se}from"@angular/core";import{NgComponentOutlet as B}from"@angular/common";import{takeUntilDestroyed as _}from"@angular/core/rxjs-interop";import*as p from"@angular/forms";import{Validators as m,ReactiveFormsModule as k,FormsModule as G,FormBuilder as Ce}from"@angular/forms";import{switchMap as $,catchError as Ie,startWith as ke,map as De}from"rxjs/operators";import{DgaField as j,DgaInput as W,DgaSelect as H,DgaCheckbox as K,DgaRadio as U,DgaPhoneInput as Y,DgaDatePicker as J,DgaChipInput as X,DgaSwitch as Z,DgaUpload as Q,DgaNumberInput as ee,DgaOtp as te,DgaButton as oe,DgaStepper as ae,DgaToastService as Oe}from"@a.nemreen/dga-ui";import{of as h}from"rxjs";import{HttpClient as re,HttpHeaders as Le}from"@angular/common/http";const D=new g("DGA_LOOKUP_ADAPTER"),O=new g("DGA_SUBMIT_ADAPTER"),L=new g("DGA_FORM_TOAST_ADAPTER"),T=new g("DGA_CAPTCHA_ADAPTER"),N=new g("DGA_FORM_I18N_ADAPTER"),ne=new g("DGA_DYNAMIC_FORM_FIELD_REGISTRY"),ie=["checkbox","multiselect","chips","file"],Te=["toggle"];function le(a){return a.value!==void 0?a.value:ie.includes(a.type)?[]:Te.includes(a.type)?!1:""}function A(a,e){const t=[],o=a.validation;(e||a.required||o?.required)&&(ie.includes(a.type)?t.push(Ne):a.type==="toggle"?t.push(m.requiredTrue):t.push(m.required)),(a.type==="email"||o?.pattern==="email")&&t.push(m.email);const r=o?.minLength;r!=null&&t.push(m.minLength(r));const n=o?.maxLength??a.maxLength;if(n!=null&&t.push(m.maxLength(n)),o?.pattern&&o.pattern!=="email")try{t.push(m.pattern(o.pattern))}catch{}return o?.min!=null&&t.push(m.min(o.min)),o?.max!=null&&t.push(m.max(o.max)),a.maxChips!=null&&a.type==="chips"&&t.push(Ae(a.maxChips)),t}function Ne(a){const e=a.value;return Array.isArray(e)&&e.length>0?null:{required:!0}}function Ae(a){return e=>{const t=e.value;return Array.isArray(t)&&t.length>a?{maxChips:{max:a,actual:t.length}}:null}}function se(a,e,t=!0){const o=!!e.disabled;return a.control({value:le(e),disabled:o},{validators:A(e,t&&!e.requiredWhen)})}function de(a,e){const t={};for(const o of e){if(o.hidden&&o.type==="hidden"){t[o.name]=a.control(o.value??"");continue}t[o.name]=se(a,o)}return a.group(t)}function ce(a,e){return e?.length?e.flatMap(t=>t.fields):a}function ue(a,e,t){a.setValidators(A(e,t)),a.updateValueAndValidity({emitEvent:!1})}function F(a,e){return Array.isArray(a)?a.some(t=>t==e):a==e}function w(a,e,t){return a?[...t??[],...a.options??[],...a.groupedOptions?.flatMap(r=>r.options)??[]].find(r=>r.value==e):void 0}function M(a,e,t){if(a.hidden&&a.type!=="hidden"||a.type==="hidden")return!1;if(!a.dependsOn)return!0;const o=e[a.dependsOn];if(a.showOnValues?.length&&!a.showOnValues.some(r=>F(o,r)))return!1;if(a.showWhenOptionProperty){const r=t?.(a.dependsOn),n=w({name:a.dependsOn,type:"select",label:{en:"",ar:""}},o,r),l=a.showWhenOptionProperty.property;if(!n||n[l]!==a.showWhenOptionProperty.value)return!1}return!(!a.showOnValues?.length&&!a.showWhenOptionProperty&&(o==null||o===""||Array.isArray(o)&&o.length===0))}function V(a,e,t){const o=a.requiredWhen;if(!o)return!!a.required;const r=e[o.field];if(o.optionProperty!=null){const n=t?.(o.field),l=w({name:o.field,type:"select",label:{en:"",ar:""}},r,n);return!!l&&l[o.optionProperty]===o.optionPropertyValue}return o.values?.length?o.values.some(n=>F(r,n)):o.value!==void 0?F(r,o.value):r!=null&&r!==""}class v{submitAdapter=s(O,{optional:!0});captcha=s(T,{optional:!0});buildPayload(e,t,o){const r=new Set(o.filter(l=>l.excludeFromPayload).map(l=>l.name));let n={};for(const[l,x]of Object.entries(t)){if(r.has(l))continue;const c=e.fieldMapping?.[l]??l;n[c]=x}return e.payloadTransformer?e.payloadTransformer(n):n}hasFileValues(e){return Object.values(e).some(t=>t instanceof File||Array.isArray(t)&&t.some(o=>o instanceof File||o?.file instanceof File))}toFormData(e){const t=new FormData;for(const[o,r]of Object.entries(e))if(r!=null)if(r instanceof File)t.append(o,r);else if(Array.isArray(r))for(const n of r)n instanceof File?t.append(o,n):n?.file instanceof File?t.append(o,n.file,n.name):t.append(o,typeof n=="string"?n:JSON.stringify(n));else typeof r=="object"?t.append(o,JSON.stringify(r)):t.append(o,String(r));return t}submitForm(e,t,o){if(!e.endpoint||!this.submitAdapter)return h(t);const r=this.buildPayload(e,t,o),n=r instanceof FormData?r:this.hasFileValues(r)?this.toFormData(r):r,l={};return e.idempotencyKey&&(l["Idempotency-Key"]=e.idempotencyKey),(this.captcha?.getToken("submit")??h(null)).pipe($(c=>(c&&(l["X-Recaptcha-Token"]=c),h({endpoint:e.endpoint,method:e.method??"POST",body:n,headers:l})))).pipe($(c=>this.submitAdapter.submit(c)),Ie(c=>{throw c}))}saveDraft(e,t){typeof localStorage>"u"||localStorage.setItem(this.storageKey(e),JSON.stringify(t))}loadDraft(e){if(typeof localStorage>"u")return null;const t=localStorage.getItem(this.storageKey(e));if(!t)return null;try{return JSON.parse(t)}catch{return null}}clearDraft(e){typeof localStorage>"u"||localStorage.removeItem(this.storageKey(e))}storageKey(e){return`dga-dynamic-form:${e}`}static \u0275fac=i.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:v,deps:[],target:i.\u0275\u0275FactoryTarget.Injectable});static \u0275prov=i.\u0275\u0275ngDeclareInjectable({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:v,providedIn:"root"})}i.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:v,decorators:[{type:P,args:[{providedIn:"root"}]}]});function b(a,e,t=""){return a?e==="en"?a.en||a.ar:a.ar||a.en:t}function pe(){return typeof document>"u"?"ar":(document.documentElement.lang||"ar").toLowerCase().startsWith("en")?"en":"ar"}class y{extras=s(ne,{optional:!0});map=new Map;constructor(){for(const e of this.extras??[])this.register(e)}register(e){this.map.set(e.type,e.component)}get(e){return this.map.get(e)??null}hasCustom(e){return this.map.has(e)}isBuiltIn(e){return Fe.has(e)}static \u0275fac=i.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:y,deps:[],target:i.\u0275\u0275FactoryTarget.Injectable});static \u0275prov=i.\u0275\u0275ngDeclareInjectable({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:y,providedIn:"root"})}i.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:y,decorators:[{type:P,args:[{providedIn:"root"}]}],ctorParameters:()=>[]});const Fe=new Set(["text","email","textarea","select","multiselect","checkbox","radio","phone","file","date","chips","toggle","hidden","number","otp"]);class f{field=u.required(...ngDevMode?[{debugName:"field"}]:[]);form=u.required(...ngDevMode?[{debugName:"form"}]:[]);locale=u("ar",...ngDevMode?[{debugName:"locale"}]:[]);required=u(!1,{...ngDevMode?{debugName:"required"}:{},transform:ye});error=u("",...ngDevMode?[{debugName:"error"}]:[]);runtimeOptions=u([],...ngDevMode?[{debugName:"runtimeOptions"}]:[]);optionsLoaded=S();lookup=s(D,{optional:!0});destroyRef=s(E);loadedOptions=C([],...ngDevMode?[{debugName:"loadedOptions"}]:[]);controlId=`dga-df-${Math.random().toString(36).slice(2,9)}`;get control(){return this.form().controls[this.field().name]}labelText=d(()=>b(this.field().label,this.locale()),...ngDevMode?[{debugName:"labelText"}]:[]);hintText=d(()=>b(this.field().hint,this.locale()),...ngDevMode?[{debugName:"hintText"}]:[]);placeholderText=d(()=>b(this.field().placeholder,this.locale()),...ngDevMode?[{debugName:"placeholderText"}]:[]);resolvedOptions=d(()=>{const e=this.runtimeOptions();if(e.length)return e;const t=this.loadedOptions();return t.length?t:this.field().options??[]},...ngDevMode?[{debugName:"resolvedOptions"}]:[]);selectOptions=d(()=>this.resolvedOptions().map(e=>({value:String(e.value),label:this.optionLabel(e),disabled:!!e.disabled})),...ngDevMode?[{debugName:"selectOptions"}]:[]);ngOnInit(){const e=this.field();e.lookupDomain&&e.lookupName&&this.lookup&&!e.searchLookup&&this.lookup.lookup({domain:e.lookupDomain,name:e.lookupName}).pipe(_(this.destroyRef)).subscribe(t=>{const o=this.applyLookupFilter(t);this.loadedOptions.set(o),this.optionsLoaded.emit({name:e.name,options:o})})}optionLabel(e){return b(e.label,this.locale())}isChecked(e){const t=this.control?.value;return Array.isArray(t)&&t.some(o=>o==e)}toggleCheckbox(e,t){const o=Array.isArray(this.control.value)?[...this.control.value]:[],r=t?o.some(n=>n==e)?o:[...o,e]:o.filter(n=>n!=e);this.control.setValue(r),this.control.markAsDirty()}onSelectSearch(e){const t=this.field();!t.searchLookup||!this.lookup||!t.lookupDomain||t.lookupName}onRestrict(e){const t=this.field().inputRestriction;if(!t)return;const o=e.target;let r=o.value;t==="numbers"&&(r=r.replace(/\D+/g,"")),t==="english"&&(r=r.replace(/[^a-zA-Z0-9\s.,\-_/]/g,"")),t==="arabic"&&(r=r.replace(/[^\u0600-\u06FF0-9\s.,\-_/]/g,"")),r!==o.value&&(o.value=r,this.control.setValue(r))}applyLookupFilter(e){const t=this.field().lookupFilter;return t?e.filter(o=>o[t.property]===t.value):e}static \u0275fac=i.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:f,deps:[],target:i.\u0275\u0275FactoryTarget.Component});static \u0275cmp=i.\u0275\u0275ngDeclareComponent({minVersion:"17.0.0",version:"22.0.7",type:f,isStandalone:!0,selector:"dga-dynamic-form-field",inputs:{field:{classPropertyName:"field",publicName:"field",isSignal:!0,isRequired:!0,transformFunction:null},form:{classPropertyName:"form",publicName:"form",isSignal:!0,isRequired:!0,transformFunction:null},locale:{classPropertyName:"locale",publicName:"locale",isSignal:!0,isRequired:!1,transformFunction:null},required:{classPropertyName:"required",publicName:"required",isSignal:!0,isRequired:!1,transformFunction:null},error:{classPropertyName:"error",publicName:"error",isSignal:!0,isRequired:!1,transformFunction:null},runtimeOptions:{classPropertyName:"runtimeOptions",publicName:"runtimeOptions",isSignal:!0,isRequired:!1,transformFunction:null}},outputs:{optionsLoaded:"optionsLoaded"},host:{properties:{"class.hidden":'field().type === "hidden"'},classAttribute:"block w-full"},ngImport:i,template:`
1
+ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectable as q,input as p,booleanAttribute as Se,output as S,DestroyRef as B,signal as C,computed as c,ChangeDetectionStrategy as _,Component as z,effect as G,untracked as Ce,makeEnvironmentProviders as Ie}from"@angular/core";import{NgComponentOutlet as $}from"@angular/common";import*as m from"@angular/forms";import{Validators as f,ReactiveFormsModule as k,FormsModule as j,FormBuilder as ke}from"@angular/forms";import{switchMap as U,catchError as De,startWith as Oe,map as Le}from"rxjs/operators";import{DgaField as H,DgaInput as W,DgaSelect as K,DgaCheckbox as Y,DgaRadio as J,DgaPhoneInput as X,DgaDatePicker as Z,DgaChipInput as Q,DgaSwitch as ee,DgaUpload as te,DgaNumberInput as oe,DgaOtp as re,DgaButton as ae,DgaStepper as ne,DgaToastService as Te}from"@a.nemreen/dga-ui";import{of as b,throwError as ie}from"rxjs";import{takeUntilDestroyed as we}from"@angular/core/rxjs-interop";import{HttpClient as D,HttpHeaders as Ae}from"@angular/common/http";const O=new h("DGA_LOOKUP_ADAPTER"),L=new h("DGA_SUBMIT_ADAPTER"),T=new h("DGA_FORM_TOAST_ADAPTER"),w=new h("DGA_CAPTCHA_ADAPTER"),A=new h("DGA_FORM_I18N_ADAPTER"),le=new h("DGA_DYNAMIC_FORM_FIELD_REGISTRY"),se=["checkbox","multiselect","chips","file"],Fe=["toggle"];function de(r){return r.value!==void 0?r.value:se.includes(r.type)?[]:Fe.includes(r.type)?!1:""}function F(r,e){const t=[],o=r.validation;(e||r.required||o?.required)&&(se.includes(r.type)?t.push(Ne):r.type==="toggle"?t.push(f.requiredTrue):t.push(f.required)),(r.type==="email"||o?.pattern==="email")&&t.push(f.email);const a=o?.minLength;a!=null&&t.push(f.minLength(a));const n=o?.maxLength??r.maxLength;if(n!=null&&t.push(f.maxLength(n)),o?.pattern&&o.pattern!=="email")try{t.push(f.pattern(o.pattern))}catch{}return o?.min!=null&&t.push(f.min(o.min)),o?.max!=null&&t.push(f.max(o.max)),r.maxChips!=null&&r.type==="chips"&&t.push(Me(r.maxChips)),t}function Ne(r){const e=r.value;return Array.isArray(e)&&e.length>0?null:{required:!0}}function Me(r){return e=>{const t=e.value;return Array.isArray(t)&&t.length>r?{maxChips:{max:r,actual:t.length}}:null}}function ce(r,e,t=!0){const o=!!e.disabled;return r.control({value:de(e),disabled:o},{validators:F(e,t&&!e.requiredWhen)})}function ue(r,e){const t={};for(const o of e){if(o.hidden&&o.type==="hidden"){t[o.name]=r.control(o.value??"");continue}t[o.name]=ce(r,o)}return r.group(t)}function pe(r,e){return e?.length?e.flatMap(t=>t.fields):r}function me(r,e,t){r.setValidators(F(e,t)),r.updateValueAndValidity({emitEvent:!1})}function N(r,e){return Array.isArray(r)?r.some(t=>t==e):r==e}function M(r,e,t){return r?[...t??[],...r.options??[],...r.groupedOptions?.flatMap(a=>a.options)??[]].find(a=>a.value==e):void 0}function V(r,e,t){if(r.hidden&&r.type!=="hidden"||r.type==="hidden")return!1;if(!r.dependsOn)return!0;const o=e[r.dependsOn];if(r.showOnValues?.length&&!r.showOnValues.some(a=>N(o,a)))return!1;if(r.showWhenOptionProperty){const a=t?.(r.dependsOn),n=M({name:r.dependsOn,type:"select",label:{en:"",ar:""}},o,a),i=r.showWhenOptionProperty.property;if(!n||n[i]!==r.showWhenOptionProperty.value)return!1}return!(!r.showOnValues?.length&&!r.showWhenOptionProperty&&(o==null||o===""||Array.isArray(o)&&o.length===0))}function R(r,e,t){const o=r.requiredWhen;if(!o)return!!r.required;const a=e[o.field];if(o.optionProperty!=null){const n=t?.(o.field),i=M({name:o.field,type:"select",label:{en:"",ar:""}},a,n);return!!i&&i[o.optionProperty]===o.optionPropertyValue}return o.values?.length?o.values.some(n=>N(a,n)):o.value!==void 0?N(a,o.value):a!=null&&a!==""}class y{submitAdapter=s(L,{optional:!0});captcha=s(w,{optional:!0});buildPayload(e,t,o){const a=new Set(o.filter(i=>i.excludeFromPayload).map(i=>i.name));let n={};for(const[i,d]of Object.entries(t)){if(a.has(i))continue;const u=e.fieldMapping?.[i]??i;n[u]=d}return e.payloadTransformer?e.payloadTransformer(n):n}hasFileValues(e){return Object.values(e).some(t=>t instanceof File||Array.isArray(t)&&t.some(o=>o instanceof File||o?.file instanceof File))}toFormData(e){const t=new FormData;for(const[o,a]of Object.entries(e))if(a!=null)if(a instanceof File)t.append(o,a);else if(Array.isArray(a))for(const n of a)n instanceof File?t.append(o,n):n?.file instanceof File?t.append(o,n.file,n.name):t.append(o,typeof n=="string"?n:JSON.stringify(n));else typeof a=="object"?t.append(o,JSON.stringify(a)):t.append(o,String(a));return t}submitForm(e,t,o){if(!e.endpoint||!this.submitAdapter)return b(t);const a=this.buildPayload(e,t,o),n=a instanceof FormData?a:this.hasFileValues(a)?this.toFormData(a):a,i={};return e.idempotencyKey&&(i["Idempotency-Key"]=e.idempotencyKey),(this.captcha?.getToken("submit")??b(null)).pipe(U(u=>(u&&(i["X-Recaptcha-Token"]=u),b({endpoint:e.endpoint,method:e.method??"POST",body:n,headers:i})))).pipe(U(u=>this.submitAdapter.submit(u)),De(u=>{throw u}))}saveDraft(e,t){typeof localStorage>"u"||localStorage.setItem(this.storageKey(e),JSON.stringify(t))}loadDraft(e){if(typeof localStorage>"u")return null;const t=localStorage.getItem(this.storageKey(e));if(!t)return null;try{return JSON.parse(t)}catch{return null}}clearDraft(e){typeof localStorage>"u"||localStorage.removeItem(this.storageKey(e))}storageKey(e){return`dga-dynamic-form:${e}`}static \u0275fac=l.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:y,deps:[],target:l.\u0275\u0275FactoryTarget.Injectable});static \u0275prov=l.\u0275\u0275ngDeclareInjectable({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:y,providedIn:"root"})}l.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:y,decorators:[{type:q,args:[{providedIn:"root"}]}]});function v(r,e,t=""){return r?e==="en"?r.en||r.ar:r.ar||r.en:t}function fe(){return typeof document>"u"?"ar":(document.documentElement.lang||"ar").toLowerCase().startsWith("en")?"en":"ar"}class x{extras=s(le,{optional:!0});map=new Map;constructor(){for(const e of this.extras??[])this.register(e)}register(e){this.map.set(e.type,e.component)}get(e){return this.map.get(e)??null}hasCustom(e){return this.map.has(e)}isBuiltIn(e){return Ve.has(e)}static \u0275fac=l.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:x,deps:[],target:l.\u0275\u0275FactoryTarget.Injectable});static \u0275prov=l.\u0275\u0275ngDeclareInjectable({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:x,providedIn:"root"})}l.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:x,decorators:[{type:q,args:[{providedIn:"root"}]}],ctorParameters:()=>[]});const Ve=new Set(["text","email","textarea","select","multiselect","checkbox","radio","phone","file","date","chips","toggle","hidden","number","otp"]);class g{field=p.required(...ngDevMode?[{debugName:"field"}]:[]);form=p.required(...ngDevMode?[{debugName:"form"}]:[]);locale=p("ar",...ngDevMode?[{debugName:"locale"}]:[]);required=p(!1,{...ngDevMode?{debugName:"required"}:{},transform:Se});error=p("",...ngDevMode?[{debugName:"error"}]:[]);runtimeOptions=p([],...ngDevMode?[{debugName:"runtimeOptions"}]:[]);optionsLoaded=S();lookup=s(O,{optional:!0});destroyRef=s(B);loadedOptions=C([],...ngDevMode?[{debugName:"loadedOptions"}]:[]);controlId=`dga-df-${Math.random().toString(36).slice(2,9)}`;get control(){return this.form().controls[this.field().name]}labelText=c(()=>v(this.field().label,this.locale()),...ngDevMode?[{debugName:"labelText"}]:[]);hintText=c(()=>v(this.field().hint,this.locale()),...ngDevMode?[{debugName:"hintText"}]:[]);placeholderText=c(()=>v(this.field().placeholder,this.locale()),...ngDevMode?[{debugName:"placeholderText"}]:[]);resolvedOptions=c(()=>{const e=this.runtimeOptions();if(e.length)return e;const t=this.loadedOptions();return t.length?t:this.field().options??[]},...ngDevMode?[{debugName:"resolvedOptions"}]:[]);selectOptions=c(()=>this.resolvedOptions().map(e=>({value:String(e.value),label:this.optionLabel(e),disabled:!!e.disabled})),...ngDevMode?[{debugName:"selectOptions"}]:[]);ngOnInit(){const e=this.field();if(e.lookupDomain&&e.lookupName&&this.lookup&&!e.searchLookup){const t=e.useParentId&&e.dependsOn?this.form().get(e.dependsOn)?.value:void 0;this.lookup.lookup({domain:e.lookupDomain,name:e.lookupName,parentId:t??void 0}).pipe(we(this.destroyRef)).subscribe(o=>{const a=this.applyLookupFilter(o);this.loadedOptions.set(a),this.optionsLoaded.emit({name:e.name,options:a})})}}optionLabel(e){return v(e.label,this.locale())}isChecked(e){const t=this.control?.value;return Array.isArray(t)&&t.some(o=>o==e)}toggleCheckbox(e,t){const o=Array.isArray(this.control.value)?[...this.control.value]:[],a=t?o.some(n=>n==e)?o:[...o,e]:o.filter(n=>n!=e);this.control.setValue(a),this.control.markAsDirty()}onSelectSearch(e){const t=this.field();!t.searchLookup||!this.lookup||!t.lookupDomain||t.lookupName}onRestrict(e){const t=this.field().inputRestriction;if(!t)return;const o=e.target;let a=o.value;t==="numbers"&&(a=a.replace(/\D+/g,"")),t==="english"&&(a=a.replace(/[^a-zA-Z0-9\s.,\-_/]/g,"")),t==="arabic"&&(a=a.replace(/[^\u0600-\u06FF0-9\s.,\-_/]/g,"")),a!==o.value&&(o.value=a,this.control.setValue(a))}applyLookupFilter(e){const t=this.field().lookupFilter;return t?e.filter(o=>o[t.property]===t.value):e}static \u0275fac=l.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:g,deps:[],target:l.\u0275\u0275FactoryTarget.Component});static \u0275cmp=l.\u0275\u0275ngDeclareComponent({minVersion:"17.0.0",version:"22.0.7",type:g,isStandalone:!0,selector:"dga-dynamic-form-field",inputs:{field:{classPropertyName:"field",publicName:"field",isSignal:!0,isRequired:!0,transformFunction:null},form:{classPropertyName:"form",publicName:"form",isSignal:!0,isRequired:!0,transformFunction:null},locale:{classPropertyName:"locale",publicName:"locale",isSignal:!0,isRequired:!1,transformFunction:null},required:{classPropertyName:"required",publicName:"required",isSignal:!0,isRequired:!1,transformFunction:null},error:{classPropertyName:"error",publicName:"error",isSignal:!0,isRequired:!1,transformFunction:null},runtimeOptions:{classPropertyName:"runtimeOptions",publicName:"runtimeOptions",isSignal:!0,isRequired:!1,transformFunction:null}},outputs:{optionsLoaded:"optionsLoaded"},host:{properties:{"class.hidden":'field().type === "hidden"'},classAttribute:"block w-full"},ngImport:l,template:`
2
2
  @if (field().type !== 'hidden') {
3
3
  <dga-field
4
4
  [label]="labelText()"
@@ -61,6 +61,8 @@ import*as i from"@angular/core";import{InjectionToken as g,inject as s,Injectabl
61
61
  [disabled]="!!field().disabled"
62
62
  [locale]="locale()"
63
63
  [mode]="field().monthPicker ? 'month' : 'day'"
64
+ [minDate]="field().minDate || null"
65
+ [maxDate]="field().maxDate || null"
64
66
  />
65
67
  }
66
68
  @case ('chips') {
@@ -84,6 +86,7 @@ import*as i from"@angular/core";import{InjectionToken as g,inject as s,Injectabl
84
86
  [formControl]="control"
85
87
  [accept]="field().acceptedFileTypes || ''"
86
88
  [multiple]="(field().maxFiles ?? 1) > 1"
89
+ [maxFiles]="field().maxFiles || 0"
87
90
  [maxSize]="field().maxFileSize || 0"
88
91
  [disabled]="!!field().disabled"
89
92
  />
@@ -170,7 +173,7 @@ import*as i from"@angular/core";import{InjectionToken as g,inject as s,Injectabl
170
173
  }
171
174
  </dga-field>
172
175
  }
173
- `,isInline:!0,dependencies:[{kind:"ngmodule",type:k},{kind:"directive",type:p.DefaultValueAccessor,selector:"input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"},{kind:"directive",type:p.RadioControlValueAccessor,selector:"input[type=radio]:not([ngNoCva])[formControlName],input[type=radio]:not([ngNoCva])[formControl],input[type=radio]:not([ngNoCva])[ngModel]",inputs:["name","formControlName","value"]},{kind:"directive",type:p.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:p.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:p.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"ngmodule",type:G},{kind:"component",type:j,selector:"dga-field",inputs:["label","labelHint","hint","error","status","statusMessage","controlId","required"]},{kind:"directive",type:W,selector:"input[dgaInput], textarea[dgaInput]",inputs:["size","dgaInvalid","status"]},{kind:"component",type:H,selector:"dga-select",inputs:["options","value","multiple","searchable","openMode","clearable","size","status","placeholder","label","removeLabel","clearLabel","emptyLabel","disabled","dgaInvalid"],outputs:["valueChange"]},{kind:"directive",type:K,selector:'input[type="checkbox"][dgaCheckbox]',inputs:["size","dgaInvalid"]},{kind:"directive",type:U,selector:'input[type="radio"][dgaRadio]',inputs:["size","dgaInvalid"]},{kind:"component",type:Y,selector:"dga-phone-input",inputs:["countries","countryCode","nationalNumber","controlId","name","placeholder","autocomplete","codeLabel","disabled","required","invalid","value"],outputs:["countryCodeChange","nationalNumberChange","valueChange"]},{kind:"component",type:J,selector:"dga-date-picker",inputs:["value","startValue","endValue","calendar","range","format","mode","locale","minDate","maxDate","yearBefore","yearAfter","placeholder","controlId","name","size","disabled","required","clearable","editable","dgaInvalid","status","rangeSeparator","todayLabel","clearLabel","closeLabel","saveLabel","ariaLabel"],outputs:["startValueChange","endValueChange","valueChange","rangeChange"]},{kind:"component",type:X,selector:"dga-chip-input",inputs:["value","placeholder","label","removeLabel","disabled","dgaInvalid","status"],outputs:["valueChange"]},{kind:"component",type:Z,selector:"dga-switch",inputs:["checked","size","label","disabled"],outputs:["checkedChange","changed"]},{kind:"component",type:Q,selector:"dga-upload",inputs:["files","accept","multiple","appearance","hint","browseLabel","removeLabel","disabled","maxSize","dragging"],outputs:["filesChange","rejected","draggingChange"]},{kind:"component",type:ee,selector:"dga-number-input",inputs:["value","min","max","step","size","label","controlId","name","incrementLabel","decrementLabel","disabled","dgaInvalid","status"],outputs:["valueChange"]},{kind:"component",type:te,selector:"dga-otp",inputs:["length","value","label","alphanumeric","disabled","dgaInvalid"],outputs:["valueChange","completed"]}],changeDetection:i.ChangeDetectionStrategy.OnPush})}i.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:f,decorators:[{type:z,args:[{selector:"dga-dynamic-form-field",standalone:!0,imports:[k,G,j,W,H,K,U,Y,J,X,Z,Q,ee,te],template:`
176
+ `,isInline:!0,dependencies:[{kind:"ngmodule",type:k},{kind:"directive",type:m.DefaultValueAccessor,selector:"input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"},{kind:"directive",type:m.RadioControlValueAccessor,selector:"input[type=radio]:not([ngNoCva])[formControlName],input[type=radio]:not([ngNoCva])[formControl],input[type=radio]:not([ngNoCva])[ngModel]",inputs:["name","formControlName","value"]},{kind:"directive",type:m.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:m.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:m.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"ngmodule",type:j},{kind:"component",type:H,selector:"dga-field",inputs:["label","labelHint","hint","error","status","statusMessage","controlId","required"]},{kind:"directive",type:W,selector:"input[dgaInput], textarea[dgaInput]",inputs:["size","dgaInvalid","status"]},{kind:"component",type:K,selector:"dga-select",inputs:["options","value","multiple","searchable","openMode","clearable","size","status","placeholder","label","removeLabel","clearLabel","emptyLabel","disabled","dgaInvalid"],outputs:["valueChange"]},{kind:"directive",type:Y,selector:'input[type="checkbox"][dgaCheckbox]',inputs:["size","dgaInvalid"]},{kind:"directive",type:J,selector:'input[type="radio"][dgaRadio]',inputs:["size","dgaInvalid"]},{kind:"component",type:X,selector:"dga-phone-input",inputs:["countries","countryCode","nationalNumber","controlId","name","placeholder","autocomplete","codeLabel","disabled","required","invalid","value"],outputs:["countryCodeChange","nationalNumberChange","valueChange"]},{kind:"component",type:Z,selector:"dga-date-picker",inputs:["value","startValue","endValue","calendar","range","format","mode","locale","minDate","maxDate","yearBefore","yearAfter","placeholder","controlId","name","size","disabled","required","clearable","editable","dgaInvalid","status","rangeSeparator","todayLabel","clearLabel","closeLabel","saveLabel","ariaLabel"],outputs:["startValueChange","endValueChange","valueChange","rangeChange"]},{kind:"component",type:Q,selector:"dga-chip-input",inputs:["value","placeholder","label","removeLabel","disabled","dgaInvalid","status"],outputs:["valueChange"]},{kind:"component",type:ee,selector:"dga-switch",inputs:["checked","size","label","disabled"],outputs:["checkedChange","changed"]},{kind:"component",type:te,selector:"dga-upload",inputs:["files","accept","multiple","appearance","hint","browseLabel","removeLabel","disabled","maxSize","maxFiles","dragging"],outputs:["filesChange","rejected","draggingChange"]},{kind:"component",type:oe,selector:"dga-number-input",inputs:["value","min","max","step","size","label","controlId","name","incrementLabel","decrementLabel","disabled","dgaInvalid","status"],outputs:["valueChange"]},{kind:"component",type:re,selector:"dga-otp",inputs:["length","value","label","alphanumeric","disabled","dgaInvalid"],outputs:["valueChange","completed"]}],changeDetection:l.ChangeDetectionStrategy.OnPush})}l.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:g,decorators:[{type:z,args:[{selector:"dga-dynamic-form-field",standalone:!0,imports:[k,j,H,W,K,Y,J,X,Z,Q,ee,te,oe,re],template:`
174
177
  @if (field().type !== 'hidden') {
175
178
  <dga-field
176
179
  [label]="labelText()"
@@ -233,6 +236,8 @@ import*as i from"@angular/core";import{InjectionToken as g,inject as s,Injectabl
233
236
  [disabled]="!!field().disabled"
234
237
  [locale]="locale()"
235
238
  [mode]="field().monthPicker ? 'month' : 'day'"
239
+ [minDate]="field().minDate || null"
240
+ [maxDate]="field().maxDate || null"
236
241
  />
237
242
  }
238
243
  @case ('chips') {
@@ -256,6 +261,7 @@ import*as i from"@angular/core";import{InjectionToken as g,inject as s,Injectabl
256
261
  [formControl]="control"
257
262
  [accept]="field().acceptedFileTypes || ''"
258
263
  [multiple]="(field().maxFiles ?? 1) > 1"
264
+ [maxFiles]="field().maxFiles || 0"
259
265
  [maxSize]="field().maxFileSize || 0"
260
266
  [disabled]="!!field().disabled"
261
267
  />
@@ -342,7 +348,7 @@ import*as i from"@angular/core";import{InjectionToken as g,inject as s,Injectabl
342
348
  }
343
349
  </dga-field>
344
350
  }
345
- `,changeDetection:q.OnPush,host:{class:"block w-full","[class.hidden]":'field().type === "hidden"'}}]}],propDecorators:{field:[{type:i.Input,args:[{isSignal:!0,alias:"field",required:!0}]}],form:[{type:i.Input,args:[{isSignal:!0,alias:"form",required:!0}]}],locale:[{type:i.Input,args:[{isSignal:!0,alias:"locale",required:!1}]}],required:[{type:i.Input,args:[{isSignal:!0,alias:"required",required:!1}]}],error:[{type:i.Input,args:[{isSignal:!0,alias:"error",required:!1}]}],runtimeOptions:[{type:i.Input,args:[{isSignal:!0,alias:"runtimeOptions",required:!1}]}],optionsLoaded:[{type:i.Output,args:["optionsLoaded"]}]}});const we={1:"col-span-1",2:"col-span-2",3:"col-span-3",4:"col-span-4",5:"col-span-5",6:"col-span-6",7:"col-span-7",8:"col-span-8",9:"col-span-9",10:"col-span-10",11:"col-span-11",12:"col-span-12"},Me={1:"md:col-span-1",2:"md:col-span-2",3:"md:col-span-3",4:"md:col-span-4",5:"md:col-span-5",6:"md:col-span-6",7:"md:col-span-7",8:"md:col-span-8",9:"md:col-span-9",10:"md:col-span-10",11:"md:col-span-11",12:"md:col-span-12"},Ve={1:"lg:col-span-1",2:"lg:col-span-2",3:"lg:col-span-3",4:"lg:col-span-4",5:"lg:col-span-5",6:"lg:col-span-6",7:"lg:col-span-7",8:"lg:col-span-8",9:"lg:col-span-9",10:"lg:col-span-10",11:"lg:col-span-11",12:"lg:col-span-12"};function R(a){return Number.isFinite(a)?Math.min(12,Math.max(1,Math.round(a))):12}class I{config=u.required(...ngDevMode?[{debugName:"config"}]:[]);localeInput=u(null,{...ngDevMode?{debugName:"localeInput"}:{},alias:"locale"});formSubmitted=S();formError=S();clearButtonClick=S();draftSaved=S();fb=s(Ce);formService=s(v);toast=s(L,{optional:!0});i18n=s(N,{optional:!0});destroyRef=s(E);registry=s(y);form;activeStep=C(0,...ngDevMode?[{debugName:"activeStep"}]:[]);submitting=C(!1,...ngDevMode?[{debugName:"submitting"}]:[]);optionsMap=C({},...ngDevMode?[{debugName:"optionsMap"}]:[]);formValues=C({},...ngDevMode?[{debugName:"formValues"}]:[]);locale=d(()=>this.localeInput()??pe(),...ngDevMode?[{debugName:"locale"}]:[]);wizardEnabled=d(()=>!!this.config().wizard?.enabled&&!!this.config().wizard?.steps?.length,...ngDevMode?[{debugName:"wizardEnabled"}]:[]);allFields=d(()=>ce(this.config().fields,this.config().wizard?.steps),...ngDevMode?[{debugName:"allFields"}]:[]);currentStepFields=d(()=>this.wizardEnabled()?this.config().wizard.steps[this.activeStep()]?.fields??[]:this.config().fields,...ngDevMode?[{debugName:"currentStepFields"}]:[]);visibleFields=d(()=>{const e=this.formValues(),t=this.optionsMap();return this.currentStepFields().filter(o=>M(o,e,r=>t[r]))},...ngDevMode?[{debugName:"visibleFields"}]:[]);stepperSteps=d(()=>(this.config().wizard?.steps??[]).map(t=>({label:this.labelOf(t.title),description:t.description?this.labelOf(t.description):void 0})),...ngDevMode?[{debugName:"stepperSteps"}]:[]);descriptionText=d(()=>this.labelOf(this.config().description),...ngDevMode?[{debugName:"descriptionText"}]:[]);constructor(){xe(()=>{const e=this.formValues(),t=this.optionsMap();if(this.form)for(const o of this.allFields()){const r=this.form.get(o.name);if(!r||!o.requiredWhen)continue;const n=V(o,e,l=>t[l]);ue(r,o,n)}})}ngOnInit(){this.form=de(this.fb,this.allFields());const e=this.config().formId;if(e&&this.config().enableLocalStorageSave){const t=this.formService.loadDraft(e);t&&this.form.patchValue(t,{emitEvent:!1})}this.form.valueChanges.pipe(ke(this.form.getRawValue()),_(this.destroyRef)).subscribe(t=>this.formValues.set(t))}labelOf(e){return e?this.i18n?this.i18n.resolveLabel(e,this.locale()):b(e,this.locale()):""}submitLabel(){const e=this.config();return this.wizardEnabled()?this.labelOf(e.wizard?.submitButtonText??e.submitButtonLabel)||(this.locale()==="en"?"Submit":"\u0625\u0631\u0633\u0627\u0644"):this.labelOf(e.submitButtonLabel)||(this.locale()==="en"?"Submit":"\u0625\u0631\u0633\u0627\u0644")}nextLabel(){return this.labelOf(this.config().wizard?.nextButtonText)||(this.locale()==="en"?"Next":"\u0627\u0644\u062A\u0627\u0644\u064A")}previousLabel(){return this.labelOf(this.config().wizard?.previousButtonText)||(this.locale()==="en"?"Previous":"\u0627\u0644\u0633\u0627\u0628\u0642")}clearLabel(){return this.labelOf(this.config().clearButtonLabel)}saveLabel(){return this.labelOf(this.config().saveButtonLabel??this.config().wizard?.saveButtonText)||(this.locale()==="en"?"Save":"\u062D\u0641\u0638")}showSave(){const e=this.config();return!!(e.enableLocalStorageSave||e.wizard?.showSaveButton)}isLastStep(){return this.wizardEnabled()?this.activeStep()>=this.config().wizard.steps.length-1:!0}isRequired(e){return V(e,this.formValues(),t=>this.optionsMap()[t])}fieldError(e){const t=this.form?.get(e.name);if(!t||!(t.touched||t.dirty)||!t.errors)return"";const o=t.errors,r=e.validation?.errorMessages,n=this.locale(),l=x=>r?.[x]?this.labelOf(r[x]):"";return o.required?l("required")||(n==="en"?"Required":"\u0645\u0637\u0644\u0648\u0628"):o.email?l("email")||(n==="en"?"Invalid email":"\u0628\u0631\u064A\u062F \u063A\u064A\u0631 \u0635\u0627\u0644\u062D"):o.minlength?l("minLength")||(n==="en"?"Too short":"\u0642\u0635\u064A\u0631 \u062C\u062F\u0627\u064B"):o.maxlength?l("maxLength")||(n==="en"?"Too long":"\u0637\u0648\u064A\u0644 \u062C\u062F\u0627\u064B"):o.pattern?l("pattern")||(n==="en"?"Invalid format":"\u0635\u064A\u063A\u0629 \u063A\u064A\u0631 \u0635\u0627\u0644\u062D\u0629"):o.maxChips?l("maxChips")||(n==="en"?"Too many items":"\u0639\u062F\u062F \u0627\u0644\u0639\u0646\u0627\u0635\u0631 \u0643\u0628\u064A\u0631"):n==="en"?"Invalid":"\u063A\u064A\u0631 \u0635\u0627\u0644\u062D"}columnClass(e){const t=e.columns,o=R(t?.sm??t?.md??12),r=R(t?.md??o),n=R(t?.lg??r);return["w-full",we[o],Me[r],Ve[n]].join(" ")}customComponent(e){return this.registry.hasCustom(e.type)?this.registry.get(e.type):null}customInputs(e){return{field:e,form:this.form,locale:this.locale(),required:this.isRequired(e),error:this.fieldError(e)}}onOptionsLoaded(e){this.optionsMap.update(t=>({...t,[e.name]:e.options}))}previousStep(){this.activeStep()>0&&this.activeStep.update(e=>e-1)}nextStep(){this.config().wizard?.validateOnStepChange!==!1&&(this.markStepTouched(),!this.stepValid())||this.isLastStep()||this.activeStep.update(t=>t+1)}onClear(){this.form.reset();for(const t of this.allFields()){const o=this.form.get(t.name);o&&o.setValue(t.value!==void 0?t.value:["checkbox","multiselect","chips","file"].includes(t.type)?[]:t.type==="toggle"?!1:"")}this.activeStep.set(0);const e=this.config().formId;e&&this.formService.clearDraft(e),this.clearButtonClick.emit()}onSaveDraft(){const e=this.form.getRawValue(),t=this.config().formId;t&&this.formService.saveDraft(t,e),this.draftSaved.emit(e),this.toast?.show({variant:"success",message:this.locale()==="en"?"Draft saved":"\u062A\u0645 \u062D\u0641\u0638 \u0627\u0644\u0645\u0633\u0648\u062F\u0629"})}onSubmit(){if(this.wizardEnabled()&&!this.isLastStep()){this.nextStep();return}if(this.markVisibleTouched(),!this.visibleValid()){this.toast?.show({variant:"error",message:this.locale()==="en"?"Please fix the highlighted fields":"\u064A\u0631\u062C\u0649 \u062A\u0635\u062D\u064A\u062D \u0627\u0644\u062D\u0642\u0648\u0644 \u0627\u0644\u0645\u062D\u062F\u062F\u0629"});return}const e=this.config(),t=this.form.getRawValue(),o=new Set(this.collectVisibleFieldNames()),r={};for(const n of this.allFields())!o.has(n.name)&&n.type!=="hidden"||(r[n.name]=t[n.name]);if(e.emitOnly||!e.endpoint){this.formSubmitted.emit(r),this.toast?.show({variant:"success",message:this.labelOf(e.successMessage)||(this.locale()==="en"?"Submitted":"\u062A\u0645 \u0627\u0644\u0625\u0631\u0633\u0627\u0644")});return}this.submitting.set(!0),this.formService.submitForm(e,r,this.allFields()).subscribe({next:n=>{this.submitting.set(!1),this.formSubmitted.emit(n),e.formId&&this.formService.clearDraft(e.formId),this.toast?.show({variant:"success",message:this.labelOf(e.successMessage)||(this.locale()==="en"?"Submitted successfully":"\u062A\u0645 \u0627\u0644\u0625\u0631\u0633\u0627\u0644 \u0628\u0646\u062C\u0627\u062D")})},error:n=>{this.submitting.set(!1),this.formError.emit(n),this.toast?.show({variant:"error",message:this.labelOf(e.errorMessage)||(this.locale()==="en"?"Submission failed":"\u0641\u0634\u0644 \u0627\u0644\u0625\u0631\u0633\u0627\u0644")})}})}patchFormValues(e){this.form.patchValue(e)}getForm(){return this.form}markStepTouched(){for(const e of this.visibleFields())this.form.get(e.name)?.markAsTouched()}markVisibleTouched(){for(const e of this.collectVisibleFieldNames())this.form.get(e)?.markAsTouched()}stepValid(){return this.visibleFields().every(e=>{const t=this.form.get(e.name);return!t||t.disabled||t.valid})}visibleValid(){return this.collectVisibleFieldNames().every(e=>{const t=this.form.get(e);return!t||t.disabled||t.valid})}collectVisibleFieldNames(){const e=this.formValues(),t=this.optionsMap();return(this.wizardEnabled()?this.allFields():this.config().fields).filter(r=>r.type==="hidden"||M(r,e,n=>t[n])).map(r=>r.name)}static \u0275fac=i.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:I,deps:[],target:i.\u0275\u0275FactoryTarget.Component});static \u0275cmp=i.\u0275\u0275ngDeclareComponent({minVersion:"17.0.0",version:"22.0.7",type:I,isStandalone:!0,selector:"dga-dynamic-form",inputs:{config:{classPropertyName:"config",publicName:"config",isSignal:!0,isRequired:!0,transformFunction:null},localeInput:{classPropertyName:"localeInput",publicName:"locale",isSignal:!0,isRequired:!1,transformFunction:null}},outputs:{formSubmitted:"formSubmitted",formError:"formError",clearButtonClick:"clearButtonClick",draftSaved:"draftSaved"},host:{classAttribute:"block w-full"},ngImport:i,template:`
351
+ `,changeDetection:_.OnPush,host:{class:"block w-full","[class.hidden]":'field().type === "hidden"'}}]}],propDecorators:{field:[{type:l.Input,args:[{isSignal:!0,alias:"field",required:!0}]}],form:[{type:l.Input,args:[{isSignal:!0,alias:"form",required:!0}]}],locale:[{type:l.Input,args:[{isSignal:!0,alias:"locale",required:!1}]}],required:[{type:l.Input,args:[{isSignal:!0,alias:"required",required:!1}]}],error:[{type:l.Input,args:[{isSignal:!0,alias:"error",required:!1}]}],runtimeOptions:[{type:l.Input,args:[{isSignal:!0,alias:"runtimeOptions",required:!1}]}],optionsLoaded:[{type:l.Output,args:["optionsLoaded"]}]}});const Re={1:"col-span-1",2:"col-span-2",3:"col-span-3",4:"col-span-4",5:"col-span-5",6:"col-span-6",7:"col-span-7",8:"col-span-8",9:"col-span-9",10:"col-span-10",11:"col-span-11",12:"col-span-12"},Ee={1:"md:col-span-1",2:"md:col-span-2",3:"md:col-span-3",4:"md:col-span-4",5:"md:col-span-5",6:"md:col-span-6",7:"md:col-span-7",8:"md:col-span-8",9:"md:col-span-9",10:"md:col-span-10",11:"md:col-span-11",12:"md:col-span-12"},Pe={1:"lg:col-span-1",2:"lg:col-span-2",3:"lg:col-span-3",4:"lg:col-span-4",5:"lg:col-span-5",6:"lg:col-span-6",7:"lg:col-span-7",8:"lg:col-span-8",9:"lg:col-span-9",10:"lg:col-span-10",11:"lg:col-span-11",12:"lg:col-span-12"};function E(r){return Number.isFinite(r)?Math.min(12,Math.max(1,Math.round(r))):12}class I{config=p.required(...ngDevMode?[{debugName:"config"}]:[]);localeInput=p(null,{...ngDevMode?{debugName:"localeInput"}:{},alias:"locale"});formSubmitted=S();formError=S();clearButtonClick=S();draftSaved=S();fb=s(ke);formService=s(y);toast=s(T,{optional:!0});i18n=s(A,{optional:!0});destroyRef=s(B);registry=s(x);form;formSignature="";valueChangesSub=null;activeStep=C(0,...ngDevMode?[{debugName:"activeStep"}]:[]);submitting=C(!1,...ngDevMode?[{debugName:"submitting"}]:[]);optionsMap=C({},...ngDevMode?[{debugName:"optionsMap"}]:[]);formValues=C({},...ngDevMode?[{debugName:"formValues"}]:[]);locale=c(()=>this.localeInput()??fe(),...ngDevMode?[{debugName:"locale"}]:[]);wizardEnabled=c(()=>!!this.config().wizard?.enabled&&!!this.config().wizard?.steps?.length,...ngDevMode?[{debugName:"wizardEnabled"}]:[]);allFields=c(()=>pe(this.config().fields,this.config().wizard?.steps),...ngDevMode?[{debugName:"allFields"}]:[]);currentStepFields=c(()=>this.wizardEnabled()?this.config().wizard.steps[this.activeStep()]?.fields??[]:this.config().fields,...ngDevMode?[{debugName:"currentStepFields"}]:[]);visibleFields=c(()=>{const e=this.formValues(),t=this.optionsMap();return this.currentStepFields().filter(o=>V(o,e,a=>t[a]))},...ngDevMode?[{debugName:"visibleFields"}]:[]);stepperSteps=c(()=>(this.config().wizard?.steps??[]).map(t=>({label:this.labelOf(t.title),description:t.description?this.labelOf(t.description):void 0})),...ngDevMode?[{debugName:"stepperSteps"}]:[]);descriptionText=c(()=>this.labelOf(this.config().description),...ngDevMode?[{debugName:"descriptionText"}]:[]);constructor(){this.form=this.fb.group({}),this.destroyRef.onDestroy(()=>this.valueChangesSub?.unsubscribe()),G(()=>{const e=this.allFields(),t=e.map(a=>`${a.name}:${a.type}`).join("|"),o=this.config();Ce(()=>{if(t===this.formSignature)return;const a=Object.keys(this.form.controls).length?this.form.getRawValue():null,n=this.formSignature==="";this.formSignature=t;const i=ue(this.fb,e);if(a)i.patchValue(a,{emitEvent:!1});else if(n&&o.formId&&o.enableLocalStorageSave){const d=this.formService.loadDraft(o.formId);d&&i.patchValue(d,{emitEvent:!1})}this.form=i,n||this.activeStep.set(0),this.formValues.set(i.getRawValue()),this.valueChangesSub?.unsubscribe(),this.valueChangesSub=i.valueChanges.pipe(Oe(i.getRawValue())).subscribe(d=>this.formValues.set(d))})}),G(()=>{const e=this.formValues(),t=this.optionsMap();if(this.form)for(const o of this.allFields()){const a=this.form.get(o.name);if(!a||!o.requiredWhen)continue;const n=R(o,e,i=>t[i]);me(a,o,n)}})}labelOf(e){return e?this.i18n?this.i18n.resolveLabel(e,this.locale()):v(e,this.locale()):""}submitLabel(){const e=this.config();return this.wizardEnabled()?this.labelOf(e.wizard?.submitButtonText??e.submitButtonLabel)||(this.locale()==="en"?"Submit":"\u0625\u0631\u0633\u0627\u0644"):this.labelOf(e.submitButtonLabel)||(this.locale()==="en"?"Submit":"\u0625\u0631\u0633\u0627\u0644")}nextLabel(){return this.labelOf(this.config().wizard?.nextButtonText)||(this.locale()==="en"?"Next":"\u0627\u0644\u062A\u0627\u0644\u064A")}previousLabel(){return this.labelOf(this.config().wizard?.previousButtonText)||(this.locale()==="en"?"Previous":"\u0627\u0644\u0633\u0627\u0628\u0642")}clearLabel(){return this.labelOf(this.config().clearButtonLabel)}saveLabel(){return this.labelOf(this.config().saveButtonLabel??this.config().wizard?.saveButtonText)||(this.locale()==="en"?"Save":"\u062D\u0641\u0638")}showSave(){const e=this.config();return!!(e.enableLocalStorageSave||e.wizard?.showSaveButton)}isLastStep(){return this.wizardEnabled()?this.activeStep()>=this.config().wizard.steps.length-1:!0}isRequired(e){return R(e,this.formValues(),t=>this.optionsMap()[t])}fieldError(e){const t=this.form?.get(e.name);if(!t||!(t.touched||t.dirty)||!t.errors)return"";const o=t.errors,a=e.validation?.errorMessages,n=this.locale(),i=d=>a?.[d]?this.labelOf(a[d]):"";return o.required?i("required")||(n==="en"?"Required":"\u0645\u0637\u0644\u0648\u0628"):o.email?i("email")||(n==="en"?"Invalid email":"\u0628\u0631\u064A\u062F \u063A\u064A\u0631 \u0635\u0627\u0644\u062D"):o.minlength?i("minLength")||(n==="en"?"Too short":"\u0642\u0635\u064A\u0631 \u062C\u062F\u0627\u064B"):o.maxlength?i("maxLength")||(n==="en"?"Too long":"\u0637\u0648\u064A\u0644 \u062C\u062F\u0627\u064B"):o.pattern?i("pattern")||(n==="en"?"Invalid format":"\u0635\u064A\u063A\u0629 \u063A\u064A\u0631 \u0635\u0627\u0644\u062D\u0629"):o.maxChips?i("maxChips")||(n==="en"?"Too many items":"\u0639\u062F\u062F \u0627\u0644\u0639\u0646\u0627\u0635\u0631 \u0643\u0628\u064A\u0631"):n==="en"?"Invalid":"\u063A\u064A\u0631 \u0635\u0627\u0644\u062D"}columnClass(e){const t=e.columns,o=E(t?.sm??t?.md??12),a=E(t?.md??o),n=E(t?.lg??a);return["w-full",Re[o],Ee[a],Pe[n]].join(" ")}customComponent(e){return this.registry.hasCustom(e.type)?this.registry.get(e.type):null}customInputs(e){return{field:e,form:this.form,locale:this.locale(),required:this.isRequired(e),error:this.fieldError(e)}}onOptionsLoaded(e){this.optionsMap.update(t=>({...t,[e.name]:e.options}))}previousStep(){this.activeStep()>0&&this.activeStep.update(e=>e-1)}nextStep(){this.config().wizard?.validateOnStepChange!==!1&&(this.markStepTouched(),!this.stepValid())||this.isLastStep()||this.activeStep.update(t=>t+1)}onClear(){this.form.reset();for(const t of this.allFields()){const o=this.form.get(t.name);o&&o.setValue(t.value!==void 0?t.value:["checkbox","multiselect","chips","file"].includes(t.type)?[]:t.type==="toggle"?!1:"")}this.activeStep.set(0);const e=this.config().formId;e&&this.formService.clearDraft(e),this.clearButtonClick.emit()}onSaveDraft(){const e=this.form.getRawValue(),t=this.config().formId;t&&this.formService.saveDraft(t,e),this.draftSaved.emit(e),this.toast?.show({variant:"success",message:this.locale()==="en"?"Draft saved":"\u062A\u0645 \u062D\u0641\u0638 \u0627\u0644\u0645\u0633\u0648\u062F\u0629"})}onSubmit(){if(this.wizardEnabled()&&!this.isLastStep()){this.nextStep();return}if(this.markVisibleTouched(),!this.visibleValid()){this.toast?.show({variant:"error",message:this.locale()==="en"?"Please fix the highlighted fields":"\u064A\u0631\u062C\u0649 \u062A\u0635\u062D\u064A\u062D \u0627\u0644\u062D\u0642\u0648\u0644 \u0627\u0644\u0645\u062D\u062F\u062F\u0629"});return}const e=this.config(),t=this.form.getRawValue(),o=new Set(this.collectVisibleFieldNames()),a={};for(const n of this.allFields())!o.has(n.name)&&n.type!=="hidden"||(a[n.name]=t[n.name]);if(e.emitOnly||!e.endpoint){this.formSubmitted.emit(a),this.toast?.show({variant:"success",message:this.labelOf(e.successMessage)||(this.locale()==="en"?"Submitted":"\u062A\u0645 \u0627\u0644\u0625\u0631\u0633\u0627\u0644")});return}this.submitting.set(!0),this.formService.submitForm(e,a,this.allFields()).subscribe({next:n=>{this.submitting.set(!1),this.formSubmitted.emit(n),e.formId&&this.formService.clearDraft(e.formId),this.toast?.show({variant:"success",message:this.labelOf(e.successMessage)||(this.locale()==="en"?"Submitted successfully":"\u062A\u0645 \u0627\u0644\u0625\u0631\u0633\u0627\u0644 \u0628\u0646\u062C\u0627\u062D")})},error:n=>{this.submitting.set(!1),this.formError.emit(n),this.toast?.show({variant:"error",message:this.labelOf(e.errorMessage)||(this.locale()==="en"?"Submission failed":"\u0641\u0634\u0644 \u0627\u0644\u0625\u0631\u0633\u0627\u0644")})}})}patchFormValues(e){this.form.patchValue(e)}getForm(){return this.form}markStepTouched(){for(const e of this.visibleFields())this.form.get(e.name)?.markAsTouched()}markVisibleTouched(){for(const e of this.collectVisibleFieldNames())this.form.get(e)?.markAsTouched()}stepValid(){return this.visibleFields().every(e=>{const t=this.form.get(e.name);return!t||t.disabled||t.valid})}visibleValid(){return this.collectVisibleFieldNames().every(e=>{const t=this.form.get(e);return!t||t.disabled||t.valid})}collectVisibleFieldNames(){const e=this.formValues(),t=this.optionsMap();return(this.wizardEnabled()?this.allFields():this.config().fields).filter(a=>a.type==="hidden"||V(a,e,n=>t[n])).map(a=>a.name)}static \u0275fac=l.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:I,deps:[],target:l.\u0275\u0275FactoryTarget.Component});static \u0275cmp=l.\u0275\u0275ngDeclareComponent({minVersion:"17.0.0",version:"22.0.7",type:I,isStandalone:!0,selector:"dga-dynamic-form",inputs:{config:{classPropertyName:"config",publicName:"config",isSignal:!0,isRequired:!0,transformFunction:null},localeInput:{classPropertyName:"localeInput",publicName:"locale",isSignal:!0,isRequired:!1,transformFunction:null}},outputs:{formSubmitted:"formSubmitted",formError:"formError",clearButtonClick:"clearButtonClick",draftSaved:"draftSaved"},host:{classAttribute:"block w-full"},ngImport:l,template:`
346
352
  <form class="flex w-full flex-col gap-3xl" [formGroup]="form" (ngSubmit)="onSubmit()">
347
353
  @if (descriptionText()) {
348
354
  <p class="text-body-md text-paragraph">{{ descriptionText() }}</p>
@@ -421,7 +427,7 @@ import*as i from"@angular/core";import{InjectionToken as g,inject as s,Injectabl
421
427
  }
422
428
  </div>
423
429
  </form>
424
- `,isInline:!0,dependencies:[{kind:"ngmodule",type:k},{kind:"directive",type:p.\u0275NgNoValidate,selector:"form:not([ngNoForm]):not([ngNativeValidate])"},{kind:"directive",type:p.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:p.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"component",type:f,selector:"dga-dynamic-form-field",inputs:["field","form","locale","required","error","runtimeOptions"],outputs:["optionsLoaded"]},{kind:"directive",type:oe,selector:"button[dgaButton], a[dgaButton]",inputs:["variant","size","iconOnly","cooldownMs","loading"]},{kind:"component",type:ae,selector:"dga-stepper",inputs:["steps","active","label"],outputs:["activeChange"]},{kind:"directive",type:B,selector:"[ngComponentOutlet]",inputs:["ngComponentOutlet","ngComponentOutletInputs","ngComponentOutletInjector","ngComponentOutletEnvironmentInjector","ngComponentOutletContent","ngComponentOutletNgModule"],exportAs:["ngComponentOutlet"]}],changeDetection:i.ChangeDetectionStrategy.OnPush})}i.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:I,decorators:[{type:z,args:[{selector:"dga-dynamic-form",standalone:!0,imports:[k,f,oe,ae,B],template:`
430
+ `,isInline:!0,dependencies:[{kind:"ngmodule",type:k},{kind:"directive",type:m.\u0275NgNoValidate,selector:"form:not([ngNoForm]):not([ngNativeValidate])"},{kind:"directive",type:m.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:m.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"component",type:g,selector:"dga-dynamic-form-field",inputs:["field","form","locale","required","error","runtimeOptions"],outputs:["optionsLoaded"]},{kind:"directive",type:ae,selector:"button[dgaButton], a[dgaButton]",inputs:["variant","size","iconOnly","cooldownMs","loading"]},{kind:"component",type:ne,selector:"dga-stepper",inputs:["steps","active","label"],outputs:["activeChange"]},{kind:"directive",type:$,selector:"[ngComponentOutlet]",inputs:["ngComponentOutlet","ngComponentOutletInputs","ngComponentOutletInjector","ngComponentOutletEnvironmentInjector","ngComponentOutletContent","ngComponentOutletNgModule"],exportAs:["ngComponentOutlet"]}],changeDetection:l.ChangeDetectionStrategy.OnPush})}l.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:I,decorators:[{type:z,args:[{selector:"dga-dynamic-form",standalone:!0,imports:[k,g,ae,ne,$],template:`
425
431
  <form class="flex w-full flex-col gap-3xl" [formGroup]="form" (ngSubmit)="onSubmit()">
426
432
  @if (descriptionText()) {
427
433
  <p class="text-body-md text-paragraph">{{ descriptionText() }}</p>
@@ -500,4 +506,4 @@ import*as i from"@angular/core";import{InjectionToken as g,inject as s,Injectabl
500
506
  }
501
507
  </div>
502
508
  </form>
503
- `,changeDetection:q.OnPush,host:{class:"block w-full"}}]}],ctorParameters:()=>[],propDecorators:{config:[{type:i.Input,args:[{isSignal:!0,alias:"config",required:!0}]}],localeInput:[{type:i.Input,args:[{isSignal:!0,alias:"locale",required:!1}]}],formSubmitted:[{type:i.Output,args:["formSubmitted"]}],formError:[{type:i.Output,args:["formError"]}],clearButtonClick:[{type:i.Output,args:["clearButtonClick"]}],draftSaved:[{type:i.Output,args:["draftSaved"]}]}});const me={resolveLabel(a,e){return e==="en"?a.en:a.ar}},fe={getToken:()=>h(null)},ge={lookup:()=>h([])};function he(a=s(re)){return{lookup(e){const o=`${e.domain.replace(/\/$/,"")}/lookup/${e.name}`,r={};if(e.parentId!=null&&e.parentId!==""&&(r.parentId=String(e.parentId)),e.search){const n=e.searchParamName||"q";r[n]=e.search}return a.get(o,{params:r}).pipe(De(n=>Array.isArray(n)?n.map(Re):[]))}}}function Re(a){const e=a??{},t=e.value??e.id??e.Id??"",o=String(e.labelEn??e.titleEn??e.en??e.TitleEn??t),r=String(e.labelAr??e.titleAr??e.ar??e.TitleAr??o);return{value:t,label:{en:o,ar:r},...e}}function be(a=s(re)){return{submit(e){const t=new Le(e.headers??{}),o=e.method.toUpperCase();return o==="GET"?a.get(e.endpoint,{headers:t}):o==="DELETE"?a.delete(e.endpoint,{headers:t,body:e.body}):o==="PUT"?a.put(e.endpoint,e.body,{headers:t}):o==="PATCH"?a.patch(e.endpoint,e.body,{headers:t}):a.post(e.endpoint,e.body,{headers:t})}}}function ve(a=s(Oe)){return{show(e){a.show({title:e.title,message:e.message,variant:e.variant??"info"})}}}function Pe(a={}){const e=a.httpLookup??!1,t=a.httpSubmit??!0,o=a.dgaToast??!0;return Se([{provide:N,useValue:me},{provide:T,useValue:fe},{provide:D,useFactory:()=>e?he():ge},{provide:O,useFactory:()=>t?be():{submit:()=>h(null)}},{provide:L,useFactory:()=>o?ve():{show:()=>{}}}])}function Ee(a,e,t=""){return a?e==="en"?a.en:a.ar:t}export{T as DGA_CAPTCHA_ADAPTER,ne as DGA_DYNAMIC_FORM_FIELD_REGISTRY,N as DGA_FORM_I18N_ADAPTER,L as DGA_FORM_TOAST_ADAPTER,D as DGA_LOOKUP_ADAPTER,O as DGA_SUBMIT_ADAPTER,I as DgaDynamicForm,f as DgaDynamicFormField,y as DgaDynamicFormFieldRegistry,v as DgaDynamicFormService,ve as createDgaToastAdapter,he as createHttpLookupAdapter,be as createHttpSubmitAdapter,me as defaultFormI18nAdapter,de as dgaBuildFormGroup,A as dgaBuildValidators,ce as dgaCollectFields,se as dgaCreateControl,le as dgaDefaultValueForField,pe as dgaDetectLocale,w as dgaFindSelectedOption,M as dgaIsFieldVisible,V as dgaIsRequiredWhen,b as dgaResolveLabel,ue as dgaSetControlValidators,ge as emptyLookupAdapter,fe as noopCaptchaAdapter,Pe as provideDgaDynamicForm,Ee as resolveFormLabel};
509
+ `,changeDetection:_.OnPush,host:{class:"block w-full"}}]}],ctorParameters:()=>[],propDecorators:{config:[{type:l.Input,args:[{isSignal:!0,alias:"config",required:!0}]}],localeInput:[{type:l.Input,args:[{isSignal:!0,alias:"locale",required:!1}]}],formSubmitted:[{type:l.Output,args:["formSubmitted"]}],formError:[{type:l.Output,args:["formError"]}],clearButtonClick:[{type:l.Output,args:["clearButtonClick"]}],draftSaved:[{type:l.Output,args:["draftSaved"]}]}});const ge={resolveLabel(r,e){return e==="en"?r.en:r.ar}},he={getToken:()=>b(null)},be={lookup:()=>b([])},qe=new Set(["__proto__","constructor","prototype"]);function P(r,e){const t=r.trim();if(!t)throw new Error("DGA dynamic form: empty HTTP URL");if(t.startsWith("/")&&!t.startsWith("//"))return t.replace(/\/$/,"")||"/";let o;try{o=new URL(t)}catch{throw new Error(`DGA dynamic form: invalid HTTP URL "${r}"`)}if(o.protocol!=="http:"&&o.protocol!=="https:")throw new Error(`DGA dynamic form: blocked URL protocol "${o.protocol}"`);if(e?.length&&!e.some(n=>{try{return new URL(n).origin===o.origin}catch{return n===o.origin}}))throw new Error(`DGA dynamic form: origin not allowlisted "${o.origin}"`);return o.toString().replace(/\/$/,"")}function ve(r=s(D),e={}){return{lookup(t){try{const a=`${P(t.domain,e.allowedOrigins)}/lookup/${encodeURIComponent(t.name)}`,n={};if(t.parentId!=null&&t.parentId!==""&&(n.parentId=String(t.parentId)),t.search){const i=t.searchParamName||"q";n[i]=t.search}return r.get(a,{params:n}).pipe(Le(i=>Array.isArray(i)?i.map(Be):[]))}catch(o){return ie(()=>o)}}}}function Be(r){const e=r??{},t=e.value??e.id??e.Id??"",o=String(e.labelEn??e.titleEn??e.en??e.TitleEn??t),a=String(e.labelAr??e.titleAr??e.ar??e.TitleAr??o),n={value:t,label:{en:o,ar:a}};for(const[i,d]of Object.entries(e))qe.has(i)||i==="value"||i==="label"||i==="id"||i==="Id"||i==="labelEn"||i==="titleEn"||i==="en"||i==="TitleEn"||i==="labelAr"||i==="titleAr"||i==="ar"||i==="TitleAr"||(n[i]=d);return e.disabled!=null&&(n.disabled=!!e.disabled),e.requiresTextInput!=null&&(n.requiresTextInput=!!e.requiresTextInput),e.description&&typeof e.description=="object"&&(n.description=e.description),n}function ye(r=s(D),e={}){return{submit(t){try{const o=P(t.endpoint,e.allowedOrigins),a=new Ae(t.headers??{}),n=t.method.toUpperCase();return n==="GET"?r.get(o,{headers:a}):n==="DELETE"?r.delete(o,{headers:a,body:t.body}):n==="PUT"?r.put(o,t.body,{headers:a}):n==="PATCH"?r.patch(o,t.body,{headers:a}):r.post(o,t.body,{headers:a})}catch(o){return ie(()=>o)}}}}function xe(r=s(Te)){return{show(e){r.show({title:e.title,message:e.message,variant:e.variant??"info"})}}}function _e(r={}){const e=r.httpLookup??!1,t=r.httpSubmit??!0,o=r.dgaToast??!0,a={allowedOrigins:r.allowedOrigins};return Ie([{provide:A,useValue:ge},{provide:w,useValue:he},{provide:O,useFactory:()=>e?ve(s(D),a):be},{provide:L,useFactory:()=>t?ye(s(D),a):{submit:()=>b(null)}},{provide:T,useFactory:()=>o?xe():{show:()=>{}}}])}function ze(r,e,t=""){return r?e==="en"?r.en:r.ar:t}export{w as DGA_CAPTCHA_ADAPTER,le as DGA_DYNAMIC_FORM_FIELD_REGISTRY,A as DGA_FORM_I18N_ADAPTER,T as DGA_FORM_TOAST_ADAPTER,O as DGA_LOOKUP_ADAPTER,L as DGA_SUBMIT_ADAPTER,I as DgaDynamicForm,g as DgaDynamicFormField,x as DgaDynamicFormFieldRegistry,y as DgaDynamicFormService,xe as createDgaToastAdapter,ve as createHttpLookupAdapter,ye as createHttpSubmitAdapter,ge as defaultFormI18nAdapter,P as dgaAssertSafeHttpUrl,ue as dgaBuildFormGroup,F as dgaBuildValidators,pe as dgaCollectFields,ce as dgaCreateControl,de as dgaDefaultValueForField,fe as dgaDetectLocale,M as dgaFindSelectedOption,V as dgaIsFieldVisible,R as dgaIsRequiredWhen,v as dgaResolveLabel,me as dgaSetControlValidators,be as emptyLookupAdapter,he as noopCaptchaAdapter,_e as provideDgaDynamicForm,ze as resolveFormLabel};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a.nemreen/dga-dynamic-form",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Schema-driven dynamic forms for Angular, rendered with @a.nemreen/dga-ui controls",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -33,6 +33,9 @@
33
33
  "publishConfig": {
34
34
  "access": "public"
35
35
  },
36
+ "engines": {
37
+ "node": ">=18"
38
+ },
36
39
  "peerDependencies": {
37
40
  "@angular/common": "^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
38
41
  "@angular/core": "^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
@@ -212,7 +212,7 @@ declare class DgaDynamicFormFieldRegistry {
212
212
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<DgaDynamicFormFieldRegistry>;
213
213
  }
214
214
 
215
- declare class DgaDynamicForm implements OnInit {
215
+ declare class DgaDynamicForm {
216
216
  readonly config: _angular_core.InputSignal<DgaDynamicFormConfig>;
217
217
  readonly localeInput: _angular_core.InputSignal<DgaFormLocale | null>;
218
218
  readonly formSubmitted: _angular_core.OutputEmitterRef<unknown>;
@@ -226,6 +226,8 @@ declare class DgaDynamicForm implements OnInit {
226
226
  private readonly destroyRef;
227
227
  readonly registry: DgaDynamicFormFieldRegistry;
228
228
  form: FormGroup;
229
+ private formSignature;
230
+ private valueChangesSub;
229
231
  readonly activeStep: _angular_core.WritableSignal<number>;
230
232
  readonly submitting: _angular_core.WritableSignal<boolean>;
231
233
  readonly optionsMap: _angular_core.WritableSignal<Record<string, DgaFormFieldOption[]>>;
@@ -238,7 +240,6 @@ declare class DgaDynamicForm implements OnInit {
238
240
  readonly stepperSteps: _angular_core.Signal<DgaStep[]>;
239
241
  readonly descriptionText: _angular_core.Signal<string>;
240
242
  constructor();
241
- ngOnInit(): void;
242
243
  labelOf(label: {
243
244
  en: string;
244
245
  ar: string;
@@ -348,12 +349,25 @@ declare const defaultFormI18nAdapter: DgaFormI18nAdapter;
348
349
  declare const noopCaptchaAdapter: DgaCaptchaAdapter;
349
350
  /** Empty lookup (returns []). Override in the host app. */
350
351
  declare const emptyLookupAdapter: DgaLookupAdapter;
352
+ interface DgaHttpAdapterSecurityOptions {
353
+ /**
354
+ * Allowed URL origins for schema-driven HTTP (e.g. `https://api.example.com`).
355
+ * When set, requests outside the list are rejected.
356
+ * When omitted, only `http:` / `https:` are accepted (no `file:` / opaque URLs).
357
+ */
358
+ allowedOrigins?: string[];
359
+ }
360
+ /**
361
+ * Validates schema-supplied URLs before HttpClient calls.
362
+ * Prefer passing `allowedOrigins` in production for untrusted / CMS schemas.
363
+ */
364
+ declare function dgaAssertSafeHttpUrl(raw: string, allowedOrigins?: string[]): string;
351
365
  /**
352
366
  * HTTP lookup that expects `{domain}/lookup/{name}` JSON arrays.
353
367
  * Maps common `{ id|value, titleEn|labelEn|en, titleAr|labelAr|ar }` shapes.
354
368
  */
355
- declare function createHttpLookupAdapter(http?: HttpClient): DgaLookupAdapter;
356
- declare function createHttpSubmitAdapter(http?: HttpClient): DgaSubmitAdapter;
369
+ declare function createHttpLookupAdapter(http?: HttpClient, security?: DgaHttpAdapterSecurityOptions): DgaLookupAdapter;
370
+ declare function createHttpSubmitAdapter(http?: HttpClient, security?: DgaHttpAdapterSecurityOptions): DgaSubmitAdapter;
357
371
  declare function createDgaToastAdapter(toasts?: DgaToastService): DgaFormToastAdapter;
358
372
  interface ProvideDgaDynamicFormOptions {
359
373
  /** Use HttpClient-backed lookup (default: empty). */
@@ -362,6 +376,8 @@ interface ProvideDgaDynamicFormOptions {
362
376
  httpSubmit?: boolean;
363
377
  /** Wire DgaToastService (default: true). */
364
378
  dgaToast?: boolean;
379
+ /** Optional origin allowlist for HTTP adapters (recommended for CMS schemas). */
380
+ allowedOrigins?: string[];
365
381
  }
366
382
  /**
367
383
  * Registers default adapters for lookup, submit, toast, captcha, and i18n.
@@ -377,5 +393,5 @@ declare function resolveFormLabel(label: {
377
393
  declare function dgaResolveLabel(label: DgaFormLabel | undefined, locale: DgaFormLocale, fallback?: string): string;
378
394
  declare function dgaDetectLocale(): DgaFormLocale;
379
395
 
380
- export { DGA_CAPTCHA_ADAPTER, DGA_DYNAMIC_FORM_FIELD_REGISTRY, DGA_FORM_I18N_ADAPTER, DGA_FORM_TOAST_ADAPTER, DGA_LOOKUP_ADAPTER, DGA_SUBMIT_ADAPTER, DgaDynamicForm, DgaDynamicFormField, DgaDynamicFormFieldRegistry, DgaDynamicFormService, createDgaToastAdapter, createHttpLookupAdapter, createHttpSubmitAdapter, defaultFormI18nAdapter, dgaBuildFormGroup, dgaBuildValidators, dgaCollectFields, dgaCreateControl, dgaDefaultValueForField, dgaDetectLocale, dgaFindSelectedOption, dgaIsFieldVisible, dgaIsRequiredWhen, dgaResolveLabel, dgaSetControlValidators, emptyLookupAdapter, noopCaptchaAdapter, provideDgaDynamicForm, resolveFormLabel };
381
- export type { DgaCaptchaAdapter, DgaDynamicFieldHostContext, DgaDynamicFieldRenderer, DgaDynamicFormConfig, DgaDynamicFormPayloadTransformer, DgaFormField, DgaFormFieldColumns, DgaFormFieldOption, DgaFormFieldType, DgaFormI18nAdapter, DgaFormLabel, DgaFormLocale, DgaFormStep, DgaFormToastAdapter, DgaHttpMethod, DgaInputRestriction, DgaLookupAdapter, DgaLookupRequest, DgaSubmitAdapter, DgaSubmitRequest, DgaToastVariant, DgaWizardConfig, ProvideDgaDynamicFormOptions };
396
+ export { DGA_CAPTCHA_ADAPTER, DGA_DYNAMIC_FORM_FIELD_REGISTRY, DGA_FORM_I18N_ADAPTER, DGA_FORM_TOAST_ADAPTER, DGA_LOOKUP_ADAPTER, DGA_SUBMIT_ADAPTER, DgaDynamicForm, DgaDynamicFormField, DgaDynamicFormFieldRegistry, DgaDynamicFormService, createDgaToastAdapter, createHttpLookupAdapter, createHttpSubmitAdapter, defaultFormI18nAdapter, dgaAssertSafeHttpUrl, dgaBuildFormGroup, dgaBuildValidators, dgaCollectFields, dgaCreateControl, dgaDefaultValueForField, dgaDetectLocale, dgaFindSelectedOption, dgaIsFieldVisible, dgaIsRequiredWhen, dgaResolveLabel, dgaSetControlValidators, emptyLookupAdapter, noopCaptchaAdapter, provideDgaDynamicForm, resolveFormLabel };
397
+ export type { DgaCaptchaAdapter, DgaDynamicFieldHostContext, DgaDynamicFieldRenderer, DgaDynamicFormConfig, DgaDynamicFormPayloadTransformer, DgaFormField, DgaFormFieldColumns, DgaFormFieldOption, DgaFormFieldType, DgaFormI18nAdapter, DgaFormLabel, DgaFormLocale, DgaFormStep, DgaFormToastAdapter, DgaHttpAdapterSecurityOptions, DgaHttpMethod, DgaInputRestriction, DgaLookupAdapter, DgaLookupRequest, DgaSubmitAdapter, DgaSubmitRequest, DgaToastVariant, DgaWizardConfig, ProvideDgaDynamicFormOptions };