@zellvora/ng-form 0.1.1 → 0.1.3

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 ADDED
@@ -0,0 +1,18 @@
1
+ # @zv/ng-form
2
+
3
+ Part of the [Zellavora UI](../../README.md) design system — Angular 22, zoneless, signal-first.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @zv/ng-form @zv/ng-core
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Import the standalone APIs directly into your components. See the
14
+ [Storybook documentation](https://zellavora.github.io/zellavora-ui) for live examples.
15
+
16
+ ## License
17
+
18
+ MIT © Zellavora UI
@@ -1,106 +1,24 @@
1
- import * as i0 from '@angular/core';
2
- import { signal, forwardRef, Input, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component } from '@angular/core';
3
- import { NG_VALUE_ACCESSOR } from '@angular/forms';
4
-
5
- /**
6
- * Schema-driven form. Implements `ControlValueAccessor`, so it plugs into
7
- * `[(ngModel)]`, `formControlName`, or a Signal Forms model exactly like an
8
- * atomic control. Renders the right `@zellvora/ng-input` control per field
9
- * and keeps a single model object in sync.
10
- */
11
- class ZvDynamicForm {
12
- schema = [];
13
- model = signal({}, ...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
14
- onChange = () => { };
15
- onTouched = () => { };
16
- writeValue(v) { this.model.set(v ?? {}); }
17
- registerOnChange(fn) { this.onChange = fn; }
18
- registerOnTouched(fn) { this.onTouched = fn; }
19
- get(f) { return this.model()[f.field] ?? null; }
20
- set(f, value) {
21
- const next = { ...this.model(), [f.field]: value };
22
- this.model.set(next);
23
- this.onChange(next);
24
- this.onTouched();
25
- }
26
- visible(f) {
27
- return f.showWhen ? f.showWhen(this.model()) : true;
28
- }
29
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ZvDynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
30
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ZvDynamicForm, isStandalone: true, selector: "zv-dynamic-form", inputs: { schema: "schema" }, providers: [
31
- {
32
- provide: NG_VALUE_ACCESSOR,
33
- useExisting: forwardRef(() => ZvDynamicForm),
34
- multi: true,
35
- },
36
- ], ngImport: i0, template: `
37
- <div class="zv-dyn-grid">
38
- @for (f of schema; track f.field) {
39
- @if (visible(f)) {
40
- <div class="zv-dyn-cell" [style.grid-column]="'span ' + (f.cols || 12)">
41
- @switch (f.type) {
42
- @case ('input') { <zv-input [label]="f.label" [placeholder]="f.placeholder||''" [required]="!!f.required" [disabled]="!!f.disabled" [hint]="f.hint" [value]="get(f)" (valueChange)="set(f, $event)"></zv-input> }
43
- @case ('email') { <zv-email [label]="f.label" [required]="!!f.required" [value]="get(f)" (valueChange)="set(f, $event)"></zv-email> }
44
- @case ('password') { <zv-password [label]="f.label" [required]="!!f.required" [value]="get(f)" (valueChange)="set(f, $event)"></zv-password> }
45
- @case ('number') { <zv-number [label]="f.label" [required]="!!f.required" [value]="get(f)" (valueChange)="set(f, $event)"></zv-number> }
46
- @case ('textarea') { <zv-textarea [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-textarea> }
47
- @case ('datepicker') { <zv-datepicker [label]="f.label" [required]="!!f.required" [value]="get(f)" (valueChange)="set(f, $event)"></zv-datepicker> }
48
- @case ('timepicker') { <zv-timepicker [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-timepicker> }
49
- @case ('select') { <zv-select [label]="f.label" [items]="f.items||[]" [required]="!!f.required" [value]="get(f)" (valueChange)="set(f, $event)"></zv-select> }
50
- @case ('multiselect'){ <zv-multiselect [label]="f.label" [items]="f.items||[]" [value]="get(f)" (valueChange)="set(f, $event)"></zv-multiselect> }
51
- @case ('checkbox') { <zv-checkbox [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-checkbox> }
52
- @case ('radio') { <zv-radio [label]="f.label" [items]="f.items||[]" [value]="get(f)" (valueChange)="set(f, $event)"></zv-radio> }
53
- @case ('switch') { <zv-switch [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-switch> }
54
- @case ('pin') { <zv-pin [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-pin> }
55
- @case ('upload') { <zv-upload [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-upload> }
56
- }
57
- </div>
58
- }
59
- }
60
- </div>
61
- `, isInline: true, styles: [".zv-dyn-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:1rem}@media(max-width:640px){.zv-dyn-cell{grid-column:span 12!important}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1
+ /** Validates that a control's value matches another control's value. */
2
+ function matchControl(otherKey) {
3
+ return (control) => {
4
+ const other = control.parent?.get(otherKey);
5
+ if (!other)
6
+ return null;
7
+ return other.value === control.value ? null : { zvMatch: { otherKey } };
8
+ };
62
9
  }
63
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ZvDynamicForm, decorators: [{
64
- type: Component,
65
- args: [{ selector: 'zv-dynamic-form', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [
66
- {
67
- provide: NG_VALUE_ACCESSOR,
68
- useExisting: forwardRef(() => ZvDynamicForm),
69
- multi: true,
70
- },
71
- ], template: `
72
- <div class="zv-dyn-grid">
73
- @for (f of schema; track f.field) {
74
- @if (visible(f)) {
75
- <div class="zv-dyn-cell" [style.grid-column]="'span ' + (f.cols || 12)">
76
- @switch (f.type) {
77
- @case ('input') { <zv-input [label]="f.label" [placeholder]="f.placeholder||''" [required]="!!f.required" [disabled]="!!f.disabled" [hint]="f.hint" [value]="get(f)" (valueChange)="set(f, $event)"></zv-input> }
78
- @case ('email') { <zv-email [label]="f.label" [required]="!!f.required" [value]="get(f)" (valueChange)="set(f, $event)"></zv-email> }
79
- @case ('password') { <zv-password [label]="f.label" [required]="!!f.required" [value]="get(f)" (valueChange)="set(f, $event)"></zv-password> }
80
- @case ('number') { <zv-number [label]="f.label" [required]="!!f.required" [value]="get(f)" (valueChange)="set(f, $event)"></zv-number> }
81
- @case ('textarea') { <zv-textarea [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-textarea> }
82
- @case ('datepicker') { <zv-datepicker [label]="f.label" [required]="!!f.required" [value]="get(f)" (valueChange)="set(f, $event)"></zv-datepicker> }
83
- @case ('timepicker') { <zv-timepicker [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-timepicker> }
84
- @case ('select') { <zv-select [label]="f.label" [items]="f.items||[]" [required]="!!f.required" [value]="get(f)" (valueChange)="set(f, $event)"></zv-select> }
85
- @case ('multiselect'){ <zv-multiselect [label]="f.label" [items]="f.items||[]" [value]="get(f)" (valueChange)="set(f, $event)"></zv-multiselect> }
86
- @case ('checkbox') { <zv-checkbox [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-checkbox> }
87
- @case ('radio') { <zv-radio [label]="f.label" [items]="f.items||[]" [value]="get(f)" (valueChange)="set(f, $event)"></zv-radio> }
88
- @case ('switch') { <zv-switch [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-switch> }
89
- @case ('pin') { <zv-pin [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-pin> }
90
- @case ('upload') { <zv-upload [label]="f.label" [value]="get(f)" (valueChange)="set(f, $event)"></zv-upload> }
91
- }
92
- </div>
93
- }
94
- }
95
- </div>
96
- `, styles: [".zv-dyn-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:1rem}@media(max-width:640px){.zv-dyn-cell{grid-column:span 12!important}}\n"] }]
97
- }], propDecorators: { schema: [{
98
- type: Input
99
- }] } });
10
+ /** Strong-password validator: 8+ chars, upper, lower, digit. */
11
+ const strongPassword = (control) => {
12
+ const v = control.value ?? '';
13
+ const ok = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(v);
14
+ return ok ? null : { zvStrongPassword: true };
15
+ };
16
+
17
+ /** Public API surface of @zv/ng-form. */
100
18
 
101
19
  /**
102
20
  * Generated bundle index. Do not edit.
103
21
  */
104
22
 
105
- export { ZvDynamicForm };
23
+ export { matchControl, strongPassword };
106
24
  //# sourceMappingURL=zellvora-ng-form.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"zellvora-ng-form.mjs","sources":["../../../projects/ng-form/src/lib/dynamic/dynamic-form.component.ts","../../../projects/ng-form/src/zellvora-ng-form.ts"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n Input,\n CUSTOM_ELEMENTS_SCHEMA,\n forwardRef,\n signal,\n} from '@angular/core';\nimport { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';\nimport { ZvFormSchema, ZvFieldSchema } from './schema';\n\n/**\n * Schema-driven form. Implements `ControlValueAccessor`, so it plugs into\n * `[(ngModel)]`, `formControlName`, or a Signal Forms model exactly like an\n * atomic control. Renders the right `@zellvora/ng-input` control per field\n * and keeps a single model object in sync.\n */\n@Component({\n selector: 'zv-dynamic-form',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => ZvDynamicForm),\n multi: true,\n },\n ],\n template: `\n <div class=\"zv-dyn-grid\">\n @for (f of schema; track f.field) {\n @if (visible(f)) {\n <div class=\"zv-dyn-cell\" [style.grid-column]=\"'span ' + (f.cols || 12)\">\n @switch (f.type) {\n @case ('input') { <zv-input [label]=\"f.label\" [placeholder]=\"f.placeholder||''\" [required]=\"!!f.required\" [disabled]=\"!!f.disabled\" [hint]=\"f.hint\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-input> }\n @case ('email') { <zv-email [label]=\"f.label\" [required]=\"!!f.required\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-email> }\n @case ('password') { <zv-password [label]=\"f.label\" [required]=\"!!f.required\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-password> }\n @case ('number') { <zv-number [label]=\"f.label\" [required]=\"!!f.required\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-number> }\n @case ('textarea') { <zv-textarea [label]=\"f.label\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-textarea> }\n @case ('datepicker') { <zv-datepicker [label]=\"f.label\" [required]=\"!!f.required\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-datepicker> }\n @case ('timepicker') { <zv-timepicker [label]=\"f.label\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-timepicker> }\n @case ('select') { <zv-select [label]=\"f.label\" [items]=\"f.items||[]\" [required]=\"!!f.required\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-select> }\n @case ('multiselect'){ <zv-multiselect [label]=\"f.label\" [items]=\"f.items||[]\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-multiselect> }\n @case ('checkbox') { <zv-checkbox [label]=\"f.label\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-checkbox> }\n @case ('radio') { <zv-radio [label]=\"f.label\" [items]=\"f.items||[]\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-radio> }\n @case ('switch') { <zv-switch [label]=\"f.label\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-switch> }\n @case ('pin') { <zv-pin [label]=\"f.label\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-pin> }\n @case ('upload') { <zv-upload [label]=\"f.label\" [value]=\"get(f)\" (valueChange)=\"set(f, $event)\"></zv-upload> }\n }\n </div>\n }\n }\n </div>\n `,\n styles: [`\n .zv-dyn-grid { display:grid; grid-template-columns:repeat(12, 1fr); gap:1rem; }\n @media (max-width:640px){ .zv-dyn-cell { grid-column: span 12 !important; } }\n `],\n})\nexport class ZvDynamicForm implements ControlValueAccessor {\n @Input() schema: ZvFormSchema = [];\n private readonly model = signal<Record<string, unknown>>({});\n\n private onChange: (v: Record<string, unknown>) => void = () => {};\n private onTouched: () => void = () => {};\n\n writeValue(v: Record<string, unknown>): void { this.model.set(v ?? {}); }\n registerOnChange(fn: (v: Record<string, unknown>) => void): void { this.onChange = fn; }\n registerOnTouched(fn: () => void): void { this.onTouched = fn; }\n\n get(f: ZvFieldSchema): any { return this.model()[f.field] ?? null; }\n set(f: ZvFieldSchema, value: unknown): void {\n const next = { ...this.model(), [f.field]: value };\n this.model.set(next);\n this.onChange(next);\n this.onTouched();\n }\n visible(f: ZvFieldSchema): boolean {\n return f.showWhen ? f.showWhen(this.model()) : true;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;AAWA;;;;;AAKG;MA4CU,aAAa,CAAA;IACf,MAAM,GAAiB,EAAE;AACjB,IAAA,KAAK,GAAG,MAAM,CAA0B,EAAE,4EAAC;AAEpD,IAAA,QAAQ,GAAyC,MAAK,EAAE,CAAC;AACzD,IAAA,SAAS,GAAe,MAAK,EAAE,CAAC;AAExC,IAAA,UAAU,CAAC,CAA0B,EAAA,EAAU,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACxE,gBAAgB,CAAC,EAAwC,EAAA,EAAU,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC;IACvF,iBAAiB,CAAC,EAAc,EAAA,EAAU,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC;AAE/D,IAAA,GAAG,CAAC,CAAgB,EAAA,EAAS,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;IACnE,GAAG,CAAC,CAAgB,EAAE,KAAc,EAAA;AAClC,QAAA,MAAM,IAAI,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,EAAE;AAClD,QAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AACpB,QAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QACnB,IAAI,CAAC,SAAS,EAAE;IAClB;AACA,IAAA,OAAO,CAAC,CAAgB,EAAA;AACtB,QAAA,OAAO,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IACrD;wGApBW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAb,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,aAAa,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,EAAA,MAAA,EAAA,QAAA,EAAA,EAAA,SAAA,EAtCb;AACT,YAAA;AACE,gBAAA,OAAO,EAAE,iBAAiB;AAC1B,gBAAA,WAAW,EAAE,UAAU,CAAC,MAAM,aAAa,CAAC;AAC5C,gBAAA,KAAK,EAAE,IAAI;AACZ,aAAA;SACF,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EACS;;;;;;;;;;;;;;;;;;;;;;;;;AAyBT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,gJAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;4FAMU,aAAa,EAAA,UAAA,EAAA,CAAA;kBA3CzB,SAAS;+BACE,iBAAiB,EAAA,UAAA,EACf,IAAI,EAAA,eAAA,EACC,uBAAuB,CAAC,MAAM,EAAA,OAAA,EACtC,CAAC,sBAAsB,CAAC,EAAA,SAAA,EACtB;AACT,wBAAA;AACE,4BAAA,OAAO,EAAE,iBAAiB;AAC1B,4BAAA,WAAW,EAAE,UAAU,CAAC,mBAAmB,CAAC;AAC5C,4BAAA,KAAK,EAAE,IAAI;AACZ,yBAAA;qBACF,EAAA,QAAA,EACS;;;;;;;;;;;;;;;;;;;;;;;;;AAyBT,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,gJAAA,CAAA,EAAA;;sBAOA;;;AC7DH;;AAEG;;;;"}
1
+ {"version":3,"file":"zellvora-ng-form.mjs","sources":["../../../projects/ng-form/src/lib/validators.ts","../../../projects/ng-form/src/public-api.ts","../../../projects/ng-form/src/zellvora-ng-form.ts"],"sourcesContent":["import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';\n\n/** Validates that a control's value matches another control's value. */\nexport function matchControl(otherKey: string): ValidatorFn {\n return (control: AbstractControl): ValidationErrors | null => {\n const other = control.parent?.get(otherKey);\n if (!other) return null;\n return other.value === control.value ? null : { zvMatch: { otherKey } };\n };\n}\n\n/** Strong-password validator: 8+ chars, upper, lower, digit. */\nexport const strongPassword: ValidatorFn = (control) => {\n const v: string = control.value ?? '';\n const ok = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$/.test(v);\n return ok ? null : { zvStrongPassword: true };\n};\n","/** Public API surface of @zv/ng-form. */\nexport * from './lib/validators';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"AAEA;AACM,SAAU,YAAY,CAAC,QAAgB,EAAA;IAC3C,OAAO,CAAC,OAAwB,KAA6B;QAC3D,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC;AAC3C,QAAA,IAAI,CAAC,KAAK;AAAE,YAAA,OAAO,IAAI;QACvB,OAAO,KAAK,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,GAAG,IAAI,GAAG,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE;AACzE,IAAA,CAAC;AACH;AAEA;AACO,MAAM,cAAc,GAAgB,CAAC,OAAO,KAAI;AACrD,IAAA,MAAM,CAAC,GAAW,OAAO,CAAC,KAAK,IAAI,EAAE;IACrC,MAAM,EAAE,GAAG,uCAAuC,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1D,IAAA,OAAO,EAAE,GAAG,IAAI,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE;AAC/C;;AChBA;;ACAA;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,14 +1,34 @@
1
1
  {
2
2
  "name": "@zellvora/ng-form",
3
- "version": "0.1.1",
4
- "description": "Schema-driven dynamic forms built on @zellvora/ng-input.",
5
- "peerDependencies": {
6
- "@angular/common": "^21.0.0",
7
- "@angular/core": "^21.0.0",
8
- "@angular/forms": "^21.0.0",
9
- "@zellvora/ng-input": "^0.1.0"
3
+ "version": "0.1.3",
4
+ "description": "Reactive form building blocks and validators — part of the Zellavora UI design system.",
5
+ "keywords": [
6
+ "angular",
7
+ "zoneless",
8
+ "signals",
9
+ "zellavora",
10
+ "ui",
11
+ "ng-form"
12
+ ],
13
+ "author": "Zellavora UI Team <suriyarabin@gmail.com>",
14
+ "license": "MIT",
15
+ "homepage": "https://github.com/zellavora/zellavora-ui/tree/main/projects/ng-form#readme",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/zellavora/zellavora-ui.git",
19
+ "directory": "projects/ng-form"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/zellavora/zellavora-ui/issues"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
10
26
  },
11
27
  "sideEffects": false,
28
+ "peerDependencies": {
29
+ "@angular/common": "^22.0.0",
30
+ "@angular/core": "^22.0.0"
31
+ },
12
32
  "module": "fesm2022/zellvora-ng-form.mjs",
13
33
  "typings": "types/zellvora-ng-form.d.ts",
14
34
  "exports": {
@@ -1,52 +1,8 @@
1
- import { ValidatorFn, AsyncValidatorFn, ControlValueAccessor } from '@angular/forms';
2
- import * as i0 from '@angular/core';
1
+ import { ValidatorFn } from '@angular/forms';
3
2
 
4
- type ZvFieldType = 'input' | 'email' | 'password' | 'number' | 'textarea' | 'datepicker' | 'timepicker' | 'select' | 'multiselect' | 'checkbox' | 'radio' | 'switch' | 'pin' | 'upload';
5
- /** One field in a dynamic form schema. */
6
- interface ZvFieldSchema {
7
- type: ZvFieldType;
8
- field: string;
9
- label?: string;
10
- placeholder?: string;
11
- hint?: string;
12
- required?: boolean;
13
- disabled?: boolean;
14
- /** For select/multiselect/radio. */
15
- items?: unknown[];
16
- /** Built-in validator names, e.g. ['required','email']. */
17
- validators?: string[];
18
- /** Custom sync validators. */
19
- validatorFns?: ValidatorFn[];
20
- asyncValidatorFns?: AsyncValidatorFn[];
21
- /** Arbitrary extra inputs forwarded to the control. */
22
- props?: Record<string, unknown>;
23
- /** Responsive column span (1-12). */
24
- cols?: number;
25
- /** Show only when this predicate over the model is true. */
26
- showWhen?: (model: Record<string, unknown>) => boolean;
27
- }
28
- type ZvFormSchema = ZvFieldSchema[];
3
+ /** Validates that a control's value matches another control's value. */
4
+ declare function matchControl(otherKey: string): ValidatorFn;
5
+ /** Strong-password validator: 8+ chars, upper, lower, digit. */
6
+ declare const strongPassword: ValidatorFn;
29
7
 
30
- /**
31
- * Schema-driven form. Implements `ControlValueAccessor`, so it plugs into
32
- * `[(ngModel)]`, `formControlName`, or a Signal Forms model exactly like an
33
- * atomic control. Renders the right `@zellvora/ng-input` control per field
34
- * and keeps a single model object in sync.
35
- */
36
- declare class ZvDynamicForm implements ControlValueAccessor {
37
- schema: ZvFormSchema;
38
- private readonly model;
39
- private onChange;
40
- private onTouched;
41
- writeValue(v: Record<string, unknown>): void;
42
- registerOnChange(fn: (v: Record<string, unknown>) => void): void;
43
- registerOnTouched(fn: () => void): void;
44
- get(f: ZvFieldSchema): any;
45
- set(f: ZvFieldSchema, value: unknown): void;
46
- visible(f: ZvFieldSchema): boolean;
47
- static ɵfac: i0.ɵɵFactoryDeclaration<ZvDynamicForm, never>;
48
- static ɵcmp: i0.ɵɵComponentDeclaration<ZvDynamicForm, "zv-dynamic-form", never, { "schema": { "alias": "schema"; "required": false; }; }, {}, never, never, true, never>;
49
- }
50
-
51
- export { ZvDynamicForm };
52
- export type { ZvFieldSchema, ZvFieldType, ZvFormSchema };
8
+ export { matchControl, strongPassword };