ngx-form-draft 1.0.0 → 1.0.2

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 CHANGED
@@ -18,6 +18,18 @@ Zero-dependency Angular form draft auto-save and restore. Works with any Angular
18
18
  npm install ngx-form-draft
19
19
  ```
20
20
 
21
+ ### Angular 12 Users
22
+
23
+ If you're using Angular 12, enable Ivy in your `tsconfig.json`:
24
+
25
+ ```json
26
+ {
27
+ "angularCompilerOptions": {
28
+ "enableIvy": true
29
+ }
30
+ }
31
+ ```
32
+
21
33
  ## Usage
22
34
 
23
35
  ### 1. Import the module
@@ -1,11 +1,11 @@
1
- import { EventEmitter } from '@angular/core';
2
- export declare class FormDraftBannerComponent {
3
- visible: boolean;
4
- timeLabel: string;
5
- isRestored: boolean;
6
- restoredText: string;
7
- savedText: string;
8
- savedLabel: string;
9
- discardText: string;
10
- discard: EventEmitter<void>;
11
- }
1
+ import { EventEmitter } from '@angular/core';
2
+ export declare class FormDraftBannerComponent {
3
+ visible: boolean;
4
+ timeLabel: string;
5
+ isRestored: boolean;
6
+ restoredText: string;
7
+ savedText: string;
8
+ savedLabel: string;
9
+ discardText: string;
10
+ discard: EventEmitter<void>;
11
+ }
@@ -1,53 +1,53 @@
1
- import { __decorate, __metadata } from "tslib";
2
- import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
3
- import { trigger, transition, style, animate } from '@angular/animations';
4
- let FormDraftBannerComponent = class FormDraftBannerComponent {
5
- constructor() {
6
- this.visible = false;
7
- this.timeLabel = '';
8
- this.isRestored = false;
9
- this.restoredText = 'Draft restored';
10
- this.savedText = 'Draft saved';
11
- this.savedLabel = 'saved';
12
- this.discardText = 'Discard';
13
- this.discard = new EventEmitter();
14
- }
15
- };
16
- __decorate([
17
- Input(),
18
- __metadata("design:type", Object)
19
- ], FormDraftBannerComponent.prototype, "visible", void 0);
20
- __decorate([
21
- Input(),
22
- __metadata("design:type", Object)
23
- ], FormDraftBannerComponent.prototype, "timeLabel", void 0);
24
- __decorate([
25
- Input(),
26
- __metadata("design:type", Object)
27
- ], FormDraftBannerComponent.prototype, "isRestored", void 0);
28
- __decorate([
29
- Input(),
30
- __metadata("design:type", Object)
31
- ], FormDraftBannerComponent.prototype, "restoredText", void 0);
32
- __decorate([
33
- Input(),
34
- __metadata("design:type", Object)
35
- ], FormDraftBannerComponent.prototype, "savedText", void 0);
36
- __decorate([
37
- Input(),
38
- __metadata("design:type", Object)
39
- ], FormDraftBannerComponent.prototype, "savedLabel", void 0);
40
- __decorate([
41
- Input(),
42
- __metadata("design:type", Object)
43
- ], FormDraftBannerComponent.prototype, "discardText", void 0);
44
- __decorate([
45
- Output(),
46
- __metadata("design:type", Object)
47
- ], FormDraftBannerComponent.prototype, "discard", void 0);
48
- FormDraftBannerComponent = __decorate([
49
- Component({
50
- selector: 'ngx-form-draft-banner',
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
3
+ import { trigger, transition, style, animate } from '@angular/animations';
4
+ let FormDraftBannerComponent = class FormDraftBannerComponent {
5
+ constructor() {
6
+ this.visible = false;
7
+ this.timeLabel = '';
8
+ this.isRestored = false;
9
+ this.restoredText = 'Draft restored';
10
+ this.savedText = 'Draft saved';
11
+ this.savedLabel = 'saved';
12
+ this.discardText = 'Discard';
13
+ this.discard = new EventEmitter();
14
+ }
15
+ };
16
+ __decorate([
17
+ Input(),
18
+ __metadata("design:type", Object)
19
+ ], FormDraftBannerComponent.prototype, "visible", void 0);
20
+ __decorate([
21
+ Input(),
22
+ __metadata("design:type", Object)
23
+ ], FormDraftBannerComponent.prototype, "timeLabel", void 0);
24
+ __decorate([
25
+ Input(),
26
+ __metadata("design:type", Object)
27
+ ], FormDraftBannerComponent.prototype, "isRestored", void 0);
28
+ __decorate([
29
+ Input(),
30
+ __metadata("design:type", Object)
31
+ ], FormDraftBannerComponent.prototype, "restoredText", void 0);
32
+ __decorate([
33
+ Input(),
34
+ __metadata("design:type", Object)
35
+ ], FormDraftBannerComponent.prototype, "savedText", void 0);
36
+ __decorate([
37
+ Input(),
38
+ __metadata("design:type", Object)
39
+ ], FormDraftBannerComponent.prototype, "savedLabel", void 0);
40
+ __decorate([
41
+ Input(),
42
+ __metadata("design:type", Object)
43
+ ], FormDraftBannerComponent.prototype, "discardText", void 0);
44
+ __decorate([
45
+ Output(),
46
+ __metadata("design:type", Object)
47
+ ], FormDraftBannerComponent.prototype, "discard", void 0);
48
+ FormDraftBannerComponent = __decorate([
49
+ Component({
50
+ selector: 'ngx-form-draft-banner',
51
51
  template: `
52
52
  <div class="form-draft-banner" *ngIf="visible" [@slideDown]>
53
53
  <div class="form-draft-banner__icon">
@@ -71,7 +71,7 @@ FormDraftBannerComponent = __decorate([
71
71
  </button>
72
72
  </div>
73
73
  </div>
74
- `,
74
+ `,
75
75
  styles: [`
76
76
  :host { display: block; width: 100%; }
77
77
  .form-draft-banner {
@@ -93,19 +93,19 @@ FormDraftBannerComponent = __decorate([
93
93
  }
94
94
  .form-draft-banner__btn--discard { background: transparent; color: #8899a6; border: 1px solid #d0dce6; }
95
95
  .form-draft-banner__btn--discard:hover { background: #fef2f2; color: #e62e43; border-color: #e62e43; }
96
- `],
97
- animations: [
98
- trigger('slideDown', [
99
- transition(':enter', [
100
- style({ opacity: 0, transform: 'translateY(-8px)' }),
101
- animate('250ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 1, transform: 'translateY(0)' }))
102
- ]),
103
- transition(':leave', [
104
- animate('200ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 0, transform: 'translateY(-8px)' }))
105
- ])
106
- ])
107
- ],
108
- changeDetection: ChangeDetectionStrategy.OnPush,
109
- })
110
- ], FormDraftBannerComponent);
111
- export { FormDraftBannerComponent };
96
+ `],
97
+ animations: [
98
+ trigger('slideDown', [
99
+ transition(':enter', [
100
+ style({ opacity: 0, transform: 'translateY(-8px)' }),
101
+ animate('250ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 1, transform: 'translateY(0)' }))
102
+ ]),
103
+ transition(':leave', [
104
+ animate('200ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 0, transform: 'translateY(-8px)' }))
105
+ ])
106
+ ])
107
+ ],
108
+ changeDetection: ChangeDetectionStrategy.OnPush,
109
+ })
110
+ ], FormDraftBannerComponent);
111
+ export { FormDraftBannerComponent };
@@ -1,47 +1,47 @@
1
- import { OnInit, OnDestroy, ViewContainerRef, ChangeDetectorRef, ElementRef, Renderer2 } from '@angular/core';
2
- import { NgForm, FormGroupDirective } from '@angular/forms';
3
- import { FormDraftService } from './form-draft.service';
4
- /**
5
- * Auto-saves and restores form drafts
6
- *
7
- * @example
8
- * <form [formGroup]="myForm" ngxFormDraft="myFormId">
9
- *
10
- * @example
11
- * <form [formGroup]="myForm" [ngxFormDraft]="'edit_' + entityId" [draftExcludeFields]="['password']">
12
- */
13
- export declare class FormDraftDirective implements OnInit, OnDestroy {
14
- private formGroupDir;
15
- private ngForm;
16
- private draftService;
17
- private viewContainerRef;
18
- private cdRef;
19
- private elRef;
20
- private renderer;
21
- formId: string;
22
- draftDebounce: number;
23
- draftExcludeFields: string[];
24
- draftShowOnChange: boolean;
25
- draftRestoredText: string;
26
- draftSavedText: string;
27
- draftSavedLabel: string;
28
- draftDiscardText: string;
29
- private destroy$;
30
- private bannerRef;
31
- private formControl;
32
- private initialValues;
33
- private isRestoring;
34
- constructor(formGroupDir: FormGroupDirective, ngForm: NgForm, draftService: FormDraftService, viewContainerRef: ViewContainerRef, cdRef: ChangeDetectorRef, elRef: ElementRef, renderer: Renderer2);
35
- ngOnInit(): void;
36
- ngOnDestroy(): void;
37
- private saveDraft;
38
- private filterFields;
39
- private isAllEmpty;
40
- private matchesInitialValues;
41
- private restoreDraft;
42
- private prepareFormArrays;
43
- private discardDraft;
44
- private showBanner;
45
- private destroyBanner;
46
- clearDraft(): void;
47
- }
1
+ import { OnInit, OnDestroy, ViewContainerRef, ChangeDetectorRef, ElementRef, Renderer2 } from '@angular/core';
2
+ import { NgForm, FormGroupDirective } from '@angular/forms';
3
+ import { FormDraftService } from './form-draft.service';
4
+ /**
5
+ * Auto-saves and restores form drafts
6
+ *
7
+ * @example
8
+ * <form [formGroup]="myForm" ngxFormDraft="myFormId">
9
+ *
10
+ * @example
11
+ * <form [formGroup]="myForm" [ngxFormDraft]="'edit_' + entityId" [draftExcludeFields]="['password']">
12
+ */
13
+ export declare class FormDraftDirective implements OnInit, OnDestroy {
14
+ private formGroupDir;
15
+ private ngForm;
16
+ private draftService;
17
+ private viewContainerRef;
18
+ private cdRef;
19
+ private elRef;
20
+ private renderer;
21
+ formId: string;
22
+ draftDebounce: number;
23
+ draftExcludeFields: string[];
24
+ draftShowOnChange: boolean;
25
+ draftRestoredText: string;
26
+ draftSavedText: string;
27
+ draftSavedLabel: string;
28
+ draftDiscardText: string;
29
+ private destroy$;
30
+ private bannerRef;
31
+ private formControl;
32
+ private initialValues;
33
+ private isRestoring;
34
+ constructor(formGroupDir: FormGroupDirective, ngForm: NgForm, draftService: FormDraftService, viewContainerRef: ViewContainerRef, cdRef: ChangeDetectorRef, elRef: ElementRef, renderer: Renderer2);
35
+ ngOnInit(): void;
36
+ ngOnDestroy(): void;
37
+ private saveDraft;
38
+ private filterFields;
39
+ private isAllEmpty;
40
+ private matchesInitialValues;
41
+ private restoreDraft;
42
+ private prepareFormArrays;
43
+ private discardDraft;
44
+ private showBanner;
45
+ private destroyBanner;
46
+ clearDraft(): void;
47
+ }
@@ -1,240 +1,240 @@
1
- import { __decorate, __metadata, __param } from "tslib";
2
- import { Directive, Input, Optional, ViewContainerRef, ChangeDetectorRef, ElementRef, Renderer2, } from '@angular/core';
3
- import { NgForm, FormGroupDirective, FormArray, FormGroup, FormControl } from '@angular/forms';
4
- import { Subject } from 'rxjs';
5
- import { debounceTime, takeUntil } from 'rxjs/operators';
6
- import { FormDraftService } from './form-draft.service';
7
- import { FormDraftBannerComponent } from './form-draft-banner.component';
8
- /**
9
- * Auto-saves and restores form drafts
10
- *
11
- * @example
12
- * <form [formGroup]="myForm" ngxFormDraft="myFormId">
13
- *
14
- * @example
15
- * <form [formGroup]="myForm" [ngxFormDraft]="'edit_' + entityId" [draftExcludeFields]="['password']">
16
- */
17
- let FormDraftDirective = class FormDraftDirective {
18
- constructor(formGroupDir, ngForm, draftService, viewContainerRef, cdRef, elRef, renderer) {
19
- this.formGroupDir = formGroupDir;
20
- this.ngForm = ngForm;
21
- this.draftService = draftService;
22
- this.viewContainerRef = viewContainerRef;
23
- this.cdRef = cdRef;
24
- this.elRef = elRef;
25
- this.renderer = renderer;
26
- this.draftDebounce = 800;
27
- this.draftExcludeFields = [];
28
- this.draftShowOnChange = false;
29
- this.draftRestoredText = 'Draft restored';
30
- this.draftSavedText = 'Draft saved';
31
- this.draftSavedLabel = 'saved';
32
- this.draftDiscardText = 'Discard';
33
- this.destroy$ = new Subject();
34
- this.bannerRef = null;
35
- this.formControl = null;
36
- this.initialValues = {};
37
- this.isRestoring = false;
38
- }
39
- ngOnInit() {
40
- var _a, _b;
41
- this.formControl = ((_a = this.formGroupDir) === null || _a === void 0 ? void 0 : _a.form) || ((_b = this.ngForm) === null || _b === void 0 ? void 0 : _b.form) || null;
42
- if (!this.formControl || !this.formId)
43
- return;
44
- this.initialValues = JSON.parse(JSON.stringify(this.formControl.value));
45
- const draft = this.draftService.load(this.formId);
46
- if (draft) {
47
- this.restoreDraft(draft.values);
48
- this.showBanner(draft.savedAt, true);
49
- }
50
- this.formControl.valueChanges
51
- .pipe(debounceTime(this.draftDebounce), takeUntil(this.destroy$))
52
- .subscribe((values) => {
53
- if (this.isRestoring)
54
- return;
55
- this.saveDraft(values);
56
- });
57
- }
58
- ngOnDestroy() {
59
- this.destroy$.next();
60
- this.destroy$.complete();
61
- this.destroyBanner();
62
- }
63
- saveDraft(values) {
64
- const filtered = this.filterFields(values);
65
- if (this.isAllEmpty(filtered) || this.matchesInitialValues(filtered)) {
66
- return;
67
- }
68
- this.draftService.save(this.formId, filtered);
69
- if (this.draftShowOnChange && !this.bannerRef) {
70
- this.showBanner(Date.now(), false);
71
- }
72
- }
73
- filterFields(values) {
74
- if (!this.draftExcludeFields.length)
75
- return values;
76
- const result = Object.assign({}, values);
77
- this.draftExcludeFields.forEach(field => delete result[field]);
78
- return result;
79
- }
80
- isAllEmpty(values) {
81
- return Object.values(values).every(v => v === null || v === undefined || v === '' || (Array.isArray(v) && v.length === 0));
82
- }
83
- matchesInitialValues(values) {
84
- return JSON.stringify(values) === JSON.stringify(this.initialValues);
85
- }
86
- restoreDraft(values) {
87
- var _a, _b;
88
- if (!this.formControl)
89
- return;
90
- this.isRestoring = true;
91
- const form = ((_a = this.formGroupDir) === null || _a === void 0 ? void 0 : _a.form) || ((_b = this.ngForm) === null || _b === void 0 ? void 0 : _b.form);
92
- if (form) {
93
- this.prepareFormArrays(form, values);
94
- form.patchValue(values);
95
- }
96
- setTimeout(() => this.isRestoring = false, 100);
97
- }
98
- prepareFormArrays(control, value) {
99
- if (!control || value == null)
100
- return;
101
- if (control instanceof FormGroup && value && typeof value === 'object' && !Array.isArray(value)) {
102
- Object.keys(value).forEach(key => {
103
- const childControl = control.get(key);
104
- if (childControl)
105
- this.prepareFormArrays(childControl, value[key]);
106
- });
107
- return;
108
- }
109
- if (control instanceof FormArray && Array.isArray(value)) {
110
- const formArray = control;
111
- while (formArray.length < value.length) {
112
- const template = formArray.at(0);
113
- if (template instanceof FormGroup) {
114
- const newGroup = new FormGroup({});
115
- Object.keys(template.controls).forEach(ctrlName => {
116
- const existing = template.get(ctrlName);
117
- if (existing instanceof FormArray) {
118
- newGroup.addControl(ctrlName, new FormArray([]));
119
- }
120
- else if (existing instanceof FormGroup) {
121
- newGroup.addControl(ctrlName, new FormGroup({}));
122
- }
123
- else {
124
- newGroup.addControl(ctrlName, new FormControl(null));
125
- }
126
- });
127
- formArray.push(newGroup);
128
- }
129
- else if (template) {
130
- formArray.push(new FormControl(null));
131
- }
132
- else {
133
- const firstValue = value[0];
134
- if (firstValue && typeof firstValue === 'object' && !Array.isArray(firstValue)) {
135
- const group = new FormGroup({});
136
- Object.keys(firstValue).forEach(key => group.addControl(key, new FormControl(null)));
137
- formArray.push(group);
138
- }
139
- else {
140
- formArray.push(new FormControl(null));
141
- }
142
- }
143
- }
144
- while (formArray.length > value.length) {
145
- formArray.removeAt(formArray.length - 1);
146
- }
147
- value.forEach((childValue, index) => {
148
- this.prepareFormArrays(formArray.at(index), childValue);
149
- });
150
- }
151
- }
152
- discardDraft() {
153
- var _a, _b;
154
- this.draftService.clear(this.formId);
155
- if (this.formControl) {
156
- this.isRestoring = true;
157
- const form = ((_a = this.formGroupDir) === null || _a === void 0 ? void 0 : _a.form) || ((_b = this.ngForm) === null || _b === void 0 ? void 0 : _b.form);
158
- if (form) {
159
- this.prepareFormArrays(form, this.initialValues);
160
- form.reset(this.initialValues);
161
- }
162
- setTimeout(() => {
163
- this.isRestoring = false;
164
- }, this.draftDebounce + 200);
165
- }
166
- this.destroyBanner();
167
- }
168
- showBanner(savedAt, isRestored = false) {
169
- this.bannerRef = this.viewContainerRef.createComponent(FormDraftBannerComponent);
170
- this.bannerRef.setInput('visible', true);
171
- this.bannerRef.setInput('isRestored', isRestored);
172
- this.bannerRef.setInput('timeLabel', isRestored ? this.draftService.formatTimestamp(savedAt) : '');
173
- this.bannerRef.setInput('restoredText', this.draftRestoredText);
174
- this.bannerRef.setInput('savedText', this.draftSavedText);
175
- this.bannerRef.setInput('savedLabel', this.draftSavedLabel);
176
- this.bannerRef.setInput('discardText', this.draftDiscardText);
177
- this.bannerRef.instance.discard.subscribe(() => this.discardDraft());
178
- const bannerEl = this.bannerRef.location.nativeElement;
179
- const formEl = this.elRef.nativeElement;
180
- this.renderer.insertBefore(formEl, bannerEl, formEl.firstChild);
181
- this.cdRef.detectChanges();
182
- }
183
- destroyBanner() {
184
- if (this.bannerRef) {
185
- this.bannerRef.destroy();
186
- this.bannerRef = null;
187
- }
188
- }
189
- clearDraft() {
190
- this.draftService.clear(this.formId);
191
- this.destroyBanner();
192
- }
193
- };
194
- __decorate([
195
- Input('ngxFormDraft'),
196
- __metadata("design:type", String)
197
- ], FormDraftDirective.prototype, "formId", void 0);
198
- __decorate([
199
- Input(),
200
- __metadata("design:type", Object)
201
- ], FormDraftDirective.prototype, "draftDebounce", void 0);
202
- __decorate([
203
- Input(),
204
- __metadata("design:type", Array)
205
- ], FormDraftDirective.prototype, "draftExcludeFields", void 0);
206
- __decorate([
207
- Input(),
208
- __metadata("design:type", Object)
209
- ], FormDraftDirective.prototype, "draftShowOnChange", void 0);
210
- __decorate([
211
- Input(),
212
- __metadata("design:type", Object)
213
- ], FormDraftDirective.prototype, "draftRestoredText", void 0);
214
- __decorate([
215
- Input(),
216
- __metadata("design:type", Object)
217
- ], FormDraftDirective.prototype, "draftSavedText", void 0);
218
- __decorate([
219
- Input(),
220
- __metadata("design:type", Object)
221
- ], FormDraftDirective.prototype, "draftSavedLabel", void 0);
222
- __decorate([
223
- Input(),
224
- __metadata("design:type", Object)
225
- ], FormDraftDirective.prototype, "draftDiscardText", void 0);
226
- FormDraftDirective = __decorate([
227
- Directive({
228
- selector: '[ngxFormDraft]',
229
- }),
230
- __param(0, Optional()),
231
- __param(1, Optional()),
232
- __metadata("design:paramtypes", [FormGroupDirective,
233
- NgForm,
234
- FormDraftService,
235
- ViewContainerRef,
236
- ChangeDetectorRef,
237
- ElementRef,
238
- Renderer2])
239
- ], FormDraftDirective);
240
- export { FormDraftDirective };
1
+ import { __decorate, __metadata, __param } from "tslib";
2
+ import { Directive, Input, Optional, ViewContainerRef, ChangeDetectorRef, ElementRef, Renderer2, } from '@angular/core';
3
+ import { NgForm, FormGroupDirective, FormArray, FormGroup, FormControl } from '@angular/forms';
4
+ import { Subject } from 'rxjs';
5
+ import { debounceTime, takeUntil } from 'rxjs/operators';
6
+ import { FormDraftService } from './form-draft.service';
7
+ import { FormDraftBannerComponent } from './form-draft-banner.component';
8
+ /**
9
+ * Auto-saves and restores form drafts
10
+ *
11
+ * @example
12
+ * <form [formGroup]="myForm" ngxFormDraft="myFormId">
13
+ *
14
+ * @example
15
+ * <form [formGroup]="myForm" [ngxFormDraft]="'edit_' + entityId" [draftExcludeFields]="['password']">
16
+ */
17
+ let FormDraftDirective = class FormDraftDirective {
18
+ constructor(formGroupDir, ngForm, draftService, viewContainerRef, cdRef, elRef, renderer) {
19
+ this.formGroupDir = formGroupDir;
20
+ this.ngForm = ngForm;
21
+ this.draftService = draftService;
22
+ this.viewContainerRef = viewContainerRef;
23
+ this.cdRef = cdRef;
24
+ this.elRef = elRef;
25
+ this.renderer = renderer;
26
+ this.draftDebounce = 800;
27
+ this.draftExcludeFields = [];
28
+ this.draftShowOnChange = false;
29
+ this.draftRestoredText = 'Draft restored';
30
+ this.draftSavedText = 'Draft saved';
31
+ this.draftSavedLabel = 'saved';
32
+ this.draftDiscardText = 'Discard';
33
+ this.destroy$ = new Subject();
34
+ this.bannerRef = null;
35
+ this.formControl = null;
36
+ this.initialValues = {};
37
+ this.isRestoring = false;
38
+ }
39
+ ngOnInit() {
40
+ var _a, _b;
41
+ this.formControl = ((_a = this.formGroupDir) === null || _a === void 0 ? void 0 : _a.form) || ((_b = this.ngForm) === null || _b === void 0 ? void 0 : _b.form) || null;
42
+ if (!this.formControl || !this.formId)
43
+ return;
44
+ this.initialValues = JSON.parse(JSON.stringify(this.formControl.value));
45
+ const draft = this.draftService.load(this.formId);
46
+ if (draft) {
47
+ this.restoreDraft(draft.values);
48
+ this.showBanner(draft.savedAt, true);
49
+ }
50
+ this.formControl.valueChanges
51
+ .pipe(debounceTime(this.draftDebounce), takeUntil(this.destroy$))
52
+ .subscribe((values) => {
53
+ if (this.isRestoring)
54
+ return;
55
+ this.saveDraft(values);
56
+ });
57
+ }
58
+ ngOnDestroy() {
59
+ this.destroy$.next();
60
+ this.destroy$.complete();
61
+ this.destroyBanner();
62
+ }
63
+ saveDraft(values) {
64
+ const filtered = this.filterFields(values);
65
+ if (this.isAllEmpty(filtered) || this.matchesInitialValues(filtered)) {
66
+ return;
67
+ }
68
+ this.draftService.save(this.formId, filtered);
69
+ if (this.draftShowOnChange && !this.bannerRef) {
70
+ this.showBanner(Date.now(), false);
71
+ }
72
+ }
73
+ filterFields(values) {
74
+ if (!this.draftExcludeFields.length)
75
+ return values;
76
+ const result = Object.assign({}, values);
77
+ this.draftExcludeFields.forEach(field => delete result[field]);
78
+ return result;
79
+ }
80
+ isAllEmpty(values) {
81
+ return Object.values(values).every(v => v === null || v === undefined || v === '' || (Array.isArray(v) && v.length === 0));
82
+ }
83
+ matchesInitialValues(values) {
84
+ return JSON.stringify(values) === JSON.stringify(this.initialValues);
85
+ }
86
+ restoreDraft(values) {
87
+ var _a, _b;
88
+ if (!this.formControl)
89
+ return;
90
+ this.isRestoring = true;
91
+ const form = ((_a = this.formGroupDir) === null || _a === void 0 ? void 0 : _a.form) || ((_b = this.ngForm) === null || _b === void 0 ? void 0 : _b.form);
92
+ if (form) {
93
+ this.prepareFormArrays(form, values);
94
+ form.patchValue(values);
95
+ }
96
+ setTimeout(() => this.isRestoring = false, 100);
97
+ }
98
+ prepareFormArrays(control, value) {
99
+ if (!control || value == null)
100
+ return;
101
+ if (control instanceof FormGroup && value && typeof value === 'object' && !Array.isArray(value)) {
102
+ Object.keys(value).forEach(key => {
103
+ const childControl = control.get(key);
104
+ if (childControl)
105
+ this.prepareFormArrays(childControl, value[key]);
106
+ });
107
+ return;
108
+ }
109
+ if (control instanceof FormArray && Array.isArray(value)) {
110
+ const formArray = control;
111
+ while (formArray.length < value.length) {
112
+ const template = formArray.at(0);
113
+ if (template instanceof FormGroup) {
114
+ const newGroup = new FormGroup({});
115
+ Object.keys(template.controls).forEach(ctrlName => {
116
+ const existing = template.get(ctrlName);
117
+ if (existing instanceof FormArray) {
118
+ newGroup.addControl(ctrlName, new FormArray([]));
119
+ }
120
+ else if (existing instanceof FormGroup) {
121
+ newGroup.addControl(ctrlName, new FormGroup({}));
122
+ }
123
+ else {
124
+ newGroup.addControl(ctrlName, new FormControl(null));
125
+ }
126
+ });
127
+ formArray.push(newGroup);
128
+ }
129
+ else if (template) {
130
+ formArray.push(new FormControl(null));
131
+ }
132
+ else {
133
+ const firstValue = value[0];
134
+ if (firstValue && typeof firstValue === 'object' && !Array.isArray(firstValue)) {
135
+ const group = new FormGroup({});
136
+ Object.keys(firstValue).forEach(key => group.addControl(key, new FormControl(null)));
137
+ formArray.push(group);
138
+ }
139
+ else {
140
+ formArray.push(new FormControl(null));
141
+ }
142
+ }
143
+ }
144
+ while (formArray.length > value.length) {
145
+ formArray.removeAt(formArray.length - 1);
146
+ }
147
+ value.forEach((childValue, index) => {
148
+ this.prepareFormArrays(formArray.at(index), childValue);
149
+ });
150
+ }
151
+ }
152
+ discardDraft() {
153
+ var _a, _b;
154
+ this.draftService.clear(this.formId);
155
+ if (this.formControl) {
156
+ this.isRestoring = true;
157
+ const form = ((_a = this.formGroupDir) === null || _a === void 0 ? void 0 : _a.form) || ((_b = this.ngForm) === null || _b === void 0 ? void 0 : _b.form);
158
+ if (form) {
159
+ this.prepareFormArrays(form, this.initialValues);
160
+ form.reset(this.initialValues);
161
+ }
162
+ setTimeout(() => {
163
+ this.isRestoring = false;
164
+ }, this.draftDebounce + 200);
165
+ }
166
+ this.destroyBanner();
167
+ }
168
+ showBanner(savedAt, isRestored = false) {
169
+ this.bannerRef = this.viewContainerRef.createComponent(FormDraftBannerComponent);
170
+ this.bannerRef.setInput('visible', true);
171
+ this.bannerRef.setInput('isRestored', isRestored);
172
+ this.bannerRef.setInput('timeLabel', isRestored ? this.draftService.formatTimestamp(savedAt) : '');
173
+ this.bannerRef.setInput('restoredText', this.draftRestoredText);
174
+ this.bannerRef.setInput('savedText', this.draftSavedText);
175
+ this.bannerRef.setInput('savedLabel', this.draftSavedLabel);
176
+ this.bannerRef.setInput('discardText', this.draftDiscardText);
177
+ this.bannerRef.instance.discard.subscribe(() => this.discardDraft());
178
+ const bannerEl = this.bannerRef.location.nativeElement;
179
+ const formEl = this.elRef.nativeElement;
180
+ this.renderer.insertBefore(formEl, bannerEl, formEl.firstChild);
181
+ this.cdRef.detectChanges();
182
+ }
183
+ destroyBanner() {
184
+ if (this.bannerRef) {
185
+ this.bannerRef.destroy();
186
+ this.bannerRef = null;
187
+ }
188
+ }
189
+ clearDraft() {
190
+ this.draftService.clear(this.formId);
191
+ this.destroyBanner();
192
+ }
193
+ };
194
+ __decorate([
195
+ Input('ngxFormDraft'),
196
+ __metadata("design:type", String)
197
+ ], FormDraftDirective.prototype, "formId", void 0);
198
+ __decorate([
199
+ Input(),
200
+ __metadata("design:type", Object)
201
+ ], FormDraftDirective.prototype, "draftDebounce", void 0);
202
+ __decorate([
203
+ Input(),
204
+ __metadata("design:type", Array)
205
+ ], FormDraftDirective.prototype, "draftExcludeFields", void 0);
206
+ __decorate([
207
+ Input(),
208
+ __metadata("design:type", Object)
209
+ ], FormDraftDirective.prototype, "draftShowOnChange", void 0);
210
+ __decorate([
211
+ Input(),
212
+ __metadata("design:type", Object)
213
+ ], FormDraftDirective.prototype, "draftRestoredText", void 0);
214
+ __decorate([
215
+ Input(),
216
+ __metadata("design:type", Object)
217
+ ], FormDraftDirective.prototype, "draftSavedText", void 0);
218
+ __decorate([
219
+ Input(),
220
+ __metadata("design:type", Object)
221
+ ], FormDraftDirective.prototype, "draftSavedLabel", void 0);
222
+ __decorate([
223
+ Input(),
224
+ __metadata("design:type", Object)
225
+ ], FormDraftDirective.prototype, "draftDiscardText", void 0);
226
+ FormDraftDirective = __decorate([
227
+ Directive({
228
+ selector: '[ngxFormDraft]',
229
+ }),
230
+ __param(0, Optional()),
231
+ __param(1, Optional()),
232
+ __metadata("design:paramtypes", [FormGroupDirective,
233
+ NgForm,
234
+ FormDraftService,
235
+ ViewContainerRef,
236
+ ChangeDetectorRef,
237
+ ElementRef,
238
+ Renderer2])
239
+ ], FormDraftDirective);
240
+ export { FormDraftDirective };
@@ -1,14 +1,14 @@
1
- export interface FormDraftData {
2
- values: Record<string, any>;
3
- savedAt: number;
4
- formId: string;
5
- }
6
- export declare class FormDraftService {
7
- private readonly STORAGE_PREFIX;
8
- private readonly MAX_AGE_MS;
9
- private buildKey;
10
- save(formId: string, values: Record<string, any>): void;
11
- load(formId: string): FormDraftData | null;
12
- clear(formId: string): void;
13
- formatTimestamp(timestamp: number): string;
14
- }
1
+ export interface FormDraftData {
2
+ values: Record<string, any>;
3
+ savedAt: number;
4
+ formId: string;
5
+ }
6
+ export declare class FormDraftService {
7
+ private readonly STORAGE_PREFIX;
8
+ private readonly MAX_AGE_MS;
9
+ private buildKey;
10
+ save(formId: string, values: Record<string, any>): void;
11
+ load(formId: string): FormDraftData | null;
12
+ clear(formId: string): void;
13
+ formatTimestamp(timestamp: number): string;
14
+ }
@@ -1,64 +1,64 @@
1
- import { __decorate } from "tslib";
2
- import { Injectable } from '@angular/core';
3
- let FormDraftService = class FormDraftService {
4
- constructor() {
5
- this.STORAGE_PREFIX = 'form_draft_';
6
- this.MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
7
- }
8
- buildKey(formId) {
9
- return `${this.STORAGE_PREFIX}${formId}`;
10
- }
11
- save(formId, values) {
12
- try {
13
- const draft = { values, savedAt: Date.now(), formId };
14
- localStorage.setItem(this.buildKey(formId), JSON.stringify(draft));
15
- }
16
- catch (e) {
17
- console.warn('[FormDraft] Could not save draft:', e);
18
- }
19
- }
20
- load(formId) {
21
- try {
22
- const raw = localStorage.getItem(this.buildKey(formId));
23
- if (!raw)
24
- return null;
25
- const draft = JSON.parse(raw);
26
- if (Date.now() - draft.savedAt > this.MAX_AGE_MS) {
27
- this.clear(formId);
28
- return null;
29
- }
30
- return draft;
31
- }
32
- catch (e) {
33
- console.warn('[FormDraft] Could not load draft:', e);
34
- return null;
35
- }
36
- }
37
- clear(formId) {
38
- try {
39
- localStorage.removeItem(this.buildKey(formId));
40
- }
41
- catch (e) {
42
- console.warn('[FormDraft] Could not clear draft:', e);
43
- }
44
- }
45
- formatTimestamp(timestamp) {
46
- const seconds = Math.floor((Date.now() - timestamp) / 1000);
47
- if (seconds < 60)
48
- return 'just now';
49
- const minutes = Math.floor(seconds / 60);
50
- if (minutes < 60)
51
- return `${minutes}m ago`;
52
- const hours = Math.floor(minutes / 60);
53
- if (hours < 24)
54
- return `${hours}h ago`;
55
- const days = Math.floor(hours / 24);
56
- return `${days}d ago`;
57
- }
58
- };
59
- FormDraftService = __decorate([
60
- Injectable({
61
- providedIn: 'root'
62
- })
63
- ], FormDraftService);
64
- export { FormDraftService };
1
+ import { __decorate } from "tslib";
2
+ import { Injectable } from '@angular/core';
3
+ let FormDraftService = class FormDraftService {
4
+ constructor() {
5
+ this.STORAGE_PREFIX = 'form_draft_';
6
+ this.MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
7
+ }
8
+ buildKey(formId) {
9
+ return `${this.STORAGE_PREFIX}${formId}`;
10
+ }
11
+ save(formId, values) {
12
+ try {
13
+ const draft = { values, savedAt: Date.now(), formId };
14
+ localStorage.setItem(this.buildKey(formId), JSON.stringify(draft));
15
+ }
16
+ catch (e) {
17
+ console.warn('[FormDraft] Could not save draft:', e);
18
+ }
19
+ }
20
+ load(formId) {
21
+ try {
22
+ const raw = localStorage.getItem(this.buildKey(formId));
23
+ if (!raw)
24
+ return null;
25
+ const draft = JSON.parse(raw);
26
+ if (Date.now() - draft.savedAt > this.MAX_AGE_MS) {
27
+ this.clear(formId);
28
+ return null;
29
+ }
30
+ return draft;
31
+ }
32
+ catch (e) {
33
+ console.warn('[FormDraft] Could not load draft:', e);
34
+ return null;
35
+ }
36
+ }
37
+ clear(formId) {
38
+ try {
39
+ localStorage.removeItem(this.buildKey(formId));
40
+ }
41
+ catch (e) {
42
+ console.warn('[FormDraft] Could not clear draft:', e);
43
+ }
44
+ }
45
+ formatTimestamp(timestamp) {
46
+ const seconds = Math.floor((Date.now() - timestamp) / 1000);
47
+ if (seconds < 60)
48
+ return 'just now';
49
+ const minutes = Math.floor(seconds / 60);
50
+ if (minutes < 60)
51
+ return `${minutes}m ago`;
52
+ const hours = Math.floor(minutes / 60);
53
+ if (hours < 24)
54
+ return `${hours}h ago`;
55
+ const days = Math.floor(hours / 24);
56
+ return `${days}d ago`;
57
+ }
58
+ };
59
+ FormDraftService = __decorate([
60
+ Injectable({
61
+ providedIn: 'root'
62
+ })
63
+ ], FormDraftService);
64
+ export { FormDraftService };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from './form-draft.directive';
2
- export * from './form-draft-banner.component';
3
- export * from './form-draft.service';
4
- export * from './ngx-form-draft.module';
1
+ export * from './form-draft.directive';
2
+ export * from './form-draft-banner.component';
3
+ export * from './form-draft.service';
4
+ export * from './ngx-form-draft.module';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export * from './form-draft.directive';
2
- export * from './form-draft-banner.component';
3
- export * from './form-draft.service';
4
- export * from './ngx-form-draft.module';
1
+ export * from './form-draft.directive';
2
+ export * from './form-draft-banner.component';
3
+ export * from './form-draft.service';
4
+ export * from './ngx-form-draft.module';
@@ -1,2 +1,2 @@
1
- export declare class NgxFormDraftModule {
2
- }
1
+ export declare class NgxFormDraftModule {
2
+ }
@@ -1,15 +1,15 @@
1
- import { __decorate } from "tslib";
2
- import { NgModule } from '@angular/core';
3
- import { CommonModule } from '@angular/common';
4
- import { FormDraftDirective } from './form-draft.directive';
5
- import { FormDraftBannerComponent } from './form-draft-banner.component';
6
- let NgxFormDraftModule = class NgxFormDraftModule {
7
- };
8
- NgxFormDraftModule = __decorate([
9
- NgModule({
10
- declarations: [FormDraftDirective, FormDraftBannerComponent],
11
- imports: [CommonModule],
12
- exports: [FormDraftDirective, FormDraftBannerComponent],
13
- })
14
- ], NgxFormDraftModule);
15
- export { NgxFormDraftModule };
1
+ import { __decorate } from "tslib";
2
+ import { NgModule } from '@angular/core';
3
+ import { CommonModule } from '@angular/common';
4
+ import { FormDraftDirective } from './form-draft.directive';
5
+ import { FormDraftBannerComponent } from './form-draft-banner.component';
6
+ let NgxFormDraftModule = class NgxFormDraftModule {
7
+ };
8
+ NgxFormDraftModule = __decorate([
9
+ NgModule({
10
+ declarations: [FormDraftDirective, FormDraftBannerComponent],
11
+ imports: [CommonModule],
12
+ exports: [FormDraftDirective, FormDraftBannerComponent],
13
+ })
14
+ ], NgxFormDraftModule);
15
+ export { NgxFormDraftModule };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-form-draft",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Auto-save and restore Angular form drafts with zero dependencies",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",