@a.nemreen/dga-dynamic-form 0.1.7 → 0.1.8

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/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.1** — API may move until freeze.
5
+ **Version 0.1.8** — password match, staged uploads, lookups, wizard polish, and cross-field validators.
6
6
 
7
7
  ## Install
8
8
 
@@ -25,16 +25,86 @@ export const appConfig = {
25
25
  httpSubmit: true,
26
26
  httpLookup: true,
27
27
  dgaToast: true,
28
- // Recommended when schemas/endpoints come from CMS or untrusted config:
29
- // allowedOrigins: ['https://api.example.com'],
28
+ allowedOrigins: ['https://api.example.com'],
30
29
  }),
31
30
  ],
32
31
  };
33
32
  ```
34
33
 
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.
34
+ **Security:** Treat CMS schemas as untrusted unless `allowedOrigins` is set. HTTP adapters reject non-`http(s)` URLs; same-origin paths (`/api/...`) are always allowed. Validate uploads server-side.
35
+
36
+ ### reCAPTCHA (production HTTP submits)
37
+
38
+ Use **reCAPTCHA v3** (invisible). There is **no** `type: 'captcha'` field in the form schema.
39
+
40
+ | Audience | What happens |
41
+ |----------|----------------|
42
+ | **End user** | No checkbox. Submit works like a normal form; Google scores in the background. |
43
+ | **Developer** | Wire `ng-recaptcha` + override `DGA_CAPTCHA_ADAPTER` in `app.config`. |
44
+ | **Backend** | Read `X-Recaptcha-Token` and verify with Google's secret key. |
45
+
46
+ ```bash
47
+ npm install ng-recaptcha
48
+ ```
49
+
50
+ ```ts
51
+ // environments/environment.ts
52
+ export const environment = {
53
+ recaptchaSiteKey: '6Lc...your-v3-site-key...',
54
+ };
55
+ ```
56
+
57
+ `provideDgaDynamicForm` registers a **no-op** `DGA_CAPTCHA_ADAPTER` (token is always `null`). Override it after `provideDgaDynamicForm`. On submit the package calls `getToken('submit')` and sends header `X-Recaptcha-Token`.
58
+
59
+ ```ts
60
+ import { importProvidersFrom } from '@angular/core';
61
+ import {
62
+ RECAPTCHA_V3_SITE_KEY,
63
+ RecaptchaV3Module,
64
+ ReCaptchaV3Service,
65
+ } from 'ng-recaptcha';
66
+ import { catchError, of } from 'rxjs';
67
+ import {
68
+ DGA_CAPTCHA_ADAPTER,
69
+ provideDgaDynamicForm,
70
+ } from '@a.nemreen/dga-dynamic-form';
71
+
72
+ export const appConfig = {
73
+ providers: [
74
+ provideHttpClient(),
75
+ importProvidersFrom(RecaptchaV3Module),
76
+ { provide: RECAPTCHA_V3_SITE_KEY, useValue: environment.recaptchaSiteKey },
77
+ provideDgaDynamicForm({ httpSubmit: true }),
78
+ {
79
+ provide: DGA_CAPTCHA_ADAPTER,
80
+ useFactory: (recaptcha: ReCaptchaV3Service) => ({
81
+ getToken: (action?: string) => {
82
+ const googleAction = action === 'submit' ? 'form_submit' : action ?? 'form_submit';
83
+ return recaptcha.execute(googleAction).pipe(catchError(() => of(null)));
84
+ },
85
+ }),
86
+ deps: [ReCaptchaV3Service],
87
+ },
88
+ ],
89
+ };
90
+ ```
91
+
92
+ Form config — set `endpoint` (do **not** use `emitOnly` for live POSTs):
93
+
94
+ ```ts
95
+ readonly config: DgaDynamicFormConfig = {
96
+ endpoint: 'https://api.example.com/contact',
97
+ method: 'POST',
98
+ fields: [/* no captcha field */],
99
+ };
100
+ ```
101
+
102
+ **Backend:** verify token via `https://www.google.com/recaptcha/api/siteverify` with secret key; check `action === 'form_submit'` and score threshold.
103
+
104
+ **Troubleshooting:** token null → provide site key + `RecaptchaV3Module`; captcha skipped → form uses `emitOnly`; script blocked → CSP must allow `www.google.com` and `www.gstatic.com`. Use `RecaptchaV3Module` only (not v2 `RecaptchaModule` checkbox).
105
+
106
+ Full guide: demo `/dynamic-form` → reCAPTCHA section.
107
+
38
108
  ```css
39
109
  @source "../node_modules/@a.nemreen/dga-dynamic-form/**/*.{mjs,js}";
40
110
  @source "../node_modules/@a.nemreen/dga-ui/**/*.{mjs,js}";
@@ -51,31 +121,46 @@ readonly config: DgaDynamicFormConfig = {
51
121
  submitButtonLabel: { en: 'Send', ar: 'إرسال' },
52
122
  fields: [
53
123
  {
54
- name: 'fullName',
55
- type: 'text',
56
- label: { en: 'Full name', ar: 'الاسم الكامل' },
124
+ name: 'password',
125
+ type: 'password',
126
+ label: { en: 'Password', ar: 'كلمة المرور' },
57
127
  required: true,
58
- columns: { md: 6 },
128
+ },
129
+ {
130
+ name: 'confirmPassword',
131
+ type: 'password',
132
+ label: { en: 'Confirm', ar: 'تأكيد' },
133
+ required: true,
134
+ validation: {
135
+ errorMessages: {
136
+ passwordMismatch: { en: 'Passwords must match', ar: 'يجب أن تتطابق كلمات المرور' },
137
+ },
138
+ },
59
139
  },
60
140
  ],
61
141
  };
62
142
  ```
63
143
 
64
144
  ```html
65
- <dga-dynamic-form [config]="config" (formSubmitted)="onSubmit($event)" />
145
+ <dga-dynamic-form
146
+ [config]="config"
147
+ [locale]="'ar'"
148
+ (formSubmitted)="onSubmit($event)"
149
+ (fileDownload)="onDownload($event)"
150
+ />
66
151
  ```
67
152
 
153
+ When both `password` and `confirmPassword` fields exist, `passwordMismatch` validation is applied automatically.
154
+
68
155
  ---
69
156
 
70
157
  ## Config reference (`DgaDynamicFormConfig`)
71
158
 
72
- Passed as `[config]` on `<dga-dynamic-form>`.
73
-
74
159
  | Key | Type | Default | Description |
75
160
  |-----|------|---------|-------------|
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`. |
161
+ | `endpoint` | `string?` | — | Submit URL. Omit or use `emitOnly` to only emit `formSubmitted`. |
162
+ | `method` | `DgaHttpMethod` | `'POST'` | HTTP method for submit adapter. |
163
+ | `emitOnly` | `boolean?` | `false` | Skip HTTP; emit payload on `formSubmitted`. |
79
164
  | `fields` | `DgaFormField[]` | **required** | Field list. Can be `[]` when using `wizard.steps` only. |
80
165
  | `description` | `DgaFormLabel?` | — | Bilingual text above the form. |
81
166
  | `submitButtonLabel` | `DgaFormLabel?` | Submit / إرسال | Primary button label. |
@@ -83,47 +168,31 @@ Passed as `[config]` on `<dga-dynamic-form>`.
83
168
  | `saveButtonLabel` | `DgaFormLabel?` | — | Draft save button label. |
84
169
  | `successMessage` | `DgaFormLabel?` | — | Toast on success. |
85
170
  | `errorMessage` | `DgaFormLabel?` | — | Toast on failure. |
86
- | `fieldMapping` | `Record<string, string>?` | — | Rename field keys in the submit payload. |
171
+ | `fieldMapping` | `Record<string, string>?` | — | Rename field keys in submit payload. |
87
172
  | `payloadTransformer` | `(data) => object \| FormData` | — | Final reshape before submit. |
88
173
  | `formId` | `string?` | — | localStorage namespace for drafts. |
89
174
  | `enableLocalStorageSave` | `boolean?` | `false` | Enable Save draft + restore on init. |
90
175
  | `idempotencyKey` | `string?` | — | Sent as `Idempotency-Key` header. |
176
+ | `consentBaseUrl` | `string?` | — | CMS base for `consentServiceName` fields → GET `{base}/consents/{name}`. |
91
177
  | `wizard` | `DgaWizardConfig?` | — | Multi-step mode (see below). |
92
178
 
93
179
  `DgaFormLabel` is always `{ en: string; ar: string }`.
94
180
 
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
181
  ---
108
182
 
109
- ## Wizard (`config.wizard` → `DgaWizardConfig`)
110
-
111
- When `wizard.enabled` is true, fields are taken from `wizard.steps[].fields` (root `fields` can be empty).
183
+ ## Wizard (`config.wizard`)
112
184
 
113
185
  | Key | Type | Default | Description |
114
186
  |-----|------|---------|-------------|
115
187
  | `enabled` | `boolean` | — | Turn on stepper UI. |
116
188
  | `steps` | `DgaFormStep[]` | — | Ordered steps. |
117
189
  | `validateOnStepChange` | `boolean?` | `true` | Block Next if current step invalid. |
118
- | `allowSkipSteps` | `boolean?` | `false` | Allow jumping ahead (reserved). |
190
+ | `allowSkipSteps` | `boolean?` | `false` | Allow jumping ahead in stepper. |
119
191
  | `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 / حفظ | |
192
+ | `autoSave` | `boolean?` | — | When true + `saveEndpoint`, HTTP draft save on Save. |
193
+ | `saveEndpoint` | `string?` | — | Draft HTTP URL. |
194
+ | `saveMethod` | `DgaHttpMethod?` | `'POST'` | Method for `saveEndpoint`. |
195
+ | `nextButtonText` / `previousButtonText` / `submitButtonText` / `saveButtonText` | `DgaFormLabel?` | | Wizard chrome labels. |
127
196
 
128
197
  ### Step (`DgaFormStep`)
129
198
 
@@ -133,113 +202,93 @@ When `wizard.enabled` is true, fields are taken from `wizard.steps[].fields` (ro
133
202
  | `title` | `DgaFormLabel` | Stepper title. |
134
203
  | `description` | `DgaFormLabel?` | Stepper subtitle. |
135
204
  | `fields` | `DgaFormField[]` | Fields in this step. |
136
- | `optional` | `boolean?` | Mark step optional. |
205
+ | `optional` | `boolean?` | Skip validation for this step. |
206
+ | `initialData` | `Record<string, unknown>?` | Pre-fill when entering the step. |
137
207
 
138
208
  ---
139
209
 
140
210
  ## Field (`DgaFormField`)
141
211
 
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
212
  ### Built-in `type` values
163
213
 
164
- `text` · `email` · `textarea` · `number` · `otp` · `phone` · `select` · `multiselect` · `checkbox` · `radio` · `date` · `chips` · `toggle` · `file` · `hidden`
214
+ `text` · `email` · `password` · `textarea` · `number` · `otp` · `phone` · `select` · `multiselect` · `checkbox` · `radio` · `date` · `chips` · `toggle` · `file` · `multifile` · `multifilestaged` · `hidden`
165
215
 
166
- ### Options (select / radio / checkbox)
216
+ Fields named `password` or `confirmPassword` with `type: 'text'` are rendered as password inputs.
167
217
 
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`). |
218
+ ### Visibility & conditional required
173
219
 
174
- **Option object:** `value`, `label`, optional `requiresTextInput`, `description`, `disabled`.
220
+ | Key | Description |
221
+ |-----|-------------|
222
+ | `dependsOn` | Other field that controls visibility. |
223
+ | `showOnValues` | Show when `dependsOn` value is in list. |
224
+ | `showOnTitleEn` / `showOnTitleAr` | Show when selected option label matches. |
225
+ | `showWhenOptionProperty` | Show when selected option has `property === value`. |
226
+ | `extraField` | Show when selected option has `requiresTextInput: true`. |
227
+ | `extraFieldTrigger` | Trigger field for `extraField` (defaults to `dependsOn`). |
228
+ | `requiredWhen` | `{ field, value?, values?, optionProperty?, optionPropertyValue? }` |
175
229
 
176
- ### Visibility & conditional required
230
+ ### Lookup (remote options)
177
231
 
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). |
232
+ | Key | Description |
233
+ |-----|-------------|
234
+ | `lookupDomain` | Base URL for lookup adapter. |
235
+ | `lookupName` | Resource `{domain}/lookup/{name}`. |
236
+ | `lookupFilter` | Client-side filter on loaded options. |
237
+ | `useParentId` | Pass `dependsOn` value as `parentId`; reloads when parent changes. |
238
+ | `searchLookup` | Searchable select; remote search via `DGA_LOOKUP_ADAPTER`. |
239
+ | `searchParamName` | Query param (default `q`). |
240
+ | `minSearchLength` | Min chars before search (default `3` when `searchLookup`). |
184
241
 
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
- ```
242
+ HTTP adapter maps `Id/Title`, `Code/Name`, and `{UserName,Email,DisplayName}` shapes.
194
243
 
195
- ### Lookup (remote options)
244
+ ### Files
196
245
 
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**. |
246
+ | Key | Types | Description |
247
+ |-----|-------|-------------|
248
+ | `acceptedFileTypes` | file, multifile, multifilestaged | e.g. `.pdf,.png`. |
249
+ | `maxFileSize` | all file types | Max bytes per file. |
250
+ | `maxFiles` | all file types | Max file count. |
251
+ | `uploadEndpoint` | multifilestaged | POST staging URL `{ fileId, fileName? }`. |
252
+ | `deleteEndpoint` | multifilestaged | DELETE/POST removal URL (`:id` / `:fileId`). |
253
+ | `deleteMethod` | multifilestaged | `'DELETE' \| 'POST'`. |
254
+
255
+ `multifilestaged` value: `DgaStagedFileRef[]`. Edit mode: `DgaExistingAttachment[]` on file fields.
206
256
 
207
257
  ### Type-specific
208
258
 
209
259
  | Key | Types | Description |
210
260
  |-----|-------|-------------|
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`. |
261
+ | `minDate` / `maxDate` | date | ISO bounds on picker. |
262
+ | `monthPicker` | date | Month-only mode. |
263
+ | `maxChips` | chips | Max chip count validator. |
264
+ | `allowDuplicates` | chips | Allow duplicate chip values. |
265
+ | `alwaysOpen` | select | `openMode="always"` on `dga-select`. |
266
+ | `consentServiceName` | checkbox | Fetch label from CMS (`consentBaseUrl`). |
267
+ | `excludeFromDraft` / `sensitive` | any | Strip from localStorage drafts. |
218
268
 
219
269
  ### Validation (`field.validation`)
220
270
 
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`. |
271
+ Supports `pattern`, `minLength`, `maxLength`, `min`, `max`, `minDate`, `maxDate`, `required`.
272
+
273
+ **Error message keys:** `required`, `pattern`, `minLength`, `maxLength`, `email`, `min`, `max`, `minDate`, `maxDate`, `maxChips`, `invalidFormat`, `invalidPrefix`, `invalidLength`, `passwordMismatch`, `endDateAfterStartDate`, `uploadInProgress`, `maxFiles`.
274
+
275
+ **Auto cross-field rules:** `password` + `confirmPassword` match validator; known date pairs (`StartDate`/`EndDate`, `UseCaseStartDate`/`UseCaseEndDate`, `startDate`/`endDate`) → end-after-start.
228
276
 
229
277
  ---
230
278
 
231
279
  ## Component API
232
280
 
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`. |
281
+ | Member | Description |
282
+ |--------|-------------|
283
+ | `[config]` | `DgaDynamicFormConfig` (required). |
284
+ | `[locale]` | `'ar' \| 'en'` (default from `<html lang>`). |
285
+ | `(formSubmitted)` | HTTP response or payload when `emitOnly` / no endpoint. |
286
+ | `(formError)` | Submit failure. |
287
+ | `(clearButtonClick)` | After Clear. |
288
+ | `(draftSaved)` | After localStorage save. |
289
+ | `(fileDownload)` | Existing attachment download (`DgaExistingAttachment`). |
290
+ | `patchFormValues(values)` | Patch controls; config `value` changes also patch at runtime. |
291
+ | `getForm()` | Returns `FormGroup`. |
243
292
 
244
293
  ---
245
294
 
@@ -248,31 +297,17 @@ requiredWhen: {
248
297
  | Token | Role |
249
298
  |-------|------|
250
299
  | `DGA_SUBMIT_ADAPTER` | `submit({ endpoint, method, body, headers })` |
251
- | `DGA_LOOKUP_ADAPTER` | `lookup({ domain, name, parentId?, search? })` |
300
+ | `DGA_LOOKUP_ADAPTER` | `lookup({ domain, name, parentId?, search?, searchParamName? })` |
252
301
  | `DGA_FORM_TOAST_ADAPTER` | `show({ title?, message?, variant? })` |
253
302
  | `DGA_CAPTCHA_ADAPTER` | `getToken(action?) → Observable<string \| null>` |
254
303
  | `DGA_FORM_I18N_ADAPTER` | `resolveLabel(label, locale)` |
304
+ | `DGA_HTTP_SECURITY` | `{ allowedOrigins? }` for schema-driven URLs |
255
305
  | `DGA_DYNAMIC_FORM_FIELD_REGISTRY` | `{ type, component }[]` custom field renderers |
256
306
 
257
- ```ts
258
- provideDgaDynamicForm({
259
- httpSubmit: true,
260
- httpLookup: true,
261
- dgaToast: true,
262
- });
263
- ```
307
+ ### Exported utilities
264
308
 
265
- ### Custom field type
266
-
267
- ```ts
268
- {
269
- provide: DGA_DYNAMIC_FORM_FIELD_REGISTRY,
270
- useValue: [{ type: 'nationalId', component: NationalIdField }],
271
- }
272
- ```
309
+ `dgaBuildFormGroup`, `dgaIsFieldVisible`, `dgaIsRequiredWhen`, `dgaApplyCrossFieldValidators`, `dgaSaudiMobileValidator`, `dgaPasswordMatchValidator`, `dgaAssertSafeHttpUrl`, `createHttpLookupAdapter`, `createHttpSubmitAdapter`.
273
310
 
274
311
  ## License
275
312
 
276
313
  MIT — see [LICENSE](./LICENSE). Copyright (c) 2026 Ahmed Nemreen.
277
-
278
- Use it in any project. **Do not rebrand and sell it as your own unique product.**
@@ -1,4 +1,160 @@
1
- import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectable as P,input as p,booleanAttribute as Ce,output as S,DestroyRef as B,signal as C,computed as u,ChangeDetectionStrategy as _,Component as z,effect as G,untracked as Ie,isDevMode as ke,makeEnvironmentProviders as De}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 Oe}from"@angular/forms";import{switchMap as U,catchError as we,startWith as Te,map as Le}from"rxjs/operators";import{DgaField as H,DgaInput as K,DgaSelect as W,DgaCheckbox as Y,DgaRadio as J,DgaPhoneInput as X,DgaChipInput as Z,DgaSwitch as Q,DgaUpload as ee,DgaNumberInput as te,DgaOtp as oe,DgaButton as re,DgaStepper as ae,DgaToastService as Ae}from"@a.nemreen/dga-ui";import{of as b,throwError as ne}from"rxjs";import{takeUntilDestroyed as Fe}from"@angular/core/rxjs-interop";import{DgaDatePicker as ie}from"@a.nemreen/dga-ui/date-picker";import{HttpClient as D,HttpHeaders as Ne}from"@angular/common/http";const O=new h("DGA_LOOKUP_ADAPTER"),w=new h("DGA_SUBMIT_ADAPTER"),T=new h("DGA_FORM_TOAST_ADAPTER"),L=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"],Me=["toggle"];function de(r){return r.value!==void 0?r.value:se.includes(r.type)?[]:Me.includes(r.type)?!1:""}function F(r,e){const t=[],o=r.validation;(e||r.required||o?.required)&&(se.includes(r.type)?t.push(Ve):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"){const i=o.pattern;if(typeof i=="string"&&i.length>0&&i.length<=200)try{t.push(f.pattern(i))}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(Re(r.maxChips)),t}function Ve(r){const e=r.value;return Array.isArray(e)&&e.length>0?null:{required:!0}}function Re(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!==""}const Ee=new Set(["otp","file"]),fe=/password|passwd|secret|token|otp|national.?id|ssn|pin$/i;class y{submitAdapter=s(w,{optional:!0});captcha=s(L,{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 c=e.fieldMapping?.[i]??i;n[c]=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(c=>(c&&(i["X-Recaptcha-Token"]=c),b({endpoint:e.endpoint,method:e.method??"POST",body:n,headers:i})))).pipe(U(c=>this.submitAdapter.submit(c)),we(c=>{throw c}))}saveDraft(e,t,o=[]){if(typeof localStorage>"u")return;const a=new Map(o.map(i=>[i.name,i])),n={};for(const[i,d]of Object.entries(t)){const c=a.get(i);c&&this.isDraftBlocked(c)||!c&&fe.test(i)||d instanceof File||(n[i]=d)}localStorage.setItem(this.storageKey(e),JSON.stringify(n))}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))}isDraftBlocked(e){return e.excludeFromDraft||e.sensitive||e.excludeFromPayload||Ee.has(e.type)?!0:fe.test(e.name)}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:P,args:[{providedIn:"root"}]}]});function v(r,e,t=""){return r?e==="en"?r.en||r.ar:r.ar||r.en:t}function ge(){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 qe.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:P,args:[{providedIn:"root"}]}],ctorParameters:()=>[]});const qe=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:Ce});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=u(()=>v(this.field().label,this.locale()),...ngDevMode?[{debugName:"labelText"}]:[]);hintText=u(()=>v(this.field().hint,this.locale()),...ngDevMode?[{debugName:"hintText"}]:[]);placeholderText=u(()=>v(this.field().placeholder,this.locale()),...ngDevMode?[{debugName:"placeholderText"}]:[]);resolvedOptions=u(()=>{const e=this.runtimeOptions();if(e.length)return e;const t=this.loadedOptions();return t.length?t:this.field().options??[]},...ngDevMode?[{debugName:"resolvedOptions"}]:[]);selectOptions=u(()=>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(Fe(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:`
1
+ import*as l from"@angular/core";import{InjectionToken as y,inject as c,Injectable as Y,isDevMode as ze,makeEnvironmentProviders as Be,input as u,booleanAttribute as J,signal as m,computed as p,output as g,ChangeDetectionStrategy as V,Component as R,DestroyRef as Q,effect as k,untracked as q}from"@angular/core";import{NgComponentOutlet as X}from"@angular/common";import*as h from"@angular/forms";import{Validators as b,ReactiveFormsModule as O,FormsModule as Z,FormBuilder as _e}from"@angular/forms";import{switchMap as ee,catchError as $e,map as Ue,startWith as Ge}from"rxjs/operators";import{takeUntilDestroyed as T}from"@angular/core/rxjs-interop";import{DgaToastService as je,DgaButton as A,DgaIcon as te,dgaProvideValueAccessor as ae,DgaField as ie,DgaInput as ne,DgaSelect as oe,DgaCheckbox as re,DgaRadio as le,DgaPhoneInput as se,DgaChipInput as de,DgaSwitch as ue,DgaUpload as ce,DgaNumberInput as pe,DgaOtp as fe,DgaStepper as me}from"@a.nemreen/dga-ui";import{of as x,throwError as ge,startWith as We,catchError as Ke}from"rxjs";import{HttpClient as S,HttpHeaders as He}from"@angular/common/http";import{DgaDatePicker as he}from"@a.nemreen/dga-ui/date-picker";const z=new y("DGA_LOOKUP_ADAPTER"),E=new y("DGA_SUBMIT_ADAPTER"),B=new y("DGA_FORM_TOAST_ADAPTER"),_=new y("DGA_CAPTCHA_ADAPTER"),$=new y("DGA_HTTP_SECURITY"),U=new y("DGA_FORM_I18N_ADAPTER"),be=new y("DGA_DYNAMIC_FORM_FIELD_REGISTRY"),ve=[["StartDate","EndDate"],["UseCaseStartDate","UseCaseEndDate"],["startDate","endDate"]];function ye(i){return e=>e.value&&i.value!==e.value?{passwordMismatch:!0}:null}function Ye(i){return e=>{if(!e.value||!i.value)return null;const t=new Date(e.value),a=new Date(i.value),n=new Date(a);return n.setDate(n.getDate()+1),t>=n?null:{endDateAfterStartDate:!0}}}function xe(i){const e=(i.value??"").toString().trim();if(!e)return null;let t=e;return e.startsWith("+966")?t=e.slice(4):e.startsWith("966")&&(t=e.slice(3)),/^\d+$/.test(t)?t.startsWith("0")?t.length===10?null:{invalidLength:!0}:t.startsWith("5")?t.length===9?null:{invalidLength:!0}:{invalidPrefix:!0}:{invalidFormat:!0}}function Je(i){return e=>{if(!e.value)return null;const t=new Date(e.value),a=new Date(i);return t>=a?null:{minDate:!0}}}function Qe(i){return e=>{if(!e.value)return null;const t=new Date(e.value),a=new Date(i);return t<=a?null:{maxDate:!0}}}function Xe(i){const e=i.value;return Array.isArray(e)&&e.some(a=>a&&typeof a=="object"&&a.status==="uploading")?{uploadInProgress:!0}:null}function De(i,e){const t=i.get("password"),a=i.get("confirmPassword");t&&a&&(a.addValidators(ye(t)),a.updateValueAndValidity({emitEvent:!1}));for(const[n,o]of ve){const r=i.get(n),s=i.get(o);if(!r||!s)continue;s.addValidators(Ye(r));const d=e.find(w=>w.name===o),f=w=>{if(d?.validation){if(w){const qe=new Date(w),P=new Date(qe);P.setDate(P.getDate()+1),d.validation.minDate=P.toISOString().split("T")[0]}else delete d.validation.minDate;s.updateValueAndValidity({emitEvent:!1})}};f(r.value),r.valueChanges.subscribe(w=>f(w))}}const we=["checkbox","multiselect","chips","file","multifile","multifilestaged"],Ze=["toggle"];function Se(i){return i.value!==void 0?i.value:we.includes(i.type)?[]:Ze.includes(i.type)?!1:""}function G(i,e){const t=[],a=i.validation;(e||i.required||a?.required)&&(we.includes(i.type)?t.push(et):i.type==="toggle"?t.push(b.requiredTrue):t.push(b.required)),(i.type==="email"||a?.pattern==="email")&&t.push(b.email);const n=a?.minLength;n!=null&&t.push(b.minLength(n));const o=a?.maxLength??i.maxLength;if(o!=null&&t.push(b.maxLength(o)),a?.pattern&&a.pattern!=="email"){const r=a.pattern;if(typeof r=="string"&&r.length>0&&r.length<=200)try{t.push(b.pattern(r))}catch{}}return a?.min!=null&&t.push(b.min(a.min)),a?.max!=null&&t.push(b.max(a.max)),i.maxChips!=null&&i.type==="chips"&&t.push(tt(i.maxChips)),i.type==="phone"&&t.push(xe),i.type==="multifilestaged"&&t.push(Xe),a?.minDate&&t.push(Je(a.minDate)),a?.maxDate&&t.push(Qe(a.maxDate)),t}function et(i){const e=i.value;return Array.isArray(e)&&e.length>0?null:{required:!0}}function tt(i){return e=>{const t=e.value;return Array.isArray(t)&&t.length>i?{maxChips:{max:i,actual:t.length}}:null}}function Ie(i,e,t=!0){const a=!!e.disabled;return i.control({value:Se(e),disabled:a},{validators:G(e,t&&!e.requiredWhen)})}function Ce(i,e){const t={};for(const a of e){if(a.hidden&&a.type==="hidden"){t[a.name]=i.control(a.value??"");continue}t[a.name]=Ie(i,a)}return i.group(t)}function Le(i,e){return e?.length?e.flatMap(t=>t.fields):i}function Fe(i,e,t){i.setValidators(G(e,t)),i.updateValueAndValidity({emitEvent:!1})}function j(i,e){return Array.isArray(i)?i.some(t=>t==e):i==e}function at(i){return i.label?.en??""}function it(i){return i.label?.ar??""}function M(i,e,t,a){return ke(a?.(i)??{name:i,type:"select",label:{en:"",ar:""}},e,t?.(i))}function nt(i,e,t,a,n){if(!i.showOnTitleEn&&!i.showOnTitleAr||t==null||t==="")return!1;const o=M(e,t,a,n);if(!o)return!1;const r=i.showOnTitleEn?at(o)===i.showOnTitleEn:!0,s=i.showOnTitleAr?it(o)===i.showOnTitleAr:!0;return r&&s}function Ne(i,e,t,a){if(!i.extraField)return!0;const n=i.extraFieldTrigger||i.dependsOn;if(!n)return!1;const o=e[n];return M(n,o,t,a)?.requiresTextInput===!0}function ke(i,e,t){return i?[...t??[],...i.options??[],...i.groupedOptions?.flatMap(n=>n.options)??[]].find(n=>n.value==e):void 0}function W(i,e,t,a){if(i.hidden&&i.type!=="hidden"||i.type==="hidden")return!1;if(!i.dependsOn&&!i.extraField)return!0;const n=i.dependsOn??i.extraFieldTrigger;if(!n)return Ne(i,e,t,a);const o=e[n];if(i.showOnValues?.length&&!i.showOnValues.some(r=>j(o,r))||(i.showOnTitleEn||i.showOnTitleAr)&&!nt(i,n,o,t,a))return!1;if(i.showWhenOptionProperty){const r=M(n,o,t,a),s=i.showWhenOptionProperty.property;if(!r||r[s]!==i.showWhenOptionProperty.value)return!1}return!(i.extraField&&!Ne(i,e,t,a)||i.dependsOn&&!i.showOnValues?.length&&!i.showWhenOptionProperty&&!i.showOnTitleEn&&!i.showOnTitleAr&&!i.extraField&&(o==null||o===""||Array.isArray(o)&&o.length===0))}function K(i,e,t,a){const n=i.requiredWhen;if(!n)return!!i.required;const o=e[n.field];if(n.optionProperty!=null){const r=M(n.field,o,t,a);return!!r&&r[n.optionProperty]===n.optionPropertyValue}return n.values?.length?n.values.some(r=>j(o,r)):n.value!==void 0?j(o,n.value):o!=null&&o!==""}const ot=new Set(["otp","file"]),Oe=/password|passwd|secret|token|otp|national.?id|ssn|pin$/i;class C{submitAdapter=c(E,{optional:!0});captcha=c(_,{optional:!0});buildPayload(e,t,a){const n=new Set(a.filter(r=>r.excludeFromPayload).map(r=>r.name));let o={};for(const[r,s]of Object.entries(t)){if(n.has(r))continue;const d=e.fieldMapping?.[r]??r;o[d]=s}return e.payloadTransformer?e.payloadTransformer(o):o}hasFileValues(e){return Object.values(e).some(t=>t instanceof File||Array.isArray(t)&&t.some(a=>a instanceof File||a?.file instanceof File))}toFormData(e){const t=new FormData;for(const[a,n]of Object.entries(e))if(n!=null)if(n instanceof File)t.append(a,n);else if(Array.isArray(n))for(const o of n)o instanceof File?t.append(a,o):o?.file instanceof File?t.append(a,o.file,o.name):t.append(a,typeof o=="string"?o:JSON.stringify(o));else typeof n=="object"?t.append(a,JSON.stringify(n)):t.append(a,String(n));return t}submitForm(e,t,a){if(!e.endpoint||!this.submitAdapter)return x(t);const n=this.buildPayload(e,t,a),o=n instanceof FormData?n:this.hasFileValues(n)?this.toFormData(n):n,r={};return e.idempotencyKey&&(r["Idempotency-Key"]=e.idempotencyKey),(this.captcha?.getToken("submit")??x(null)).pipe(ee(d=>(d&&(r["X-Recaptcha-Token"]=d),x({endpoint:e.endpoint,method:e.method??"POST",body:o,headers:r})))).pipe(ee(d=>this.submitAdapter.submit(d)),$e(d=>{throw d}))}saveDraft(e,t,a=[]){if(typeof localStorage>"u")return;const n=new Map(a.map(r=>[r.name,r])),o={};for(const[r,s]of Object.entries(t)){const d=n.get(r);d&&this.isDraftBlocked(d)||!d&&Oe.test(r)||s instanceof File||(o[r]=s)}localStorage.setItem(this.storageKey(e),JSON.stringify(o))}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))}isDraftBlocked(e){return e.excludeFromDraft||e.sensitive||e.excludeFromPayload||ot.has(e.type)?!0:Oe.test(e.name)}storageKey(e){return`dga-dynamic-form:${e}`}static ɵfac=l.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:C,deps:[],target:l.ɵɵFactoryTarget.Injectable});static ɵprov=l.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:C,providedIn:"root"})}l.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:C,decorators:[{type:Y,args:[{providedIn:"root"}]}]});function I(i,e,t=""){return i?e==="en"?i.en||i.ar:i.ar||i.en:t}function Te(){return typeof document>"u"?"ar":(document.documentElement.lang||"ar").toLowerCase().startsWith("en")?"en":"ar"}class L{extras=c(be,{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 rt.has(e)}static ɵfac=l.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:L,deps:[],target:l.ɵɵFactoryTarget.Injectable});static ɵprov=l.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:L,providedIn:"root"})}l.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:L,decorators:[{type:Y,args:[{providedIn:"root"}]}],ctorParameters:()=>[]});const rt=new Set(["text","email","textarea","select","multiselect","checkbox","radio","phone","file","multifile","multifilestaged","date","chips","toggle","hidden","number","otp","password"]),Ae={resolveLabel(i,e){return e==="en"?i.en:i.ar}},Ee={getToken:()=>x(null)},Me={lookup:()=>x([])},lt=new Set(["__proto__","constructor","prototype"]);function F(i,e){const t=i.trim();if(!t)throw new Error("DGA dynamic form: empty HTTP URL");if(t.startsWith("/")&&!t.startsWith("//"))return t.replace(/\/$/,"")||"/";let a;try{a=new URL(t)}catch{throw new Error(`DGA dynamic form: invalid HTTP URL "${i}"`)}if(a.protocol!=="http:"&&a.protocol!=="https:")throw new Error(`DGA dynamic form: blocked URL protocol "${a.protocol}"`);if(e?.length&&!e.some(o=>{try{return new URL(o).origin===a.origin}catch{return o===a.origin}}))throw new Error(`DGA dynamic form: origin not allowlisted "${a.origin}"`);return a.toString().replace(/\/$/,"")}function Pe(i=c(S),e={}){return{lookup(t){try{const n=`${F(t.domain,e.allowedOrigins)}/lookup/${encodeURIComponent(t.name)}`,o={};if(t.parentId!=null&&t.parentId!==""&&(o.parentId=String(t.parentId)),t.search){const r=t.searchParamName||"q";o[r]=t.search}return i.get(n,{params:o}).pipe(Ue(r=>Array.isArray(r)?r.map(st):[]))}catch(a){return ge(()=>a)}}}}function st(i){const e=i??{},t=e.DisplayName??e.UserName??e.Email,a=e.value??e.id??e.Id??e.Code??t??"",n=String(e.labelEn??e.titleEn??e.en??e.TitleEn??e.Name??e.name??t??a),o=String(e.labelAr??e.titleAr??e.ar??e.TitleAr??e.NameAr??e.nameAr??n),r={value:a,label:{en:n,ar:o}};for(const[s,d]of Object.entries(e))lt.has(s)||s==="value"||s==="label"||s==="id"||s==="Id"||s==="labelEn"||s==="titleEn"||s==="en"||s==="TitleEn"||s==="labelAr"||s==="titleAr"||s==="ar"||s==="TitleAr"||(r[s]=d);return e.disabled!=null&&(r.disabled=!!e.disabled),e.requiresTextInput!=null&&(r.requiresTextInput=!!e.requiresTextInput),e.description&&typeof e.description=="object"&&(r.description=e.description),r}function Ve(i=c(S),e={}){return{submit(t){try{const a=F(t.endpoint,e.allowedOrigins),n=new He(t.headers??{}),o=t.method.toUpperCase();return o==="GET"?i.get(a,{headers:n}):o==="DELETE"?i.delete(a,{headers:n,body:t.body}):o==="PUT"?i.put(a,t.body,{headers:n}):o==="PATCH"?i.patch(a,t.body,{headers:n}):i.post(a,t.body,{headers:n})}catch(a){return ge(()=>a)}}}}function Re(i=c(je)){return{show(e){i.show({title:e.title,message:e.message,variant:e.variant??"info"})}}}function dt(i={}){const e=i.httpLookup??!1,t=i.httpSubmit??!0,a=i.dgaToast??!0,n=i.requireAllowedOrigins??!1,o={allowedOrigins:i.allowedOrigins};if(n&&(e||t)&&!i.allowedOrigins?.length)throw new Error("DGA dynamic form: provideDgaDynamicForm({ allowedOrigins: [...] }) is required when requireAllowedOrigins is true. ");return ze()&&(e||t)&&!i.allowedOrigins?.length&&console.warn("[dga-dynamic-form] HTTP adapters enabled without allowedOrigins. Absolute schema URLs can reach any https host — set allowedOrigins (and requireAllowedOrigins: true) for CMS schemas."),Be([{provide:$,useValue:o},{provide:U,useValue:Ae},{provide:_,useValue:Ee},{provide:z,useFactory:()=>e?Pe(c(S),o):Me},{provide:E,useFactory:()=>t?Ve(c(S),o):{submit:()=>x(null)}},{provide:B,useFactory:()=>a?Re():{show:()=>{}}}])}function ut(i,e,t=""){return i?e==="en"?i.en:i.ar:t}class v{accept=u("",...ngDevMode?[{debugName:"accept"}]:[]);maxFileSize=u(0,...ngDevMode?[{debugName:"maxFileSize"}]:[]);maxFiles=u(0,...ngDevMode?[{debugName:"maxFiles"}]:[]);uploadEndpoint=u("",...ngDevMode?[{debugName:"uploadEndpoint"}]:[]);deleteEndpoint=u("",...ngDevMode?[{debugName:"deleteEndpoint"}]:[]);deleteMethod=u("DELETE",...ngDevMode?[{debugName:"deleteMethod"}]:[]);hint=u("Drag files here or browse",...ngDevMode?[{debugName:"hint"}]:[]);browseLabel=u("Choose files",...ngDevMode?[{debugName:"browseLabel"}]:[]);removeLabel=u("Remove",...ngDevMode?[{debugName:"removeLabel"}]:[]);downloadLabel=u("Download",...ngDevMode?[{debugName:"downloadLabel"}]:[]);uploadingLabel=u("Uploading…",...ngDevMode?[{debugName:"uploadingLabel"}]:[]);errorLabel=u("Upload failed",...ngDevMode?[{debugName:"errorLabel"}]:[]);disabledInput=u(!1,{...ngDevMode?{debugName:"disabledInput"}:{},alias:"disabled",transform:J});cvaDisabled=m(!1,...ngDevMode?[{debugName:"cvaDisabled"}]:[]);disabled=p(()=>this.disabledInput()||this.cvaDisabled(),...ngDevMode?[{debugName:"disabled"}]:[]);fileDownload=g();http=c(S);httpSecurity=c($,{optional:!0});files=m([],...ngDevMode?[{debugName:"files"}]:[]);dragging=m(!1,...ngDevMode?[{debugName:"dragging"}]:[]);onChange=()=>{};onTouched=()=>{};atLimit=p(()=>{const e=this.maxFiles();return e>0&&this.files().length>=e},...ngDevMode?[{debugName:"atLimit"}]:[]);onDragOver(e){e.preventDefault(),!this.disabled()&&!this.atLimit()&&this.dragging.set(!0)}onDrop(e){if(e.preventDefault(),this.dragging.set(!1),this.disabled()||this.atLimit())return;const t=e.dataTransfer?.files;t?.length&&this.addFiles(Array.from(t))}onPick(e){const t=e.target;t.files?.length&&this.addFiles(Array.from(t.files)),t.value=""}remove(e){if(e.status==="uploading")return;const t=this.isExisting(e),a=this.deleteEndpoint();if(!t&&a&&e.fileId){const n=a.includes(":id")?a.replace(":id",encodeURIComponent(e.fileId)):a.includes(":fileId")?a.replace(":fileId",encodeURIComponent(e.fileId)):a;try{const o=F(n,this.httpSecurity?.allowedOrigins);this.deleteMethod()==="POST"?this.http.post(o,{FileName:e.fileName}).subscribe({complete:()=>this.commit(this.files().filter(r=>r!==e))}):this.http.delete(o).subscribe({complete:()=>this.commit(this.files().filter(r=>r!==e))});return}catch{}}this.commit(this.files().filter(n=>n!==e))}onDownload(e){this.isExisting(e)&&this.fileDownload.emit({Id:Number(e.fileId),FileName:e.fileName,ContentType:""})}isExisting(e){return e.listKey?.startsWith("existing-")??!1}addFiles(e){const t=this.maxFileSize(),a=this.maxFiles();let n=a>0?a-this.files().length:e.length;if(!(n<=0))for(const o of e.slice(0,n))t>0&&o.size>t||this.uploadOne(o)}uploadOne(e){const t=`new-${e.name}-${Date.now()}-${Math.random().toString(36).slice(2,7)}`,a={fileId:"",fileName:e.name,sizeBytes:e.size,status:"uploading",listKey:t},n=[...this.files(),a];this.files.set(n),this.onChange(n);const o=this.uploadEndpoint();if(!o){a.status="success",a.fileId=t,this.commit([...this.files()]);return}try{const r=F(o,this.httpSecurity?.allowedOrigins),s=new FormData;s.append("file",e),this.http.post(r,s).subscribe({next:d=>{a.status="success",a.fileId=d.fileId??t,d.fileName&&(a.fileName=d.fileName),this.commit([...this.files()])},error:d=>{a.status="error",a.errorMessage=d?.message??this.errorLabel(),this.commit([...this.files()])}})}catch(r){a.status="error",a.errorMessage=r?.message??this.errorLabel(),this.commit([...this.files()])}}commit(e){this.files.set(e),this.onChange(e),this.onTouched()}writeValue(e){const a=(Array.isArray(e)?e:[]).map(n=>{if("Id"in n&&"FileName"in n){const o=n;return{fileId:String(o.Id),fileName:o.FileName,status:"success",listKey:`existing-${o.Id}`}}return n});this.files.set(a)}registerOnChange(e){this.onChange=e}registerOnTouched(e){this.onTouched=e}setDisabledState(e){this.cvaDisabled.set(e)}static ɵfac=l.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:v,deps:[],target:l.ɵɵFactoryTarget.Component});static ɵcmp=l.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"22.0.7",type:v,isStandalone:!0,selector:"dga-dynamic-form-staged-upload",inputs:{accept:{classPropertyName:"accept",publicName:"accept",isSignal:!0,isRequired:!1,transformFunction:null},maxFileSize:{classPropertyName:"maxFileSize",publicName:"maxFileSize",isSignal:!0,isRequired:!1,transformFunction:null},maxFiles:{classPropertyName:"maxFiles",publicName:"maxFiles",isSignal:!0,isRequired:!1,transformFunction:null},uploadEndpoint:{classPropertyName:"uploadEndpoint",publicName:"uploadEndpoint",isSignal:!0,isRequired:!1,transformFunction:null},deleteEndpoint:{classPropertyName:"deleteEndpoint",publicName:"deleteEndpoint",isSignal:!0,isRequired:!1,transformFunction:null},deleteMethod:{classPropertyName:"deleteMethod",publicName:"deleteMethod",isSignal:!0,isRequired:!1,transformFunction:null},hint:{classPropertyName:"hint",publicName:"hint",isSignal:!0,isRequired:!1,transformFunction:null},browseLabel:{classPropertyName:"browseLabel",publicName:"browseLabel",isSignal:!0,isRequired:!1,transformFunction:null},removeLabel:{classPropertyName:"removeLabel",publicName:"removeLabel",isSignal:!0,isRequired:!1,transformFunction:null},downloadLabel:{classPropertyName:"downloadLabel",publicName:"downloadLabel",isSignal:!0,isRequired:!1,transformFunction:null},uploadingLabel:{classPropertyName:"uploadingLabel",publicName:"uploadingLabel",isSignal:!0,isRequired:!1,transformFunction:null},errorLabel:{classPropertyName:"errorLabel",publicName:"errorLabel",isSignal:!0,isRequired:!1,transformFunction:null},disabledInput:{classPropertyName:"disabledInput",publicName:"disabled",isSignal:!0,isRequired:!1,transformFunction:null}},outputs:{fileDownload:"fileDownload"},host:{classAttribute:"block w-full"},providers:[ae(v)],ngImport:l,template:`
2
+ <div class="flex flex-col gap-md">
3
+ <div
4
+ class="flex flex-col items-center justify-center gap-md rounded-lg border border-dashed border-border
5
+ bg-surface px-xl py-3xl text-center"
6
+ [class.opacity-60]="disabled() || atLimit()"
7
+ [class.pointer-events-none]="disabled() || atLimit()"
8
+ (dragover)="onDragOver($event)"
9
+ (dragleave)="dragging.set(false)"
10
+ (drop)="onDrop($event)"
11
+ >
12
+ <p class="text-body-sm text-muted">{{ hint() }}</p>
13
+ <button
14
+ dgaButton
15
+ type="button"
16
+ variant="secondary"
17
+ [disabled]="disabled() || atLimit()"
18
+ (click)="fileInput.click()"
19
+ >
20
+ {{ browseLabel() }}
21
+ </button>
22
+ <input
23
+ #fileInput
24
+ type="file"
25
+ class="sr-only"
26
+ [attr.accept]="accept() || null"
27
+ multiple
28
+ [disabled]="disabled() || atLimit()"
29
+ (change)="onPick($event)"
30
+ />
31
+ @if (maxFiles() > 0) {
32
+ <p class="text-body-xs text-muted">
33
+ {{ files().length }}/{{ maxFiles() }}
34
+ </p>
35
+ }
36
+ </div>
37
+
38
+ @if (files().length) {
39
+ <ul class="flex flex-col gap-sm" role="list">
40
+ @for (file of files(); track file.listKey || file.fileId) {
41
+ <li
42
+ class="flex items-center justify-between gap-md rounded-md border border-border bg-surface px-md py-sm"
43
+ >
44
+ <div class="min-w-0 flex-1">
45
+ <p class="truncate text-body-sm text-fg">{{ file.fileName }}</p>
46
+ @if (file.status === 'uploading') {
47
+ <p class="text-body-xs text-muted">{{ uploadingLabel() }}</p>
48
+ }
49
+ @if (file.status === 'error') {
50
+ <p class="text-body-xs text-error">{{ file.errorMessage || errorLabel() }}</p>
51
+ }
52
+ </div>
53
+ <div class="flex shrink-0 items-center gap-sm">
54
+ @if (file.status === 'success' && isExisting(file)) {
55
+ <button
56
+ type="button"
57
+ class="inline-flex size-4xl items-center justify-center rounded-sm text-muted hover:text-fg"
58
+ [attr.aria-label]="downloadLabel() + ': ' + file.fileName"
59
+ (click)="onDownload(file)"
60
+ >
61
+ <dga-icon name="download" size="sm" tone="current" />
62
+ </button>
63
+ }
64
+ <button
65
+ type="button"
66
+ class="inline-flex size-4xl items-center justify-center rounded-sm text-muted hover:text-fg"
67
+ [attr.aria-label]="removeLabel() + ': ' + file.fileName"
68
+ [disabled]="disabled() || file.status === 'uploading'"
69
+ (click)="remove(file)"
70
+ >
71
+ <dga-icon name="close" size="sm" tone="current" />
72
+ </button>
73
+ </div>
74
+ </li>
75
+ }
76
+ </ul>
77
+ }
78
+ </div>
79
+ `,isInline:!0,dependencies:[{kind:"directive",type:A,selector:"button[dgaButton], a[dgaButton]",inputs:["variant","size","iconOnly","cooldownMs","loading","dgaButtonClass"]},{kind:"component",type:te,selector:"dga-icon",inputs:["name","glyph","size","tone","label"]}],changeDetection:l.ChangeDetectionStrategy.OnPush})}l.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:v,decorators:[{type:R,args:[{selector:"dga-dynamic-form-staged-upload",standalone:!0,imports:[A,te],template:`
80
+ <div class="flex flex-col gap-md">
81
+ <div
82
+ class="flex flex-col items-center justify-center gap-md rounded-lg border border-dashed border-border
83
+ bg-surface px-xl py-3xl text-center"
84
+ [class.opacity-60]="disabled() || atLimit()"
85
+ [class.pointer-events-none]="disabled() || atLimit()"
86
+ (dragover)="onDragOver($event)"
87
+ (dragleave)="dragging.set(false)"
88
+ (drop)="onDrop($event)"
89
+ >
90
+ <p class="text-body-sm text-muted">{{ hint() }}</p>
91
+ <button
92
+ dgaButton
93
+ type="button"
94
+ variant="secondary"
95
+ [disabled]="disabled() || atLimit()"
96
+ (click)="fileInput.click()"
97
+ >
98
+ {{ browseLabel() }}
99
+ </button>
100
+ <input
101
+ #fileInput
102
+ type="file"
103
+ class="sr-only"
104
+ [attr.accept]="accept() || null"
105
+ multiple
106
+ [disabled]="disabled() || atLimit()"
107
+ (change)="onPick($event)"
108
+ />
109
+ @if (maxFiles() > 0) {
110
+ <p class="text-body-xs text-muted">
111
+ {{ files().length }}/{{ maxFiles() }}
112
+ </p>
113
+ }
114
+ </div>
115
+
116
+ @if (files().length) {
117
+ <ul class="flex flex-col gap-sm" role="list">
118
+ @for (file of files(); track file.listKey || file.fileId) {
119
+ <li
120
+ class="flex items-center justify-between gap-md rounded-md border border-border bg-surface px-md py-sm"
121
+ >
122
+ <div class="min-w-0 flex-1">
123
+ <p class="truncate text-body-sm text-fg">{{ file.fileName }}</p>
124
+ @if (file.status === 'uploading') {
125
+ <p class="text-body-xs text-muted">{{ uploadingLabel() }}</p>
126
+ }
127
+ @if (file.status === 'error') {
128
+ <p class="text-body-xs text-error">{{ file.errorMessage || errorLabel() }}</p>
129
+ }
130
+ </div>
131
+ <div class="flex shrink-0 items-center gap-sm">
132
+ @if (file.status === 'success' && isExisting(file)) {
133
+ <button
134
+ type="button"
135
+ class="inline-flex size-4xl items-center justify-center rounded-sm text-muted hover:text-fg"
136
+ [attr.aria-label]="downloadLabel() + ': ' + file.fileName"
137
+ (click)="onDownload(file)"
138
+ >
139
+ <dga-icon name="download" size="sm" tone="current" />
140
+ </button>
141
+ }
142
+ <button
143
+ type="button"
144
+ class="inline-flex size-4xl items-center justify-center rounded-sm text-muted hover:text-fg"
145
+ [attr.aria-label]="removeLabel() + ': ' + file.fileName"
146
+ [disabled]="disabled() || file.status === 'uploading'"
147
+ (click)="remove(file)"
148
+ >
149
+ <dga-icon name="close" size="sm" tone="current" />
150
+ </button>
151
+ </div>
152
+ </li>
153
+ }
154
+ </ul>
155
+ }
156
+ </div>
157
+ `,changeDetection:V.OnPush,host:{class:"block w-full"},providers:[ae(v)]}]}],propDecorators:{accept:[{type:l.Input,args:[{isSignal:!0,alias:"accept",required:!1}]}],maxFileSize:[{type:l.Input,args:[{isSignal:!0,alias:"maxFileSize",required:!1}]}],maxFiles:[{type:l.Input,args:[{isSignal:!0,alias:"maxFiles",required:!1}]}],uploadEndpoint:[{type:l.Input,args:[{isSignal:!0,alias:"uploadEndpoint",required:!1}]}],deleteEndpoint:[{type:l.Input,args:[{isSignal:!0,alias:"deleteEndpoint",required:!1}]}],deleteMethod:[{type:l.Input,args:[{isSignal:!0,alias:"deleteMethod",required:!1}]}],hint:[{type:l.Input,args:[{isSignal:!0,alias:"hint",required:!1}]}],browseLabel:[{type:l.Input,args:[{isSignal:!0,alias:"browseLabel",required:!1}]}],removeLabel:[{type:l.Input,args:[{isSignal:!0,alias:"removeLabel",required:!1}]}],downloadLabel:[{type:l.Input,args:[{isSignal:!0,alias:"downloadLabel",required:!1}]}],uploadingLabel:[{type:l.Input,args:[{isSignal:!0,alias:"uploadingLabel",required:!1}]}],errorLabel:[{type:l.Input,args:[{isSignal:!0,alias:"errorLabel",required:!1}]}],disabledInput:[{type:l.Input,args:[{isSignal:!0,alias:"disabled",required:!1}]}],fileDownload:[{type:l.Output,args:["fileDownload"]}]}});class D{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:J});error=u("",...ngDevMode?[{debugName:"error"}]:[]);runtimeOptions=u([],...ngDevMode?[{debugName:"runtimeOptions"}]:[]);consentBaseUrl=u(...ngDevMode?[void 0,{debugName:"consentBaseUrl"}]:[]);optionsLoaded=g();fileDownload=g();lookup=c(z,{optional:!0});http=c(S,{optional:!0});destroyRef=c(Q);loadedOptions=m([],...ngDevMode?[{debugName:"loadedOptions"}]:[]);controlId=`dga-df-${Math.random().toString(36).slice(2,9)}`;get control(){return this.form().controls[this.field().name]}labelText=p(()=>I(this.field().label,this.locale()),...ngDevMode?[{debugName:"labelText"}]:[]);hintText=p(()=>I(this.field().hint,this.locale()),...ngDevMode?[{debugName:"hintText"}]:[]);placeholderText=p(()=>I(this.field().placeholder,this.locale()),...ngDevMode?[{debugName:"placeholderText"}]:[]);resolvedOptions=p(()=>{const e=this.runtimeOptions();if(e.length)return e;const t=this.loadedOptions();return t.length?t:this.field().options??[]},...ngDevMode?[{debugName:"resolvedOptions"}]:[]);selectOptions=p(()=>this.resolvedOptions().map(e=>({value:String(e.value),label:this.optionLabel(e),disabled:!!e.disabled})),...ngDevMode?[{debugName:"selectOptions"}]:[]);ngOnInit(){const e=this.field();if(this.mapExistingFileValue(),e.consentServiceName&&this.consentBaseUrl()&&this.http&&this.fetchConsent(e),e.lookupDomain&&e.lookupName&&this.lookup){if(e.dependsOn&&e.useParentId!==!1){const t=this.form().get(e.dependsOn);if(t){t.valueChanges.pipe(We(t.value),T(this.destroyRef)).subscribe(a=>this.fetchLookup(e,a));return}}e.searchLookup||this.fetchLookup(e)}}mapExistingFileValue(){const e=this.field();if(e.type!=="file"&&e.type!=="multifile")return;const t=this.control.value;if(!Array.isArray(t)||!t.length)return;const a=t[0];if(!a||!("Id"in a)||!("FileName"in a))return;const n=t.map(o=>({id:String(o.Id),name:o.FileName,size:0}));this.control.setValue(n,{emitEvent:!1})}fetchConsent(e){const a=`${this.consentBaseUrl().replace(/\/$/,"")}/consents/${encodeURIComponent(e.consentServiceName)}`;this.http.get(a).pipe(Ke(()=>x(null)),T(this.destroyRef)).subscribe(n=>{if(!n?.Content)return;const o={en:n.Content.En||n.Content.Ar||"",ar:n.Content.Ar||n.Content.En||""};if(e.type==="checkbox"){const r=e.options?.length?[...e.options]:[{value:!0,label:{en:"",ar:""}}];r[0]={...r[0],label:o},this.loadedOptions.set(r),this.optionsLoaded.emit({name:e.name,options:r})}})}fetchLookup(e,t,a){if(!this.lookup||!e.lookupDomain||!e.lookupName)return;const n=e.minSearchLength??(e.searchLookup?3:0);a!=null&&a.length<n||this.lookup.lookup({domain:e.lookupDomain,name:e.lookupName,parentId:e.useParentId!==!1&&t!=null&&t!==""?t:void 0,search:a||void 0,searchParamName:e.searchParamName}).pipe(T(this.destroyRef)).subscribe(o=>{const r=this.applyLookupFilter(o);this.loadedOptions.set(r),this.optionsLoaded.emit({name:e.name,options:r})})}optionLabel(e){return I(e.label,this.locale())}isPasswordField(){const e=this.field().name;return e==="password"||e==="confirmPassword"}isChecked(e){const t=this.control?.value;return Array.isArray(t)&&t.some(a=>a==e)}toggleCheckbox(e,t){const a=Array.isArray(this.control.value)?[...this.control.value]:[],n=t?a.some(o=>o==e)?a:[...a,e]:a.filter(o=>o!=e);this.control.setValue(n),this.control.markAsDirty()}onSelectSearchQuery(e){const t=this.field();t.searchLookup&&this.fetchLookup(t,this.form().get(t.dependsOn??"")?.value,e)}onStagedFileDownload(e){this.fileDownload.emit(e)}onRestrict(e){const t=this.field().inputRestriction;if(!t)return;const a=e.target;let n=a.value;t==="numbers"&&(n=n.replace(/\D+/g,"")),t==="english"&&(n=n.replace(/[^a-zA-Z0-9\s.,\-_/]/g,"")),t==="arabic"&&(n=n.replace(/[^\u0600-\u06FF0-9\s.,\-_/]/g,"")),n!==a.value&&(a.value=n,this.control.setValue(n))}applyLookupFilter(e){const t=this.field().lookupFilter;return t?e.filter(a=>a[t.property]===t.value):e}static ɵfac=l.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:D,deps:[],target:l.ɵɵFactoryTarget.Component});static ɵcmp=l.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"22.0.7",type:D,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},consentBaseUrl:{classPropertyName:"consentBaseUrl",publicName:"consentBaseUrl",isSignal:!0,isRequired:!1,transformFunction:null}},outputs:{optionsLoaded:"optionsLoaded",fileDownload:"fileDownload"},host:{properties:{"class.hidden":'field().type === "hidden"'},classAttribute:"block w-full"},ngImport:l,template:`
2
158
  @if (field().type !== 'hidden') {
3
159
  <dga-field
4
160
  [label]="labelText()"
@@ -72,6 +228,7 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
72
228
  [dgaInvalid]="!!error()"
73
229
  [disabled]="!!field().disabled"
74
230
  [label]="labelText()"
231
+ [allowDuplicates]="field().allowDuplicates ?? false"
75
232
  />
76
233
  }
77
234
  @case ('toggle') {
@@ -91,16 +248,40 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
91
248
  [disabled]="!!field().disabled"
92
249
  />
93
250
  }
251
+ @case ('multifile') {
252
+ <dga-upload
253
+ [formControl]="control"
254
+ [accept]="field().acceptedFileTypes || ''"
255
+ multiple
256
+ [maxFiles]="field().maxFiles || 0"
257
+ [maxSize]="field().maxFileSize || 0"
258
+ [disabled]="!!field().disabled"
259
+ />
260
+ }
261
+ @case ('multifilestaged') {
262
+ <dga-dynamic-form-staged-upload
263
+ [formControl]="control"
264
+ [accept]="field().acceptedFileTypes || ''"
265
+ [maxFileSize]="field().maxFileSize || 0"
266
+ [maxFiles]="field().maxFiles || 0"
267
+ [uploadEndpoint]="field().uploadEndpoint || ''"
268
+ [deleteEndpoint]="field().deleteEndpoint || ''"
269
+ [deleteMethod]="field().deleteMethod || 'DELETE'"
270
+ [disabled]="!!field().disabled"
271
+ (fileDownload)="onStagedFileDownload($event)"
272
+ />
273
+ }
94
274
  @case ('select') {
95
275
  <dga-select
96
276
  [formControl]="control"
97
277
  [options]="selectOptions()"
98
278
  [searchable]="!!field().searchLookup || (selectOptions().length > 8)"
279
+ [openMode]="field().alwaysOpen ? 'always' : 'dropdown'"
99
280
  [placeholder]="placeholderText()"
100
281
  [label]="labelText()"
101
282
  [dgaInvalid]="!!error()"
102
283
  [disabled]="!!field().disabled"
103
- (valueChange)="onSelectSearch($event)"
284
+ (searchChange)="onSelectSearchQuery($event)"
104
285
  />
105
286
  }
106
287
  @case ('multiselect') {
@@ -157,10 +338,23 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
157
338
  }
158
339
  </div>
159
340
  }
341
+ @case ('password') {
342
+ <input
343
+ dgaInput
344
+ type="password"
345
+ [id]="controlId"
346
+ [attr.placeholder]="placeholderText()"
347
+ [attr.maxlength]="field().maxLength || null"
348
+ [readonly]="field().readonly || null"
349
+ [formControl]="control"
350
+ [dgaInvalid]="!!error()"
351
+ autocomplete="new-password"
352
+ />
353
+ }
160
354
  @default {
161
355
  <input
162
356
  dgaInput
163
- type="text"
357
+ [attr.type]="isPasswordField() ? 'password' : 'text'"
164
358
  [id]="controlId"
165
359
  [attr.placeholder]="placeholderText()"
166
360
  [attr.maxlength]="field().maxLength || null"
@@ -173,7 +367,7 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
173
367
  }
174
368
  </dga-field>
175
369
  }
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:K,selector:"input[dgaInput], textarea[dgaInput]",inputs:["size","dgaInvalid","status"]},{kind:"component",type:W,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:ie,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:Z,selector:"dga-chip-input",inputs:["value","placeholder","label","removeLabel","disabled","dgaInvalid","status"],outputs:["valueChange"]},{kind:"component",type:Q,selector:"dga-switch",inputs:["checked","size","label","disabled"],outputs:["checkedChange","changed"]},{kind:"component",type:ee,selector:"dga-upload",inputs:["files","accept","multiple","appearance","hint","browseLabel","removeLabel","disabled","maxSize","maxFiles","dragging"],outputs:["filesChange","rejected","draggingChange"]},{kind:"component",type:te,selector:"dga-number-input",inputs:["value","min","max","step","size","label","controlId","name","incrementLabel","decrementLabel","disabled","dgaInvalid","status"],outputs:["valueChange"]},{kind:"component",type:oe,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,K,W,Y,J,X,ie,Z,Q,ee,te,oe],template:`
370
+ `,isInline:!0,dependencies:[{kind:"ngmodule",type:O},{kind:"directive",type:h.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:h.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:h.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:h.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:h.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"ngmodule",type:Z},{kind:"component",type:ie,selector:"dga-field",inputs:["label","labelHint","hint","error","status","statusMessage","controlId","required"]},{kind:"directive",type:ne,selector:"input[dgaInput], textarea[dgaInput]",inputs:["size","dgaInvalid","status"]},{kind:"component",type:oe,selector:"dga-select",inputs:["options","value","multiple","searchable","openMode","clearable","size","status","placeholder","label","removeLabel","clearLabel","emptyLabel","disabled","dgaInvalid"],outputs:["valueChange","searchChange"]},{kind:"directive",type:re,selector:'input[type="checkbox"][dgaCheckbox]',inputs:["size","dgaInvalid"]},{kind:"directive",type:le,selector:'input[type="radio"][dgaRadio]',inputs:["size","dgaInvalid"]},{kind:"component",type:se,selector:"dga-phone-input",inputs:["countries","countryCode","nationalNumber","controlId","name","placeholder","autocomplete","codeLabel","disabled","required","invalid","value"],outputs:["countryCodeChange","nationalNumberChange","valueChange"]},{kind:"component",type:he,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:de,selector:"dga-chip-input",inputs:["value","placeholder","label","removeLabel","disabled","allowDuplicates","dgaInvalid","status"],outputs:["valueChange"]},{kind:"component",type:ue,selector:"dga-switch",inputs:["checked","size","label","disabled"],outputs:["checkedChange","changed"]},{kind:"component",type:ce,selector:"dga-upload",inputs:["files","accept","multiple","appearance","hint","browseLabel","removeLabel","disabled","maxSize","maxFiles","dragging"],outputs:["filesChange","rejected","draggingChange"]},{kind:"component",type:pe,selector:"dga-number-input",inputs:["value","min","max","step","size","label","controlId","name","incrementLabel","decrementLabel","disabled","dgaInvalid","status"],outputs:["valueChange"]},{kind:"component",type:fe,selector:"dga-otp",inputs:["length","value","label","alphanumeric","disabled","dgaInvalid"],outputs:["valueChange","completed"]},{kind:"component",type:v,selector:"dga-dynamic-form-staged-upload",inputs:["accept","maxFileSize","maxFiles","uploadEndpoint","deleteEndpoint","deleteMethod","hint","browseLabel","removeLabel","downloadLabel","uploadingLabel","errorLabel","disabled"],outputs:["fileDownload"]}],changeDetection:l.ChangeDetectionStrategy.OnPush})}l.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:D,decorators:[{type:R,args:[{selector:"dga-dynamic-form-field",standalone:!0,imports:[O,Z,ie,ne,oe,re,le,se,he,de,ue,ce,pe,fe,v],template:`
177
371
  @if (field().type !== 'hidden') {
178
372
  <dga-field
179
373
  [label]="labelText()"
@@ -247,6 +441,7 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
247
441
  [dgaInvalid]="!!error()"
248
442
  [disabled]="!!field().disabled"
249
443
  [label]="labelText()"
444
+ [allowDuplicates]="field().allowDuplicates ?? false"
250
445
  />
251
446
  }
252
447
  @case ('toggle') {
@@ -266,16 +461,40 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
266
461
  [disabled]="!!field().disabled"
267
462
  />
268
463
  }
464
+ @case ('multifile') {
465
+ <dga-upload
466
+ [formControl]="control"
467
+ [accept]="field().acceptedFileTypes || ''"
468
+ multiple
469
+ [maxFiles]="field().maxFiles || 0"
470
+ [maxSize]="field().maxFileSize || 0"
471
+ [disabled]="!!field().disabled"
472
+ />
473
+ }
474
+ @case ('multifilestaged') {
475
+ <dga-dynamic-form-staged-upload
476
+ [formControl]="control"
477
+ [accept]="field().acceptedFileTypes || ''"
478
+ [maxFileSize]="field().maxFileSize || 0"
479
+ [maxFiles]="field().maxFiles || 0"
480
+ [uploadEndpoint]="field().uploadEndpoint || ''"
481
+ [deleteEndpoint]="field().deleteEndpoint || ''"
482
+ [deleteMethod]="field().deleteMethod || 'DELETE'"
483
+ [disabled]="!!field().disabled"
484
+ (fileDownload)="onStagedFileDownload($event)"
485
+ />
486
+ }
269
487
  @case ('select') {
270
488
  <dga-select
271
489
  [formControl]="control"
272
490
  [options]="selectOptions()"
273
491
  [searchable]="!!field().searchLookup || (selectOptions().length > 8)"
492
+ [openMode]="field().alwaysOpen ? 'always' : 'dropdown'"
274
493
  [placeholder]="placeholderText()"
275
494
  [label]="labelText()"
276
495
  [dgaInvalid]="!!error()"
277
496
  [disabled]="!!field().disabled"
278
- (valueChange)="onSelectSearch($event)"
497
+ (searchChange)="onSelectSearchQuery($event)"
279
498
  />
280
499
  }
281
500
  @case ('multiselect') {
@@ -332,10 +551,23 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
332
551
  }
333
552
  </div>
334
553
  }
554
+ @case ('password') {
555
+ <input
556
+ dgaInput
557
+ type="password"
558
+ [id]="controlId"
559
+ [attr.placeholder]="placeholderText()"
560
+ [attr.maxlength]="field().maxLength || null"
561
+ [readonly]="field().readonly || null"
562
+ [formControl]="control"
563
+ [dgaInvalid]="!!error()"
564
+ autocomplete="new-password"
565
+ />
566
+ }
335
567
  @default {
336
568
  <input
337
569
  dgaInput
338
- type="text"
570
+ [attr.type]="isPasswordField() ? 'password' : 'text'"
339
571
  [id]="controlId"
340
572
  [attr.placeholder]="placeholderText()"
341
573
  [attr.maxlength]="field().maxLength || null"
@@ -348,7 +580,7 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
348
580
  }
349
581
  </dga-field>
350
582
  }
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 Pe={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"},Be={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"},_e={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(Oe);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=u(()=>this.localeInput()??ge(),...ngDevMode?[{debugName:"locale"}]:[]);wizardEnabled=u(()=>!!this.config().wizard?.enabled&&!!this.config().wizard?.steps?.length,...ngDevMode?[{debugName:"wizardEnabled"}]:[]);allFields=u(()=>pe(this.config().fields,this.config().wizard?.steps),...ngDevMode?[{debugName:"allFields"}]:[]);currentStepFields=u(()=>this.wizardEnabled()?this.config().wizard.steps[this.activeStep()]?.fields??[]:this.config().fields,...ngDevMode?[{debugName:"currentStepFields"}]:[]);visibleFields=u(()=>{const e=this.formValues(),t=this.optionsMap();return this.currentStepFields().filter(o=>V(o,e,a=>t[a]))},...ngDevMode?[{debugName:"visibleFields"}]:[]);stepperSteps=u(()=>{const e=this.config().wizard?.steps??[],t=this.activeStep(),o=!!this.config().wizard?.allowSkipSteps;return e.map((a,n)=>({label:this.labelOf(a.title),description:a.description?this.labelOf(a.description):void 0,disabled:!o&&n>t}))},...ngDevMode?[{debugName:"stepperSteps"}]:[]);descriptionText=u(()=>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();Ie(()=>{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(Te(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",Pe[o],Be[a],_e[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)}canActivateStep=(e,t)=>{if(e<=t)return!0;const o=this.config().wizard;return!o?.allowSkipSteps&&e>t+1?!1:o?.validateOnStepChange===!1?!0:(this.markStepTouched(),this.stepValid())};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.allFields()),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:`
583
+ `,changeDetection:V.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}]}],consentBaseUrl:[{type:l.Input,args:[{isSignal:!0,alias:"consentBaseUrl",required:!1}]}],optionsLoaded:[{type:l.Output,args:["optionsLoaded"]}],fileDownload:[{type:l.Output,args:["fileDownload"]}]}});const ct={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"},pt={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"},ft={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 H(i){return Number.isFinite(i)?Math.min(12,Math.max(1,Math.round(i))):12}class N{config=u.required(...ngDevMode?[{debugName:"config"}]:[]);localeInput=u(null,{...ngDevMode?{debugName:"localeInput"}:{},alias:"locale"});formSubmitted=g();formError=g();clearButtonClick=g();draftSaved=g();fileDownload=g();fb=c(_e);formService=c(C);submitAdapter=c(E,{optional:!0});toast=c(B,{optional:!0});i18n=c(U,{optional:!0});destroyRef=c(Q);registry=c(L);form;formSignature="";valueChangesSub=null;activeStep=m(0,...ngDevMode?[{debugName:"activeStep"}]:[]);submitting=m(!1,...ngDevMode?[{debugName:"submitting"}]:[]);optionsMap=m({},...ngDevMode?[{debugName:"optionsMap"}]:[]);formValues=m({},...ngDevMode?[{debugName:"formValues"}]:[]);locale=p(()=>this.localeInput()??Te(),...ngDevMode?[{debugName:"locale"}]:[]);wizardEnabled=p(()=>!!this.config().wizard?.enabled&&!!this.config().wizard?.steps?.length,...ngDevMode?[{debugName:"wizardEnabled"}]:[]);allFields=p(()=>Le(this.config().fields,this.config().wizard?.steps),...ngDevMode?[{debugName:"allFields"}]:[]);currentStepFields=p(()=>this.wizardEnabled()?this.config().wizard.steps[this.activeStep()]?.fields??[]:this.config().fields,...ngDevMode?[{debugName:"currentStepFields"}]:[]);visibleFields=p(()=>{const e=this.formValues(),t=this.optionsMap(),a=this.allFields(),n=o=>a.find(r=>r.name===o);return this.currentStepFields().filter(o=>W(o,e,r=>t[r],n))},...ngDevMode?[{debugName:"visibleFields"}]:[]);stepperSteps=p(()=>{const e=this.config().wizard?.steps??[],t=this.activeStep(),a=!!this.config().wizard?.allowSkipSteps;return e.map((n,o)=>({label:this.labelOf(n.title),description:n.description?this.labelOf(n.description):void 0,disabled:!a&&o>t}))},...ngDevMode?[{debugName:"stepperSteps"}]:[]);descriptionText=p(()=>this.labelOf(this.config().description),...ngDevMode?[{debugName:"descriptionText"}]:[]);constructor(){this.form=this.fb.group({}),this.destroyRef.onDestroy(()=>this.valueChangesSub?.unsubscribe()),k(()=>{const e=this.allFields(),t=e.map(n=>`${n.name}:${n.type}`).join("|"),a=this.config();q(()=>{if(t===this.formSignature)return;const n=Object.keys(this.form.controls).length?this.form.getRawValue():null,o=this.formSignature==="";this.formSignature=t;const r=Ce(this.fb,e);if(n)r.patchValue(n,{emitEvent:!1});else if(o&&a.formId&&a.enableLocalStorageSave){const f=this.formService.loadDraft(a.formId);f&&r.patchValue(f,{emitEvent:!1})}this.form=r,De(r,e);const s=r.get("password"),d=r.get("confirmPassword");s&&d&&s.valueChanges.pipe(T(this.destroyRef)).subscribe(()=>d.updateValueAndValidity({emitEvent:!1})),o||this.activeStep.set(0),this.formValues.set(r.getRawValue()),this.valueChangesSub?.unsubscribe(),this.valueChangesSub=r.valueChanges.pipe(Ge(r.getRawValue())).subscribe(f=>this.formValues.set(f))})}),k(()=>{const e=this.formValues(),t=this.optionsMap();if(this.form)for(const a of this.allFields()){const n=this.form.get(a.name);if(!n||!a.requiredWhen)continue;const o=this.allFields(),s=K(a,e,d=>t[d],d=>o.find(f=>f.name===d));Fe(n,a,s)}}),k(()=>{const e=this.allFields();q(()=>{if(!this.form)return;const t={};for(const a of e)a.value!==void 0&&(t[a.name]=a.value);Object.keys(t).length&&this.form.patchValue(t,{emitEvent:!1})})}),k(()=>{const e=this.activeStep(),t=this.config().wizard;q(()=>{!t?.steps?.[e]?.initialData||!this.form||this.form.patchValue(t.steps[e].initialData,{emitEvent:!1})})})}labelOf(e){return e?this.i18n?this.i18n.resolveLabel(e,this.locale()):I(e,this.locale()):""}submitLabel(){const e=this.config();return this.wizardEnabled()?this.labelOf(e.wizard?.submitButtonText??e.submitButtonLabel)||(this.locale()==="en"?"Submit":"إرسال"):this.labelOf(e.submitButtonLabel)||(this.locale()==="en"?"Submit":"إرسال")}nextLabel(){return this.labelOf(this.config().wizard?.nextButtonText)||(this.locale()==="en"?"Next":"التالي")}previousLabel(){return this.labelOf(this.config().wizard?.previousButtonText)||(this.locale()==="en"?"Previous":"السابق")}clearLabel(){return this.labelOf(this.config().clearButtonLabel)}saveLabel(){return this.labelOf(this.config().saveButtonLabel??this.config().wizard?.saveButtonText)||(this.locale()==="en"?"Save":"حفظ")}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){const t=this.allFields(),a=n=>t.find(o=>o.name===n);return K(e,this.formValues(),n=>this.optionsMap()[n],a)}fieldError(e){const t=this.form?.get(e.name);if(!t||!(t.touched||t.dirty)||!t.errors)return"";const a=t.errors,n=e.validation?.errorMessages,o=this.locale(),r=s=>n?.[s]?this.labelOf(n[s]):"";return a.required?r("required")||(o==="en"?"Required":"مطلوب"):a.email?r("email")||(o==="en"?"Invalid email":"بريد غير صالح"):a.minlength?r("minLength")||(o==="en"?"Too short":"قصير جداً"):a.maxlength?r("maxLength")||(o==="en"?"Too long":"طويل جداً"):a.pattern?r("pattern")||(o==="en"?"Invalid format":"صيغة غير صالحة"):a.maxChips?r("maxChips")||(o==="en"?"Too many items":"عدد العناصر كبير"):a.invalidFormat?r("invalidFormat")||(o==="en"?"Invalid format":"صيغة غير صالحة"):a.invalidPrefix?r("invalidPrefix")||(o==="en"?"Invalid prefix":"بادئة غير صالحة"):a.invalidLength?r("invalidLength")||(o==="en"?"Invalid length":"طول غير صالح"):a.passwordMismatch?r("passwordMismatch")||(o==="en"?"Passwords do not match":"كلمات المرور غير متطابقة"):a.endDateAfterStartDate?r("endDateAfterStartDate")||(o==="en"?"End date must be after start date":"تاريخ النهاية يجب أن يكون بعد تاريخ البداية"):a.uploadInProgress?r("uploadInProgress")||(o==="en"?"Upload in progress":"جاري رفع الملفات"):a.maxFiles?r("maxFiles")||(o==="en"?"Too many files":"عدد الملفات كبير"):a.minDate?r("minDate")||(o==="en"?"Date is too early":"التاريخ مبكر جداً"):a.maxDate?r("maxDate")||(o==="en"?"Date is too late":"التاريخ متأخر جداً"):o==="en"?"Invalid":"غير صالح"}columnClass(e){const t=e.columns,a=H(t?.sm??t?.md??12),n=H(t?.md??a),o=H(t?.lg??n);return["w-full",ct[a],pt[n],ft[o]].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)}canActivateStep=(e,t)=>{if(e<=t)return!0;const a=this.config().wizard;return!a?.allowSkipSteps&&e>t+1?!1:a?.validateOnStepChange===!1?!0:(this.markStepTouched(),this.stepValid())};nextStep(){this.config().wizard?.validateOnStepChange!==!1&&(this.markStepTouched(),!this.stepValid())||this.isLastStep()||(this.activeStep.update(t=>t+1),this.skipEmptyStepsForward())}skipEmptyStepsForward(){const e=this.config().wizard;if(e?.steps)for(;this.activeStep()<e.steps.length-1&&this.visibleFields().length===0;)this.activeStep.update(t=>t+1)}onClear(){this.form.reset();for(const t of this.allFields()){const a=this.form.get(t.name);a&&a.setValue(t.value!==void 0?t.value:["checkbox","multiselect","chips","file","multifile","multifilestaged"].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(),a=t.formId;a&&this.formService.saveDraft(a,e,this.allFields());const n=t.wizard;if(n?.autoSave&&n.saveEndpoint&&this.submitAdapter){const o=this.formService.buildPayload(t,e,this.allFields());this.submitAdapter.submit({endpoint:n.saveEndpoint,method:n.saveMethod??"POST",body:o}).subscribe({error:r=>this.formError.emit(r)})}this.draftSaved.emit(e),this.toast?.show({variant:"success",message:this.locale()==="en"?"Draft saved":"تم حفظ المسودة"})}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":"يرجى تصحيح الحقول المحددة"});return}const e=this.config(),t=this.form.getRawValue(),a=new Set(this.collectVisibleFieldNames()),n={};for(const o of this.allFields())!a.has(o.name)&&o.type!=="hidden"||(n[o.name]=t[o.name]);if(e.emitOnly||!e.endpoint){this.formSubmitted.emit(n),this.toast?.show({variant:"success",message:this.labelOf(e.successMessage)||(this.locale()==="en"?"Submitted":"تم الإرسال")});return}this.submitting.set(!0),this.formService.submitForm(e,n,this.allFields()).subscribe({next:o=>{this.submitting.set(!1),this.formSubmitted.emit(o),e.formId&&this.formService.clearDraft(e.formId),this.toast?.show({variant:"success",message:this.labelOf(e.successMessage)||(this.locale()==="en"?"Submitted successfully":"تم الإرسال بنجاح")})},error:o=>{this.submitting.set(!1),this.formError.emit(o),this.toast?.show({variant:"error",message:this.labelOf(e.errorMessage)||(this.locale()==="en"?"Submission failed":"فشل الإرسال")})}})}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.config().wizard?.steps[this.activeStep()]?.optional?!0:this.visibleFields().every(t=>{const a=this.form.get(t.name);return!a||a.disabled||a.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(),a=this.wizardEnabled()?this.allFields():this.config().fields,n=o=>a.find(r=>r.name===o);return a.filter(o=>o.type==="hidden"||W(o,e,r=>t[r],n)).map(o=>o.name)}static ɵfac=l.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:N,deps:[],target:l.ɵɵFactoryTarget.Component});static ɵcmp=l.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"22.0.7",type:N,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",fileDownload:"fileDownload"},host:{classAttribute:"block w-full"},ngImport:l,template:`
352
584
  <form class="flex w-full flex-col gap-3xl" [formGroup]="form" (ngSubmit)="onSubmit()">
353
585
  @if (descriptionText()) {
354
586
  <p class="text-body-md text-paragraph">{{ descriptionText() }}</p>
@@ -359,7 +591,7 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
359
591
  [steps]="stepperSteps()"
360
592
  [(active)]="activeStep"
361
593
  [beforeActiveChange]="canActivateStep"
362
- [label]="locale() === 'en' ? 'Steps' : '\u0627\u0644\u062E\u0637\u0648\u0627\u062A'"
594
+ [label]="locale() === 'en' ? 'Steps' : 'الخطوات'"
363
595
  />
364
596
  }
365
597
 
@@ -376,7 +608,9 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
376
608
  [required]="isRequired(field)"
377
609
  [error]="fieldError(field)"
378
610
  [runtimeOptions]="optionsMap()[field.name] || []"
611
+ [consentBaseUrl]="config().consentBaseUrl"
379
612
  (optionsLoaded)="onOptionsLoaded($event)"
613
+ (fileDownload)="fileDownload.emit($event)"
380
614
  />
381
615
  }
382
616
  </div>
@@ -428,7 +662,7 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
428
662
  }
429
663
  </div>
430
664
  </form>
431
- `,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:re,selector:"button[dgaButton], a[dgaButton]",inputs:["variant","size","iconOnly","cooldownMs","loading","dgaButtonClass"]},{kind:"component",type:ae,selector:"dga-stepper",inputs:["steps","active","label","beforeActiveChange"],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,re,ae,$],template:`
665
+ `,isInline:!0,dependencies:[{kind:"ngmodule",type:O},{kind:"directive",type:h.ɵNgNoValidate,selector:"form:not([ngNoForm]):not([ngNativeValidate])"},{kind:"directive",type:h.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:h.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"component",type:D,selector:"dga-dynamic-form-field",inputs:["field","form","locale","required","error","runtimeOptions","consentBaseUrl"],outputs:["optionsLoaded","fileDownload"]},{kind:"directive",type:A,selector:"button[dgaButton], a[dgaButton]",inputs:["variant","size","iconOnly","cooldownMs","loading","dgaButtonClass"]},{kind:"component",type:me,selector:"dga-stepper",inputs:["steps","active","label","beforeActiveChange"],outputs:["activeChange"]},{kind:"directive",type:X,selector:"[ngComponentOutlet]",inputs:["ngComponentOutlet","ngComponentOutletInputs","ngComponentOutletInjector","ngComponentOutletEnvironmentInjector","ngComponentOutletContent","ngComponentOutletNgModule"],exportAs:["ngComponentOutlet"]}],changeDetection:l.ChangeDetectionStrategy.OnPush})}l.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:l,type:N,decorators:[{type:R,args:[{selector:"dga-dynamic-form",standalone:!0,imports:[O,D,A,me,X],template:`
432
666
  <form class="flex w-full flex-col gap-3xl" [formGroup]="form" (ngSubmit)="onSubmit()">
433
667
  @if (descriptionText()) {
434
668
  <p class="text-body-md text-paragraph">{{ descriptionText() }}</p>
@@ -439,7 +673,7 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
439
673
  [steps]="stepperSteps()"
440
674
  [(active)]="activeStep"
441
675
  [beforeActiveChange]="canActivateStep"
442
- [label]="locale() === 'en' ? 'Steps' : '\u0627\u0644\u062E\u0637\u0648\u0627\u062A'"
676
+ [label]="locale() === 'en' ? 'Steps' : 'الخطوات'"
443
677
  />
444
678
  }
445
679
 
@@ -456,7 +690,9 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
456
690
  [required]="isRequired(field)"
457
691
  [error]="fieldError(field)"
458
692
  [runtimeOptions]="optionsMap()[field.name] || []"
693
+ [consentBaseUrl]="config().consentBaseUrl"
459
694
  (optionsLoaded)="onOptionsLoaded($event)"
695
+ (fileDownload)="fileDownload.emit($event)"
460
696
  />
461
697
  }
462
698
  </div>
@@ -508,4 +744,4 @@ import*as l from"@angular/core";import{InjectionToken as h,inject as s,Injectabl
508
744
  }
509
745
  </div>
510
746
  </form>
511
- `,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 he={resolveLabel(r,e){return e==="en"?r.en:r.ar}},be={getToken:()=>b(null)},ve={lookup:()=>b([])},ze=new Set(["__proto__","constructor","prototype"]);function q(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 ye(r=s(D),e={}){return{lookup(t){try{const a=`${q(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(Ge):[]))}catch(o){return ne(()=>o)}}}}function Ge(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))ze.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 xe(r=s(D),e={}){return{submit(t){try{const o=q(t.endpoint,e.allowedOrigins),a=new Ne(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 ne(()=>o)}}}}function Se(r=s(Ae)){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=r.requireAllowedOrigins??!1,n={allowedOrigins:r.allowedOrigins};if(a&&(e||t)&&!r.allowedOrigins?.length)throw new Error("DGA dynamic form: provideDgaDynamicForm({ allowedOrigins: [...] }) is required when requireAllowedOrigins is true. ");return ke()&&(e||t)&&!r.allowedOrigins?.length&&console.warn("[dga-dynamic-form] HTTP adapters enabled without allowedOrigins. Absolute schema URLs can reach any https host \u2014 set allowedOrigins (and requireAllowedOrigins: true) for CMS schemas."),De([{provide:A,useValue:he},{provide:L,useValue:be},{provide:O,useFactory:()=>e?ye(s(D),n):ve},{provide:w,useFactory:()=>t?xe(s(D),n):{submit:()=>b(null)}},{provide:T,useFactory:()=>o?Se():{show:()=>{}}}])}function je(r,e,t=""){return r?e==="en"?r.en:r.ar:t}export{L 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,w as DGA_SUBMIT_ADAPTER,I as DgaDynamicForm,g as DgaDynamicFormField,x as DgaDynamicFormFieldRegistry,y as DgaDynamicFormService,Se as createDgaToastAdapter,ye as createHttpLookupAdapter,xe as createHttpSubmitAdapter,he as defaultFormI18nAdapter,q as dgaAssertSafeHttpUrl,ue as dgaBuildFormGroup,F as dgaBuildValidators,pe as dgaCollectFields,ce as dgaCreateControl,de as dgaDefaultValueForField,ge as dgaDetectLocale,M as dgaFindSelectedOption,V as dgaIsFieldVisible,R as dgaIsRequiredWhen,v as dgaResolveLabel,me as dgaSetControlValidators,ve as emptyLookupAdapter,be as noopCaptchaAdapter,$e as provideDgaDynamicForm,je as resolveFormLabel};
747
+ `,changeDetection:V.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"]}],fileDownload:[{type:l.Output,args:["fileDownload"]}]}});export{_ as DGA_CAPTCHA_ADAPTER,ve as DGA_DATE_RANGE_PAIRS,be as DGA_DYNAMIC_FORM_FIELD_REGISTRY,U as DGA_FORM_I18N_ADAPTER,B as DGA_FORM_TOAST_ADAPTER,$ as DGA_HTTP_SECURITY,z as DGA_LOOKUP_ADAPTER,E as DGA_SUBMIT_ADAPTER,N as DgaDynamicForm,D as DgaDynamicFormField,L as DgaDynamicFormFieldRegistry,C as DgaDynamicFormService,Re as createDgaToastAdapter,Pe as createHttpLookupAdapter,Ve as createHttpSubmitAdapter,Ae as defaultFormI18nAdapter,De as dgaApplyCrossFieldValidators,F as dgaAssertSafeHttpUrl,Ce as dgaBuildFormGroup,G as dgaBuildValidators,Le as dgaCollectFields,Ie as dgaCreateControl,Se as dgaDefaultValueForField,Te as dgaDetectLocale,ke as dgaFindSelectedOption,W as dgaIsFieldVisible,K as dgaIsRequiredWhen,ye as dgaPasswordMatchValidator,I as dgaResolveLabel,xe as dgaSaudiMobileValidator,Fe as dgaSetControlValidators,Me as emptyLookupAdapter,Ee as noopCaptchaAdapter,dt as provideDgaDynamicForm,ut as resolveFormLabel};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@a.nemreen/dga-dynamic-form",
3
- "version": "0.1.7",
4
- "description": "Schema-driven dynamic forms for Angular, rendered with @a.nemreen/dga-ui controls",
3
+ "version": "0.1.8",
4
+ "description": "Schema-driven Angular dynamic forms 18 field types, wizard, cross-field validators (password, Saudi phone, date range), staged uploads, lookup, reCAPTCHA v3, and conditional visibility. Built on @a.nemreen/dga-ui.",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Ahmed Nemreen",
@@ -22,6 +22,10 @@
22
22
  "dynamic-form",
23
23
  "schema-form",
24
24
  "reactive-forms",
25
+ "wizard",
26
+ "validators",
27
+ "file-upload",
28
+ "recaptcha",
25
29
  "dga",
26
30
  "design-system",
27
31
  "rtl",
@@ -1,6 +1,6 @@
1
1
  import * as _angular_core from '@angular/core';
2
2
  import { InjectionToken, Type, OnInit, EnvironmentProviders } from '@angular/core';
3
- import { FormGroup, FormControl, FormBuilder, ValidatorFn, AbstractControl } from '@angular/forms';
3
+ import { FormGroup, FormControl, FormBuilder, ValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms';
4
4
  import { DgaStep, DgaSelectOption, DgaToastService } from '@a.nemreen/dga-ui';
5
5
  import { Observable } from 'rxjs';
6
6
  import { HttpClient } from '@angular/common/http';
@@ -24,7 +24,24 @@ interface DgaFormFieldOption {
24
24
  disabled?: boolean;
25
25
  [key: string]: unknown;
26
26
  }
27
- type DgaFormFieldType = 'text' | 'email' | 'textarea' | 'select' | 'multiselect' | 'checkbox' | 'radio' | 'phone' | 'file' | 'date' | 'chips' | 'toggle' | 'hidden' | 'number' | 'otp';
27
+ type DgaFormFieldType = 'text' | 'email' | 'textarea' | 'select' | 'multiselect' | 'checkbox' | 'radio' | 'phone' | 'file' | 'multifile' | 'multifilestaged' | 'date' | 'chips' | 'toggle' | 'hidden' | 'number' | 'otp' | 'password';
28
+ /** Pre-populated attachment from the server (edit mode). */
29
+ interface DgaExistingAttachment {
30
+ Id: number;
31
+ FileName: string;
32
+ ContentType: string;
33
+ }
34
+ type DgaStagedFileStatus = 'uploading' | 'success' | 'error';
35
+ /** Value shape for `multifilestaged` fields. */
36
+ interface DgaStagedFileRef {
37
+ fileId: string;
38
+ fileName: string;
39
+ sizeBytes?: number;
40
+ status?: DgaStagedFileStatus;
41
+ errorMessage?: string;
42
+ /** Client-only list identity when server reuses fileId. */
43
+ listKey?: string;
44
+ }
28
45
  type DgaInputRestriction = 'numbers' | 'english' | 'arabic';
29
46
  type DgaHttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
30
47
  interface DgaFormField {
@@ -40,10 +57,22 @@ interface DgaFormField {
40
57
  hidden?: boolean;
41
58
  dependsOn?: string;
42
59
  showOnValues?: unknown[];
60
+ /** Show when selected option English label matches. */
61
+ showOnTitleEn?: string;
62
+ /** Show when selected option Arabic label matches. */
63
+ showOnTitleAr?: string;
64
+ /** Extra text field shown when selected option has `requiresTextInput`. */
65
+ extraField?: boolean;
66
+ /** Trigger field for `extraField` (defaults to `dependsOn`). */
67
+ extraFieldTrigger?: string;
43
68
  showWhenOptionProperty?: {
44
69
  property: string;
45
70
  value: unknown;
46
71
  };
72
+ /** CMS consent key — fetched from `consentBaseUrl` on the form config. */
73
+ consentServiceName?: string;
74
+ /** Keep select dropdown open (DGA select `openMode="always"`). */
75
+ alwaysOpen?: boolean;
47
76
  lookupDomain?: string;
48
77
  lookupName?: string;
49
78
  lookupFilter?: {
@@ -60,6 +89,11 @@ interface DgaFormField {
60
89
  acceptedFileTypes?: string;
61
90
  maxFileSize?: number;
62
91
  maxFiles?: number;
92
+ /** POST URL for `multifilestaged` staging uploads. Response: `{ fileId, fileName? }`. */
93
+ uploadEndpoint?: string;
94
+ /** DELETE/POST URL for staged file removal. Use `:id` or `:fileId` placeholder. */
95
+ deleteEndpoint?: string;
96
+ deleteMethod?: 'DELETE' | 'POST';
63
97
  rows?: number;
64
98
  minDate?: string;
65
99
  maxDate?: string;
@@ -94,7 +128,9 @@ interface DgaFormField {
94
128
  required?: boolean;
95
129
  min?: number;
96
130
  max?: number;
97
- errorMessages?: Partial<Record<'required' | 'pattern' | 'minLength' | 'maxLength' | 'email' | 'min' | 'max' | 'minDate' | 'maxDate' | 'maxChips' | 'invalidFormat', DgaFormLabel>>;
131
+ minDate?: string;
132
+ maxDate?: string;
133
+ errorMessages?: Partial<Record<'required' | 'pattern' | 'minLength' | 'maxLength' | 'email' | 'min' | 'max' | 'minDate' | 'maxDate' | 'maxChips' | 'invalidFormat' | 'invalidPrefix' | 'invalidLength' | 'passwordMismatch' | 'endDateAfterStartDate' | 'uploadInProgress' | 'maxFiles', DgaFormLabel>>;
98
134
  };
99
135
  }
100
136
  interface DgaFormStep {
@@ -103,6 +139,8 @@ interface DgaFormStep {
103
139
  description?: DgaFormLabel;
104
140
  fields: DgaFormField[];
105
141
  optional?: boolean;
142
+ /** Pre-fill values when entering this wizard step. */
143
+ initialData?: Record<string, unknown>;
106
144
  }
107
145
  interface DgaWizardConfig {
108
146
  enabled: boolean;
@@ -138,6 +176,11 @@ interface DgaDynamicFormConfig {
138
176
  idempotencyKey?: string;
139
177
  /** When true, HTTP submit is skipped and only `formSubmitted` is emitted. */
140
178
  emitOnly?: boolean;
179
+ /**
180
+ * Base URL for CMS consent fetch (`consentServiceName` fields).
181
+ * e.g. `https://cms.example.com/api` → GET `{base}/consents/{name}`.
182
+ */
183
+ consentBaseUrl?: string;
141
184
  }
142
185
  type DgaFormLocale = 'ar' | 'en';
143
186
 
@@ -191,6 +234,10 @@ declare const DGA_LOOKUP_ADAPTER: InjectionToken<DgaLookupAdapter>;
191
234
  declare const DGA_SUBMIT_ADAPTER: InjectionToken<DgaSubmitAdapter>;
192
235
  declare const DGA_FORM_TOAST_ADAPTER: InjectionToken<DgaFormToastAdapter>;
193
236
  declare const DGA_CAPTCHA_ADAPTER: InjectionToken<DgaCaptchaAdapter>;
237
+ interface DgaHttpSecurityOptions {
238
+ allowedOrigins?: string[];
239
+ }
240
+ declare const DGA_HTTP_SECURITY: InjectionToken<DgaHttpSecurityOptions>;
194
241
  declare const DGA_FORM_I18N_ADAPTER: InjectionToken<DgaFormI18nAdapter>;
195
242
  declare const DGA_DYNAMIC_FORM_FIELD_REGISTRY: InjectionToken<DgaDynamicFieldRenderer[]>;
196
243
  /** Context passed into custom field renderer components (optional inject). */
@@ -226,8 +273,10 @@ declare class DgaDynamicForm {
226
273
  readonly formError: _angular_core.OutputEmitterRef<unknown>;
227
274
  readonly clearButtonClick: _angular_core.OutputEmitterRef<void>;
228
275
  readonly draftSaved: _angular_core.OutputEmitterRef<Record<string, unknown>>;
276
+ readonly fileDownload: _angular_core.OutputEmitterRef<DgaExistingAttachment>;
229
277
  private readonly fb;
230
278
  private readonly formService;
279
+ private readonly submitAdapter;
231
280
  private readonly toast;
232
281
  private readonly i18n;
233
282
  private readonly destroyRef;
@@ -271,6 +320,7 @@ declare class DgaDynamicForm {
271
320
  /** Keep stepper clicks aligned with Next validation / allowSkip rules. */
272
321
  readonly canActivateStep: (next: number, current: number) => boolean;
273
322
  nextStep(): void;
323
+ private skipEmptyStepsForward;
274
324
  onClear(): void;
275
325
  onSaveDraft(): void;
276
326
  onSubmit(): void;
@@ -283,7 +333,7 @@ declare class DgaDynamicForm {
283
333
  private visibleValid;
284
334
  private collectVisibleFieldNames;
285
335
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DgaDynamicForm, never>;
286
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DgaDynamicForm, "dga-dynamic-form", never, { "config": { "alias": "config"; "required": true; "isSignal": true; }; "localeInput": { "alias": "locale"; "required": false; "isSignal": true; }; }, { "formSubmitted": "formSubmitted"; "formError": "formError"; "clearButtonClick": "clearButtonClick"; "draftSaved": "draftSaved"; }, never, never, true, never>;
336
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DgaDynamicForm, "dga-dynamic-form", never, { "config": { "alias": "config"; "required": true; "isSignal": true; }; "localeInput": { "alias": "locale"; "required": false; "isSignal": true; }; }, { "formSubmitted": "formSubmitted"; "formError": "formError"; "clearButtonClick": "clearButtonClick"; "draftSaved": "draftSaved"; "fileDownload": "fileDownload"; }, never, never, true, never>;
287
337
  }
288
338
 
289
339
  declare class DgaDynamicFormField implements OnInit {
@@ -293,11 +343,14 @@ declare class DgaDynamicFormField implements OnInit {
293
343
  readonly required: _angular_core.InputSignalWithTransform<boolean, unknown>;
294
344
  readonly error: _angular_core.InputSignal<string>;
295
345
  readonly runtimeOptions: _angular_core.InputSignal<DgaFormFieldOption[]>;
346
+ readonly consentBaseUrl: _angular_core.InputSignal<string | undefined>;
296
347
  readonly optionsLoaded: _angular_core.OutputEmitterRef<{
297
348
  name: string;
298
349
  options: DgaFormFieldOption[];
299
350
  }>;
351
+ readonly fileDownload: _angular_core.OutputEmitterRef<DgaExistingAttachment>;
300
352
  private readonly lookup;
353
+ private readonly http;
301
354
  private readonly destroyRef;
302
355
  readonly loadedOptions: _angular_core.WritableSignal<DgaFormFieldOption[]>;
303
356
  readonly controlId: string;
@@ -308,14 +361,19 @@ declare class DgaDynamicFormField implements OnInit {
308
361
  readonly resolvedOptions: _angular_core.Signal<DgaFormFieldOption[]>;
309
362
  readonly selectOptions: _angular_core.Signal<DgaSelectOption[]>;
310
363
  ngOnInit(): void;
364
+ private mapExistingFileValue;
365
+ private fetchConsent;
366
+ private fetchLookup;
311
367
  optionLabel(opt: DgaFormFieldOption): string;
368
+ isPasswordField(): boolean;
312
369
  isChecked(value: unknown): boolean;
313
370
  toggleCheckbox(value: unknown, checked: boolean): void;
314
- onSelectSearch(_value: string | string[]): void;
371
+ onSelectSearchQuery(query: string): void;
372
+ onStagedFileDownload(event: DgaExistingAttachment): void;
315
373
  onRestrict(event: Event): void;
316
374
  private applyLookupFilter;
317
375
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DgaDynamicFormField, never>;
318
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DgaDynamicFormField, "dga-dynamic-form-field", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "form": { "alias": "form"; "required": true; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "runtimeOptions": { "alias": "runtimeOptions"; "required": false; "isSignal": true; }; }, { "optionsLoaded": "optionsLoaded"; }, never, never, true, never>;
376
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DgaDynamicFormField, "dga-dynamic-form-field", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "form": { "alias": "form"; "required": true; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "runtimeOptions": { "alias": "runtimeOptions"; "required": false; "isSignal": true; }; "consentBaseUrl": { "alias": "consentBaseUrl"; "required": false; "isSignal": true; }; }, { "optionsLoaded": "optionsLoaded"; "fileDownload": "fileDownload"; }, never, never, true, never>;
319
377
  }
320
378
 
321
379
  declare class DgaDynamicFormService {
@@ -354,8 +412,19 @@ declare function dgaFindSelectedOption(field: DgaFormField | undefined, value: u
354
412
  /**
355
413
  * Whether a field should be visible given the current form values.
356
414
  */
357
- declare function dgaIsFieldVisible(field: DgaFormField, values: Record<string, unknown>, getOptions?: (name: string) => DgaFormFieldOption[] | undefined): boolean;
358
- declare function dgaIsRequiredWhen(field: DgaFormField, values: Record<string, unknown>, getOptions?: (name: string) => DgaFormFieldOption[] | undefined): boolean;
415
+ declare function dgaIsFieldVisible(field: DgaFormField, values: Record<string, unknown>, getOptions?: (name: string) => DgaFormFieldOption[] | undefined, getField?: (name: string) => DgaFormField | undefined): boolean;
416
+ declare function dgaIsRequiredWhen(field: DgaFormField, values: Record<string, unknown>, getOptions?: (name: string) => DgaFormFieldOption[] | undefined, getField?: (name: string) => DgaFormField | undefined): boolean;
417
+
418
+ /** Known start/end date field pairs used by cross-field validators. */
419
+ declare const DGA_DATE_RANGE_PAIRS: ReadonlyArray<[string, string]>;
420
+ declare function dgaPasswordMatchValidator(passwordControl: AbstractControl): ValidatorFn;
421
+ /**
422
+ * Saudi mobile validation for type: phone fields.
423
+ * Accepts E.164 (+966…) or national digits (0xxxxxxxx or 5xxxxxxxx).
424
+ */
425
+ declare function dgaSaudiMobileValidator(control: AbstractControl): ValidationErrors | null;
426
+ /** Wire password match + date-range validators after form group is built. */
427
+ declare function dgaApplyCrossFieldValidators(form: FormGroup, fields: DgaFormField[]): void;
359
428
 
360
429
  /** Default bilingual label resolver. */
361
430
  declare const defaultFormI18nAdapter: DgaFormI18nAdapter;
@@ -363,16 +432,7 @@ declare const defaultFormI18nAdapter: DgaFormI18nAdapter;
363
432
  declare const noopCaptchaAdapter: DgaCaptchaAdapter;
364
433
  /** Empty lookup (returns []). Override in the host app. */
365
434
  declare const emptyLookupAdapter: DgaLookupAdapter;
366
- interface DgaHttpAdapterSecurityOptions {
367
- /**
368
- * Allowed URL origins for schema-driven HTTP (e.g. `https://api.example.com`).
369
- * Absolute URLs outside the list are rejected.
370
- * Same-origin paths (`/api/...`) are always allowed.
371
- *
372
- * Strongly recommended for CMS / untrusted schemas. When `requireAllowedOrigins`
373
- * is true (see `provideDgaDynamicForm`), omitting this throws at bootstrap.
374
- */
375
- allowedOrigins?: string[];
435
+ interface DgaHttpAdapterSecurityOptions extends DgaHttpSecurityOptions {
376
436
  }
377
437
  /**
378
438
  * Validates schema-supplied URLs before HttpClient calls.
@@ -415,5 +475,5 @@ declare function resolveFormLabel(label: {
415
475
  declare function dgaResolveLabel(label: DgaFormLabel | undefined, locale: DgaFormLocale, fallback?: string): string;
416
476
  declare function dgaDetectLocale(): DgaFormLocale;
417
477
 
418
- 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 };
419
- 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 };
478
+ export { DGA_CAPTCHA_ADAPTER, DGA_DATE_RANGE_PAIRS, DGA_DYNAMIC_FORM_FIELD_REGISTRY, DGA_FORM_I18N_ADAPTER, DGA_FORM_TOAST_ADAPTER, DGA_HTTP_SECURITY, DGA_LOOKUP_ADAPTER, DGA_SUBMIT_ADAPTER, DgaDynamicForm, DgaDynamicFormField, DgaDynamicFormFieldRegistry, DgaDynamicFormService, createDgaToastAdapter, createHttpLookupAdapter, createHttpSubmitAdapter, defaultFormI18nAdapter, dgaApplyCrossFieldValidators, dgaAssertSafeHttpUrl, dgaBuildFormGroup, dgaBuildValidators, dgaCollectFields, dgaCreateControl, dgaDefaultValueForField, dgaDetectLocale, dgaFindSelectedOption, dgaIsFieldVisible, dgaIsRequiredWhen, dgaPasswordMatchValidator, dgaResolveLabel, dgaSaudiMobileValidator, dgaSetControlValidators, emptyLookupAdapter, noopCaptchaAdapter, provideDgaDynamicForm, resolveFormLabel };
479
+ export type { DgaCaptchaAdapter, DgaDynamicFieldHostContext, DgaDynamicFieldRenderer, DgaDynamicFormConfig, DgaDynamicFormPayloadTransformer, DgaExistingAttachment, DgaFormField, DgaFormFieldColumns, DgaFormFieldOption, DgaFormFieldType, DgaFormI18nAdapter, DgaFormLabel, DgaFormLocale, DgaFormStep, DgaFormToastAdapter, DgaHttpAdapterSecurityOptions, DgaHttpMethod, DgaHttpSecurityOptions, DgaInputRestriction, DgaLookupAdapter, DgaLookupRequest, DgaStagedFileRef, DgaSubmitAdapter, DgaSubmitRequest, DgaToastVariant, DgaWizardConfig, ProvideDgaDynamicFormOptions };