@zellvora/ng-form 0.1.1

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.
@@ -0,0 +1,106 @@
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 });
62
+ }
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
+ }] } });
100
+
101
+ /**
102
+ * Generated bundle index. Do not edit.
103
+ */
104
+
105
+ export { ZvDynamicForm };
106
+ //# sourceMappingURL=zellvora-ng-form.mjs.map
@@ -0,0 +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;;;;"}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
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"
10
+ },
11
+ "sideEffects": false,
12
+ "module": "fesm2022/zellvora-ng-form.mjs",
13
+ "typings": "types/zellvora-ng-form.d.ts",
14
+ "exports": {
15
+ "./package.json": {
16
+ "default": "./package.json"
17
+ },
18
+ ".": {
19
+ "types": "./types/zellvora-ng-form.d.ts",
20
+ "default": "./fesm2022/zellvora-ng-form.mjs"
21
+ }
22
+ },
23
+ "type": "module",
24
+ "dependencies": {
25
+ "tslib": "^2.3.0"
26
+ }
27
+ }
@@ -0,0 +1,52 @@
1
+ import { ValidatorFn, AsyncValidatorFn, ControlValueAccessor } from '@angular/forms';
2
+ import * as i0 from '@angular/core';
3
+
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[];
29
+
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 };