@jigisha_ruparel/taf-client-form-angular 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.
@@ -0,0 +1,301 @@
1
+ import * as i1 from '@angular/common';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { EventEmitter, inject, ChangeDetectorRef, Output, Input, ViewEncapsulation, Component } from '@angular/core';
5
+ import { TAF_METADATA, TAF_DATA, TAF_PERMISSIONS } from '@jigisha_ruparel/taf-client-angular';
6
+ import { createTafFormBinding } from '@jigisha_ruparel/taf-client-form';
7
+
8
+ /**
9
+ * `<taf-form>` — a metadata-driven, themeable form.
10
+ *
11
+ * Builds one control per field from an entity's metadata (via `createTafFormBinding`), validates
12
+ * client-side from the field config, and submits through the data slice. BYO-theme: NO bundled
13
+ * colors/fonts — only `taf-form__*` class hooks + `ViewEncapsulation.None`. Zoneless-safe
14
+ * (`markForCheck()` after each async state change).
15
+ */
16
+ class TafFormComponent {
17
+ entity;
18
+ recordId;
19
+ fields;
20
+ saved = new EventEmitter();
21
+ cancelled = new EventEmitter();
22
+ model = null;
23
+ values = {};
24
+ errors = {};
25
+ loading = false;
26
+ loadError = null;
27
+ saving = false;
28
+ submitError = null;
29
+ /** Monotonic id so a slow earlier load() can't clobber a newer one's result. */
30
+ loadRequestId = 0;
31
+ metadataClient = inject(TAF_METADATA);
32
+ dataClient = inject(TAF_DATA);
33
+ permissionsClient = inject(TAF_PERMISSIONS);
34
+ cdr = inject(ChangeDetectorRef);
35
+ binding = createTafFormBinding({
36
+ metadata: this.metadataClient,
37
+ data: this.dataClient,
38
+ permissions: this.permissionsClient,
39
+ });
40
+ get canSubmit() {
41
+ if (!this.model)
42
+ return false;
43
+ return this.model.mode === 'edit' ? this.model.access.canUpdate : this.model.access.canCreate;
44
+ }
45
+ ngOnChanges(changes) {
46
+ if (changes['entity'] || changes['recordId'] || changes['fields'])
47
+ void this.load();
48
+ }
49
+ asString(v) {
50
+ return v == null ? '' : String(v);
51
+ }
52
+ controlKind(field) {
53
+ if (field.type === 'boolean')
54
+ return 'checkbox';
55
+ if (field.options?.length)
56
+ return 'select';
57
+ return 'input';
58
+ }
59
+ inputType(field) {
60
+ if (field.type === 'number')
61
+ return 'number';
62
+ if (field.type === 'date')
63
+ return 'date';
64
+ if (field.type === 'datetime')
65
+ return 'datetime-local';
66
+ return 'text';
67
+ }
68
+ setValue(field, event) {
69
+ delete this.errors[field.field]; // clear this field's error as the user edits it
70
+ const raw = event.target.value;
71
+ if (field.type === 'number') {
72
+ if (raw === '') {
73
+ this.values[field.field] = null;
74
+ return;
75
+ }
76
+ const n = Number(raw);
77
+ // Keep invalid input as the raw string so validate()/the server reject it, not a silent null.
78
+ this.values[field.field] = Number.isNaN(n) ? raw : n;
79
+ return;
80
+ }
81
+ this.values[field.field] = raw;
82
+ }
83
+ setChecked(field, event) {
84
+ delete this.errors[field.field];
85
+ this.values[field.field] = event.target.checked;
86
+ }
87
+ /** Native date/datetime inputs need yyyy-MM-dd / yyyy-MM-ddTHH:mm, not a full ISO string. */
88
+ formatForInput(field, raw) {
89
+ if (typeof raw === 'string' && raw.length >= 10) {
90
+ if (field.type === 'date')
91
+ return raw.slice(0, 10);
92
+ if (field.type === 'datetime')
93
+ return raw.slice(0, 16);
94
+ }
95
+ return raw;
96
+ }
97
+ async onSubmit(event) {
98
+ event.preventDefault();
99
+ if (!this.model)
100
+ return;
101
+ this.errors = this.binding.validate(this.model, this.values);
102
+ if (Object.keys(this.errors).length) {
103
+ this.cdr.markForCheck();
104
+ return;
105
+ }
106
+ this.saving = true;
107
+ this.submitError = null;
108
+ this.cdr.markForCheck();
109
+ try {
110
+ const res = await this.binding.submit(this.model, this.values);
111
+ this.saved.emit(res);
112
+ }
113
+ catch (err) {
114
+ this.submitError = err instanceof Error ? err.message : 'Failed to save.';
115
+ }
116
+ finally {
117
+ this.saving = false;
118
+ this.cdr.markForCheck();
119
+ }
120
+ }
121
+ onCancel() {
122
+ this.cancelled.emit();
123
+ }
124
+ async load() {
125
+ if (!this.entity) {
126
+ this.model = null;
127
+ return;
128
+ }
129
+ const requestId = ++this.loadRequestId;
130
+ this.loading = true;
131
+ this.loadError = null;
132
+ this.errors = {};
133
+ this.submitError = null;
134
+ this.cdr.markForCheck();
135
+ try {
136
+ const model = await this.binding.load(this.entity, { id: this.recordId, fields: this.fields });
137
+ if (requestId !== this.loadRequestId)
138
+ return; // a newer load() has started — discard this result
139
+ this.model = model;
140
+ const values = { ...model.values };
141
+ for (const f of model.fields)
142
+ values[f.field] = this.formatForInput(f, values[f.field]);
143
+ this.values = values;
144
+ }
145
+ catch (err) {
146
+ if (requestId !== this.loadRequestId)
147
+ return;
148
+ this.loadError = err instanceof Error ? err.message : 'Failed to load the form.';
149
+ this.model = null;
150
+ }
151
+ finally {
152
+ if (requestId === this.loadRequestId) {
153
+ this.loading = false;
154
+ this.cdr.markForCheck();
155
+ }
156
+ }
157
+ }
158
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: TafFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
159
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.5", type: TafFormComponent, isStandalone: true, selector: "taf-form", inputs: { entity: "entity", recordId: "recordId", fields: "fields" }, outputs: { saved: "saved", cancelled: "cancelled" }, usesOnChanges: true, ngImport: i0, template: `
160
+ <form class="taf-form" (submit)="onSubmit($event)">
161
+ <div class="taf-form__loading" *ngIf="loading">Loading…</div>
162
+ <div class="taf-form__error" *ngIf="loadError">{{ loadError }}</div>
163
+
164
+ <div class="taf-form__field" *ngFor="let field of model?.fields ?? []" [attr.data-type]="field.type">
165
+ <label class="taf-form__label" [attr.for]="'taf-form-' + field.field">
166
+ {{ field.label }}<span class="taf-form__required" *ngIf="field.required && field.updatable">*</span>
167
+ </label>
168
+
169
+ <ng-container [ngSwitch]="controlKind(field)">
170
+ <input
171
+ *ngSwitchCase="'checkbox'"
172
+ class="taf-form__control"
173
+ [attr.data-type]="field.type"
174
+ [id]="'taf-form-' + field.field"
175
+ type="checkbox"
176
+ [checked]="!!values[field.field]"
177
+ [disabled]="!field.updatable"
178
+ [attr.aria-invalid]="!!errors[field.field]"
179
+ (change)="setChecked(field, $event)"
180
+ />
181
+ <select
182
+ *ngSwitchCase="'select'"
183
+ class="taf-form__control"
184
+ [attr.data-type]="field.type"
185
+ [id]="'taf-form-' + field.field"
186
+ [disabled]="!field.updatable"
187
+ [attr.aria-invalid]="!!errors[field.field]"
188
+ (change)="setValue(field, $event)"
189
+ >
190
+ <option value=""></option>
191
+ <option
192
+ *ngFor="let opt of field.options ?? []"
193
+ [value]="asString(opt.value)"
194
+ [selected]="asString(values[field.field]) === asString(opt.value)"
195
+ >{{ opt.label }}</option>
196
+ </select>
197
+ <input
198
+ *ngSwitchDefault
199
+ class="taf-form__control"
200
+ [attr.data-type]="field.type"
201
+ [id]="'taf-form-' + field.field"
202
+ [attr.type]="inputType(field)"
203
+ [value]="values[field.field] ?? ''"
204
+ [disabled]="!field.updatable"
205
+ [attr.aria-invalid]="!!errors[field.field]"
206
+ (input)="setValue(field, $event)"
207
+ />
208
+ </ng-container>
209
+
210
+ <div class="taf-form__error-text" *ngIf="errors[field.field]">{{ errors[field.field] }}</div>
211
+ </div>
212
+
213
+ <div class="taf-form__actions" *ngIf="model">
214
+ <button type="submit" class="taf-form__submit" [disabled]="saving || !canSubmit">Save</button>
215
+ <button type="button" class="taf-form__cancel" [disabled]="saving" (click)="onCancel()">Cancel</button>
216
+ <span class="taf-form__submit-error" *ngIf="submitError">{{ submitError }}</span>
217
+ </div>
218
+ </form>
219
+ `, isInline: true, styles: ["taf-form{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }], encapsulation: i0.ViewEncapsulation.None });
220
+ }
221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: TafFormComponent, decorators: [{
222
+ type: Component,
223
+ args: [{ selector: 'taf-form', standalone: true, imports: [CommonModule], encapsulation: ViewEncapsulation.None, template: `
224
+ <form class="taf-form" (submit)="onSubmit($event)">
225
+ <div class="taf-form__loading" *ngIf="loading">Loading…</div>
226
+ <div class="taf-form__error" *ngIf="loadError">{{ loadError }}</div>
227
+
228
+ <div class="taf-form__field" *ngFor="let field of model?.fields ?? []" [attr.data-type]="field.type">
229
+ <label class="taf-form__label" [attr.for]="'taf-form-' + field.field">
230
+ {{ field.label }}<span class="taf-form__required" *ngIf="field.required && field.updatable">*</span>
231
+ </label>
232
+
233
+ <ng-container [ngSwitch]="controlKind(field)">
234
+ <input
235
+ *ngSwitchCase="'checkbox'"
236
+ class="taf-form__control"
237
+ [attr.data-type]="field.type"
238
+ [id]="'taf-form-' + field.field"
239
+ type="checkbox"
240
+ [checked]="!!values[field.field]"
241
+ [disabled]="!field.updatable"
242
+ [attr.aria-invalid]="!!errors[field.field]"
243
+ (change)="setChecked(field, $event)"
244
+ />
245
+ <select
246
+ *ngSwitchCase="'select'"
247
+ class="taf-form__control"
248
+ [attr.data-type]="field.type"
249
+ [id]="'taf-form-' + field.field"
250
+ [disabled]="!field.updatable"
251
+ [attr.aria-invalid]="!!errors[field.field]"
252
+ (change)="setValue(field, $event)"
253
+ >
254
+ <option value=""></option>
255
+ <option
256
+ *ngFor="let opt of field.options ?? []"
257
+ [value]="asString(opt.value)"
258
+ [selected]="asString(values[field.field]) === asString(opt.value)"
259
+ >{{ opt.label }}</option>
260
+ </select>
261
+ <input
262
+ *ngSwitchDefault
263
+ class="taf-form__control"
264
+ [attr.data-type]="field.type"
265
+ [id]="'taf-form-' + field.field"
266
+ [attr.type]="inputType(field)"
267
+ [value]="values[field.field] ?? ''"
268
+ [disabled]="!field.updatable"
269
+ [attr.aria-invalid]="!!errors[field.field]"
270
+ (input)="setValue(field, $event)"
271
+ />
272
+ </ng-container>
273
+
274
+ <div class="taf-form__error-text" *ngIf="errors[field.field]">{{ errors[field.field] }}</div>
275
+ </div>
276
+
277
+ <div class="taf-form__actions" *ngIf="model">
278
+ <button type="submit" class="taf-form__submit" [disabled]="saving || !canSubmit">Save</button>
279
+ <button type="button" class="taf-form__cancel" [disabled]="saving" (click)="onCancel()">Cancel</button>
280
+ <span class="taf-form__submit-error" *ngIf="submitError">{{ submitError }}</span>
281
+ </div>
282
+ </form>
283
+ `, styles: ["taf-form{display:block}\n"] }]
284
+ }], propDecorators: { entity: [{
285
+ type: Input
286
+ }], recordId: [{
287
+ type: Input
288
+ }], fields: [{
289
+ type: Input
290
+ }], saved: [{
291
+ type: Output
292
+ }], cancelled: [{
293
+ type: Output
294
+ }] } });
295
+
296
+ /**
297
+ * Generated bundle index. Do not edit.
298
+ */
299
+
300
+ export { TafFormComponent };
301
+ //# sourceMappingURL=jigisha_ruparel-taf-client-form-angular.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jigisha_ruparel-taf-client-form-angular.mjs","sources":["../../src/taf-form.component.ts","../../src/jigisha_ruparel-taf-client-form-angular.ts"],"sourcesContent":["import { CommonModule } from '@angular/common';\nimport { ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewEncapsulation, inject } from '@angular/core';\nimport type { InjectionToken, OnChanges, SimpleChanges } from '@angular/core';\nimport { TAF_DATA, TAF_METADATA, TAF_PERMISSIONS } from '@jigisha_ruparel/taf-client-angular';\nimport {\n createTafFormBinding,\n type TafFormBinding,\n type TafFormField,\n type TafFormModel,\n} from '@jigisha_ruparel/taf-client-form';\n\n/**\n * The exact client shapes `createTafFormBinding` expects, derived structurally from its own\n * signature — same duplicate-declaration bridge rationale as `<taf-grid>`: the injected\n * `TAF_*` tokens are bridged through `unknown` to this package's own `InjectionToken`.\n */\ntype FormBindingDeps = Parameters<typeof createTafFormBinding>[0];\n\n/**\n * `<taf-form>` — a metadata-driven, themeable form.\n *\n * Builds one control per field from an entity's metadata (via `createTafFormBinding`), validates\n * client-side from the field config, and submits through the data slice. BYO-theme: NO bundled\n * colors/fonts — only `taf-form__*` class hooks + `ViewEncapsulation.None`. Zoneless-safe\n * (`markForCheck()` after each async state change).\n */\n@Component({\n selector: 'taf-form',\n standalone: true,\n imports: [CommonModule],\n encapsulation: ViewEncapsulation.None,\n template: `\n <form class=\"taf-form\" (submit)=\"onSubmit($event)\">\n <div class=\"taf-form__loading\" *ngIf=\"loading\">Loading…</div>\n <div class=\"taf-form__error\" *ngIf=\"loadError\">{{ loadError }}</div>\n\n <div class=\"taf-form__field\" *ngFor=\"let field of model?.fields ?? []\" [attr.data-type]=\"field.type\">\n <label class=\"taf-form__label\" [attr.for]=\"'taf-form-' + field.field\">\n {{ field.label }}<span class=\"taf-form__required\" *ngIf=\"field.required && field.updatable\">*</span>\n </label>\n\n <ng-container [ngSwitch]=\"controlKind(field)\">\n <input\n *ngSwitchCase=\"'checkbox'\"\n class=\"taf-form__control\"\n [attr.data-type]=\"field.type\"\n [id]=\"'taf-form-' + field.field\"\n type=\"checkbox\"\n [checked]=\"!!values[field.field]\"\n [disabled]=\"!field.updatable\"\n [attr.aria-invalid]=\"!!errors[field.field]\"\n (change)=\"setChecked(field, $event)\"\n />\n <select\n *ngSwitchCase=\"'select'\"\n class=\"taf-form__control\"\n [attr.data-type]=\"field.type\"\n [id]=\"'taf-form-' + field.field\"\n [disabled]=\"!field.updatable\"\n [attr.aria-invalid]=\"!!errors[field.field]\"\n (change)=\"setValue(field, $event)\"\n >\n <option value=\"\"></option>\n <option\n *ngFor=\"let opt of field.options ?? []\"\n [value]=\"asString(opt.value)\"\n [selected]=\"asString(values[field.field]) === asString(opt.value)\"\n >{{ opt.label }}</option>\n </select>\n <input\n *ngSwitchDefault\n class=\"taf-form__control\"\n [attr.data-type]=\"field.type\"\n [id]=\"'taf-form-' + field.field\"\n [attr.type]=\"inputType(field)\"\n [value]=\"values[field.field] ?? ''\"\n [disabled]=\"!field.updatable\"\n [attr.aria-invalid]=\"!!errors[field.field]\"\n (input)=\"setValue(field, $event)\"\n />\n </ng-container>\n\n <div class=\"taf-form__error-text\" *ngIf=\"errors[field.field]\">{{ errors[field.field] }}</div>\n </div>\n\n <div class=\"taf-form__actions\" *ngIf=\"model\">\n <button type=\"submit\" class=\"taf-form__submit\" [disabled]=\"saving || !canSubmit\">Save</button>\n <button type=\"button\" class=\"taf-form__cancel\" [disabled]=\"saving\" (click)=\"onCancel()\">Cancel</button>\n <span class=\"taf-form__submit-error\" *ngIf=\"submitError\">{{ submitError }}</span>\n </div>\n </form>\n `,\n styles: [\n `\n taf-form { display: block; }\n `,\n ],\n})\nexport class TafFormComponent implements OnChanges {\n @Input() entity?: string;\n @Input() recordId?: string;\n @Input() fields?: string[];\n\n @Output() saved = new EventEmitter<{ id: string }>();\n @Output() cancelled = new EventEmitter<void>();\n\n model: TafFormModel | null = null;\n values: Record<string, unknown> = {};\n errors: Record<string, string> = {};\n loading = false;\n loadError: string | null = null;\n saving = false;\n submitError: string | null = null;\n\n /** Monotonic id so a slow earlier load() can't clobber a newer one's result. */\n private loadRequestId = 0;\n\n private readonly metadataClient = inject(TAF_METADATA as unknown as InjectionToken<FormBindingDeps['metadata']>);\n private readonly dataClient = inject(TAF_DATA as unknown as InjectionToken<FormBindingDeps['data']>);\n private readonly permissionsClient = inject(TAF_PERMISSIONS as unknown as InjectionToken<FormBindingDeps['permissions']>);\n private readonly cdr = inject(ChangeDetectorRef);\n private readonly binding: TafFormBinding = createTafFormBinding({\n metadata: this.metadataClient,\n data: this.dataClient,\n permissions: this.permissionsClient,\n });\n\n get canSubmit(): boolean {\n if (!this.model) return false;\n return this.model.mode === 'edit' ? this.model.access.canUpdate : this.model.access.canCreate;\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['entity'] || changes['recordId'] || changes['fields']) void this.load();\n }\n\n asString(v: unknown): string {\n return v == null ? '' : String(v);\n }\n\n controlKind(field: TafFormField): 'checkbox' | 'select' | 'input' {\n if (field.type === 'boolean') return 'checkbox';\n if (field.options?.length) return 'select';\n return 'input';\n }\n\n inputType(field: TafFormField): string {\n if (field.type === 'number') return 'number';\n if (field.type === 'date') return 'date';\n if (field.type === 'datetime') return 'datetime-local';\n return 'text';\n }\n\n setValue(field: TafFormField, event: Event): void {\n delete this.errors[field.field]; // clear this field's error as the user edits it\n const raw = (event.target as HTMLInputElement | HTMLSelectElement).value;\n if (field.type === 'number') {\n if (raw === '') { this.values[field.field] = null; return; }\n const n = Number(raw);\n // Keep invalid input as the raw string so validate()/the server reject it, not a silent null.\n this.values[field.field] = Number.isNaN(n) ? raw : n;\n return;\n }\n this.values[field.field] = raw;\n }\n\n setChecked(field: TafFormField, event: Event): void {\n delete this.errors[field.field];\n this.values[field.field] = (event.target as HTMLInputElement).checked;\n }\n\n /** Native date/datetime inputs need yyyy-MM-dd / yyyy-MM-ddTHH:mm, not a full ISO string. */\n private formatForInput(field: TafFormField, raw: unknown): unknown {\n if (typeof raw === 'string' && raw.length >= 10) {\n if (field.type === 'date') return raw.slice(0, 10);\n if (field.type === 'datetime') return raw.slice(0, 16);\n }\n return raw;\n }\n\n async onSubmit(event: Event): Promise<void> {\n event.preventDefault();\n if (!this.model) return;\n this.errors = this.binding.validate(this.model, this.values);\n if (Object.keys(this.errors).length) { this.cdr.markForCheck(); return; }\n this.saving = true;\n this.submitError = null;\n this.cdr.markForCheck();\n try {\n const res = await this.binding.submit(this.model, this.values);\n this.saved.emit(res);\n } catch (err) {\n this.submitError = err instanceof Error ? err.message : 'Failed to save.';\n } finally {\n this.saving = false;\n this.cdr.markForCheck();\n }\n }\n\n onCancel(): void {\n this.cancelled.emit();\n }\n\n private async load(): Promise<void> {\n if (!this.entity) { this.model = null; return; }\n const requestId = ++this.loadRequestId;\n this.loading = true;\n this.loadError = null;\n this.errors = {};\n this.submitError = null;\n this.cdr.markForCheck();\n try {\n const model = await this.binding.load(this.entity, { id: this.recordId, fields: this.fields });\n if (requestId !== this.loadRequestId) return; // a newer load() has started — discard this result\n this.model = model;\n const values: Record<string, unknown> = { ...model.values };\n for (const f of model.fields) values[f.field] = this.formatForInput(f, values[f.field]);\n this.values = values;\n } catch (err) {\n if (requestId !== this.loadRequestId) return;\n this.loadError = err instanceof Error ? err.message : 'Failed to load the form.';\n this.model = null;\n } finally {\n if (requestId === this.loadRequestId) {\n this.loading = false;\n this.cdr.markForCheck();\n }\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@jigisha_ruparel/taf-client-form-angular",
3
+ "version": "0.1.0",
4
+ "license": "UNLICENSED",
5
+ "description": "TAF client SDK — Angular <taf-form>: a metadata-driven, themeable form component.",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "directory": "dist"
11
+ },
12
+ "dependencies": {
13
+ "tslib": "^2.3.0",
14
+ "@jigisha_ruparel/taf-client-form": "0.1.0",
15
+ "@jigisha_ruparel/taf-client-angular": "0.4.0"
16
+ },
17
+ "peerDependencies": {
18
+ "@angular/core": ">=17",
19
+ "@angular/common": ">=17"
20
+ },
21
+ "module": "fesm2022/jigisha_ruparel-taf-client-form-angular.mjs",
22
+ "typings": "types/jigisha_ruparel-taf-client-form-angular.d.ts",
23
+ "exports": {
24
+ "./package.json": {
25
+ "default": "./package.json"
26
+ },
27
+ ".": {
28
+ "types": "./types/jigisha_ruparel-taf-client-form-angular.d.ts",
29
+ "default": "./fesm2022/jigisha_ruparel-taf-client-form-angular.mjs"
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,51 @@
1
+ import * as i0 from '@angular/core';
2
+ import { OnChanges, EventEmitter, SimpleChanges } from '@angular/core';
3
+ import { TafFormModel, TafFormField } from '@jigisha_ruparel/taf-client-form';
4
+
5
+ /**
6
+ * `<taf-form>` — a metadata-driven, themeable form.
7
+ *
8
+ * Builds one control per field from an entity's metadata (via `createTafFormBinding`), validates
9
+ * client-side from the field config, and submits through the data slice. BYO-theme: NO bundled
10
+ * colors/fonts — only `taf-form__*` class hooks + `ViewEncapsulation.None`. Zoneless-safe
11
+ * (`markForCheck()` after each async state change).
12
+ */
13
+ declare class TafFormComponent implements OnChanges {
14
+ entity?: string;
15
+ recordId?: string;
16
+ fields?: string[];
17
+ saved: EventEmitter<{
18
+ id: string;
19
+ }>;
20
+ cancelled: EventEmitter<void>;
21
+ model: TafFormModel | null;
22
+ values: Record<string, unknown>;
23
+ errors: Record<string, string>;
24
+ loading: boolean;
25
+ loadError: string | null;
26
+ saving: boolean;
27
+ submitError: string | null;
28
+ /** Monotonic id so a slow earlier load() can't clobber a newer one's result. */
29
+ private loadRequestId;
30
+ private readonly metadataClient;
31
+ private readonly dataClient;
32
+ private readonly permissionsClient;
33
+ private readonly cdr;
34
+ private readonly binding;
35
+ get canSubmit(): boolean;
36
+ ngOnChanges(changes: SimpleChanges): void;
37
+ asString(v: unknown): string;
38
+ controlKind(field: TafFormField): 'checkbox' | 'select' | 'input';
39
+ inputType(field: TafFormField): string;
40
+ setValue(field: TafFormField, event: Event): void;
41
+ setChecked(field: TafFormField, event: Event): void;
42
+ /** Native date/datetime inputs need yyyy-MM-dd / yyyy-MM-ddTHH:mm, not a full ISO string. */
43
+ private formatForInput;
44
+ onSubmit(event: Event): Promise<void>;
45
+ onCancel(): void;
46
+ private load;
47
+ static ɵfac: i0.ɵɵFactoryDeclaration<TafFormComponent, never>;
48
+ static ɵcmp: i0.ɵɵComponentDeclaration<TafFormComponent, "taf-form", never, { "entity": { "alias": "entity"; "required": false; }; "recordId": { "alias": "recordId"; "required": false; }; "fields": { "alias": "fields"; "required": false; }; }, { "saved": "saved"; "cancelled": "cancelled"; }, never, never, true, never>;
49
+ }
50
+
51
+ export { TafFormComponent };