@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 +18 -0
- package/fesm2022/zellvora-ng-form.mjs +17 -99
- package/fesm2022/zellvora-ng-form.mjs.map +1 -1
- package/package.json +27 -7
- package/types/zellvora-ng-form.d.ts +6 -50
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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 {
|
|
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/
|
|
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.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
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
|
|
2
|
-
import * as i0 from '@angular/core';
|
|
1
|
+
import { ValidatorFn } from '@angular/forms';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 };
|