@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 +172 -137
- package/fesm2022/a.nemreen-dga-dynamic-form.mjs +247 -11
- package/package.json +6 -2
- package/types/a.nemreen-dga-dynamic-form.d.ts +80 -20
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
|
|
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
|
-
|
|
29
|
-
// allowedOrigins: ['https://api.example.com'],
|
|
28
|
+
allowedOrigins: ['https://api.example.com'],
|
|
30
29
|
}),
|
|
31
30
|
],
|
|
32
31
|
};
|
|
33
32
|
```
|
|
34
33
|
|
|
35
|
-
**Security
|
|
36
|
-
|
|
37
|
-
|
|
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: '
|
|
55
|
-
type: '
|
|
56
|
-
label: { en: '
|
|
124
|
+
name: 'password',
|
|
125
|
+
type: 'password',
|
|
126
|
+
label: { en: 'Password', ar: 'كلمة المرور' },
|
|
57
127
|
required: true,
|
|
58
|
-
|
|
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
|
|
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
|
|
77
|
-
| `method` | `
|
|
78
|
-
| `emitOnly` | `boolean?` | `false` | Skip HTTP; emit
|
|
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
|
|
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`
|
|
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
|
|
190
|
+
| `allowSkipSteps` | `boolean?` | `false` | Allow jumping ahead in stepper. |
|
|
119
191
|
| `showSaveButton` | `boolean?` | — | Show draft save in wizard chrome. |
|
|
120
|
-
| `autoSave` | `boolean?` | — |
|
|
121
|
-
| `saveEndpoint` | `string?` | — |
|
|
122
|
-
| `saveMethod` | `DgaHttpMethod?` |
|
|
123
|
-
| `nextButtonText` | `DgaFormLabel?` |
|
|
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?` |
|
|
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
|
-
|
|
216
|
+
Fields named `password` or `confirmPassword` with `type: 'text'` are rendered as password inputs.
|
|
167
217
|
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
230
|
+
### Lookup (remote options)
|
|
177
231
|
|
|
178
|
-
| Key |
|
|
179
|
-
|
|
180
|
-
| `
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
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
|
-
|
|
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
|
-
###
|
|
244
|
+
### Files
|
|
196
245
|
|
|
197
|
-
| Key |
|
|
198
|
-
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
|
|
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` |
|
|
212
|
-
| `monthPicker` |
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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 |
|
|
234
|
-
|
|
235
|
-
| `config` |
|
|
236
|
-
| `locale` |
|
|
237
|
-
| `formSubmitted` |
|
|
238
|
-
| `formError` |
|
|
239
|
-
| `clearButtonClick` |
|
|
240
|
-
| `draftSaved` |
|
|
241
|
-
| `
|
|
242
|
-
| `
|
|
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
|
-
|
|
258
|
-
provideDgaDynamicForm({
|
|
259
|
-
httpSubmit: true,
|
|
260
|
-
httpLookup: true,
|
|
261
|
-
dgaToast: true,
|
|
262
|
-
});
|
|
263
|
-
```
|
|
307
|
+
### Exported utilities
|
|
264
308
|
|
|
265
|
-
|
|
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
|
-
(
|
|
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:
|
|
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
|
-
(
|
|
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' : '
|
|
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:
|
|
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' : '
|
|
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:
|
|
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.
|
|
4
|
-
"description": "Schema-driven dynamic forms
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|