@provydon/vue-auto-save 1.0.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.
- package/README.md +264 -0
- package/dist/index.d.ts +69 -0
- package/dist/useAutoSaveForm.cjs +1 -0
- package/dist/useAutoSaveForm.mjs +115 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# useAutoSaveForm
|
|
2
|
+
|
|
3
|
+
A powerful Vue 3 composable that automatically saves form data with intelligent debouncing, field filtering, and lifecycle hooks.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@provydon/vue-auto-save)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## โจ Features
|
|
9
|
+
|
|
10
|
+
- ๐ **Auto-save on change** with configurable debounce
|
|
11
|
+
- ๐ฏ **Smart field filtering** - skip Inertia helpers or custom fields
|
|
12
|
+
- ๐ก๏ธ **Blockable watchers** - pause auto-save during initialization
|
|
13
|
+
- ๐ **Lifecycle hooks** - beforeSave, afterSave, onError callbacks
|
|
14
|
+
- ๐๏ธ **Custom serialization** - support for circular references and functions
|
|
15
|
+
- ๐งช **Custom comparators** - shallow/deep equality without stringification
|
|
16
|
+
- ๐งน **Automatic cleanup** - no memory leaks on component unmount
|
|
17
|
+
- ๐ฆ **Framework agnostic** - works with Axios, Inertia, Fetch, etc.
|
|
18
|
+
|
|
19
|
+
## ๐ Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @provydon/vue-auto-save
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```vue
|
|
26
|
+
<script setup>
|
|
27
|
+
import { reactive } from 'vue'
|
|
28
|
+
import { useAutoSaveForm } from '@provydon/vue-auto-save'
|
|
29
|
+
|
|
30
|
+
const form = reactive({
|
|
31
|
+
title: '',
|
|
32
|
+
content: '',
|
|
33
|
+
tags: []
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const { isAutoSaving } = useAutoSaveForm(form, {
|
|
37
|
+
onSave: async () => {
|
|
38
|
+
await axios.post('/api/posts', form)
|
|
39
|
+
},
|
|
40
|
+
debounce: 2000,
|
|
41
|
+
debug: true
|
|
42
|
+
})
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<form>
|
|
47
|
+
<input v-model="form.title" placeholder="Post title" />
|
|
48
|
+
<textarea v-model="form.content" placeholder="Post content" />
|
|
49
|
+
<div v-if="isAutoSaving">Saving...</div>
|
|
50
|
+
</form>
|
|
51
|
+
</template>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## ๐ API Reference
|
|
55
|
+
|
|
56
|
+
### Basic Usage
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const { isAutoSaving, blockWatcher, unblockWatcher, stop } = useAutoSaveForm(
|
|
60
|
+
form, // reactive object or ref
|
|
61
|
+
options
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Options
|
|
66
|
+
|
|
67
|
+
| Option | Type | Default | Description |
|
|
68
|
+
|--------|------|---------|-------------|
|
|
69
|
+
| `onSave` | `() => void \| Promise<void>` | **Required** | Function called when auto-save should trigger |
|
|
70
|
+
| `debounce` | `number` | `3000` | Delay in milliseconds before saving |
|
|
71
|
+
| `skipFields` | `string[]` | `[]` | Field names to exclude from tracking |
|
|
72
|
+
| `skipInertiaFields` | `boolean` | `true` | Skip common Inertia.js form helpers |
|
|
73
|
+
| `deep` | `boolean` | `true` | Deep watch the form object |
|
|
74
|
+
| `debug` | `boolean` | `false` | Enable console logging |
|
|
75
|
+
| `saveOnInit` | `boolean` | `false` | Save immediately on mount |
|
|
76
|
+
| `serialize` | `(obj) => string` | `JSON.stringify` | Custom serialization function |
|
|
77
|
+
| `compare` | `(a, b) => boolean` | `undefined` | Custom comparison function |
|
|
78
|
+
| `onBeforeSave` | `() => void` | `undefined` | Called before saving |
|
|
79
|
+
| `onAfterSave` | `() => void` | `undefined` | Called after successful save |
|
|
80
|
+
| `onError` | `(err) => void` | `undefined` | Called on save error |
|
|
81
|
+
|
|
82
|
+
### Return Values
|
|
83
|
+
|
|
84
|
+
| Property | Type | Description |
|
|
85
|
+
|----------|------|-------------|
|
|
86
|
+
| `isAutoSaving` | `Ref<boolean>` | Reactive boolean indicating save status |
|
|
87
|
+
| `blockWatcher` | `(ms?: number) => void` | Temporarily block auto-save |
|
|
88
|
+
| `unblockWatcher` | `(ms?: number \| null) => void` | Unblock and optionally save immediately |
|
|
89
|
+
| `stop` | `() => void` | Manually stop the watcher |
|
|
90
|
+
|
|
91
|
+
## ๐ฏ Examples
|
|
92
|
+
|
|
93
|
+
### With Inertia.js
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { useForm } from '@inertiajs/vue3'
|
|
97
|
+
import { useAutoSaveForm } from '@provydon/vue-auto-save'
|
|
98
|
+
|
|
99
|
+
const form = useForm({
|
|
100
|
+
title: '',
|
|
101
|
+
content: '',
|
|
102
|
+
published: false
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const { isAutoSaving } = useAutoSaveForm(form, {
|
|
106
|
+
onSave: () => form.post('/posts', { preserveState: true }),
|
|
107
|
+
skipInertiaFields: true, // Skips processing, errors, etc.
|
|
108
|
+
debounce: 1000
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Custom Serialization
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const { isAutoSaving } = useAutoSaveForm(form, {
|
|
116
|
+
onSave: saveToAPI,
|
|
117
|
+
serialize: (obj) => {
|
|
118
|
+
// Handle circular references or functions
|
|
119
|
+
return JSON.stringify(obj, (key, value) => {
|
|
120
|
+
if (typeof value === 'function') return '[Function]'
|
|
121
|
+
return value
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Custom Comparator
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { isEqual } from 'lodash-es'
|
|
131
|
+
|
|
132
|
+
const { isAutoSaving } = useAutoSaveForm(form, {
|
|
133
|
+
onSave: saveToAPI,
|
|
134
|
+
compare: (a, b) => isEqual(a, b), // Deep equality without stringification
|
|
135
|
+
serialize: undefined // Not used when compare is provided
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Block During Initialization
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const { isAutoSaving, blockWatcher } = useAutoSaveForm(form, {
|
|
143
|
+
onSave: saveToAPI,
|
|
144
|
+
saveOnInit: false
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// Block auto-save during form initialization
|
|
148
|
+
blockWatcher(5000) // Block for 5 seconds
|
|
149
|
+
|
|
150
|
+
// Or block indefinitely and unblock manually
|
|
151
|
+
blockWatcher()
|
|
152
|
+
// ... do initialization work ...
|
|
153
|
+
unblockWatcher() // Resume auto-save
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### With Ref Forms
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const form = ref({
|
|
160
|
+
name: '',
|
|
161
|
+
email: ''
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const { isAutoSaving } = useAutoSaveForm(form, {
|
|
165
|
+
onSave: saveToAPI
|
|
166
|
+
})
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## ๐ง Advanced Usage
|
|
170
|
+
|
|
171
|
+
### Lifecycle Hooks
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
const { isAutoSaving } = useAutoSaveForm(form, {
|
|
175
|
+
onSave: saveToAPI,
|
|
176
|
+
onBeforeSave: () => {
|
|
177
|
+
console.log('About to save...')
|
|
178
|
+
},
|
|
179
|
+
onAfterSave: () => {
|
|
180
|
+
console.log('Save completed!')
|
|
181
|
+
},
|
|
182
|
+
onError: (error) => {
|
|
183
|
+
console.error('Save failed:', error)
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Manual Control
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
const { isAutoSaving, stop } = useAutoSaveForm(form, {
|
|
192
|
+
onSave: saveToAPI
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
// Manually stop the watcher
|
|
196
|
+
stop()
|
|
197
|
+
|
|
198
|
+
// The watcher will also stop automatically on component unmount
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## ๐จ Styling Examples
|
|
202
|
+
|
|
203
|
+
### Loading Indicator
|
|
204
|
+
|
|
205
|
+
```vue
|
|
206
|
+
<template>
|
|
207
|
+
<div class="form-container">
|
|
208
|
+
<input v-model="form.title" />
|
|
209
|
+
<div v-if="isAutoSaving" class="save-indicator">
|
|
210
|
+
<span class="spinner"></span>
|
|
211
|
+
Auto-saving...
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</template>
|
|
215
|
+
|
|
216
|
+
<style scoped>
|
|
217
|
+
.save-indicator {
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
gap: 8px;
|
|
221
|
+
color: #666;
|
|
222
|
+
font-size: 14px;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.spinner {
|
|
226
|
+
width: 16px;
|
|
227
|
+
height: 16px;
|
|
228
|
+
border: 2px solid #ddd;
|
|
229
|
+
border-top: 2px solid #007bff;
|
|
230
|
+
border-radius: 50%;
|
|
231
|
+
animation: spin 1s linear infinite;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@keyframes spin {
|
|
235
|
+
0% { transform: rotate(0deg); }
|
|
236
|
+
100% { transform: rotate(360deg); }
|
|
237
|
+
}
|
|
238
|
+
</style>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## ๐จ Important Notes
|
|
242
|
+
|
|
243
|
+
- **Circular References**: `JSON.stringify` (default serializer) will throw on circular references. Use a custom `serialize` function if needed.
|
|
244
|
+
- **Functions**: Functions won't survive `JSON.stringify`. Use custom serialization for function-heavy forms.
|
|
245
|
+
- **Vue Version**: Requires Vue 3.2+ (supports both reactive objects and refs).
|
|
246
|
+
- **Cleanup**: Watchers and timers are automatically cleaned up on component unmount.
|
|
247
|
+
|
|
248
|
+
## ๐ค Contributing
|
|
249
|
+
|
|
250
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
251
|
+
|
|
252
|
+
## ๐ License
|
|
253
|
+
|
|
254
|
+
MIT ยฉ [Providence Ifeosame](https://github.com/provydon)
|
|
255
|
+
|
|
256
|
+
## ๐ Support
|
|
257
|
+
|
|
258
|
+
If you find this package helpful, consider:
|
|
259
|
+
- โญ Starring the repository
|
|
260
|
+
- ๐ Reporting bugs
|
|
261
|
+
- ๐ก Suggesting features
|
|
262
|
+
- โ [Buying me a coffee](https://buymeacoffee.com/provydon)
|
|
263
|
+
|
|
264
|
+
Follow me on [Twitter](https://x.com/ProvyDon1) or connect on [LinkedIn](https://www.linkedin.com/in/providence-ifeosame/).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
export interface UseAutoSaveFormOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Delay in milliseconds before auto-saving after changes (default: 3000ms)
|
|
5
|
+
*/
|
|
6
|
+
debounce?: number;
|
|
7
|
+
/**
|
|
8
|
+
* List of form field keys to exclude from tracking
|
|
9
|
+
*/
|
|
10
|
+
skipFields?: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Whether to skip common Inertia form fields (default: true)
|
|
13
|
+
*/
|
|
14
|
+
skipInertiaFields?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to deeply watch the form (default: true)
|
|
17
|
+
*/
|
|
18
|
+
deep?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Enable debug logs in the console (default: false)
|
|
21
|
+
*/
|
|
22
|
+
debug?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Custom serializer function (default: JSON.stringify)
|
|
25
|
+
* Note: Functions and non-serializable fields won't survive JSON.stringify.
|
|
26
|
+
* Circular references will also cause JSON.stringify to throw.
|
|
27
|
+
* Use a custom serializer if you need to handle these cases.
|
|
28
|
+
*/
|
|
29
|
+
serialize?: (obj: Record<string, unknown>) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Custom comparator function (optional)
|
|
32
|
+
* If provided, this will be used instead of string comparison
|
|
33
|
+
*/
|
|
34
|
+
compare?: (a: Record<string, unknown>, b: Record<string, unknown>) => boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Whether to save on initial mount (default: false)
|
|
37
|
+
*/
|
|
38
|
+
saveOnInit?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Called when a save should be triggered (required)
|
|
41
|
+
*/
|
|
42
|
+
onSave: () => void | Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Called just before auto-saving starts
|
|
45
|
+
*/
|
|
46
|
+
onBeforeSave?: () => void;
|
|
47
|
+
/**
|
|
48
|
+
* Called after a successful auto-save
|
|
49
|
+
*/
|
|
50
|
+
onAfterSave?: () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Called if auto-saving throws or fails
|
|
53
|
+
*/
|
|
54
|
+
onError?: (err: unknown) => void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Automatically watches a Vue 3 form object and triggers save on change with debounce.
|
|
58
|
+
* Includes support for skipping specific fields and Inertia form helpers.
|
|
59
|
+
*
|
|
60
|
+
* @param form - The form object to watch (typically a reactive or ref object)
|
|
61
|
+
* @param options - Configuration for debounce, lifecycle hooks, and field skipping
|
|
62
|
+
* @returns An object with `isAutoSaving` and `blockWatcher()` for temporary disable
|
|
63
|
+
*/
|
|
64
|
+
export declare function useAutoSaveForm(form: Record<string, unknown> | Ref<Record<string, unknown>>, options: UseAutoSaveFormOptions): {
|
|
65
|
+
isAutoSaving: Ref<boolean, boolean>;
|
|
66
|
+
blockWatcher: (ms?: number) => void;
|
|
67
|
+
unblockWatcher: (ms?: number | null) => void;
|
|
68
|
+
stop: import('vue').WatchHandle;
|
|
69
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("vue"),P=["save","applicationId","isDirty","processing","errors","hasErrors","recentlySuccessful","wasSuccessful","data","transform","get","post","put","patch","delete","cancel","reset","clearErrors","setError","setData"];function q(l,p){const{debounce:n=3e3,skipFields:T=[],skipInertiaFields:I=!0,deep:O=!0,debug:o=!1,serialize:D=JSON.stringify,compare:S,saveOnInit:b=!1,onSave:W,onBeforeSave:h,onAfterSave:m,onError:c}=p,u=s.ref(!1),a=s.ref(!0);let r=()=>{},i=()=>{};const j=(t=1e3)=>{a.value=!1,r(),i(),setTimeout(()=>{a.value=!0},t)},z=(t=null)=>{if(a.value=!0,r(),i(),t===null)d();else{const e=A(d,t);i=e.cancel,e.call()}},f=()=>{const t=s.isRef(l)?s.unref(l):l,e={};for(const v of Object.keys(t))I&&P.includes(v)||T.includes(v)||(e[v]=t[v]);return e};let g=b?null:D(f()),y=S?b?null:f():null;const d=()=>{if(!a.value)return;const t=f();if(S){if(y&&S(y,t))return;y=t}else{const e=D(t);if(g!==null&&e===g)return;g=e}o&&console.log("[AutoSave] Detected changes. Saving..."),u.value=!0;try{h==null||h(),Promise.resolve(W()).then(()=>{m==null||m(),o&&console.log("[AutoSave] Save successful.")}).catch(e=>{c==null||c(e),o&&console.error("[AutoSave] Save failed:",e)}).finally(()=>{u.value=!1})}catch(e){c==null||c(e),o&&console.error("[AutoSave] Immediate error:",e),u.value=!1}},F=A(d,n),w=F.call;r=F.cancel;const k=s.watch(()=>f(),w,{deep:O,flush:"post"});return s.onScopeDispose(()=>{k(),r(),i()}),b&&d(),{isAutoSaving:u,blockWatcher:j,unblockWatcher:z,stop:k}}function A(l,p){let n;return{call:()=>{clearTimeout(n),n=setTimeout(()=>l(),p)},cancel:()=>{clearTimeout(n)}}}exports.useAutoSaveForm=q;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ref as F, watch as x, onScopeDispose as J, isRef as N, unref as P } from "vue";
|
|
2
|
+
const R = [
|
|
3
|
+
"save",
|
|
4
|
+
"applicationId",
|
|
5
|
+
"isDirty",
|
|
6
|
+
"processing",
|
|
7
|
+
"errors",
|
|
8
|
+
"hasErrors",
|
|
9
|
+
"recentlySuccessful",
|
|
10
|
+
"wasSuccessful",
|
|
11
|
+
"data",
|
|
12
|
+
"transform",
|
|
13
|
+
"get",
|
|
14
|
+
"post",
|
|
15
|
+
"put",
|
|
16
|
+
"patch",
|
|
17
|
+
"delete",
|
|
18
|
+
"cancel",
|
|
19
|
+
"reset",
|
|
20
|
+
"clearErrors",
|
|
21
|
+
"setError",
|
|
22
|
+
"setData"
|
|
23
|
+
];
|
|
24
|
+
function C(s, v) {
|
|
25
|
+
const {
|
|
26
|
+
debounce: l = 3e3,
|
|
27
|
+
skipFields: I = [],
|
|
28
|
+
skipInertiaFields: T = !0,
|
|
29
|
+
deep: O = !0,
|
|
30
|
+
debug: n = !1,
|
|
31
|
+
serialize: y = JSON.stringify,
|
|
32
|
+
compare: p,
|
|
33
|
+
saveOnInit: S = !1,
|
|
34
|
+
onSave: W,
|
|
35
|
+
onBeforeSave: h,
|
|
36
|
+
onAfterSave: m,
|
|
37
|
+
onError: c
|
|
38
|
+
} = v, o = F(!1), a = F(!0);
|
|
39
|
+
let u = () => {
|
|
40
|
+
}, r = () => {
|
|
41
|
+
};
|
|
42
|
+
const z = (t = 1e3) => {
|
|
43
|
+
a.value = !1, u(), r(), setTimeout(() => {
|
|
44
|
+
a.value = !0;
|
|
45
|
+
}, t);
|
|
46
|
+
}, j = (t = null) => {
|
|
47
|
+
if (a.value = !0, u(), r(), t === null)
|
|
48
|
+
f();
|
|
49
|
+
else {
|
|
50
|
+
const e = A(f, t);
|
|
51
|
+
r = e.cancel, e.call();
|
|
52
|
+
}
|
|
53
|
+
}, i = () => {
|
|
54
|
+
const t = N(s) ? P(s) : s, e = {};
|
|
55
|
+
for (const d of Object.keys(t))
|
|
56
|
+
T && R.includes(d) || I.includes(d) || (e[d] = t[d]);
|
|
57
|
+
return e;
|
|
58
|
+
};
|
|
59
|
+
let b = S ? null : y(i()), g = p ? S ? null : i() : null;
|
|
60
|
+
const f = () => {
|
|
61
|
+
if (!a.value) return;
|
|
62
|
+
const t = i();
|
|
63
|
+
if (p) {
|
|
64
|
+
if (g && p(g, t)) return;
|
|
65
|
+
g = t;
|
|
66
|
+
} else {
|
|
67
|
+
const e = y(t);
|
|
68
|
+
if (b !== null && e === b) return;
|
|
69
|
+
b = e;
|
|
70
|
+
}
|
|
71
|
+
n && console.log("[AutoSave] Detected changes. Saving..."), o.value = !0;
|
|
72
|
+
try {
|
|
73
|
+
h == null || h(), Promise.resolve(W()).then(() => {
|
|
74
|
+
m == null || m(), n && console.log("[AutoSave] Save successful.");
|
|
75
|
+
}).catch((e) => {
|
|
76
|
+
c == null || c(e), n && console.error("[AutoSave] Save failed:", e);
|
|
77
|
+
}).finally(() => {
|
|
78
|
+
o.value = !1;
|
|
79
|
+
});
|
|
80
|
+
} catch (e) {
|
|
81
|
+
c == null || c(e), n && console.error("[AutoSave] Immediate error:", e), o.value = !1;
|
|
82
|
+
}
|
|
83
|
+
}, D = A(f, l), w = D.call;
|
|
84
|
+
u = D.cancel;
|
|
85
|
+
const k = x(
|
|
86
|
+
() => i(),
|
|
87
|
+
w,
|
|
88
|
+
{
|
|
89
|
+
deep: O,
|
|
90
|
+
flush: "post"
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
return J(() => {
|
|
94
|
+
k(), u(), r();
|
|
95
|
+
}), S && f(), {
|
|
96
|
+
isAutoSaving: o,
|
|
97
|
+
blockWatcher: z,
|
|
98
|
+
unblockWatcher: j,
|
|
99
|
+
stop: k
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function A(s, v) {
|
|
103
|
+
let l;
|
|
104
|
+
return {
|
|
105
|
+
call: () => {
|
|
106
|
+
clearTimeout(l), l = setTimeout(() => s(), v);
|
|
107
|
+
},
|
|
108
|
+
cancel: () => {
|
|
109
|
+
clearTimeout(l);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
C as useAutoSaveForm
|
|
115
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@provydon/vue-auto-save",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Vue 3 composable that autosaves forms with debounce, optional field skipping, and blockable watchers.",
|
|
5
|
+
"main": "./dist/useAutoSaveForm.cjs",
|
|
6
|
+
"module": "./dist/useAutoSaveForm.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/useAutoSaveForm.mjs",
|
|
12
|
+
"require": "./dist/useAutoSaveForm.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist"],
|
|
16
|
+
"keywords": ["vue", "composable", "autosave", "form", "debounce", "vue3"],
|
|
17
|
+
"author": "Providence Ifeosame",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/provydon/vue-auto-save.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/provydon/vue-auto-save/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/provydon/vue-auto-save#readme",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"publishConfig": { "access": "public" },
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "vite build",
|
|
31
|
+
"dev": "vite",
|
|
32
|
+
"clean": "rm -rf dist",
|
|
33
|
+
"test": "vitest",
|
|
34
|
+
"test:run": "vitest run",
|
|
35
|
+
"test:coverage": "vitest run --coverage"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"vue": "^3.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"vue": "^3.5.18",
|
|
42
|
+
"typescript": "^5.9.2",
|
|
43
|
+
"vite": "^6.3.5",
|
|
44
|
+
"vite-plugin-dts": "^4.5.4",
|
|
45
|
+
"vitest": "^3.2.4",
|
|
46
|
+
"@vue/test-utils": "^2.4.5",
|
|
47
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
48
|
+
"jsdom": "^25.0.1"
|
|
49
|
+
}
|
|
50
|
+
}
|