@a.nemreen/dga-dynamic-form 0.1.0
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 +93 -0
- package/fesm2022/a.nemreen-dga-dynamic-form.mjs +503 -0
- package/package.json +59 -0
- package/types/a.nemreen-dga-dynamic-form.d.ts +381 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# `@a.nemreen/dga-dynamic-form`
|
|
2
|
+
|
|
3
|
+
Schema-driven dynamic forms for Angular, rendered with [`@a.nemreen/dga-ui`](https://www.npmjs.com/package/@a.nemreen/dga-ui) controls.
|
|
4
|
+
|
|
5
|
+
Version **0.1.0** — API may move until freeze.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @a.nemreen/dga-dynamic-form @a.nemreen/dga-ui @a.nemreen/dga-tokens @platformscode/icons
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Peers:** `@angular/core` / `common` / `forms` ^19–22, `@a.nemreen/dga-ui` ^0.2, `@a.nemreen/dga-tokens` ^0.2.
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
Provide adapters once in `app.config.ts`:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { provideHttpClient } from '@angular/common/http';
|
|
21
|
+
import { provideDgaDynamicForm } from '@a.nemreen/dga-dynamic-form';
|
|
22
|
+
|
|
23
|
+
export const appConfig = {
|
|
24
|
+
providers: [
|
|
25
|
+
provideHttpClient(),
|
|
26
|
+
provideDgaDynamicForm({ httpSubmit: true, dgaToast: true }),
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Ensure Tailwind scans the package:
|
|
32
|
+
|
|
33
|
+
```css
|
|
34
|
+
@source "../node_modules/@a.nemreen/dga-dynamic-form/**/*.{mjs,js}";
|
|
35
|
+
@source "../node_modules/@a.nemreen/dga-ui/**/*.{mjs,js}";
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { DgaDynamicForm, type DgaDynamicFormConfig } from '@a.nemreen/dga-dynamic-form';
|
|
42
|
+
|
|
43
|
+
readonly config: DgaDynamicFormConfig = {
|
|
44
|
+
emitOnly: true,
|
|
45
|
+
submitButtonLabel: { en: 'Send', ar: 'إرسال' },
|
|
46
|
+
fields: [
|
|
47
|
+
{
|
|
48
|
+
name: 'fullName',
|
|
49
|
+
type: 'text',
|
|
50
|
+
label: { en: 'Full name', ar: 'الاسم الكامل' },
|
|
51
|
+
required: true,
|
|
52
|
+
columns: { md: 6 },
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'email',
|
|
56
|
+
type: 'email',
|
|
57
|
+
label: { en: 'Email', ar: 'البريد' },
|
|
58
|
+
required: true,
|
|
59
|
+
columns: { md: 6 },
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<dga-dynamic-form [config]="config" (formSubmitted)="onSubmit($event)" />
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Adapters
|
|
70
|
+
|
|
71
|
+
Override via InjectionTokens:
|
|
72
|
+
|
|
73
|
+
| Token | Role |
|
|
74
|
+
|-------|------|
|
|
75
|
+
| `DGA_LOOKUP_ADAPTER` | Lookup / search options |
|
|
76
|
+
| `DGA_SUBMIT_ADAPTER` | HTTP submit |
|
|
77
|
+
| `DGA_FORM_TOAST_ADAPTER` | Success / error feedback |
|
|
78
|
+
| `DGA_CAPTCHA_ADAPTER` | Optional captcha token |
|
|
79
|
+
| `DGA_FORM_I18N_ADAPTER` | Label resolution |
|
|
80
|
+
| `DGA_DYNAMIC_FORM_FIELD_REGISTRY` | Custom field type → component map |
|
|
81
|
+
|
|
82
|
+
## Custom field types
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
{
|
|
86
|
+
provide: DGA_DYNAMIC_FORM_FIELD_REGISTRY,
|
|
87
|
+
useValue: [{ type: 'nationalId', component: NationalIdField }],
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Built-in field types
|
|
92
|
+
|
|
93
|
+
`text` · `email` · `textarea` · `number` · `otp` · `phone` · `select` · `multiselect` · `checkbox` · `radio` · `date` · `chips` · `toggle` · `file` · `hidden`
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import*as i from"@angular/core";import{InjectionToken as g,inject as s,Injectable as P,input as u,booleanAttribute as ye,output as S,DestroyRef as E,signal as C,computed as d,ChangeDetectionStrategy as q,Component as z,effect as xe,makeEnvironmentProviders as Se}from"@angular/core";import{NgComponentOutlet as B}from"@angular/common";import{takeUntilDestroyed as _}from"@angular/core/rxjs-interop";import*as p from"@angular/forms";import{Validators as m,ReactiveFormsModule as k,FormsModule as G,FormBuilder as Ce}from"@angular/forms";import{switchMap as $,catchError as Ie,startWith as ke,map as De}from"rxjs/operators";import{DgaField as j,DgaInput as W,DgaSelect as H,DgaCheckbox as K,DgaRadio as U,DgaPhoneInput as Y,DgaDatePicker as J,DgaChipInput as X,DgaSwitch as Z,DgaUpload as Q,DgaNumberInput as ee,DgaOtp as te,DgaButton as oe,DgaStepper as ae,DgaToastService as Oe}from"@a.nemreen/dga-ui";import{of as h}from"rxjs";import{HttpClient as re,HttpHeaders as Le}from"@angular/common/http";const D=new g("DGA_LOOKUP_ADAPTER"),O=new g("DGA_SUBMIT_ADAPTER"),L=new g("DGA_FORM_TOAST_ADAPTER"),T=new g("DGA_CAPTCHA_ADAPTER"),N=new g("DGA_FORM_I18N_ADAPTER"),ne=new g("DGA_DYNAMIC_FORM_FIELD_REGISTRY"),ie=["checkbox","multiselect","chips","file"],Te=["toggle"];function le(a){return a.value!==void 0?a.value:ie.includes(a.type)?[]:Te.includes(a.type)?!1:""}function A(a,e){const t=[],o=a.validation;(e||a.required||o?.required)&&(ie.includes(a.type)?t.push(Ne):a.type==="toggle"?t.push(m.requiredTrue):t.push(m.required)),(a.type==="email"||o?.pattern==="email")&&t.push(m.email);const r=o?.minLength;r!=null&&t.push(m.minLength(r));const n=o?.maxLength??a.maxLength;if(n!=null&&t.push(m.maxLength(n)),o?.pattern&&o.pattern!=="email")try{t.push(m.pattern(o.pattern))}catch{}return o?.min!=null&&t.push(m.min(o.min)),o?.max!=null&&t.push(m.max(o.max)),a.maxChips!=null&&a.type==="chips"&&t.push(Ae(a.maxChips)),t}function Ne(a){const e=a.value;return Array.isArray(e)&&e.length>0?null:{required:!0}}function Ae(a){return e=>{const t=e.value;return Array.isArray(t)&&t.length>a?{maxChips:{max:a,actual:t.length}}:null}}function se(a,e,t=!0){const o=!!e.disabled;return a.control({value:le(e),disabled:o},{validators:A(e,t&&!e.requiredWhen)})}function de(a,e){const t={};for(const o of e){if(o.hidden&&o.type==="hidden"){t[o.name]=a.control(o.value??"");continue}t[o.name]=se(a,o)}return a.group(t)}function ce(a,e){return e?.length?e.flatMap(t=>t.fields):a}function ue(a,e,t){a.setValidators(A(e,t)),a.updateValueAndValidity({emitEvent:!1})}function F(a,e){return Array.isArray(a)?a.some(t=>t==e):a==e}function w(a,e,t){return a?[...t??[],...a.options??[],...a.groupedOptions?.flatMap(r=>r.options)??[]].find(r=>r.value==e):void 0}function M(a,e,t){if(a.hidden&&a.type!=="hidden"||a.type==="hidden")return!1;if(!a.dependsOn)return!0;const o=e[a.dependsOn];if(a.showOnValues?.length&&!a.showOnValues.some(r=>F(o,r)))return!1;if(a.showWhenOptionProperty){const r=t?.(a.dependsOn),n=w({name:a.dependsOn,type:"select",label:{en:"",ar:""}},o,r),l=a.showWhenOptionProperty.property;if(!n||n[l]!==a.showWhenOptionProperty.value)return!1}return!(!a.showOnValues?.length&&!a.showWhenOptionProperty&&(o==null||o===""||Array.isArray(o)&&o.length===0))}function V(a,e,t){const o=a.requiredWhen;if(!o)return!!a.required;const r=e[o.field];if(o.optionProperty!=null){const n=t?.(o.field),l=w({name:o.field,type:"select",label:{en:"",ar:""}},r,n);return!!l&&l[o.optionProperty]===o.optionPropertyValue}return o.values?.length?o.values.some(n=>F(r,n)):o.value!==void 0?F(r,o.value):r!=null&&r!==""}class v{submitAdapter=s(O,{optional:!0});captcha=s(T,{optional:!0});buildPayload(e,t,o){const r=new Set(o.filter(l=>l.excludeFromPayload).map(l=>l.name));let n={};for(const[l,x]of Object.entries(t)){if(r.has(l))continue;const c=e.fieldMapping?.[l]??l;n[c]=x}return e.payloadTransformer?e.payloadTransformer(n):n}hasFileValues(e){return Object.values(e).some(t=>t instanceof File||Array.isArray(t)&&t.some(o=>o instanceof File||o?.file instanceof File))}toFormData(e){const t=new FormData;for(const[o,r]of Object.entries(e))if(r!=null)if(r instanceof File)t.append(o,r);else if(Array.isArray(r))for(const n of r)n instanceof File?t.append(o,n):n?.file instanceof File?t.append(o,n.file,n.name):t.append(o,typeof n=="string"?n:JSON.stringify(n));else typeof r=="object"?t.append(o,JSON.stringify(r)):t.append(o,String(r));return t}submitForm(e,t,o){if(!e.endpoint||!this.submitAdapter)return h(t);const r=this.buildPayload(e,t,o),n=r instanceof FormData?r:this.hasFileValues(r)?this.toFormData(r):r,l={};return e.idempotencyKey&&(l["Idempotency-Key"]=e.idempotencyKey),(this.captcha?.getToken("submit")??h(null)).pipe($(c=>(c&&(l["X-Recaptcha-Token"]=c),h({endpoint:e.endpoint,method:e.method??"POST",body:n,headers:l})))).pipe($(c=>this.submitAdapter.submit(c)),Ie(c=>{throw c}))}saveDraft(e,t){typeof localStorage>"u"||localStorage.setItem(this.storageKey(e),JSON.stringify(t))}loadDraft(e){if(typeof localStorage>"u")return null;const t=localStorage.getItem(this.storageKey(e));if(!t)return null;try{return JSON.parse(t)}catch{return null}}clearDraft(e){typeof localStorage>"u"||localStorage.removeItem(this.storageKey(e))}storageKey(e){return`dga-dynamic-form:${e}`}static \u0275fac=i.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:v,deps:[],target:i.\u0275\u0275FactoryTarget.Injectable});static \u0275prov=i.\u0275\u0275ngDeclareInjectable({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:v,providedIn:"root"})}i.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:v,decorators:[{type:P,args:[{providedIn:"root"}]}]});function b(a,e,t=""){return a?e==="en"?a.en||a.ar:a.ar||a.en:t}function pe(){return typeof document>"u"?"ar":(document.documentElement.lang||"ar").toLowerCase().startsWith("en")?"en":"ar"}class y{extras=s(ne,{optional:!0});map=new Map;constructor(){for(const e of this.extras??[])this.register(e)}register(e){this.map.set(e.type,e.component)}get(e){return this.map.get(e)??null}hasCustom(e){return this.map.has(e)}isBuiltIn(e){return Fe.has(e)}static \u0275fac=i.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:y,deps:[],target:i.\u0275\u0275FactoryTarget.Injectable});static \u0275prov=i.\u0275\u0275ngDeclareInjectable({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:y,providedIn:"root"})}i.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:y,decorators:[{type:P,args:[{providedIn:"root"}]}],ctorParameters:()=>[]});const Fe=new Set(["text","email","textarea","select","multiselect","checkbox","radio","phone","file","date","chips","toggle","hidden","number","otp"]);class f{field=u.required(...ngDevMode?[{debugName:"field"}]:[]);form=u.required(...ngDevMode?[{debugName:"form"}]:[]);locale=u("ar",...ngDevMode?[{debugName:"locale"}]:[]);required=u(!1,{...ngDevMode?{debugName:"required"}:{},transform:ye});error=u("",...ngDevMode?[{debugName:"error"}]:[]);runtimeOptions=u([],...ngDevMode?[{debugName:"runtimeOptions"}]:[]);optionsLoaded=S();lookup=s(D,{optional:!0});destroyRef=s(E);loadedOptions=C([],...ngDevMode?[{debugName:"loadedOptions"}]:[]);controlId=`dga-df-${Math.random().toString(36).slice(2,9)}`;get control(){return this.form().controls[this.field().name]}labelText=d(()=>b(this.field().label,this.locale()),...ngDevMode?[{debugName:"labelText"}]:[]);hintText=d(()=>b(this.field().hint,this.locale()),...ngDevMode?[{debugName:"hintText"}]:[]);placeholderText=d(()=>b(this.field().placeholder,this.locale()),...ngDevMode?[{debugName:"placeholderText"}]:[]);resolvedOptions=d(()=>{const e=this.runtimeOptions();if(e.length)return e;const t=this.loadedOptions();return t.length?t:this.field().options??[]},...ngDevMode?[{debugName:"resolvedOptions"}]:[]);selectOptions=d(()=>this.resolvedOptions().map(e=>({value:String(e.value),label:this.optionLabel(e),disabled:!!e.disabled})),...ngDevMode?[{debugName:"selectOptions"}]:[]);ngOnInit(){const e=this.field();e.lookupDomain&&e.lookupName&&this.lookup&&!e.searchLookup&&this.lookup.lookup({domain:e.lookupDomain,name:e.lookupName}).pipe(_(this.destroyRef)).subscribe(t=>{const o=this.applyLookupFilter(t);this.loadedOptions.set(o),this.optionsLoaded.emit({name:e.name,options:o})})}optionLabel(e){return b(e.label,this.locale())}isChecked(e){const t=this.control?.value;return Array.isArray(t)&&t.some(o=>o==e)}toggleCheckbox(e,t){const o=Array.isArray(this.control.value)?[...this.control.value]:[],r=t?o.some(n=>n==e)?o:[...o,e]:o.filter(n=>n!=e);this.control.setValue(r),this.control.markAsDirty()}onSelectSearch(e){const t=this.field();!t.searchLookup||!this.lookup||!t.lookupDomain||t.lookupName}onRestrict(e){const t=this.field().inputRestriction;if(!t)return;const o=e.target;let r=o.value;t==="numbers"&&(r=r.replace(/\D+/g,"")),t==="english"&&(r=r.replace(/[^a-zA-Z0-9\s.,\-_/]/g,"")),t==="arabic"&&(r=r.replace(/[^\u0600-\u06FF0-9\s.,\-_/]/g,"")),r!==o.value&&(o.value=r,this.control.setValue(r))}applyLookupFilter(e){const t=this.field().lookupFilter;return t?e.filter(o=>o[t.property]===t.value):e}static \u0275fac=i.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:f,deps:[],target:i.\u0275\u0275FactoryTarget.Component});static \u0275cmp=i.\u0275\u0275ngDeclareComponent({minVersion:"17.0.0",version:"22.0.7",type:f,isStandalone:!0,selector:"dga-dynamic-form-field",inputs:{field:{classPropertyName:"field",publicName:"field",isSignal:!0,isRequired:!0,transformFunction:null},form:{classPropertyName:"form",publicName:"form",isSignal:!0,isRequired:!0,transformFunction:null},locale:{classPropertyName:"locale",publicName:"locale",isSignal:!0,isRequired:!1,transformFunction:null},required:{classPropertyName:"required",publicName:"required",isSignal:!0,isRequired:!1,transformFunction:null},error:{classPropertyName:"error",publicName:"error",isSignal:!0,isRequired:!1,transformFunction:null},runtimeOptions:{classPropertyName:"runtimeOptions",publicName:"runtimeOptions",isSignal:!0,isRequired:!1,transformFunction:null}},outputs:{optionsLoaded:"optionsLoaded"},host:{properties:{"class.hidden":'field().type === "hidden"'},classAttribute:"block w-full"},ngImport:i,template:`
|
|
2
|
+
@if (field().type !== 'hidden') {
|
|
3
|
+
<dga-field
|
|
4
|
+
[label]="labelText()"
|
|
5
|
+
[hint]="hintText()"
|
|
6
|
+
[error]="error()"
|
|
7
|
+
[required]="required()"
|
|
8
|
+
[controlId]="controlId"
|
|
9
|
+
[status]="error() ? 'error' : 'default'"
|
|
10
|
+
>
|
|
11
|
+
@switch (field().type) {
|
|
12
|
+
@case ('textarea') {
|
|
13
|
+
<textarea
|
|
14
|
+
dgaInput
|
|
15
|
+
[id]="controlId"
|
|
16
|
+
[attr.rows]="field().rows || 4"
|
|
17
|
+
[attr.placeholder]="placeholderText()"
|
|
18
|
+
[attr.maxlength]="field().maxLength || null"
|
|
19
|
+
[readonly]="field().readonly || null"
|
|
20
|
+
[formControl]="control"
|
|
21
|
+
[dgaInvalid]="!!error()"
|
|
22
|
+
></textarea>
|
|
23
|
+
}
|
|
24
|
+
@case ('email') {
|
|
25
|
+
<input
|
|
26
|
+
dgaInput
|
|
27
|
+
type="email"
|
|
28
|
+
[id]="controlId"
|
|
29
|
+
[attr.placeholder]="placeholderText()"
|
|
30
|
+
[attr.maxlength]="field().maxLength || null"
|
|
31
|
+
[readonly]="field().readonly || null"
|
|
32
|
+
[formControl]="control"
|
|
33
|
+
[dgaInvalid]="!!error()"
|
|
34
|
+
(input)="onRestrict($event)"
|
|
35
|
+
/>
|
|
36
|
+
}
|
|
37
|
+
@case ('number') {
|
|
38
|
+
<dga-number-input
|
|
39
|
+
[formControl]="control"
|
|
40
|
+
[disabled]="!!field().disabled"
|
|
41
|
+
/>
|
|
42
|
+
}
|
|
43
|
+
@case ('otp') {
|
|
44
|
+
<dga-otp [formControl]="control" />
|
|
45
|
+
}
|
|
46
|
+
@case ('phone') {
|
|
47
|
+
<dga-phone-input
|
|
48
|
+
[formControl]="control"
|
|
49
|
+
[placeholder]="placeholderText()"
|
|
50
|
+
[invalid]="!!error()"
|
|
51
|
+
[disabled]="!!field().disabled"
|
|
52
|
+
[required]="required()"
|
|
53
|
+
[controlId]="controlId"
|
|
54
|
+
/>
|
|
55
|
+
}
|
|
56
|
+
@case ('date') {
|
|
57
|
+
<dga-date-picker
|
|
58
|
+
[formControl]="control"
|
|
59
|
+
[placeholder]="placeholderText()"
|
|
60
|
+
[dgaInvalid]="!!error()"
|
|
61
|
+
[disabled]="!!field().disabled"
|
|
62
|
+
[locale]="locale()"
|
|
63
|
+
[mode]="field().monthPicker ? 'month' : 'day'"
|
|
64
|
+
/>
|
|
65
|
+
}
|
|
66
|
+
@case ('chips') {
|
|
67
|
+
<dga-chip-input
|
|
68
|
+
[formControl]="control"
|
|
69
|
+
[placeholder]="placeholderText()"
|
|
70
|
+
[dgaInvalid]="!!error()"
|
|
71
|
+
[disabled]="!!field().disabled"
|
|
72
|
+
[label]="labelText()"
|
|
73
|
+
/>
|
|
74
|
+
}
|
|
75
|
+
@case ('toggle') {
|
|
76
|
+
<dga-switch
|
|
77
|
+
[formControl]="control"
|
|
78
|
+
[label]="labelText()"
|
|
79
|
+
[disabled]="!!field().disabled"
|
|
80
|
+
/>
|
|
81
|
+
}
|
|
82
|
+
@case ('file') {
|
|
83
|
+
<dga-upload
|
|
84
|
+
[formControl]="control"
|
|
85
|
+
[accept]="field().acceptedFileTypes || ''"
|
|
86
|
+
[multiple]="(field().maxFiles ?? 1) > 1"
|
|
87
|
+
[maxSize]="field().maxFileSize || 0"
|
|
88
|
+
[disabled]="!!field().disabled"
|
|
89
|
+
/>
|
|
90
|
+
}
|
|
91
|
+
@case ('select') {
|
|
92
|
+
<dga-select
|
|
93
|
+
[formControl]="control"
|
|
94
|
+
[options]="selectOptions()"
|
|
95
|
+
[searchable]="!!field().searchLookup || (selectOptions().length > 8)"
|
|
96
|
+
[placeholder]="placeholderText()"
|
|
97
|
+
[label]="labelText()"
|
|
98
|
+
[dgaInvalid]="!!error()"
|
|
99
|
+
[disabled]="!!field().disabled"
|
|
100
|
+
(valueChange)="onSelectSearch($event)"
|
|
101
|
+
/>
|
|
102
|
+
}
|
|
103
|
+
@case ('multiselect') {
|
|
104
|
+
<dga-select
|
|
105
|
+
multiple
|
|
106
|
+
[formControl]="control"
|
|
107
|
+
[options]="selectOptions()"
|
|
108
|
+
[searchable]="true"
|
|
109
|
+
[placeholder]="placeholderText()"
|
|
110
|
+
[label]="labelText()"
|
|
111
|
+
[dgaInvalid]="!!error()"
|
|
112
|
+
[disabled]="!!field().disabled"
|
|
113
|
+
/>
|
|
114
|
+
}
|
|
115
|
+
@case ('radio') {
|
|
116
|
+
<div
|
|
117
|
+
class="flex flex-wrap gap-lg"
|
|
118
|
+
[class.flex-col]="field().optionsLayout !== 'grid'"
|
|
119
|
+
role="radiogroup"
|
|
120
|
+
[attr.aria-labelledby]="controlId"
|
|
121
|
+
>
|
|
122
|
+
@for (opt of resolvedOptions(); track opt.value) {
|
|
123
|
+
<label class="inline-flex items-center gap-sm text-body-sm text-fg">
|
|
124
|
+
<input
|
|
125
|
+
type="radio"
|
|
126
|
+
dgaRadio
|
|
127
|
+
[name]="field().name"
|
|
128
|
+
[value]="opt.value"
|
|
129
|
+
[formControl]="control"
|
|
130
|
+
[dgaInvalid]="!!error()"
|
|
131
|
+
/>
|
|
132
|
+
{{ optionLabel(opt) }}
|
|
133
|
+
</label>
|
|
134
|
+
}
|
|
135
|
+
</div>
|
|
136
|
+
}
|
|
137
|
+
@case ('checkbox') {
|
|
138
|
+
<div
|
|
139
|
+
class="flex flex-wrap gap-lg"
|
|
140
|
+
[class.flex-col]="field().optionsLayout !== 'grid'"
|
|
141
|
+
>
|
|
142
|
+
@for (opt of resolvedOptions(); track opt.value) {
|
|
143
|
+
<label class="inline-flex items-center gap-sm text-body-sm text-fg">
|
|
144
|
+
<input
|
|
145
|
+
type="checkbox"
|
|
146
|
+
dgaCheckbox
|
|
147
|
+
[checked]="isChecked(opt.value)"
|
|
148
|
+
[disabled]="!!field().disabled"
|
|
149
|
+
[dgaInvalid]="!!error()"
|
|
150
|
+
(change)="toggleCheckbox(opt.value, $any($event.target).checked)"
|
|
151
|
+
/>
|
|
152
|
+
{{ optionLabel(opt) }}
|
|
153
|
+
</label>
|
|
154
|
+
}
|
|
155
|
+
</div>
|
|
156
|
+
}
|
|
157
|
+
@default {
|
|
158
|
+
<input
|
|
159
|
+
dgaInput
|
|
160
|
+
type="text"
|
|
161
|
+
[id]="controlId"
|
|
162
|
+
[attr.placeholder]="placeholderText()"
|
|
163
|
+
[attr.maxlength]="field().maxLength || null"
|
|
164
|
+
[readonly]="field().readonly || null"
|
|
165
|
+
[formControl]="control"
|
|
166
|
+
[dgaInvalid]="!!error()"
|
|
167
|
+
(input)="onRestrict($event)"
|
|
168
|
+
/>
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
</dga-field>
|
|
172
|
+
}
|
|
173
|
+
`,isInline:!0,dependencies:[{kind:"ngmodule",type:k},{kind:"directive",type:p.DefaultValueAccessor,selector:"input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"},{kind:"directive",type:p.RadioControlValueAccessor,selector:"input[type=radio]:not([ngNoCva])[formControlName],input[type=radio]:not([ngNoCva])[formControl],input[type=radio]:not([ngNoCva])[ngModel]",inputs:["name","formControlName","value"]},{kind:"directive",type:p.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:p.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:p.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"ngmodule",type:G},{kind:"component",type:j,selector:"dga-field",inputs:["label","labelHint","hint","error","status","statusMessage","controlId","required"]},{kind:"directive",type:W,selector:"input[dgaInput], textarea[dgaInput]",inputs:["size","dgaInvalid","status"]},{kind:"component",type:H,selector:"dga-select",inputs:["options","value","multiple","searchable","openMode","clearable","size","status","placeholder","label","removeLabel","clearLabel","emptyLabel","disabled","dgaInvalid"],outputs:["valueChange"]},{kind:"directive",type:K,selector:'input[type="checkbox"][dgaCheckbox]',inputs:["size","dgaInvalid"]},{kind:"directive",type:U,selector:'input[type="radio"][dgaRadio]',inputs:["size","dgaInvalid"]},{kind:"component",type:Y,selector:"dga-phone-input",inputs:["countries","countryCode","nationalNumber","controlId","name","placeholder","autocomplete","codeLabel","disabled","required","invalid","value"],outputs:["countryCodeChange","nationalNumberChange","valueChange"]},{kind:"component",type:J,selector:"dga-date-picker",inputs:["value","startValue","endValue","calendar","range","format","mode","locale","minDate","maxDate","yearBefore","yearAfter","placeholder","controlId","name","size","disabled","required","clearable","editable","dgaInvalid","status","rangeSeparator","todayLabel","clearLabel","closeLabel","saveLabel","ariaLabel"],outputs:["startValueChange","endValueChange","valueChange","rangeChange"]},{kind:"component",type:X,selector:"dga-chip-input",inputs:["value","placeholder","label","removeLabel","disabled","dgaInvalid","status"],outputs:["valueChange"]},{kind:"component",type:Z,selector:"dga-switch",inputs:["checked","size","label","disabled"],outputs:["checkedChange","changed"]},{kind:"component",type:Q,selector:"dga-upload",inputs:["files","accept","multiple","appearance","hint","browseLabel","removeLabel","disabled","maxSize","dragging"],outputs:["filesChange","rejected","draggingChange"]},{kind:"component",type:ee,selector:"dga-number-input",inputs:["value","min","max","step","size","label","controlId","name","incrementLabel","decrementLabel","disabled","dgaInvalid","status"],outputs:["valueChange"]},{kind:"component",type:te,selector:"dga-otp",inputs:["length","value","label","alphanumeric","disabled","dgaInvalid"],outputs:["valueChange","completed"]}],changeDetection:i.ChangeDetectionStrategy.OnPush})}i.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:f,decorators:[{type:z,args:[{selector:"dga-dynamic-form-field",standalone:!0,imports:[k,G,j,W,H,K,U,Y,J,X,Z,Q,ee,te],template:`
|
|
174
|
+
@if (field().type !== 'hidden') {
|
|
175
|
+
<dga-field
|
|
176
|
+
[label]="labelText()"
|
|
177
|
+
[hint]="hintText()"
|
|
178
|
+
[error]="error()"
|
|
179
|
+
[required]="required()"
|
|
180
|
+
[controlId]="controlId"
|
|
181
|
+
[status]="error() ? 'error' : 'default'"
|
|
182
|
+
>
|
|
183
|
+
@switch (field().type) {
|
|
184
|
+
@case ('textarea') {
|
|
185
|
+
<textarea
|
|
186
|
+
dgaInput
|
|
187
|
+
[id]="controlId"
|
|
188
|
+
[attr.rows]="field().rows || 4"
|
|
189
|
+
[attr.placeholder]="placeholderText()"
|
|
190
|
+
[attr.maxlength]="field().maxLength || null"
|
|
191
|
+
[readonly]="field().readonly || null"
|
|
192
|
+
[formControl]="control"
|
|
193
|
+
[dgaInvalid]="!!error()"
|
|
194
|
+
></textarea>
|
|
195
|
+
}
|
|
196
|
+
@case ('email') {
|
|
197
|
+
<input
|
|
198
|
+
dgaInput
|
|
199
|
+
type="email"
|
|
200
|
+
[id]="controlId"
|
|
201
|
+
[attr.placeholder]="placeholderText()"
|
|
202
|
+
[attr.maxlength]="field().maxLength || null"
|
|
203
|
+
[readonly]="field().readonly || null"
|
|
204
|
+
[formControl]="control"
|
|
205
|
+
[dgaInvalid]="!!error()"
|
|
206
|
+
(input)="onRestrict($event)"
|
|
207
|
+
/>
|
|
208
|
+
}
|
|
209
|
+
@case ('number') {
|
|
210
|
+
<dga-number-input
|
|
211
|
+
[formControl]="control"
|
|
212
|
+
[disabled]="!!field().disabled"
|
|
213
|
+
/>
|
|
214
|
+
}
|
|
215
|
+
@case ('otp') {
|
|
216
|
+
<dga-otp [formControl]="control" />
|
|
217
|
+
}
|
|
218
|
+
@case ('phone') {
|
|
219
|
+
<dga-phone-input
|
|
220
|
+
[formControl]="control"
|
|
221
|
+
[placeholder]="placeholderText()"
|
|
222
|
+
[invalid]="!!error()"
|
|
223
|
+
[disabled]="!!field().disabled"
|
|
224
|
+
[required]="required()"
|
|
225
|
+
[controlId]="controlId"
|
|
226
|
+
/>
|
|
227
|
+
}
|
|
228
|
+
@case ('date') {
|
|
229
|
+
<dga-date-picker
|
|
230
|
+
[formControl]="control"
|
|
231
|
+
[placeholder]="placeholderText()"
|
|
232
|
+
[dgaInvalid]="!!error()"
|
|
233
|
+
[disabled]="!!field().disabled"
|
|
234
|
+
[locale]="locale()"
|
|
235
|
+
[mode]="field().monthPicker ? 'month' : 'day'"
|
|
236
|
+
/>
|
|
237
|
+
}
|
|
238
|
+
@case ('chips') {
|
|
239
|
+
<dga-chip-input
|
|
240
|
+
[formControl]="control"
|
|
241
|
+
[placeholder]="placeholderText()"
|
|
242
|
+
[dgaInvalid]="!!error()"
|
|
243
|
+
[disabled]="!!field().disabled"
|
|
244
|
+
[label]="labelText()"
|
|
245
|
+
/>
|
|
246
|
+
}
|
|
247
|
+
@case ('toggle') {
|
|
248
|
+
<dga-switch
|
|
249
|
+
[formControl]="control"
|
|
250
|
+
[label]="labelText()"
|
|
251
|
+
[disabled]="!!field().disabled"
|
|
252
|
+
/>
|
|
253
|
+
}
|
|
254
|
+
@case ('file') {
|
|
255
|
+
<dga-upload
|
|
256
|
+
[formControl]="control"
|
|
257
|
+
[accept]="field().acceptedFileTypes || ''"
|
|
258
|
+
[multiple]="(field().maxFiles ?? 1) > 1"
|
|
259
|
+
[maxSize]="field().maxFileSize || 0"
|
|
260
|
+
[disabled]="!!field().disabled"
|
|
261
|
+
/>
|
|
262
|
+
}
|
|
263
|
+
@case ('select') {
|
|
264
|
+
<dga-select
|
|
265
|
+
[formControl]="control"
|
|
266
|
+
[options]="selectOptions()"
|
|
267
|
+
[searchable]="!!field().searchLookup || (selectOptions().length > 8)"
|
|
268
|
+
[placeholder]="placeholderText()"
|
|
269
|
+
[label]="labelText()"
|
|
270
|
+
[dgaInvalid]="!!error()"
|
|
271
|
+
[disabled]="!!field().disabled"
|
|
272
|
+
(valueChange)="onSelectSearch($event)"
|
|
273
|
+
/>
|
|
274
|
+
}
|
|
275
|
+
@case ('multiselect') {
|
|
276
|
+
<dga-select
|
|
277
|
+
multiple
|
|
278
|
+
[formControl]="control"
|
|
279
|
+
[options]="selectOptions()"
|
|
280
|
+
[searchable]="true"
|
|
281
|
+
[placeholder]="placeholderText()"
|
|
282
|
+
[label]="labelText()"
|
|
283
|
+
[dgaInvalid]="!!error()"
|
|
284
|
+
[disabled]="!!field().disabled"
|
|
285
|
+
/>
|
|
286
|
+
}
|
|
287
|
+
@case ('radio') {
|
|
288
|
+
<div
|
|
289
|
+
class="flex flex-wrap gap-lg"
|
|
290
|
+
[class.flex-col]="field().optionsLayout !== 'grid'"
|
|
291
|
+
role="radiogroup"
|
|
292
|
+
[attr.aria-labelledby]="controlId"
|
|
293
|
+
>
|
|
294
|
+
@for (opt of resolvedOptions(); track opt.value) {
|
|
295
|
+
<label class="inline-flex items-center gap-sm text-body-sm text-fg">
|
|
296
|
+
<input
|
|
297
|
+
type="radio"
|
|
298
|
+
dgaRadio
|
|
299
|
+
[name]="field().name"
|
|
300
|
+
[value]="opt.value"
|
|
301
|
+
[formControl]="control"
|
|
302
|
+
[dgaInvalid]="!!error()"
|
|
303
|
+
/>
|
|
304
|
+
{{ optionLabel(opt) }}
|
|
305
|
+
</label>
|
|
306
|
+
}
|
|
307
|
+
</div>
|
|
308
|
+
}
|
|
309
|
+
@case ('checkbox') {
|
|
310
|
+
<div
|
|
311
|
+
class="flex flex-wrap gap-lg"
|
|
312
|
+
[class.flex-col]="field().optionsLayout !== 'grid'"
|
|
313
|
+
>
|
|
314
|
+
@for (opt of resolvedOptions(); track opt.value) {
|
|
315
|
+
<label class="inline-flex items-center gap-sm text-body-sm text-fg">
|
|
316
|
+
<input
|
|
317
|
+
type="checkbox"
|
|
318
|
+
dgaCheckbox
|
|
319
|
+
[checked]="isChecked(opt.value)"
|
|
320
|
+
[disabled]="!!field().disabled"
|
|
321
|
+
[dgaInvalid]="!!error()"
|
|
322
|
+
(change)="toggleCheckbox(opt.value, $any($event.target).checked)"
|
|
323
|
+
/>
|
|
324
|
+
{{ optionLabel(opt) }}
|
|
325
|
+
</label>
|
|
326
|
+
}
|
|
327
|
+
</div>
|
|
328
|
+
}
|
|
329
|
+
@default {
|
|
330
|
+
<input
|
|
331
|
+
dgaInput
|
|
332
|
+
type="text"
|
|
333
|
+
[id]="controlId"
|
|
334
|
+
[attr.placeholder]="placeholderText()"
|
|
335
|
+
[attr.maxlength]="field().maxLength || null"
|
|
336
|
+
[readonly]="field().readonly || null"
|
|
337
|
+
[formControl]="control"
|
|
338
|
+
[dgaInvalid]="!!error()"
|
|
339
|
+
(input)="onRestrict($event)"
|
|
340
|
+
/>
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
</dga-field>
|
|
344
|
+
}
|
|
345
|
+
`,changeDetection:q.OnPush,host:{class:"block w-full","[class.hidden]":'field().type === "hidden"'}}]}],propDecorators:{field:[{type:i.Input,args:[{isSignal:!0,alias:"field",required:!0}]}],form:[{type:i.Input,args:[{isSignal:!0,alias:"form",required:!0}]}],locale:[{type:i.Input,args:[{isSignal:!0,alias:"locale",required:!1}]}],required:[{type:i.Input,args:[{isSignal:!0,alias:"required",required:!1}]}],error:[{type:i.Input,args:[{isSignal:!0,alias:"error",required:!1}]}],runtimeOptions:[{type:i.Input,args:[{isSignal:!0,alias:"runtimeOptions",required:!1}]}],optionsLoaded:[{type:i.Output,args:["optionsLoaded"]}]}});const we={1:"col-span-1",2:"col-span-2",3:"col-span-3",4:"col-span-4",5:"col-span-5",6:"col-span-6",7:"col-span-7",8:"col-span-8",9:"col-span-9",10:"col-span-10",11:"col-span-11",12:"col-span-12"},Me={1:"md:col-span-1",2:"md:col-span-2",3:"md:col-span-3",4:"md:col-span-4",5:"md:col-span-5",6:"md:col-span-6",7:"md:col-span-7",8:"md:col-span-8",9:"md:col-span-9",10:"md:col-span-10",11:"md:col-span-11",12:"md:col-span-12"},Ve={1:"lg:col-span-1",2:"lg:col-span-2",3:"lg:col-span-3",4:"lg:col-span-4",5:"lg:col-span-5",6:"lg:col-span-6",7:"lg:col-span-7",8:"lg:col-span-8",9:"lg:col-span-9",10:"lg:col-span-10",11:"lg:col-span-11",12:"lg:col-span-12"};function R(a){return Number.isFinite(a)?Math.min(12,Math.max(1,Math.round(a))):12}class I{config=u.required(...ngDevMode?[{debugName:"config"}]:[]);localeInput=u(null,{...ngDevMode?{debugName:"localeInput"}:{},alias:"locale"});formSubmitted=S();formError=S();clearButtonClick=S();draftSaved=S();fb=s(Ce);formService=s(v);toast=s(L,{optional:!0});i18n=s(N,{optional:!0});destroyRef=s(E);registry=s(y);form;activeStep=C(0,...ngDevMode?[{debugName:"activeStep"}]:[]);submitting=C(!1,...ngDevMode?[{debugName:"submitting"}]:[]);optionsMap=C({},...ngDevMode?[{debugName:"optionsMap"}]:[]);formValues=C({},...ngDevMode?[{debugName:"formValues"}]:[]);locale=d(()=>this.localeInput()??pe(),...ngDevMode?[{debugName:"locale"}]:[]);wizardEnabled=d(()=>!!this.config().wizard?.enabled&&!!this.config().wizard?.steps?.length,...ngDevMode?[{debugName:"wizardEnabled"}]:[]);allFields=d(()=>ce(this.config().fields,this.config().wizard?.steps),...ngDevMode?[{debugName:"allFields"}]:[]);currentStepFields=d(()=>this.wizardEnabled()?this.config().wizard.steps[this.activeStep()]?.fields??[]:this.config().fields,...ngDevMode?[{debugName:"currentStepFields"}]:[]);visibleFields=d(()=>{const e=this.formValues(),t=this.optionsMap();return this.currentStepFields().filter(o=>M(o,e,r=>t[r]))},...ngDevMode?[{debugName:"visibleFields"}]:[]);stepperSteps=d(()=>(this.config().wizard?.steps??[]).map(t=>({label:this.labelOf(t.title),description:t.description?this.labelOf(t.description):void 0})),...ngDevMode?[{debugName:"stepperSteps"}]:[]);descriptionText=d(()=>this.labelOf(this.config().description),...ngDevMode?[{debugName:"descriptionText"}]:[]);constructor(){xe(()=>{const e=this.formValues(),t=this.optionsMap();if(this.form)for(const o of this.allFields()){const r=this.form.get(o.name);if(!r||!o.requiredWhen)continue;const n=V(o,e,l=>t[l]);ue(r,o,n)}})}ngOnInit(){this.form=de(this.fb,this.allFields());const e=this.config().formId;if(e&&this.config().enableLocalStorageSave){const t=this.formService.loadDraft(e);t&&this.form.patchValue(t,{emitEvent:!1})}this.form.valueChanges.pipe(ke(this.form.getRawValue()),_(this.destroyRef)).subscribe(t=>this.formValues.set(t))}labelOf(e){return e?this.i18n?this.i18n.resolveLabel(e,this.locale()):b(e,this.locale()):""}submitLabel(){const e=this.config();return this.wizardEnabled()?this.labelOf(e.wizard?.submitButtonText??e.submitButtonLabel)||(this.locale()==="en"?"Submit":"\u0625\u0631\u0633\u0627\u0644"):this.labelOf(e.submitButtonLabel)||(this.locale()==="en"?"Submit":"\u0625\u0631\u0633\u0627\u0644")}nextLabel(){return this.labelOf(this.config().wizard?.nextButtonText)||(this.locale()==="en"?"Next":"\u0627\u0644\u062A\u0627\u0644\u064A")}previousLabel(){return this.labelOf(this.config().wizard?.previousButtonText)||(this.locale()==="en"?"Previous":"\u0627\u0644\u0633\u0627\u0628\u0642")}clearLabel(){return this.labelOf(this.config().clearButtonLabel)}saveLabel(){return this.labelOf(this.config().saveButtonLabel??this.config().wizard?.saveButtonText)||(this.locale()==="en"?"Save":"\u062D\u0641\u0638")}showSave(){const e=this.config();return!!(e.enableLocalStorageSave||e.wizard?.showSaveButton)}isLastStep(){return this.wizardEnabled()?this.activeStep()>=this.config().wizard.steps.length-1:!0}isRequired(e){return V(e,this.formValues(),t=>this.optionsMap()[t])}fieldError(e){const t=this.form?.get(e.name);if(!t||!(t.touched||t.dirty)||!t.errors)return"";const o=t.errors,r=e.validation?.errorMessages,n=this.locale(),l=x=>r?.[x]?this.labelOf(r[x]):"";return o.required?l("required")||(n==="en"?"Required":"\u0645\u0637\u0644\u0648\u0628"):o.email?l("email")||(n==="en"?"Invalid email":"\u0628\u0631\u064A\u062F \u063A\u064A\u0631 \u0635\u0627\u0644\u062D"):o.minlength?l("minLength")||(n==="en"?"Too short":"\u0642\u0635\u064A\u0631 \u062C\u062F\u0627\u064B"):o.maxlength?l("maxLength")||(n==="en"?"Too long":"\u0637\u0648\u064A\u0644 \u062C\u062F\u0627\u064B"):o.pattern?l("pattern")||(n==="en"?"Invalid format":"\u0635\u064A\u063A\u0629 \u063A\u064A\u0631 \u0635\u0627\u0644\u062D\u0629"):o.maxChips?l("maxChips")||(n==="en"?"Too many items":"\u0639\u062F\u062F \u0627\u0644\u0639\u0646\u0627\u0635\u0631 \u0643\u0628\u064A\u0631"):n==="en"?"Invalid":"\u063A\u064A\u0631 \u0635\u0627\u0644\u062D"}columnClass(e){const t=e.columns,o=R(t?.sm??t?.md??12),r=R(t?.md??o),n=R(t?.lg??r);return["w-full",we[o],Me[r],Ve[n]].join(" ")}customComponent(e){return this.registry.hasCustom(e.type)?this.registry.get(e.type):null}customInputs(e){return{field:e,form:this.form,locale:this.locale(),required:this.isRequired(e),error:this.fieldError(e)}}onOptionsLoaded(e){this.optionsMap.update(t=>({...t,[e.name]:e.options}))}previousStep(){this.activeStep()>0&&this.activeStep.update(e=>e-1)}nextStep(){this.config().wizard?.validateOnStepChange!==!1&&(this.markStepTouched(),!this.stepValid())||this.isLastStep()||this.activeStep.update(t=>t+1)}onClear(){this.form.reset();for(const t of this.allFields()){const o=this.form.get(t.name);o&&o.setValue(t.value!==void 0?t.value:["checkbox","multiselect","chips","file"].includes(t.type)?[]:t.type==="toggle"?!1:"")}this.activeStep.set(0);const e=this.config().formId;e&&this.formService.clearDraft(e),this.clearButtonClick.emit()}onSaveDraft(){const e=this.form.getRawValue(),t=this.config().formId;t&&this.formService.saveDraft(t,e),this.draftSaved.emit(e),this.toast?.show({variant:"success",message:this.locale()==="en"?"Draft saved":"\u062A\u0645 \u062D\u0641\u0638 \u0627\u0644\u0645\u0633\u0648\u062F\u0629"})}onSubmit(){if(this.wizardEnabled()&&!this.isLastStep()){this.nextStep();return}if(this.markVisibleTouched(),!this.visibleValid()){this.toast?.show({variant:"error",message:this.locale()==="en"?"Please fix the highlighted fields":"\u064A\u0631\u062C\u0649 \u062A\u0635\u062D\u064A\u062D \u0627\u0644\u062D\u0642\u0648\u0644 \u0627\u0644\u0645\u062D\u062F\u062F\u0629"});return}const e=this.config(),t=this.form.getRawValue(),o=new Set(this.collectVisibleFieldNames()),r={};for(const n of this.allFields())!o.has(n.name)&&n.type!=="hidden"||(r[n.name]=t[n.name]);if(e.emitOnly||!e.endpoint){this.formSubmitted.emit(r),this.toast?.show({variant:"success",message:this.labelOf(e.successMessage)||(this.locale()==="en"?"Submitted":"\u062A\u0645 \u0627\u0644\u0625\u0631\u0633\u0627\u0644")});return}this.submitting.set(!0),this.formService.submitForm(e,r,this.allFields()).subscribe({next:n=>{this.submitting.set(!1),this.formSubmitted.emit(n),e.formId&&this.formService.clearDraft(e.formId),this.toast?.show({variant:"success",message:this.labelOf(e.successMessage)||(this.locale()==="en"?"Submitted successfully":"\u062A\u0645 \u0627\u0644\u0625\u0631\u0633\u0627\u0644 \u0628\u0646\u062C\u0627\u062D")})},error:n=>{this.submitting.set(!1),this.formError.emit(n),this.toast?.show({variant:"error",message:this.labelOf(e.errorMessage)||(this.locale()==="en"?"Submission failed":"\u0641\u0634\u0644 \u0627\u0644\u0625\u0631\u0633\u0627\u0644")})}})}patchFormValues(e){this.form.patchValue(e)}getForm(){return this.form}markStepTouched(){for(const e of this.visibleFields())this.form.get(e.name)?.markAsTouched()}markVisibleTouched(){for(const e of this.collectVisibleFieldNames())this.form.get(e)?.markAsTouched()}stepValid(){return this.visibleFields().every(e=>{const t=this.form.get(e.name);return!t||t.disabled||t.valid})}visibleValid(){return this.collectVisibleFieldNames().every(e=>{const t=this.form.get(e);return!t||t.disabled||t.valid})}collectVisibleFieldNames(){const e=this.formValues(),t=this.optionsMap();return(this.wizardEnabled()?this.allFields():this.config().fields).filter(r=>r.type==="hidden"||M(r,e,n=>t[n])).map(r=>r.name)}static \u0275fac=i.\u0275\u0275ngDeclareFactory({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:I,deps:[],target:i.\u0275\u0275FactoryTarget.Component});static \u0275cmp=i.\u0275\u0275ngDeclareComponent({minVersion:"17.0.0",version:"22.0.7",type:I,isStandalone:!0,selector:"dga-dynamic-form",inputs:{config:{classPropertyName:"config",publicName:"config",isSignal:!0,isRequired:!0,transformFunction:null},localeInput:{classPropertyName:"localeInput",publicName:"locale",isSignal:!0,isRequired:!1,transformFunction:null}},outputs:{formSubmitted:"formSubmitted",formError:"formError",clearButtonClick:"clearButtonClick",draftSaved:"draftSaved"},host:{classAttribute:"block w-full"},ngImport:i,template:`
|
|
346
|
+
<form class="flex w-full flex-col gap-3xl" [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
347
|
+
@if (descriptionText()) {
|
|
348
|
+
<p class="text-body-md text-paragraph">{{ descriptionText() }}</p>
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@if (wizardEnabled()) {
|
|
352
|
+
<dga-stepper
|
|
353
|
+
[steps]="stepperSteps()"
|
|
354
|
+
[(active)]="activeStep"
|
|
355
|
+
[label]="locale() === 'en' ? 'Steps' : '\u0627\u0644\u062E\u0637\u0648\u0627\u062A'"
|
|
356
|
+
/>
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
<div class="grid grid-cols-12 gap-xl">
|
|
360
|
+
@for (field of visibleFields(); track field.name) {
|
|
361
|
+
<div [class]="columnClass(field)">
|
|
362
|
+
@if (customComponent(field); as cmp) {
|
|
363
|
+
<ng-container *ngComponentOutlet="cmp; inputs: customInputs(field)" />
|
|
364
|
+
} @else {
|
|
365
|
+
<dga-dynamic-form-field
|
|
366
|
+
[field]="field"
|
|
367
|
+
[form]="form"
|
|
368
|
+
[locale]="locale()"
|
|
369
|
+
[required]="isRequired(field)"
|
|
370
|
+
[error]="fieldError(field)"
|
|
371
|
+
[runtimeOptions]="optionsMap()[field.name] || []"
|
|
372
|
+
(optionsLoaded)="onOptionsLoaded($event)"
|
|
373
|
+
/>
|
|
374
|
+
}
|
|
375
|
+
</div>
|
|
376
|
+
}
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
<div class="flex flex-wrap items-center gap-md">
|
|
380
|
+
@if (wizardEnabled() && activeStep() > 0) {
|
|
381
|
+
<button
|
|
382
|
+
dgaButton
|
|
383
|
+
type="button"
|
|
384
|
+
variant="secondary"
|
|
385
|
+
(click)="previousStep()"
|
|
386
|
+
>
|
|
387
|
+
{{ previousLabel() }}
|
|
388
|
+
</button>
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
@if (wizardEnabled() && !isLastStep()) {
|
|
392
|
+
<button dgaButton type="button" (click)="nextStep()">
|
|
393
|
+
{{ nextLabel() }}
|
|
394
|
+
</button>
|
|
395
|
+
} @else {
|
|
396
|
+
<button dgaButton type="submit" [disabled]="submitting()">
|
|
397
|
+
{{ submitLabel() }}
|
|
398
|
+
</button>
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
@if (config().clearButtonLabel) {
|
|
402
|
+
<button
|
|
403
|
+
dgaButton
|
|
404
|
+
type="button"
|
|
405
|
+
variant="ghost"
|
|
406
|
+
(click)="onClear()"
|
|
407
|
+
>
|
|
408
|
+
{{ clearLabel() }}
|
|
409
|
+
</button>
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
@if (showSave()) {
|
|
413
|
+
<button
|
|
414
|
+
dgaButton
|
|
415
|
+
type="button"
|
|
416
|
+
variant="secondary"
|
|
417
|
+
(click)="onSaveDraft()"
|
|
418
|
+
>
|
|
419
|
+
{{ saveLabel() }}
|
|
420
|
+
</button>
|
|
421
|
+
}
|
|
422
|
+
</div>
|
|
423
|
+
</form>
|
|
424
|
+
`,isInline:!0,dependencies:[{kind:"ngmodule",type:k},{kind:"directive",type:p.\u0275NgNoValidate,selector:"form:not([ngNoForm]):not([ngNativeValidate])"},{kind:"directive",type:p.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:p.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"component",type:f,selector:"dga-dynamic-form-field",inputs:["field","form","locale","required","error","runtimeOptions"],outputs:["optionsLoaded"]},{kind:"directive",type:oe,selector:"button[dgaButton], a[dgaButton]",inputs:["variant","size","iconOnly","cooldownMs","loading"]},{kind:"component",type:ae,selector:"dga-stepper",inputs:["steps","active","label"],outputs:["activeChange"]},{kind:"directive",type:B,selector:"[ngComponentOutlet]",inputs:["ngComponentOutlet","ngComponentOutletInputs","ngComponentOutletInjector","ngComponentOutletEnvironmentInjector","ngComponentOutletContent","ngComponentOutletNgModule"],exportAs:["ngComponentOutlet"]}],changeDetection:i.ChangeDetectionStrategy.OnPush})}i.\u0275\u0275ngDeclareClassMetadata({minVersion:"12.0.0",version:"22.0.7",ngImport:i,type:I,decorators:[{type:z,args:[{selector:"dga-dynamic-form",standalone:!0,imports:[k,f,oe,ae,B],template:`
|
|
425
|
+
<form class="flex w-full flex-col gap-3xl" [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
426
|
+
@if (descriptionText()) {
|
|
427
|
+
<p class="text-body-md text-paragraph">{{ descriptionText() }}</p>
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
@if (wizardEnabled()) {
|
|
431
|
+
<dga-stepper
|
|
432
|
+
[steps]="stepperSteps()"
|
|
433
|
+
[(active)]="activeStep"
|
|
434
|
+
[label]="locale() === 'en' ? 'Steps' : '\u0627\u0644\u062E\u0637\u0648\u0627\u062A'"
|
|
435
|
+
/>
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
<div class="grid grid-cols-12 gap-xl">
|
|
439
|
+
@for (field of visibleFields(); track field.name) {
|
|
440
|
+
<div [class]="columnClass(field)">
|
|
441
|
+
@if (customComponent(field); as cmp) {
|
|
442
|
+
<ng-container *ngComponentOutlet="cmp; inputs: customInputs(field)" />
|
|
443
|
+
} @else {
|
|
444
|
+
<dga-dynamic-form-field
|
|
445
|
+
[field]="field"
|
|
446
|
+
[form]="form"
|
|
447
|
+
[locale]="locale()"
|
|
448
|
+
[required]="isRequired(field)"
|
|
449
|
+
[error]="fieldError(field)"
|
|
450
|
+
[runtimeOptions]="optionsMap()[field.name] || []"
|
|
451
|
+
(optionsLoaded)="onOptionsLoaded($event)"
|
|
452
|
+
/>
|
|
453
|
+
}
|
|
454
|
+
</div>
|
|
455
|
+
}
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
<div class="flex flex-wrap items-center gap-md">
|
|
459
|
+
@if (wizardEnabled() && activeStep() > 0) {
|
|
460
|
+
<button
|
|
461
|
+
dgaButton
|
|
462
|
+
type="button"
|
|
463
|
+
variant="secondary"
|
|
464
|
+
(click)="previousStep()"
|
|
465
|
+
>
|
|
466
|
+
{{ previousLabel() }}
|
|
467
|
+
</button>
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
@if (wizardEnabled() && !isLastStep()) {
|
|
471
|
+
<button dgaButton type="button" (click)="nextStep()">
|
|
472
|
+
{{ nextLabel() }}
|
|
473
|
+
</button>
|
|
474
|
+
} @else {
|
|
475
|
+
<button dgaButton type="submit" [disabled]="submitting()">
|
|
476
|
+
{{ submitLabel() }}
|
|
477
|
+
</button>
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
@if (config().clearButtonLabel) {
|
|
481
|
+
<button
|
|
482
|
+
dgaButton
|
|
483
|
+
type="button"
|
|
484
|
+
variant="ghost"
|
|
485
|
+
(click)="onClear()"
|
|
486
|
+
>
|
|
487
|
+
{{ clearLabel() }}
|
|
488
|
+
</button>
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
@if (showSave()) {
|
|
492
|
+
<button
|
|
493
|
+
dgaButton
|
|
494
|
+
type="button"
|
|
495
|
+
variant="secondary"
|
|
496
|
+
(click)="onSaveDraft()"
|
|
497
|
+
>
|
|
498
|
+
{{ saveLabel() }}
|
|
499
|
+
</button>
|
|
500
|
+
}
|
|
501
|
+
</div>
|
|
502
|
+
</form>
|
|
503
|
+
`,changeDetection:q.OnPush,host:{class:"block w-full"}}]}],ctorParameters:()=>[],propDecorators:{config:[{type:i.Input,args:[{isSignal:!0,alias:"config",required:!0}]}],localeInput:[{type:i.Input,args:[{isSignal:!0,alias:"locale",required:!1}]}],formSubmitted:[{type:i.Output,args:["formSubmitted"]}],formError:[{type:i.Output,args:["formError"]}],clearButtonClick:[{type:i.Output,args:["clearButtonClick"]}],draftSaved:[{type:i.Output,args:["draftSaved"]}]}});const me={resolveLabel(a,e){return e==="en"?a.en:a.ar}},fe={getToken:()=>h(null)},ge={lookup:()=>h([])};function he(a=s(re)){return{lookup(e){const o=`${e.domain.replace(/\/$/,"")}/lookup/${e.name}`,r={};if(e.parentId!=null&&e.parentId!==""&&(r.parentId=String(e.parentId)),e.search){const n=e.searchParamName||"q";r[n]=e.search}return a.get(o,{params:r}).pipe(De(n=>Array.isArray(n)?n.map(Re):[]))}}}function Re(a){const e=a??{},t=e.value??e.id??e.Id??"",o=String(e.labelEn??e.titleEn??e.en??e.TitleEn??t),r=String(e.labelAr??e.titleAr??e.ar??e.TitleAr??o);return{value:t,label:{en:o,ar:r},...e}}function be(a=s(re)){return{submit(e){const t=new Le(e.headers??{}),o=e.method.toUpperCase();return o==="GET"?a.get(e.endpoint,{headers:t}):o==="DELETE"?a.delete(e.endpoint,{headers:t,body:e.body}):o==="PUT"?a.put(e.endpoint,e.body,{headers:t}):o==="PATCH"?a.patch(e.endpoint,e.body,{headers:t}):a.post(e.endpoint,e.body,{headers:t})}}}function ve(a=s(Oe)){return{show(e){a.show({title:e.title,message:e.message,variant:e.variant??"info"})}}}function Pe(a={}){const e=a.httpLookup??!1,t=a.httpSubmit??!0,o=a.dgaToast??!0;return Se([{provide:N,useValue:me},{provide:T,useValue:fe},{provide:D,useFactory:()=>e?he():ge},{provide:O,useFactory:()=>t?be():{submit:()=>h(null)}},{provide:L,useFactory:()=>o?ve():{show:()=>{}}}])}function Ee(a,e,t=""){return a?e==="en"?a.en:a.ar:t}export{T as DGA_CAPTCHA_ADAPTER,ne as DGA_DYNAMIC_FORM_FIELD_REGISTRY,N as DGA_FORM_I18N_ADAPTER,L as DGA_FORM_TOAST_ADAPTER,D as DGA_LOOKUP_ADAPTER,O as DGA_SUBMIT_ADAPTER,I as DgaDynamicForm,f as DgaDynamicFormField,y as DgaDynamicFormFieldRegistry,v as DgaDynamicFormService,ve as createDgaToastAdapter,he as createHttpLookupAdapter,be as createHttpSubmitAdapter,me as defaultFormI18nAdapter,de as dgaBuildFormGroup,A as dgaBuildValidators,ce as dgaCollectFields,se as dgaCreateControl,le as dgaDefaultValueForField,pe as dgaDetectLocale,w as dgaFindSelectedOption,M as dgaIsFieldVisible,V as dgaIsRequiredWhen,b as dgaResolveLabel,ue as dgaSetControlValidators,ge as emptyLookupAdapter,fe as noopCaptchaAdapter,Pe as provideDgaDynamicForm,Ee as resolveFormLabel};
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@a.nemreen/dga-dynamic-form",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Schema-driven dynamic forms for Angular, rendered with @a.nemreen/dga-ui controls",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Ahmed Nemreen",
|
|
8
|
+
"email": "a.nemreen@gmail.com",
|
|
9
|
+
"url": "https://nemreen.info"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://nemreen.info/dga/",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/nemreen/dga-angular.git",
|
|
15
|
+
"directory": "projects/dga-dynamic-form"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/nemreen/dga-angular/issues"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"angular",
|
|
22
|
+
"dynamic-form",
|
|
23
|
+
"schema-form",
|
|
24
|
+
"reactive-forms",
|
|
25
|
+
"dga",
|
|
26
|
+
"design-system",
|
|
27
|
+
"rtl",
|
|
28
|
+
"arabic",
|
|
29
|
+
"saudi",
|
|
30
|
+
"government",
|
|
31
|
+
"forms"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@angular/common": "^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
38
|
+
"@angular/core": "^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
39
|
+
"@angular/forms": "^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
40
|
+
"@a.nemreen/dga-tokens": "^0.2.0",
|
|
41
|
+
"@a.nemreen/dga-ui": "^0.2.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"tslib": "^2.3.0"
|
|
45
|
+
},
|
|
46
|
+
"sideEffects": false,
|
|
47
|
+
"module": "fesm2022/a.nemreen-dga-dynamic-form.mjs",
|
|
48
|
+
"typings": "types/a.nemreen-dga-dynamic-form.d.ts",
|
|
49
|
+
"exports": {
|
|
50
|
+
"./package.json": {
|
|
51
|
+
"default": "./package.json"
|
|
52
|
+
},
|
|
53
|
+
".": {
|
|
54
|
+
"types": "./types/a.nemreen-dga-dynamic-form.d.ts",
|
|
55
|
+
"default": "./fesm2022/a.nemreen-dga-dynamic-form.mjs"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"type": "module"
|
|
59
|
+
}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { InjectionToken, Type, OnInit, EnvironmentProviders } from '@angular/core';
|
|
3
|
+
import { FormGroup, FormControl, FormBuilder, ValidatorFn, AbstractControl } from '@angular/forms';
|
|
4
|
+
import { DgaStep, DgaSelectOption, DgaToastService } from '@a.nemreen/dga-ui';
|
|
5
|
+
import { Observable } from 'rxjs';
|
|
6
|
+
import { HttpClient } from '@angular/common/http';
|
|
7
|
+
|
|
8
|
+
/** Bilingual label used across the schema. */
|
|
9
|
+
interface DgaFormLabel {
|
|
10
|
+
en: string;
|
|
11
|
+
ar: string;
|
|
12
|
+
}
|
|
13
|
+
interface DgaFormFieldColumns {
|
|
14
|
+
xl?: number;
|
|
15
|
+
lg?: number;
|
|
16
|
+
md?: number;
|
|
17
|
+
sm?: number;
|
|
18
|
+
}
|
|
19
|
+
interface DgaFormFieldOption {
|
|
20
|
+
value: string | number | boolean;
|
|
21
|
+
label: DgaFormLabel;
|
|
22
|
+
requiresTextInput?: boolean;
|
|
23
|
+
description?: DgaFormLabel;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
type DgaFormFieldType = 'text' | 'email' | 'textarea' | 'select' | 'multiselect' | 'checkbox' | 'radio' | 'phone' | 'file' | 'date' | 'chips' | 'toggle' | 'hidden' | 'number' | 'otp';
|
|
28
|
+
type DgaInputRestriction = 'numbers' | 'english' | 'arabic';
|
|
29
|
+
type DgaHttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
30
|
+
interface DgaFormField {
|
|
31
|
+
name: string;
|
|
32
|
+
type: DgaFormFieldType;
|
|
33
|
+
label: DgaFormLabel;
|
|
34
|
+
required?: boolean;
|
|
35
|
+
options?: DgaFormFieldOption[];
|
|
36
|
+
groupedOptions?: Array<{
|
|
37
|
+
groupLabel: DgaFormLabel;
|
|
38
|
+
options: DgaFormFieldOption[];
|
|
39
|
+
}>;
|
|
40
|
+
hidden?: boolean;
|
|
41
|
+
dependsOn?: string;
|
|
42
|
+
showOnValues?: unknown[];
|
|
43
|
+
showWhenOptionProperty?: {
|
|
44
|
+
property: string;
|
|
45
|
+
value: unknown;
|
|
46
|
+
};
|
|
47
|
+
lookupDomain?: string;
|
|
48
|
+
lookupName?: string;
|
|
49
|
+
lookupFilter?: {
|
|
50
|
+
property: string;
|
|
51
|
+
value: unknown;
|
|
52
|
+
};
|
|
53
|
+
useParentId?: boolean;
|
|
54
|
+
searchLookup?: boolean;
|
|
55
|
+
searchParamName?: string;
|
|
56
|
+
minSearchLength?: number;
|
|
57
|
+
maxLength?: number;
|
|
58
|
+
placeholder?: DgaFormLabel;
|
|
59
|
+
hint?: DgaFormLabel;
|
|
60
|
+
acceptedFileTypes?: string;
|
|
61
|
+
maxFileSize?: number;
|
|
62
|
+
maxFiles?: number;
|
|
63
|
+
rows?: number;
|
|
64
|
+
minDate?: string;
|
|
65
|
+
maxDate?: string;
|
|
66
|
+
monthPicker?: boolean;
|
|
67
|
+
maxChips?: number;
|
|
68
|
+
allowDuplicates?: boolean;
|
|
69
|
+
disabled?: boolean;
|
|
70
|
+
readonly?: boolean;
|
|
71
|
+
value?: unknown;
|
|
72
|
+
columns?: DgaFormFieldColumns;
|
|
73
|
+
optionsLayout?: 'stack' | 'grid';
|
|
74
|
+
excludeFromPayload?: boolean;
|
|
75
|
+
inputRestriction?: DgaInputRestriction;
|
|
76
|
+
requiredWhen?: {
|
|
77
|
+
field: string;
|
|
78
|
+
value?: unknown;
|
|
79
|
+
values?: unknown[];
|
|
80
|
+
optionProperty?: string;
|
|
81
|
+
optionPropertyValue?: unknown;
|
|
82
|
+
};
|
|
83
|
+
validation?: {
|
|
84
|
+
pattern?: string;
|
|
85
|
+
minLength?: number;
|
|
86
|
+
maxLength?: number;
|
|
87
|
+
required?: boolean;
|
|
88
|
+
min?: number;
|
|
89
|
+
max?: number;
|
|
90
|
+
errorMessages?: Partial<Record<'required' | 'pattern' | 'minLength' | 'maxLength' | 'email' | 'min' | 'max' | 'minDate' | 'maxDate' | 'maxChips' | 'invalidFormat', DgaFormLabel>>;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
interface DgaFormStep {
|
|
94
|
+
id: string;
|
|
95
|
+
title: DgaFormLabel;
|
|
96
|
+
description?: DgaFormLabel;
|
|
97
|
+
fields: DgaFormField[];
|
|
98
|
+
optional?: boolean;
|
|
99
|
+
}
|
|
100
|
+
interface DgaWizardConfig {
|
|
101
|
+
enabled: boolean;
|
|
102
|
+
validateOnStepChange?: boolean;
|
|
103
|
+
allowSkipSteps?: boolean;
|
|
104
|
+
showSaveButton?: boolean;
|
|
105
|
+
autoSave?: boolean;
|
|
106
|
+
saveEndpoint?: string;
|
|
107
|
+
saveMethod?: DgaHttpMethod;
|
|
108
|
+
saveButtonText?: DgaFormLabel;
|
|
109
|
+
nextButtonText?: DgaFormLabel;
|
|
110
|
+
previousButtonText?: DgaFormLabel;
|
|
111
|
+
submitButtonText?: DgaFormLabel;
|
|
112
|
+
steps: DgaFormStep[];
|
|
113
|
+
}
|
|
114
|
+
type DgaDynamicFormPayloadTransformer = (data: Record<string, unknown>) => Record<string, unknown> | FormData;
|
|
115
|
+
interface DgaDynamicFormConfig {
|
|
116
|
+
/** Submit URL. Optional when the consumer handles `formSubmitted` only. */
|
|
117
|
+
endpoint?: string;
|
|
118
|
+
method?: DgaHttpMethod;
|
|
119
|
+
fields: DgaFormField[];
|
|
120
|
+
description?: DgaFormLabel;
|
|
121
|
+
wizard?: DgaWizardConfig;
|
|
122
|
+
submitButtonLabel?: DgaFormLabel;
|
|
123
|
+
clearButtonLabel?: DgaFormLabel;
|
|
124
|
+
saveButtonLabel?: DgaFormLabel;
|
|
125
|
+
successMessage?: DgaFormLabel;
|
|
126
|
+
errorMessage?: DgaFormLabel;
|
|
127
|
+
fieldMapping?: Record<string, string>;
|
|
128
|
+
payloadTransformer?: DgaDynamicFormPayloadTransformer;
|
|
129
|
+
formId?: string;
|
|
130
|
+
enableLocalStorageSave?: boolean;
|
|
131
|
+
idempotencyKey?: string;
|
|
132
|
+
/** When true, HTTP submit is skipped and only `formSubmitted` is emitted. */
|
|
133
|
+
emitOnly?: boolean;
|
|
134
|
+
}
|
|
135
|
+
type DgaFormLocale = 'ar' | 'en';
|
|
136
|
+
|
|
137
|
+
interface DgaLookupRequest {
|
|
138
|
+
domain: string;
|
|
139
|
+
name: string;
|
|
140
|
+
parentId?: string | number | null;
|
|
141
|
+
search?: string;
|
|
142
|
+
searchParamName?: string;
|
|
143
|
+
}
|
|
144
|
+
interface DgaLookupAdapter {
|
|
145
|
+
lookup(request: DgaLookupRequest): Observable<DgaFormFieldOption[]>;
|
|
146
|
+
}
|
|
147
|
+
interface DgaSubmitRequest {
|
|
148
|
+
endpoint: string;
|
|
149
|
+
method: DgaHttpMethod;
|
|
150
|
+
body: Record<string, unknown> | FormData;
|
|
151
|
+
headers?: Record<string, string>;
|
|
152
|
+
}
|
|
153
|
+
interface DgaSubmitAdapter {
|
|
154
|
+
submit<T = unknown>(request: DgaSubmitRequest): Observable<T>;
|
|
155
|
+
}
|
|
156
|
+
type DgaToastVariant = 'neutral' | 'success' | 'info' | 'warning' | 'error';
|
|
157
|
+
interface DgaFormToastAdapter {
|
|
158
|
+
show(options: {
|
|
159
|
+
title?: string;
|
|
160
|
+
message?: string;
|
|
161
|
+
variant?: DgaToastVariant;
|
|
162
|
+
}): void;
|
|
163
|
+
}
|
|
164
|
+
interface DgaCaptchaAdapter {
|
|
165
|
+
/** Returns a captcha token (or null when captcha is disabled). */
|
|
166
|
+
getToken(action?: string): Observable<string | null>;
|
|
167
|
+
}
|
|
168
|
+
interface DgaFormI18nAdapter {
|
|
169
|
+
/** Resolve a bilingual label for the active locale. */
|
|
170
|
+
resolveLabel(label: {
|
|
171
|
+
en: string;
|
|
172
|
+
ar: string;
|
|
173
|
+
}, locale: DgaFormLocale): string;
|
|
174
|
+
/** Optional free-form key lookup (e.g. ngx-translate). */
|
|
175
|
+
translate?(key: string, locale: DgaFormLocale): string;
|
|
176
|
+
}
|
|
177
|
+
/** Custom field renderer registered via `DGA_DYNAMIC_FORM_FIELD_REGISTRY`. */
|
|
178
|
+
interface DgaDynamicFieldRenderer {
|
|
179
|
+
/** Unique type id matching `DgaFormField.type` (or a custom string). */
|
|
180
|
+
type: string;
|
|
181
|
+
component: Type<unknown>;
|
|
182
|
+
}
|
|
183
|
+
declare const DGA_LOOKUP_ADAPTER: InjectionToken<DgaLookupAdapter>;
|
|
184
|
+
declare const DGA_SUBMIT_ADAPTER: InjectionToken<DgaSubmitAdapter>;
|
|
185
|
+
declare const DGA_FORM_TOAST_ADAPTER: InjectionToken<DgaFormToastAdapter>;
|
|
186
|
+
declare const DGA_CAPTCHA_ADAPTER: InjectionToken<DgaCaptchaAdapter>;
|
|
187
|
+
declare const DGA_FORM_I18N_ADAPTER: InjectionToken<DgaFormI18nAdapter>;
|
|
188
|
+
declare const DGA_DYNAMIC_FORM_FIELD_REGISTRY: InjectionToken<DgaDynamicFieldRenderer[]>;
|
|
189
|
+
/** Context passed into custom field renderer components (optional inject). */
|
|
190
|
+
interface DgaDynamicFieldHostContext {
|
|
191
|
+
field: DgaFormField;
|
|
192
|
+
controlName: string;
|
|
193
|
+
locale: DgaFormLocale;
|
|
194
|
+
invalid: boolean;
|
|
195
|
+
error: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Resolves built-in + consumer-registered field renderers by `type`.
|
|
200
|
+
* Built-in types are rendered by `DgaDynamicFormField` directly;
|
|
201
|
+
* custom types use the registered component via `NgComponentOutlet`.
|
|
202
|
+
*/
|
|
203
|
+
declare class DgaDynamicFormFieldRegistry {
|
|
204
|
+
private readonly extras;
|
|
205
|
+
private readonly map;
|
|
206
|
+
constructor();
|
|
207
|
+
register(entry: DgaDynamicFieldRenderer): void;
|
|
208
|
+
get(type: string): Type<unknown> | null;
|
|
209
|
+
hasCustom(type: string): boolean;
|
|
210
|
+
isBuiltIn(type: string): type is DgaFormFieldType;
|
|
211
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DgaDynamicFormFieldRegistry, never>;
|
|
212
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<DgaDynamicFormFieldRegistry>;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
declare class DgaDynamicForm implements OnInit {
|
|
216
|
+
readonly config: _angular_core.InputSignal<DgaDynamicFormConfig>;
|
|
217
|
+
readonly localeInput: _angular_core.InputSignal<DgaFormLocale | null>;
|
|
218
|
+
readonly formSubmitted: _angular_core.OutputEmitterRef<unknown>;
|
|
219
|
+
readonly formError: _angular_core.OutputEmitterRef<unknown>;
|
|
220
|
+
readonly clearButtonClick: _angular_core.OutputEmitterRef<void>;
|
|
221
|
+
readonly draftSaved: _angular_core.OutputEmitterRef<Record<string, unknown>>;
|
|
222
|
+
private readonly fb;
|
|
223
|
+
private readonly formService;
|
|
224
|
+
private readonly toast;
|
|
225
|
+
private readonly i18n;
|
|
226
|
+
private readonly destroyRef;
|
|
227
|
+
readonly registry: DgaDynamicFormFieldRegistry;
|
|
228
|
+
form: FormGroup;
|
|
229
|
+
readonly activeStep: _angular_core.WritableSignal<number>;
|
|
230
|
+
readonly submitting: _angular_core.WritableSignal<boolean>;
|
|
231
|
+
readonly optionsMap: _angular_core.WritableSignal<Record<string, DgaFormFieldOption[]>>;
|
|
232
|
+
private readonly formValues;
|
|
233
|
+
readonly locale: _angular_core.Signal<DgaFormLocale>;
|
|
234
|
+
readonly wizardEnabled: _angular_core.Signal<boolean>;
|
|
235
|
+
readonly allFields: _angular_core.Signal<DgaFormField[]>;
|
|
236
|
+
readonly currentStepFields: _angular_core.Signal<DgaFormField[]>;
|
|
237
|
+
readonly visibleFields: _angular_core.Signal<DgaFormField[]>;
|
|
238
|
+
readonly stepperSteps: _angular_core.Signal<DgaStep[]>;
|
|
239
|
+
readonly descriptionText: _angular_core.Signal<string>;
|
|
240
|
+
constructor();
|
|
241
|
+
ngOnInit(): void;
|
|
242
|
+
labelOf(label: {
|
|
243
|
+
en: string;
|
|
244
|
+
ar: string;
|
|
245
|
+
} | undefined): string;
|
|
246
|
+
submitLabel(): string;
|
|
247
|
+
nextLabel(): string;
|
|
248
|
+
previousLabel(): string;
|
|
249
|
+
clearLabel(): string;
|
|
250
|
+
saveLabel(): string;
|
|
251
|
+
showSave(): boolean;
|
|
252
|
+
isLastStep(): boolean;
|
|
253
|
+
isRequired(field: DgaFormField): boolean;
|
|
254
|
+
fieldError(field: DgaFormField): string;
|
|
255
|
+
columnClass(field: DgaFormField): string;
|
|
256
|
+
customComponent(field: DgaFormField): _angular_core.Type<unknown> | null;
|
|
257
|
+
customInputs(field: DgaFormField): Record<string, unknown>;
|
|
258
|
+
onOptionsLoaded(event: {
|
|
259
|
+
name: string;
|
|
260
|
+
options: DgaFormFieldOption[];
|
|
261
|
+
}): void;
|
|
262
|
+
previousStep(): void;
|
|
263
|
+
nextStep(): void;
|
|
264
|
+
onClear(): void;
|
|
265
|
+
onSaveDraft(): void;
|
|
266
|
+
onSubmit(): void;
|
|
267
|
+
/** Imperative helpers for host apps. */
|
|
268
|
+
patchFormValues(values: Record<string, unknown>): void;
|
|
269
|
+
getForm(): FormGroup;
|
|
270
|
+
private markStepTouched;
|
|
271
|
+
private markVisibleTouched;
|
|
272
|
+
private stepValid;
|
|
273
|
+
private visibleValid;
|
|
274
|
+
private collectVisibleFieldNames;
|
|
275
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DgaDynamicForm, never>;
|
|
276
|
+
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>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
declare class DgaDynamicFormField implements OnInit {
|
|
280
|
+
readonly field: _angular_core.InputSignal<DgaFormField>;
|
|
281
|
+
readonly form: _angular_core.InputSignal<FormGroup<any>>;
|
|
282
|
+
readonly locale: _angular_core.InputSignal<DgaFormLocale>;
|
|
283
|
+
readonly required: _angular_core.InputSignalWithTransform<boolean, unknown>;
|
|
284
|
+
readonly error: _angular_core.InputSignal<string>;
|
|
285
|
+
readonly runtimeOptions: _angular_core.InputSignal<DgaFormFieldOption[]>;
|
|
286
|
+
readonly optionsLoaded: _angular_core.OutputEmitterRef<{
|
|
287
|
+
name: string;
|
|
288
|
+
options: DgaFormFieldOption[];
|
|
289
|
+
}>;
|
|
290
|
+
private readonly lookup;
|
|
291
|
+
private readonly destroyRef;
|
|
292
|
+
readonly loadedOptions: _angular_core.WritableSignal<DgaFormFieldOption[]>;
|
|
293
|
+
readonly controlId: string;
|
|
294
|
+
get control(): FormControl;
|
|
295
|
+
readonly labelText: _angular_core.Signal<string>;
|
|
296
|
+
readonly hintText: _angular_core.Signal<string>;
|
|
297
|
+
readonly placeholderText: _angular_core.Signal<string>;
|
|
298
|
+
readonly resolvedOptions: _angular_core.Signal<DgaFormFieldOption[]>;
|
|
299
|
+
readonly selectOptions: _angular_core.Signal<DgaSelectOption[]>;
|
|
300
|
+
ngOnInit(): void;
|
|
301
|
+
optionLabel(opt: DgaFormFieldOption): string;
|
|
302
|
+
isChecked(value: unknown): boolean;
|
|
303
|
+
toggleCheckbox(value: unknown, checked: boolean): void;
|
|
304
|
+
onSelectSearch(_value: string | string[]): void;
|
|
305
|
+
onRestrict(event: Event): void;
|
|
306
|
+
private applyLookupFilter;
|
|
307
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DgaDynamicFormField, never>;
|
|
308
|
+
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>;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
declare class DgaDynamicFormService {
|
|
312
|
+
private readonly submitAdapter;
|
|
313
|
+
private readonly captcha;
|
|
314
|
+
/**
|
|
315
|
+
* Build a JSON payload from form values, applying fieldMapping / transformer / exclusions.
|
|
316
|
+
*/
|
|
317
|
+
buildPayload(config: DgaDynamicFormConfig, values: Record<string, unknown>, fields: DgaFormField[]): Record<string, unknown> | FormData;
|
|
318
|
+
hasFileValues(data: Record<string, unknown>): boolean;
|
|
319
|
+
toFormData(data: Record<string, unknown>): FormData;
|
|
320
|
+
submitForm(config: DgaDynamicFormConfig, values: Record<string, unknown>, fields: DgaFormField[]): Observable<unknown>;
|
|
321
|
+
saveDraft(formId: string, values: Record<string, unknown>): void;
|
|
322
|
+
loadDraft(formId: string): Record<string, unknown> | null;
|
|
323
|
+
clearDraft(formId: string): void;
|
|
324
|
+
private storageKey;
|
|
325
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DgaDynamicFormService, never>;
|
|
326
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<DgaDynamicFormService>;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
declare function dgaDefaultValueForField(field: DgaFormField): unknown;
|
|
330
|
+
declare function dgaBuildValidators(field: DgaFormField, includeRequired: boolean): ValidatorFn[];
|
|
331
|
+
declare function dgaCreateControl(fb: FormBuilder, field: DgaFormField, includeRequired?: boolean): FormControl;
|
|
332
|
+
declare function dgaBuildFormGroup(fb: FormBuilder, fields: DgaFormField[]): FormGroup;
|
|
333
|
+
declare function dgaCollectFields(fields: DgaFormField[], wizardSteps?: {
|
|
334
|
+
fields: DgaFormField[];
|
|
335
|
+
}[]): DgaFormField[];
|
|
336
|
+
declare function dgaSetControlValidators(control: AbstractControl, field: DgaFormField, includeRequired: boolean): void;
|
|
337
|
+
|
|
338
|
+
declare function dgaFindSelectedOption(field: DgaFormField | undefined, value: unknown, runtimeOptions?: DgaFormFieldOption[]): DgaFormFieldOption | undefined;
|
|
339
|
+
/**
|
|
340
|
+
* Whether a field should be visible given the current form values.
|
|
341
|
+
*/
|
|
342
|
+
declare function dgaIsFieldVisible(field: DgaFormField, values: Record<string, unknown>, getOptions?: (name: string) => DgaFormFieldOption[] | undefined): boolean;
|
|
343
|
+
declare function dgaIsRequiredWhen(field: DgaFormField, values: Record<string, unknown>, getOptions?: (name: string) => DgaFormFieldOption[] | undefined): boolean;
|
|
344
|
+
|
|
345
|
+
/** Default bilingual label resolver. */
|
|
346
|
+
declare const defaultFormI18nAdapter: DgaFormI18nAdapter;
|
|
347
|
+
/** No-op captcha (always null token). */
|
|
348
|
+
declare const noopCaptchaAdapter: DgaCaptchaAdapter;
|
|
349
|
+
/** Empty lookup (returns []). Override in the host app. */
|
|
350
|
+
declare const emptyLookupAdapter: DgaLookupAdapter;
|
|
351
|
+
/**
|
|
352
|
+
* HTTP lookup that expects `{domain}/lookup/{name}` JSON arrays.
|
|
353
|
+
* Maps common `{ id|value, titleEn|labelEn|en, titleAr|labelAr|ar }` shapes.
|
|
354
|
+
*/
|
|
355
|
+
declare function createHttpLookupAdapter(http?: HttpClient): DgaLookupAdapter;
|
|
356
|
+
declare function createHttpSubmitAdapter(http?: HttpClient): DgaSubmitAdapter;
|
|
357
|
+
declare function createDgaToastAdapter(toasts?: DgaToastService): DgaFormToastAdapter;
|
|
358
|
+
interface ProvideDgaDynamicFormOptions {
|
|
359
|
+
/** Use HttpClient-backed lookup (default: empty). */
|
|
360
|
+
httpLookup?: boolean;
|
|
361
|
+
/** Use HttpClient-backed submit (default: true). */
|
|
362
|
+
httpSubmit?: boolean;
|
|
363
|
+
/** Wire DgaToastService (default: true). */
|
|
364
|
+
dgaToast?: boolean;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Registers default adapters for lookup, submit, toast, captcha, and i18n.
|
|
368
|
+
* Call from `app.config.ts` providers.
|
|
369
|
+
*/
|
|
370
|
+
declare function provideDgaDynamicForm(options?: ProvideDgaDynamicFormOptions): EnvironmentProviders;
|
|
371
|
+
/** Helper for templates / host apps. */
|
|
372
|
+
declare function resolveFormLabel(label: {
|
|
373
|
+
en: string;
|
|
374
|
+
ar: string;
|
|
375
|
+
} | undefined, locale: DgaFormLocale, fallback?: string): string;
|
|
376
|
+
|
|
377
|
+
declare function dgaResolveLabel(label: DgaFormLabel | undefined, locale: DgaFormLocale, fallback?: string): string;
|
|
378
|
+
declare function dgaDetectLocale(): DgaFormLocale;
|
|
379
|
+
|
|
380
|
+
export { DGA_CAPTCHA_ADAPTER, DGA_DYNAMIC_FORM_FIELD_REGISTRY, DGA_FORM_I18N_ADAPTER, DGA_FORM_TOAST_ADAPTER, DGA_LOOKUP_ADAPTER, DGA_SUBMIT_ADAPTER, DgaDynamicForm, DgaDynamicFormField, DgaDynamicFormFieldRegistry, DgaDynamicFormService, createDgaToastAdapter, createHttpLookupAdapter, createHttpSubmitAdapter, defaultFormI18nAdapter, dgaBuildFormGroup, dgaBuildValidators, dgaCollectFields, dgaCreateControl, dgaDefaultValueForField, dgaDetectLocale, dgaFindSelectedOption, dgaIsFieldVisible, dgaIsRequiredWhen, dgaResolveLabel, dgaSetControlValidators, emptyLookupAdapter, noopCaptchaAdapter, provideDgaDynamicForm, resolveFormLabel };
|
|
381
|
+
export type { DgaCaptchaAdapter, DgaDynamicFieldHostContext, DgaDynamicFieldRenderer, DgaDynamicFormConfig, DgaDynamicFormPayloadTransformer, DgaFormField, DgaFormFieldColumns, DgaFormFieldOption, DgaFormFieldType, DgaFormI18nAdapter, DgaFormLabel, DgaFormLocale, DgaFormStep, DgaFormToastAdapter, DgaHttpMethod, DgaInputRestriction, DgaLookupAdapter, DgaLookupRequest, DgaSubmitAdapter, DgaSubmitRequest, DgaToastVariant, DgaWizardConfig, ProvideDgaDynamicFormOptions };
|