@provydon/vue-auto-save 1.1.1 → 1.3.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.
- package/README.md +25 -0
- package/dist/index.d.ts +17 -0
- package/dist/useAutoSaveForm.cjs +1 -1
- package/dist/useAutoSaveForm.mjs +96 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,6 +68,8 @@ const { isAutoSaving, blockWatcher, unblockWatcher, stop } = useAutoSaveForm(
|
|
|
68
68
|
|--------|------|---------|-------------|
|
|
69
69
|
| `onSave` | `() => void \| Promise<void>` | **Required** | Function called when auto-save should trigger |
|
|
70
70
|
| `debounce` | `number` | `3000` | Delay in milliseconds before saving |
|
|
71
|
+
| `minSaveInterval` | `number` | `2000` | Minimum time in milliseconds between saves (prevents rapid successive saves) |
|
|
72
|
+
| `trackLastSaved` | `boolean` | `true` | Track last successfully saved state to prevent saving identical data (prevents infinite loops) |
|
|
71
73
|
| `skipFields` | `string[]` | `[]` | Field names to exclude from tracking |
|
|
72
74
|
| `skipInertiaFields` | `boolean` | `true` | Skip common Inertia.js form helpers |
|
|
73
75
|
| `deep` | `boolean` | `true` | Deep watch the form object |
|
|
@@ -77,6 +79,7 @@ const { isAutoSaving, blockWatcher, unblockWatcher, stop } = useAutoSaveForm(
|
|
|
77
79
|
| `compare` | `(a, b) => boolean` | `undefined` | Custom comparison function |
|
|
78
80
|
| `onBeforeSave` | `() => void` | `undefined` | Called before saving |
|
|
79
81
|
| `onAfterSave` | `() => void` | `undefined` | Called after successful save |
|
|
82
|
+
| `onSaveSuccess` | `(savedData) => void` | `undefined` | Called when save completes with saved form data (useful for updating tracked state with server response) |
|
|
80
83
|
| `onError` | `(err) => void` | `undefined` | Called on save error |
|
|
81
84
|
|
|
82
85
|
### Return Values
|
|
@@ -153,6 +156,28 @@ blockWatcher()
|
|
|
153
156
|
unblockWatcher() // Resume auto-save
|
|
154
157
|
```
|
|
155
158
|
|
|
159
|
+
### Prevent Infinite Loops with Server Updates (Default Behavior)
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// trackLastSaved and minSaveInterval are enabled by default
|
|
163
|
+
const { isAutoSaving } = useAutoSaveForm(form, {
|
|
164
|
+
onSave: async () => {
|
|
165
|
+
const response = await api.post('/save', form)
|
|
166
|
+
// Server response updates form data - no infinite loop!
|
|
167
|
+
Object.assign(form, response.data)
|
|
168
|
+
}
|
|
169
|
+
// trackLastSaved: true (default)
|
|
170
|
+
// minSaveInterval: 2000 (default)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// To disable these features if needed:
|
|
174
|
+
const { isAutoSaving } = useAutoSaveForm(form, {
|
|
175
|
+
onSave: saveToAPI,
|
|
176
|
+
trackLastSaved: false, // Disable if you want to save identical data
|
|
177
|
+
minSaveInterval: 0, // Disable minimum interval
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
156
181
|
### With Ref Forms
|
|
157
182
|
|
|
158
183
|
```ts
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,17 @@ export interface UseAutoSaveFormOptions {
|
|
|
4
4
|
* Delay in milliseconds before auto-saving after changes (default: 3000ms)
|
|
5
5
|
*/
|
|
6
6
|
debounce?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Minimum time in milliseconds between saves (default: 2000ms)
|
|
9
|
+
* Prevents rapid successive saves even if data changes
|
|
10
|
+
*/
|
|
11
|
+
minSaveInterval?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Track last successfully saved state to prevent saving identical data (default: true)
|
|
14
|
+
* When enabled, only saves if current state differs from last saved state
|
|
15
|
+
* Prevents infinite loops when server responses update form data
|
|
16
|
+
*/
|
|
17
|
+
trackLastSaved?: boolean;
|
|
7
18
|
/**
|
|
8
19
|
* List of form field keys to exclude from tracking
|
|
9
20
|
*/
|
|
@@ -46,8 +57,14 @@ export interface UseAutoSaveFormOptions {
|
|
|
46
57
|
onBeforeSave?: () => void;
|
|
47
58
|
/**
|
|
48
59
|
* Called after a successful auto-save
|
|
60
|
+
* If trackLastSaved is true, the form state at this point will be tracked as the last saved state
|
|
49
61
|
*/
|
|
50
62
|
onAfterSave?: () => void;
|
|
63
|
+
/**
|
|
64
|
+
* Called when save completes successfully with the saved form data
|
|
65
|
+
* Use this to update the tracked last saved state with server response data
|
|
66
|
+
*/
|
|
67
|
+
onSaveSuccess?: (savedData: Record<string, unknown>) => void;
|
|
51
68
|
/**
|
|
52
69
|
* Called if auto-saving throws or fails
|
|
53
70
|
*/
|
package/dist/useAutoSaveForm.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("vue"),E=["save","applicationId","isDirty","processing","errors","hasErrors","recentlySuccessful","wasSuccessful","data","transform","get","post","put","patch","delete","cancel","reset","clearErrors","setError","setData"];function G(c,D){const{debounce:f=3e3,minSaveInterval:O=2e3,trackLastSaved:p=!0,skipFields:L=[],skipInertiaFields:q=!0,deep:J=!0,debug:n=!1,serialize:v=JSON.stringify,compare:s,saveOnInit:g=!1,onSave:M,onBeforeSave:F,onAfterSave:w,onSaveSuccess:I,onError:o}=D,b=a.ref(!1),m=a.ref(!0);let h=null,k=null,j=0,y=()=>{},A=()=>{},i=!1;const z=(e=1e3)=>{m.value=!1,y(),A(),setTimeout(()=>{m.value=!0},e)},N=(e=null)=>{if(m.value=!0,y(),A(),e===null)i=!0,s?r=null:u=null,T(),i=!1;else{const l=x(T,e);A=l.cancel,l.call()}},d=()=>{const e=a.isRef(c)?a.unref(c):c,l={};for(const t of Object.keys(e))q&&E.includes(t)||L.includes(t)||(l[t]=e[t]);return l};let u=g?null:v(d()),r=s?g?null:d():null;if(p&&!g){const e=d();s?k={...e}:h=v(e)}const R=e=>{p&&(s?k={...e}:h=v(e))},T=()=>{if(!m.value&&!i)return;const e=Date.now();if(!i&&O>0&&e-j<O){n&&console.log("[AutoSave] Skipping save - too soon after last save");return}const l=d();if(i)s?r=l:u=v(l);else if(s){if(p&&k&&s(k,l)){n&&console.log("[AutoSave] Skipping save - identical to last saved state"),r=l;return}if(r&&s(r,l))return;r=l}else{const t=v(l);if(p&&h!==null&&t===h){n&&console.log("[AutoSave] Skipping save - identical to last saved state"),u=t;return}if(u!==null&&t===u)return;u=t}n&&console.log("[AutoSave] Detected changes. Saving..."),b.value=!0,j=e;try{F==null||F();const t=Promise.resolve(M()).then(async()=>{await a.nextTick(),await a.nextTick();const S=d();R(S),I==null||I(S),w==null||w(),n&&console.log("[AutoSave] Save successful.")}).catch(S=>{o==null||o(S),n&&console.error("[AutoSave] Save failed:",S)}).finally(()=>{b.value=!1});return i||z(5e3),t}catch(t){o==null||o(t),n&&console.error("[AutoSave] Immediate error:",t),b.value=!1}},W=x(T,f),C=W.call;y=W.cancel;const P=a.watch(c,C,{deep:J,flush:"post"});return a.onScopeDispose(()=>{P(),y(),A()}),g&&T(),{isAutoSaving:b,blockWatcher:z,unblockWatcher:N,stop:P}}function x(c,D){let f;return{call:()=>{clearTimeout(f),f=setTimeout(()=>c(),D)},cancel:()=>{clearTimeout(f)}}}exports.useAutoSaveForm=G;
|
package/dist/useAutoSaveForm.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ref as
|
|
2
|
-
const
|
|
1
|
+
import { ref as x, watch as H, onScopeDispose as K, isRef as M, unref as Q, nextTick as L } from "vue";
|
|
2
|
+
const U = [
|
|
3
3
|
"save",
|
|
4
4
|
"applicationId",
|
|
5
5
|
"isDirty",
|
|
@@ -21,95 +21,123 @@ const R = [
|
|
|
21
21
|
"setError",
|
|
22
22
|
"setData"
|
|
23
23
|
];
|
|
24
|
-
function
|
|
24
|
+
function X(n, D) {
|
|
25
25
|
const {
|
|
26
|
-
debounce:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
26
|
+
debounce: u = 3e3,
|
|
27
|
+
minSaveInterval: I = 2e3,
|
|
28
|
+
trackLastSaved: S = !0,
|
|
29
|
+
skipFields: J = [],
|
|
30
|
+
skipInertiaFields: N = !0,
|
|
31
|
+
deep: R = !0,
|
|
32
|
+
debug: a = !1,
|
|
33
|
+
serialize: f = JSON.stringify,
|
|
34
|
+
compare: s,
|
|
35
|
+
saveOnInit: p = !1,
|
|
36
|
+
onSave: q,
|
|
37
|
+
onBeforeSave: T,
|
|
38
|
+
onAfterSave: w,
|
|
39
|
+
onSaveSuccess: F,
|
|
40
|
+
onError: c
|
|
41
|
+
} = D, g = x(!1), m = x(!0);
|
|
42
|
+
let b = null, h = null, O = 0, k = () => {
|
|
43
|
+
}, A = () => {
|
|
44
|
+
}, o = !1;
|
|
45
|
+
const z = (e = 1e3) => {
|
|
46
|
+
m.value = !1, k(), A(), setTimeout(() => {
|
|
47
|
+
m.value = !0;
|
|
48
|
+
}, e);
|
|
49
|
+
}, C = (e = null) => {
|
|
50
|
+
if (m.value = !0, k(), A(), e === null)
|
|
51
|
+
o = !0, s ? r = null : i = null, y(), o = !1;
|
|
49
52
|
else {
|
|
50
|
-
const
|
|
51
|
-
|
|
53
|
+
const l = P(y, e);
|
|
54
|
+
A = l.cancel, l.call();
|
|
52
55
|
}
|
|
53
|
-
},
|
|
54
|
-
const
|
|
55
|
-
for (const
|
|
56
|
-
|
|
57
|
-
return
|
|
56
|
+
}, v = () => {
|
|
57
|
+
const e = M(n) ? Q(n) : n, l = {};
|
|
58
|
+
for (const t of Object.keys(e))
|
|
59
|
+
N && U.includes(t) || J.includes(t) || (l[t] = e[t]);
|
|
60
|
+
return l;
|
|
58
61
|
};
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
let i = p ? null : f(v()), r = s ? p ? null : v() : null;
|
|
63
|
+
if (S && !p) {
|
|
64
|
+
const e = v();
|
|
65
|
+
s ? h = { ...e } : b = f(e);
|
|
66
|
+
}
|
|
67
|
+
const E = (e) => {
|
|
68
|
+
S && (s ? h = { ...e } : b = f(e));
|
|
69
|
+
}, y = () => {
|
|
70
|
+
if (!m.value && !o) return;
|
|
71
|
+
const e = Date.now();
|
|
72
|
+
if (!o && I > 0 && e - O < I) {
|
|
73
|
+
a && console.log("[AutoSave] Skipping save - too soon after last save");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const l = v();
|
|
77
|
+
if (o)
|
|
78
|
+
s ? r = l : i = f(l);
|
|
79
|
+
else if (s) {
|
|
80
|
+
if (S && h && s(h, l)) {
|
|
81
|
+
a && console.log("[AutoSave] Skipping save - identical to last saved state"), r = l;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (r && s(r, l)) return;
|
|
85
|
+
r = l;
|
|
66
86
|
} else {
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
|
|
87
|
+
const t = f(l);
|
|
88
|
+
if (S && b !== null && t === b) {
|
|
89
|
+
a && console.log("[AutoSave] Skipping save - identical to last saved state"), i = t;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (i !== null && t === i) return;
|
|
93
|
+
i = t;
|
|
70
94
|
}
|
|
71
|
-
|
|
95
|
+
a && console.log("[AutoSave] Detected changes. Saving..."), g.value = !0, O = e;
|
|
72
96
|
try {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
97
|
+
T == null || T();
|
|
98
|
+
const t = Promise.resolve(q()).then(async () => {
|
|
99
|
+
await L(), await L();
|
|
100
|
+
const d = v();
|
|
101
|
+
E(d), F == null || F(d), w == null || w(), a && console.log("[AutoSave] Save successful.");
|
|
102
|
+
}).catch((d) => {
|
|
103
|
+
c == null || c(d), a && console.error("[AutoSave] Save failed:", d);
|
|
77
104
|
}).finally(() => {
|
|
78
|
-
|
|
105
|
+
g.value = !1;
|
|
79
106
|
});
|
|
80
|
-
|
|
81
|
-
|
|
107
|
+
return o || z(5e3), t;
|
|
108
|
+
} catch (t) {
|
|
109
|
+
c == null || c(t), a && console.error("[AutoSave] Immediate error:", t), g.value = !1;
|
|
82
110
|
}
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
111
|
+
}, W = P(y, u), G = W.call;
|
|
112
|
+
k = W.cancel;
|
|
113
|
+
const j = H(
|
|
114
|
+
n,
|
|
115
|
+
G,
|
|
88
116
|
{
|
|
89
|
-
deep:
|
|
117
|
+
deep: R,
|
|
90
118
|
flush: "post"
|
|
91
119
|
}
|
|
92
120
|
);
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
}),
|
|
96
|
-
isAutoSaving:
|
|
121
|
+
return K(() => {
|
|
122
|
+
j(), k(), A();
|
|
123
|
+
}), p && y(), {
|
|
124
|
+
isAutoSaving: g,
|
|
97
125
|
blockWatcher: z,
|
|
98
|
-
unblockWatcher:
|
|
99
|
-
stop:
|
|
126
|
+
unblockWatcher: C,
|
|
127
|
+
stop: j
|
|
100
128
|
};
|
|
101
129
|
}
|
|
102
|
-
function
|
|
103
|
-
let
|
|
130
|
+
function P(n, D) {
|
|
131
|
+
let u;
|
|
104
132
|
return {
|
|
105
133
|
call: () => {
|
|
106
|
-
clearTimeout(
|
|
134
|
+
clearTimeout(u), u = setTimeout(() => n(), D);
|
|
107
135
|
},
|
|
108
136
|
cancel: () => {
|
|
109
|
-
clearTimeout(
|
|
137
|
+
clearTimeout(u);
|
|
110
138
|
}
|
|
111
139
|
};
|
|
112
140
|
}
|
|
113
141
|
export {
|
|
114
|
-
|
|
142
|
+
X as useAutoSaveForm
|
|
115
143
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@provydon/vue-auto-save",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "A Vue 3 composable that autosaves forms with debounce, optional field skipping, and blockable watchers.",
|
|
5
5
|
"main": "./dist/useAutoSaveForm.cjs",
|
|
6
6
|
"module": "./dist/useAutoSaveForm.mjs",
|