vue-context-storage 0.1.22 → 0.1.23
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 +642 -357
- package/dist/index.d.ts +181 -33
- package/dist/index.js +262 -11
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,357 +1,642 @@
|
|
|
1
|
-
# vue-context-storage
|
|
2
|
-
|
|
3
|
-
Vue 3 context storage system with URL query synchronization support.
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/vue-context-storage)
|
|
6
|
-
[](https://www.typescriptlang.org/)
|
|
7
|
-
[](https://vuejs.org/)
|
|
8
|
-
[](https://bundlephobia.com/package/vue-context-storage)
|
|
9
|
-
[](https://github.com/lviobio/vue-context-storage/issues)
|
|
10
|
-
[](https://github.com/lviobio/vue-context-storage)
|
|
11
|
-

|
|
12
|
-

|
|
13
|
-
[](https://codecov.io/gh/lviobio/vue-context-storage)
|
|
14
|
-
[](https://lviobio.github.io/vue-context-storage/)
|
|
15
|
-
|
|
16
|
-
A powerful state management solution for Vue 3 applications that provides:
|
|
17
|
-
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- ✅ **
|
|
38
|
-
- ✅ **
|
|
39
|
-
- ✅ **
|
|
40
|
-
- ✅ **
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
1
|
+
# vue-context-storage
|
|
2
|
+
|
|
3
|
+
Vue 3 context storage system with URL query, localStorage, and sessionStorage synchronization support.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/vue-context-storage)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](https://vuejs.org/)
|
|
8
|
+
[](https://bundlephobia.com/package/vue-context-storage)
|
|
9
|
+
[](https://github.com/lviobio/vue-context-storage/issues)
|
|
10
|
+
[](https://github.com/lviobio/vue-context-storage)
|
|
11
|
+

|
|
12
|
+

|
|
13
|
+
[](https://codecov.io/gh/lviobio/vue-context-storage)
|
|
14
|
+
[](https://lviobio.github.io/vue-context-storage/)
|
|
15
|
+
|
|
16
|
+
A powerful state management solution for Vue 3 applications that provides:
|
|
17
|
+
|
|
18
|
+
- **Context-based storage** using Vue's provide/inject API
|
|
19
|
+
- **Automatic URL query synchronization** for preserving state across page reloads
|
|
20
|
+
- **localStorage & sessionStorage handlers** for persistent and session-scoped state
|
|
21
|
+
- **Multiple storage contexts** with activation management
|
|
22
|
+
- **Type-safe** TypeScript support
|
|
23
|
+
- **Tree-shakeable** and lightweight
|
|
24
|
+
|
|
25
|
+
## Live Demo
|
|
26
|
+
|
|
27
|
+
🚀 **[Try the interactive playground](https://lviobio.github.io/vue-context-storage)**
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install vue-context-storage
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- ✅ **Vue 3 Composition API** - Built with modern Vue patterns
|
|
38
|
+
- ✅ **URL Query Sync** - Automatically sync state with URL parameters
|
|
39
|
+
- ✅ **localStorage Handler** - Persist state to localStorage with cross-tab sync
|
|
40
|
+
- ✅ **sessionStorage Handler** - Session-scoped state that survives page refreshes
|
|
41
|
+
- ✅ **Multiple Contexts** - Support multiple independent storage contexts
|
|
42
|
+
- ✅ **TypeScript** - Full type safety and IntelliSense support
|
|
43
|
+
- ✅ **Flexible** - Works with vue-router 4+
|
|
44
|
+
- ✅ **Transform Helpers** - Built-in utilities for type conversion
|
|
45
|
+
|
|
46
|
+
## Basic Usage
|
|
47
|
+
|
|
48
|
+
### Option 1: Manual Component Import (Recommended)
|
|
49
|
+
|
|
50
|
+
Import ContextStorage component in your `App.vue`:
|
|
51
|
+
|
|
52
|
+
```vue
|
|
53
|
+
<template>
|
|
54
|
+
<ContextStorage>
|
|
55
|
+
<router-view />
|
|
56
|
+
</ContextStorage>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script setup lang="ts">
|
|
60
|
+
import { ContextStorage } from 'vue-context-storage/components'
|
|
61
|
+
</script>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Option 2: Using Vue Plugin
|
|
65
|
+
|
|
66
|
+
Register the plugin in your main app file:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { createApp } from 'vue'
|
|
70
|
+
import { VueContextStoragePlugin } from 'vue-context-storage/plugin'
|
|
71
|
+
import App from './App.vue'
|
|
72
|
+
|
|
73
|
+
const app = createApp(App)
|
|
74
|
+
|
|
75
|
+
// Register components globally
|
|
76
|
+
app.use(VueContextStoragePlugin)
|
|
77
|
+
|
|
78
|
+
app.mount('#app')
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Then use components without importing in your `App.vue`:
|
|
82
|
+
|
|
83
|
+
```vue
|
|
84
|
+
<template>
|
|
85
|
+
<ContextStorage>
|
|
86
|
+
<router-view />
|
|
87
|
+
</ContextStorage>
|
|
88
|
+
</template>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Unified Composable
|
|
92
|
+
|
|
93
|
+
`useContextStorage()` provides a single entry point for all handler types:
|
|
94
|
+
|
|
95
|
+
```vue
|
|
96
|
+
<script setup lang="ts">
|
|
97
|
+
import { reactive } from 'vue'
|
|
98
|
+
import { useContextStorage } from 'vue-context-storage'
|
|
99
|
+
|
|
100
|
+
const filters = reactive({
|
|
101
|
+
search: '',
|
|
102
|
+
status: 'active',
|
|
103
|
+
page: 1,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Sync with URL query
|
|
107
|
+
useContextStorage('query', filters, {
|
|
108
|
+
prefix: 'filters',
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Sync with localStorage
|
|
112
|
+
useContextStorage('localStorage', filters, {
|
|
113
|
+
key: 'saved-filters',
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// Sync with sessionStorage
|
|
117
|
+
useContextStorage('sessionStorage', filters, {
|
|
118
|
+
key: 'temp-filters',
|
|
119
|
+
})
|
|
120
|
+
</script>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Options are type-checked per handler — `'query'` accepts query options, `'localStorage'` and `'sessionStorage'` require a `key`, etc.
|
|
124
|
+
|
|
125
|
+
You can also pass an injection key directly instead of a string:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { contextStorageQueryHandlerInjectKey } from 'vue-context-storage'
|
|
129
|
+
|
|
130
|
+
useContextStorage(contextStorageQueryHandlerInjectKey, filters, {
|
|
131
|
+
prefix: 'filters',
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Registering Custom Handlers
|
|
136
|
+
|
|
137
|
+
Register your own handlers at runtime and extend the type map for full type safety:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { defineContextStorageHandler } from 'vue-context-storage'
|
|
141
|
+
import { myHandlerInjectionKey } from './my-handler'
|
|
142
|
+
|
|
143
|
+
// Runtime registration
|
|
144
|
+
defineContextStorageHandler('myHandler', myHandlerInjectionKey)
|
|
145
|
+
|
|
146
|
+
// TypeScript augmentation (e.g. in a .d.ts or at module level)
|
|
147
|
+
declare module 'vue-context-storage' {
|
|
148
|
+
interface ContextStorageHandlerMap {
|
|
149
|
+
myHandler: { key: string }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Now fully type-checked
|
|
154
|
+
useContextStorage('myHandler', data, { key: 'example' })
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Use Query Handler in Components
|
|
158
|
+
|
|
159
|
+
Sync reactive state with URL query parameters:
|
|
160
|
+
|
|
161
|
+
```vue
|
|
162
|
+
<script setup lang="ts">
|
|
163
|
+
import { reactive } from 'vue'
|
|
164
|
+
import { useContextStorage } from 'vue-context-storage'
|
|
165
|
+
|
|
166
|
+
interface Filters {
|
|
167
|
+
search: string
|
|
168
|
+
status: string
|
|
169
|
+
page: number
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const filters = reactive<Filters>({
|
|
173
|
+
search: '',
|
|
174
|
+
status: 'active',
|
|
175
|
+
page: 1,
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// Automatically syncs filters with URL query
|
|
179
|
+
useContextStorage('query', filters, {
|
|
180
|
+
prefix: 'filters', // URL will be: ?filters[search]=...&filters[status]=...
|
|
181
|
+
})
|
|
182
|
+
</script>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Also available as a dedicated composable:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { useContextStorageQueryHandler } from 'vue-context-storage'
|
|
189
|
+
|
|
190
|
+
useContextStorageQueryHandler(filters, {
|
|
191
|
+
prefix: 'filters',
|
|
192
|
+
})
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Advanced Usage
|
|
196
|
+
|
|
197
|
+
### Using Transform Helpers
|
|
198
|
+
|
|
199
|
+
Convert URL query string values to proper types:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { ref } from 'vue'
|
|
203
|
+
import { useContextStorage, transform } from 'vue-context-storage'
|
|
204
|
+
|
|
205
|
+
interface TableState {
|
|
206
|
+
page: number
|
|
207
|
+
search: string
|
|
208
|
+
perPage: number
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const state = ref<TableState>({
|
|
212
|
+
page: 1,
|
|
213
|
+
search: '',
|
|
214
|
+
perPage: 25,
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
useContextStorage('query', state, {
|
|
218
|
+
prefix: 'table',
|
|
219
|
+
transform: (deserialized, initial) => ({
|
|
220
|
+
page: transform.asNumber(deserialized.page, { fallback: 1 }),
|
|
221
|
+
search: transform.asString(deserialized.search, { fallback: '' }),
|
|
222
|
+
perPage: transform.asNumber(deserialized.perPage, { fallback: 25 }),
|
|
223
|
+
}),
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Available Transform Helpers
|
|
228
|
+
|
|
229
|
+
- `asNumber(value, options)` - Convert to number
|
|
230
|
+
- `asString(value, options)` - Convert to string
|
|
231
|
+
- `asBoolean(value, options)` - Convert to boolean
|
|
232
|
+
- `asArray(value, options)` - Convert to array
|
|
233
|
+
- `asNumberArray(value, options)` - Convert to number array
|
|
234
|
+
|
|
235
|
+
### Using Zod Schemas
|
|
236
|
+
|
|
237
|
+
Alternatively, you can use [Zod](https://zod.dev/) schemas for automatic validation and type inference:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { z } from 'zod'
|
|
241
|
+
import { useContextStorage } from 'vue-context-storage'
|
|
242
|
+
|
|
243
|
+
// Define schema with automatic coercion
|
|
244
|
+
const FiltersSchema = z.object({
|
|
245
|
+
search: z.string().default(''),
|
|
246
|
+
page: z.coerce.number().int().positive().default(1),
|
|
247
|
+
status: z.enum(['active', 'inactive']).default('active'),
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
const filters = ref(FiltersSchema.parse({}))
|
|
251
|
+
|
|
252
|
+
// Use schema for automatic validation
|
|
253
|
+
useContextStorage('query', filters, {
|
|
254
|
+
prefix: 'filters',
|
|
255
|
+
schema: FiltersSchema,
|
|
256
|
+
})
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Benefits:**
|
|
260
|
+
|
|
261
|
+
- Automatic type coercion (strings → numbers, etc.)
|
|
262
|
+
- Runtime validation with detailed errors
|
|
263
|
+
- Automatic TypeScript type inference
|
|
264
|
+
- Less boilerplate code
|
|
265
|
+
- Single source of truth for structure and validation
|
|
266
|
+
|
|
267
|
+
### Preserve Empty State
|
|
268
|
+
|
|
269
|
+
Keep empty state in URL to prevent resetting on reload:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
useContextStorage('query', filters, {
|
|
273
|
+
prefix: 'filters',
|
|
274
|
+
preserveEmptyState: true,
|
|
275
|
+
// Empty filters will show as: ?filters
|
|
276
|
+
// Without this option, empty filters would clear the URL completely
|
|
277
|
+
})
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Configure Query Handler
|
|
281
|
+
|
|
282
|
+
Customize global behavior:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import { ContextStorageQueryHandler } from 'vue-context-storage'
|
|
286
|
+
|
|
287
|
+
ContextStorageQueryHandler.configure({
|
|
288
|
+
mode: 'push', // 'replace' (default) or 'push' for history
|
|
289
|
+
preserveUnusedKeys: true, // Keep other query params
|
|
290
|
+
preserveEmptyState: false,
|
|
291
|
+
})
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Use localStorage Handler in Components
|
|
295
|
+
|
|
296
|
+
Persist reactive state to `localStorage`. Data is automatically synced across browser tabs.
|
|
297
|
+
|
|
298
|
+
```vue
|
|
299
|
+
<script setup lang="ts">
|
|
300
|
+
import { reactive } from 'vue'
|
|
301
|
+
import { useContextStorage } from 'vue-context-storage'
|
|
302
|
+
|
|
303
|
+
const settings = reactive({
|
|
304
|
+
theme: 'light',
|
|
305
|
+
fontSize: 14,
|
|
306
|
+
sidebarOpen: true,
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
// Automatically syncs settings with localStorage under the key "app-settings"
|
|
310
|
+
useContextStorage('localStorage', settings, {
|
|
311
|
+
key: 'app-settings',
|
|
312
|
+
})
|
|
313
|
+
</script>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Also available as a dedicated composable:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import { useContextStorageLocalStorage } from 'vue-context-storage'
|
|
320
|
+
|
|
321
|
+
useContextStorageLocalStorage(settings, {
|
|
322
|
+
key: 'app-settings',
|
|
323
|
+
})
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Configure localStorage Handler
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { ContextStorageLocalStorageHandler } from 'vue-context-storage'
|
|
330
|
+
|
|
331
|
+
ContextStorageLocalStorageHandler.configure({
|
|
332
|
+
listenToStorageEvents: true, // Cross-tab sync (default: true)
|
|
333
|
+
})
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Use sessionStorage Handler in Components
|
|
337
|
+
|
|
338
|
+
Persist reactive state to `sessionStorage`. Data survives page refreshes but is cleared when the tab is closed.
|
|
339
|
+
|
|
340
|
+
```vue
|
|
341
|
+
<script setup lang="ts">
|
|
342
|
+
import { reactive } from 'vue'
|
|
343
|
+
import { useContextStorage } from 'vue-context-storage'
|
|
344
|
+
|
|
345
|
+
const formDraft = reactive({
|
|
346
|
+
email: '',
|
|
347
|
+
message: '',
|
|
348
|
+
step: 1,
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
// Automatically syncs form draft with sessionStorage
|
|
352
|
+
useContextStorage('sessionStorage', formDraft, {
|
|
353
|
+
key: 'contact-form-draft',
|
|
354
|
+
})
|
|
355
|
+
</script>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Also available as a dedicated composable:
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import { useContextStorageSessionStorage } from 'vue-context-storage'
|
|
362
|
+
|
|
363
|
+
useContextStorageSessionStorage(formDraft, {
|
|
364
|
+
key: 'contact-form-draft',
|
|
365
|
+
})
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Using Prefix
|
|
369
|
+
|
|
370
|
+
Store multiple data objects under a single storage key using prefixes:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
const filters = reactive({ search: '', status: 'active' })
|
|
374
|
+
|
|
375
|
+
useContextStorage('sessionStorage', filters, {
|
|
376
|
+
key: 'app-state',
|
|
377
|
+
prefix: 'filters', // Stored as { filters: { search: '', status: 'active' } }
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
const pagination = reactive({ page: 1, perPage: 25 })
|
|
381
|
+
|
|
382
|
+
useContextStorage('sessionStorage', pagination, {
|
|
383
|
+
key: 'app-state',
|
|
384
|
+
prefix: 'pagination', // Stored as { filters: {...}, pagination: { page: 1, perPage: 25 } }
|
|
385
|
+
})
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Using Transform with Storage Handlers
|
|
389
|
+
|
|
390
|
+
Convert stored values to proper types when reading from storage:
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { useContextStorage, transform } from 'vue-context-storage'
|
|
394
|
+
|
|
395
|
+
const settings = reactive({
|
|
396
|
+
theme: 'light',
|
|
397
|
+
fontSize: 14,
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
useContextStorage('localStorage', settings, {
|
|
401
|
+
key: 'app-settings',
|
|
402
|
+
transform: (deserialized, initial) => ({
|
|
403
|
+
theme: transform.asString(deserialized.theme, { fallback: 'light' }),
|
|
404
|
+
fontSize: transform.asNumber(deserialized.fontSize, { fallback: 14 }),
|
|
405
|
+
}),
|
|
406
|
+
})
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Using Zod Schemas with Storage Handlers
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
import { z } from 'zod'
|
|
413
|
+
import { useContextStorageLocalStorage } from 'vue-context-storage'
|
|
414
|
+
|
|
415
|
+
const SettingsSchema = z.object({
|
|
416
|
+
theme: z.enum(['light', 'dark']).default('light'),
|
|
417
|
+
fontSize: z.number().int().positive().default(14),
|
|
418
|
+
sidebarOpen: z.boolean().default(true),
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
const settings = reactive(SettingsSchema.parse({}))
|
|
422
|
+
|
|
423
|
+
useContextStorage('localStorage', settings, {
|
|
424
|
+
key: 'app-settings',
|
|
425
|
+
schema: SettingsSchema,
|
|
426
|
+
})
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Custom Serialization
|
|
430
|
+
|
|
431
|
+
Provide custom serializer/deserializer functions:
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
useContextStorage('localStorage', settings, {
|
|
435
|
+
key: 'app-settings',
|
|
436
|
+
serializer: (data) => btoa(JSON.stringify(data)),
|
|
437
|
+
deserializer: (str) => JSON.parse(atob(str)),
|
|
438
|
+
})
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## API Reference
|
|
442
|
+
|
|
443
|
+
### Composables
|
|
444
|
+
|
|
445
|
+
#### `useContextStorage(type, data, options)`
|
|
446
|
+
|
|
447
|
+
Unified composable that delegates to the correct handler based on `type`.
|
|
448
|
+
|
|
449
|
+
**Parameters:**
|
|
450
|
+
|
|
451
|
+
- `type: 'query' | 'localStorage' | 'sessionStorage' | InjectionKey` - Handler type or injection key
|
|
452
|
+
- `data: MaybeRefOrGetter<T>` - Reactive reference to sync
|
|
453
|
+
- `options` - Handler-specific options (type-checked per handler)
|
|
454
|
+
|
|
455
|
+
**Custom handler registration:**
|
|
456
|
+
|
|
457
|
+
- `defineContextStorageHandler(name, injectionKey)` - Register a custom handler
|
|
458
|
+
- `resolveHandlerInjectionKey(type)` - Look up an injection key by name
|
|
459
|
+
|
|
460
|
+
#### `useContextStorageQueryHandler<T>(data, options)`
|
|
461
|
+
|
|
462
|
+
Registers reactive data for URL query synchronization.
|
|
463
|
+
|
|
464
|
+
**Parameters:**
|
|
465
|
+
|
|
466
|
+
- `data: MaybeRefOrGetter<T>` - Reactive reference to sync
|
|
467
|
+
- `options?: RegisterQueryHandlerOptions<T>`
|
|
468
|
+
- `prefix?: string` - Query parameter prefix
|
|
469
|
+
- `transform?: (deserialized, initial) => T` - Transform function
|
|
470
|
+
- `preserveEmptyState?: boolean` - Keep empty state in URL
|
|
471
|
+
- `mergeOnlyExistingKeysWithoutTransform?: boolean` - Only merge existing keys (default: true)
|
|
472
|
+
|
|
473
|
+
### Classes
|
|
474
|
+
|
|
475
|
+
#### `ContextStorageQueryHandler`
|
|
476
|
+
|
|
477
|
+
Main handler for URL query synchronization.
|
|
478
|
+
|
|
479
|
+
**Static Methods:**
|
|
480
|
+
|
|
481
|
+
- `configure(options): ContextStorageHandlerConstructor` - Configure global options
|
|
482
|
+
- `getInitialStateResolver(): () => LocationQuery` - Get initial state resolver
|
|
483
|
+
|
|
484
|
+
**Methods:**
|
|
485
|
+
|
|
486
|
+
- `register<T>(data, options): () => void` - Register data for sync
|
|
487
|
+
- `setEnabled(state, initial): void` - Enable/disable handler
|
|
488
|
+
- `setInitialState(state): void` - Set initial state
|
|
489
|
+
|
|
490
|
+
#### `useContextStorageLocalStorage<T>(data, options)`
|
|
491
|
+
|
|
492
|
+
Registers reactive data for localStorage synchronization.
|
|
493
|
+
|
|
494
|
+
**Parameters:**
|
|
495
|
+
|
|
496
|
+
- `data: MaybeRefOrGetter<T>` - Reactive reference to sync
|
|
497
|
+
- `options: RegisterWebStorageHandlerBaseOptions<T>`
|
|
498
|
+
- `key: string` - Storage key (required)
|
|
499
|
+
- `prefix?: string` - Namespace within the storage key
|
|
500
|
+
- `transform?: (deserialized, initial) => T` - Transform function
|
|
501
|
+
- `schema?: ZodSchema` - Zod schema for validation
|
|
502
|
+
- `serializer?: (data: T) => string` - Custom serializer (default: `JSON.stringify`)
|
|
503
|
+
- `deserializer?: (str: string) => unknown` - Custom deserializer (default: `JSON.parse`)
|
|
504
|
+
|
|
505
|
+
#### `useContextStorageSessionStorage<T>(data, options)`
|
|
506
|
+
|
|
507
|
+
Registers reactive data for sessionStorage synchronization. Same options as `useContextStorageLocalStorage`.
|
|
508
|
+
|
|
509
|
+
### Classes
|
|
510
|
+
|
|
511
|
+
#### `ContextStorageLocalStorageHandler`
|
|
512
|
+
|
|
513
|
+
Handler for localStorage synchronization. Supports cross-tab sync via `storage` events.
|
|
514
|
+
|
|
515
|
+
**Static Methods:**
|
|
516
|
+
|
|
517
|
+
- `configure(options): ContextStorageHandlerConstructor` - Configure global options
|
|
518
|
+
- `listenToStorageEvents?: boolean` - Enable cross-tab sync (default: `true`)
|
|
519
|
+
|
|
520
|
+
#### `ContextStorageSessionStorageHandler`
|
|
521
|
+
|
|
522
|
+
Handler for sessionStorage synchronization. Data is scoped to the current tab.
|
|
523
|
+
|
|
524
|
+
**Static Methods:**
|
|
525
|
+
|
|
526
|
+
- `configure(options): ContextStorageHandlerConstructor` - Configure global options
|
|
527
|
+
- `listenToStorageEvents?: boolean` - Listen to storage events (default: `false`)
|
|
528
|
+
|
|
529
|
+
### Transform Helpers
|
|
530
|
+
|
|
531
|
+
All transform helpers support nullable and missable options:
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
transform.asNumber(value, {
|
|
535
|
+
fallback: 0, // Default value
|
|
536
|
+
nullable: false, // Allow null return
|
|
537
|
+
missable: false, // Allow undefined return
|
|
538
|
+
})
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
## TypeScript Support
|
|
542
|
+
|
|
543
|
+
Full TypeScript support with type inference:
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
import type {
|
|
547
|
+
ContextStorageHandler,
|
|
548
|
+
ContextStorageHandlerConstructor,
|
|
549
|
+
IContextStorageQueryHandler,
|
|
550
|
+
QueryValue,
|
|
551
|
+
SerializeOptions,
|
|
552
|
+
} from 'vue-context-storage'
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
When using Zod schemas, TypeScript will automatically infer types:
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
const FiltersSchema = z.object({
|
|
559
|
+
search: z.string().default(''),
|
|
560
|
+
page: z.coerce.number().default(1),
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
type Filters = z.infer<typeof FiltersSchema>
|
|
564
|
+
// Result: { search: string; page: number }
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
## Examples
|
|
568
|
+
|
|
569
|
+
### Pagination with URL Sync
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
import { ref } from 'vue'
|
|
573
|
+
import { useContextStorageQueryHandler, transform } from 'vue-context-storage'
|
|
574
|
+
|
|
575
|
+
const pagination = ref({
|
|
576
|
+
page: 1,
|
|
577
|
+
perPage: 25,
|
|
578
|
+
total: 0,
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
useContextStorageQueryHandler(pagination, {
|
|
582
|
+
prefix: 'page',
|
|
583
|
+
transform: (data, initial) => ({
|
|
584
|
+
page: transform.asNumber(data.page, { fallback: 1 }),
|
|
585
|
+
perPage: transform.asNumber(data.perPage, { fallback: 25 }),
|
|
586
|
+
total: initial.total, // Don't sync total from URL
|
|
587
|
+
}),
|
|
588
|
+
})
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
## Peer Dependencies
|
|
592
|
+
|
|
593
|
+
- `vue`: ^3.5.0
|
|
594
|
+
- `vue-router`: ^4.0.0
|
|
595
|
+
- `zod`: ^4.0.0 (optional - only if using schema validation)
|
|
596
|
+
|
|
597
|
+
## License
|
|
598
|
+
|
|
599
|
+
MIT
|
|
600
|
+
|
|
601
|
+
## Development
|
|
602
|
+
|
|
603
|
+
### Running Playground Locally
|
|
604
|
+
|
|
605
|
+
```bash
|
|
606
|
+
# Development mode (hot reload)
|
|
607
|
+
npm run play
|
|
608
|
+
|
|
609
|
+
# Production preview
|
|
610
|
+
npm run build:playground
|
|
611
|
+
npm run preview:playground
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Building
|
|
615
|
+
|
|
616
|
+
```bash
|
|
617
|
+
# Build library
|
|
618
|
+
npm run build
|
|
619
|
+
|
|
620
|
+
# Build playground for deployment
|
|
621
|
+
npm run build:playground
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Testing & Quality
|
|
625
|
+
|
|
626
|
+
```bash
|
|
627
|
+
# Run all checks
|
|
628
|
+
npm run check
|
|
629
|
+
|
|
630
|
+
# Type checking
|
|
631
|
+
npm run ts:check
|
|
632
|
+
|
|
633
|
+
# Linting
|
|
634
|
+
npm run lint
|
|
635
|
+
|
|
636
|
+
# Formatting
|
|
637
|
+
npm run format
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
## Contributing
|
|
641
|
+
|
|
642
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|