vue-context-storage 0.1.13 → 0.1.15

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
@@ -1,356 +1,354 @@
1
- # vue-context-storage
2
-
3
- Vue 3 context storage system with URL query synchronization support.
4
-
5
- [![npm downloads](https://img.shields.io/npm/dm/vue-context-storage.svg)](https://www.npmjs.com/package/vue-context-storage)
6
- [![TypeScript](https://badgen.net/badge/icon/TypeScript?icon=typescript&label)](https://www.typescriptlang.org/)
7
- [![Vue 3](https://img.shields.io/badge/vue-3.x-brightgreen.svg)](https://vuejs.org/)
8
- [![Bundle Size](https://img.shields.io/bundlephobia/minzip/vue-context-storage)](https://bundlephobia.com/package/vue-context-storage)
9
- [![GitHub issues](https://img.shields.io/github/issues/lviobio/vue-context-storage)](https://github.com/lviobio/vue-context-storage/issues)
10
- [![GitHub License](https://img.shields.io/github/license/lviobio/vue-context-storage)](https://github.com/lviobio/vue-context-storage)
11
- [![Live Demo](https://img.shields.io/badge/demo-live-brightgreen)](https://lviobio.github.io/vue-context-storage/)
12
-
13
- A powerful state management solution for Vue 3 applications that provides:
14
- - **Context-based storage** using Vue's provide/inject API
15
- - **Automatic URL query synchronization** for preserving state across page reloads
16
- - **Multiple storage contexts** with activation management
17
- - **Type-safe** TypeScript support
18
- - **Tree-shakeable** and lightweight
19
-
20
- ## Live Demo
21
-
22
- 🚀 **[Try the interactive playground](https://lviobio.github.io/vue-context-storage)**
23
-
24
- ## Installation
25
-
26
- ```bash
27
- npm install vue-context-storage
28
- ```
29
-
30
- ## Features
31
-
32
- - ✅ **Vue 3 Composition API** - Built with modern Vue patterns
33
- - ✅ **URL Query Sync** - Automatically sync state with URL parameters
34
- - ✅ **Multiple Contexts** - Support multiple independent storage contexts
35
- - ✅ **TypeScript** - Full type safety and IntelliSense support
36
- - ✅ **Flexible** - Works with vue-router 4+
37
- - ✅ **Transform Helpers** - Built-in utilities for type conversion
38
-
39
- ## Basic Usage
40
-
41
- ### Option 1: Using Vue Plugin (Recommended)
42
-
43
- Register the plugin in your main app file:
44
-
45
- ```typescript
46
- import { createApp } from 'vue'
47
- import { VueContextStoragePlugin } from 'vue-context-storage/plugin'
48
- import App from './App.vue'
49
-
50
- const app = createApp(App)
51
-
52
- // Register components globally
53
- app.use(VueContextStoragePlugin)
54
-
55
- app.mount('#app')
56
- ```
57
-
58
- Then use components without importing in your `App.vue`:
59
-
60
- ```vue
61
- <template>
62
- <ContextStorage>
63
- <router-view />
64
- </ContextStorage>
65
- </template>
66
- ```
67
-
68
- ### Option 2: Manual Component Import
69
-
70
- Import components individually when needed in your `App.vue`:
71
-
72
- ```vue
73
- <template>
74
- <ContextStorage>
75
- <router-view />
76
- </ContextStorage>
77
- </template>
78
-
79
- <script setup lang="ts">
80
- import { ContextStorage } from 'vue-context-storage/components'
81
- </script>
82
- ```
83
-
84
- ## Use Query Handler in Components
85
-
86
- Sync reactive state with URL query parameters:
87
-
88
- ```vue
89
- <script setup lang="ts">
90
- import { ref } from 'vue'
91
- import { useContextStorageQueryHandler } from 'vue-context-storage'
92
-
93
- interface Filters {
94
- search: string
95
- status: string
96
- page: number
97
- }
98
-
99
- const filters = reactive<Filters>({
100
- search: '',
101
- status: 'active',
102
- page: 1,
103
- })
104
-
105
- // Automatically syncs filters with URL query
106
- useContextStorageQueryHandler(filters, {
107
- prefix: 'filters', // URL will be: ?filters[search]=...&filters[status]=...
108
- })
109
- </script>
110
- ```
111
-
112
- ## Advanced Usage
113
-
114
- ### Using Transform Helpers
115
-
116
- Convert URL query string values to proper types:
117
-
118
- ```typescript
119
- import { ref } from 'vue'
120
- import { useContextStorageQueryHandler, asNumber, asString } from 'vue-context-storage'
121
-
122
- interface TableState {
123
- page: number
124
- search: string
125
- perPage: number
126
- }
127
-
128
- const state = ref<TableState>({
129
- page: 1,
130
- search: '',
131
- perPage: 25,
132
- })
133
-
134
- useContextStorageQueryHandler(state, {
135
- prefix: 'table',
136
- transform: (deserialized, initial) => ({
137
- page: asNumber(deserialized.page, { fallback: 1 }),
138
- search: asString(deserialized.search, { fallback: '' }),
139
- perPage: asNumber(deserialized.perPage, { fallback: 25 }),
140
- }),
141
- })
142
- ```
143
-
144
- ### Available Transform Helpers
145
-
146
- - `asNumber(value, options)` - Convert to number
147
- - `asString(value, options)` - Convert to string
148
- - `asBoolean(value, options)` - Convert to boolean
149
- - `asArray(value, options)` - Convert to array
150
- - `asNumberArray(value, options)` - Convert to number array
151
-
152
- ### Using Zod Schemas
153
-
154
- Alternatively, you can use [Zod](https://zod.dev/) schemas for automatic validation and type inference:
155
-
156
- ```typescript
157
- import { z } from 'zod'
158
- import { useContextStorageQueryHandler } from 'vue-context-storage'
159
-
160
- // Define schema with automatic coercion
161
- const FiltersSchema = z.object({
162
- search: z.string().default(''),
163
- page: z.coerce.number().int().positive().default(1),
164
- status: z.enum(['active', 'inactive']).default('active'),
165
- })
166
-
167
- const filters = ref(FiltersSchema.parse({}))
168
-
169
- // Use schema for automatic validation
170
- useContextStorageQueryHandler(filters, {
171
- prefix: 'filters',
172
- schema: FiltersSchema,
173
- })
174
- ```
175
-
176
- **Benefits:**
177
- - Automatic type coercion (strings → numbers, etc.)
178
- - Runtime validation with detailed errors
179
- - Automatic TypeScript type inference
180
- - Less boilerplate code
181
- - Single source of truth for structure and validation
182
-
183
- See [ZOD_INTEGRATION.md](./ZOD_INTEGRATION.md) for detailed examples and comparison with manual transforms.
184
-
185
- ### Preserve Empty State
186
-
187
- Keep empty state in URL to prevent resetting on reload:
188
-
189
- ```typescript
190
- useContextStorageQueryHandler(filters, {
191
- prefix: 'filters',
192
- preserveEmptyState: true,
193
- // Empty filters will show as: ?filters
194
- // Without this option, empty filters would clear the URL completely
195
- })
196
- ```
197
-
198
- ### Configure Query Handler
199
-
200
- Customize global behavior:
201
-
202
- ```typescript
203
- import { ContextStorageQueryHandler } from 'vue-context-storage'
204
-
205
- ContextStorageQueryHandler.configure({
206
- mode: 'push', // 'replace' (default) or 'push' for history
207
- preserveUnusedKeys: true, // Keep other query params
208
- preserveEmptyState: false,
209
- })
210
- ```
211
-
212
- ## API Reference
213
-
214
- ### Composables
215
-
216
- #### `useContextStorageQueryHandler<T>(data, options)`
217
-
218
- Registers reactive data for URL query synchronization.
219
-
220
- **Parameters:**
221
- - `data: MaybeRefOrGetter<T>` - Reactive reference to sync
222
- - `options?: RegisterQueryHandlerOptions<T>`
223
- - `prefix?: string` - Query parameter prefix
224
- - `transform?: (deserialized, initial) => T` - Transform function
225
- - `preserveEmptyState?: boolean` - Keep empty state in URL
226
- - `mergeOnlyExistingKeysWithoutTransform?: boolean` - Only merge existing keys (default: true)
227
-
228
- ### Classes
229
-
230
- #### `ContextStorageQueryHandler`
231
-
232
- Main handler for URL query synchronization.
233
-
234
- **Static Methods:**
235
- - `configure(options): ContextStorageHandlerConstructor` - Configure global options
236
- - `getInitialStateResolver(): () => LocationQuery` - Get initial state resolver
237
-
238
- **Methods:**
239
- - `register<T>(data, options): () => void` - Register data for sync
240
- - `setEnabled(state, initial): void` - Enable/disable handler
241
- - `setInitialState(state): void` - Set initial state
242
-
243
- ### Transform Helpers
244
-
245
- All transform helpers support nullable and missable options:
246
-
247
- ```typescript
248
- asNumber(value, {
249
- fallback: 0, // Default value
250
- nullable: false, // Allow null return
251
- missable: false, // Allow undefined return
252
- })
253
- ```
254
-
255
- ## TypeScript Support
256
-
257
- Full TypeScript support with type inference:
258
-
259
- ```typescript
260
- import type {
261
- ContextStorageHandler,
262
- ContextStorageHandlerConstructor,
263
- IContextStorageQueryHandler,
264
- QueryValue,
265
- SerializeOptions,
266
- } from 'vue-context-storage'
267
- ```
268
-
269
- When using Zod schemas, TypeScript will automatically infer types:
270
-
271
- ```typescript
272
- const FiltersSchema = z.object({
273
- search: z.string().default(''),
274
- page: z.coerce.number().default(1),
275
- })
276
-
277
- type Filters = z.infer<typeof FiltersSchema>
278
- // Result: { search: string; page: number }
279
- ```
280
-
281
- ## Examples
282
-
283
- ### Pagination with URL Sync
284
-
285
- ```typescript
286
- import { ref } from 'vue'
287
- import { useContextStorageQueryHandler, asNumber } from 'vue-context-storage'
288
-
289
- const pagination = ref({
290
- page: 1,
291
- perPage: 25,
292
- total: 0,
293
- })
294
-
295
- useContextStorageQueryHandler(pagination, {
296
- prefix: 'page',
297
- transform: (data, initial) => ({
298
- page: asNumber(data.page, { fallback: 1 }),
299
- perPage: asNumber(data.perPage, { fallback: 25 }),
300
- total: initial.total, // Don't sync total from URL
301
- })
302
- })
303
- ```
304
-
305
- ## Peer Dependencies
306
-
307
- - `vue`: ^3.5.0
308
- - `vue-router`: ^4.0.0
309
- - `zod`: ^4.0.0 (optional - only if using schema validation)
310
-
311
- ## License
312
-
313
- MIT
314
-
315
- ## Development
316
-
317
- ### Running Playground Locally
318
-
319
- ```bash
320
- # Development mode (hot reload)
321
- npm run play
322
-
323
- # Production preview
324
- npm run build:playground
325
- npm run preview:playground
326
- ```
327
-
328
- ### Building
329
-
330
- ```bash
331
- # Build library
332
- npm run build
333
-
334
- # Build playground for deployment
335
- npm run build:playground
336
- ```
337
-
338
- ### Testing & Quality
339
-
340
- ```bash
341
- # Run all checks
342
- npm run check
343
-
344
- # Type checking
345
- npm run ts:check
346
-
347
- # Linting
348
- npm run lint
349
-
350
- # Formatting
351
- npm run format
352
- ```
353
-
354
- ## Contributing
355
-
356
- Contributions are welcome! Please feel free to submit a Pull Request.
1
+ # vue-context-storage
2
+
3
+ Vue 3 context storage system with URL query synchronization support.
4
+
5
+ [![npm downloads](https://img.shields.io/npm/dm/vue-context-storage.svg)](https://www.npmjs.com/package/vue-context-storage)
6
+ [![TypeScript](https://badgen.net/badge/icon/TypeScript?icon=typescript&label)](https://www.typescriptlang.org/)
7
+ [![Vue 3](https://img.shields.io/badge/vue-3.x-brightgreen.svg)](https://vuejs.org/)
8
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/vue-context-storage)](https://bundlephobia.com/package/vue-context-storage)
9
+ [![GitHub issues](https://img.shields.io/github/issues/lviobio/vue-context-storage)](https://github.com/lviobio/vue-context-storage/issues)
10
+ [![GitHub License](https://img.shields.io/github/license/lviobio/vue-context-storage)](https://github.com/lviobio/vue-context-storage)
11
+ [![Live Demo](https://img.shields.io/badge/demo-live-brightgreen)](https://lviobio.github.io/vue-context-storage/)
12
+
13
+ A powerful state management solution for Vue 3 applications that provides:
14
+ - **Context-based storage** using Vue's provide/inject API
15
+ - **Automatic URL query synchronization** for preserving state across page reloads
16
+ - **Multiple storage contexts** with activation management
17
+ - **Type-safe** TypeScript support
18
+ - **Tree-shakeable** and lightweight
19
+
20
+ ## Live Demo
21
+
22
+ 🚀 **[Try the interactive playground](https://lviobio.github.io/vue-context-storage)**
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install vue-context-storage
28
+ ```
29
+
30
+ ## Features
31
+
32
+ - ✅ **Vue 3 Composition API** - Built with modern Vue patterns
33
+ - ✅ **URL Query Sync** - Automatically sync state with URL parameters
34
+ - ✅ **Multiple Contexts** - Support multiple independent storage contexts
35
+ - ✅ **TypeScript** - Full type safety and IntelliSense support
36
+ - ✅ **Flexible** - Works with vue-router 4+
37
+ - ✅ **Transform Helpers** - Built-in utilities for type conversion
38
+
39
+ ## Basic Usage
40
+
41
+ ### Option 1: Using Vue Plugin (Recommended)
42
+
43
+ Register the plugin in your main app file:
44
+
45
+ ```typescript
46
+ import { createApp } from 'vue'
47
+ import { VueContextStoragePlugin } from 'vue-context-storage/plugin'
48
+ import App from './App.vue'
49
+
50
+ const app = createApp(App)
51
+
52
+ // Register components globally
53
+ app.use(VueContextStoragePlugin)
54
+
55
+ app.mount('#app')
56
+ ```
57
+
58
+ Then use components without importing in your `App.vue`:
59
+
60
+ ```vue
61
+ <template>
62
+ <ContextStorage>
63
+ <router-view />
64
+ </ContextStorage>
65
+ </template>
66
+ ```
67
+
68
+ ### Option 2: Manual Component Import
69
+
70
+ Import components individually when needed in your `App.vue`:
71
+
72
+ ```vue
73
+ <template>
74
+ <ContextStorage>
75
+ <router-view />
76
+ </ContextStorage>
77
+ </template>
78
+
79
+ <script setup lang="ts">
80
+ import { ContextStorage } from 'vue-context-storage/components'
81
+ </script>
82
+ ```
83
+
84
+ ## Use Query Handler in Components
85
+
86
+ Sync reactive state with URL query parameters:
87
+
88
+ ```vue
89
+ <script setup lang="ts">
90
+ import { ref } from 'vue'
91
+ import { useContextStorageQueryHandler } from 'vue-context-storage'
92
+
93
+ interface Filters {
94
+ search: string
95
+ status: string
96
+ page: number
97
+ }
98
+
99
+ const filters = reactive<Filters>({
100
+ search: '',
101
+ status: 'active',
102
+ page: 1,
103
+ })
104
+
105
+ // Automatically syncs filters with URL query
106
+ useContextStorageQueryHandler(filters, {
107
+ prefix: 'filters', // URL will be: ?filters[search]=...&filters[status]=...
108
+ })
109
+ </script>
110
+ ```
111
+
112
+ ## Advanced Usage
113
+
114
+ ### Using Transform Helpers
115
+
116
+ Convert URL query string values to proper types:
117
+
118
+ ```typescript
119
+ import { ref } from 'vue'
120
+ import { useContextStorageQueryHandler, asNumber, asString } from 'vue-context-storage'
121
+
122
+ interface TableState {
123
+ page: number
124
+ search: string
125
+ perPage: number
126
+ }
127
+
128
+ const state = ref<TableState>({
129
+ page: 1,
130
+ search: '',
131
+ perPage: 25,
132
+ })
133
+
134
+ useContextStorageQueryHandler(state, {
135
+ prefix: 'table',
136
+ transform: (deserialized, initial) => ({
137
+ page: asNumber(deserialized.page, { fallback: 1 }),
138
+ search: asString(deserialized.search, { fallback: '' }),
139
+ perPage: asNumber(deserialized.perPage, { fallback: 25 }),
140
+ }),
141
+ })
142
+ ```
143
+
144
+ ### Available Transform Helpers
145
+
146
+ - `asNumber(value, options)` - Convert to number
147
+ - `asString(value, options)` - Convert to string
148
+ - `asBoolean(value, options)` - Convert to boolean
149
+ - `asArray(value, options)` - Convert to array
150
+ - `asNumberArray(value, options)` - Convert to number array
151
+
152
+ ### Using Zod Schemas
153
+
154
+ Alternatively, you can use [Zod](https://zod.dev/) schemas for automatic validation and type inference:
155
+
156
+ ```typescript
157
+ import { z } from 'zod'
158
+ import { useContextStorageQueryHandler } from 'vue-context-storage'
159
+
160
+ // Define schema with automatic coercion
161
+ const FiltersSchema = z.object({
162
+ search: z.string().default(''),
163
+ page: z.coerce.number().int().positive().default(1),
164
+ status: z.enum(['active', 'inactive']).default('active'),
165
+ })
166
+
167
+ const filters = ref(FiltersSchema.parse({}))
168
+
169
+ // Use schema for automatic validation
170
+ useContextStorageQueryHandler(filters, {
171
+ prefix: 'filters',
172
+ schema: FiltersSchema,
173
+ })
174
+ ```
175
+
176
+ **Benefits:**
177
+ - Automatic type coercion (strings → numbers, etc.)
178
+ - Runtime validation with detailed errors
179
+ - Automatic TypeScript type inference
180
+ - Less boilerplate code
181
+ - Single source of truth for structure and validation
182
+
183
+ ### Preserve Empty State
184
+
185
+ Keep empty state in URL to prevent resetting on reload:
186
+
187
+ ```typescript
188
+ useContextStorageQueryHandler(filters, {
189
+ prefix: 'filters',
190
+ preserveEmptyState: true,
191
+ // Empty filters will show as: ?filters
192
+ // Without this option, empty filters would clear the URL completely
193
+ })
194
+ ```
195
+
196
+ ### Configure Query Handler
197
+
198
+ Customize global behavior:
199
+
200
+ ```typescript
201
+ import { ContextStorageQueryHandler } from 'vue-context-storage'
202
+
203
+ ContextStorageQueryHandler.configure({
204
+ mode: 'push', // 'replace' (default) or 'push' for history
205
+ preserveUnusedKeys: true, // Keep other query params
206
+ preserveEmptyState: false,
207
+ })
208
+ ```
209
+
210
+ ## API Reference
211
+
212
+ ### Composables
213
+
214
+ #### `useContextStorageQueryHandler<T>(data, options)`
215
+
216
+ Registers reactive data for URL query synchronization.
217
+
218
+ **Parameters:**
219
+ - `data: MaybeRefOrGetter<T>` - Reactive reference to sync
220
+ - `options?: RegisterQueryHandlerOptions<T>`
221
+ - `prefix?: string` - Query parameter prefix
222
+ - `transform?: (deserialized, initial) => T` - Transform function
223
+ - `preserveEmptyState?: boolean` - Keep empty state in URL
224
+ - `mergeOnlyExistingKeysWithoutTransform?: boolean` - Only merge existing keys (default: true)
225
+
226
+ ### Classes
227
+
228
+ #### `ContextStorageQueryHandler`
229
+
230
+ Main handler for URL query synchronization.
231
+
232
+ **Static Methods:**
233
+ - `configure(options): ContextStorageHandlerConstructor` - Configure global options
234
+ - `getInitialStateResolver(): () => LocationQuery` - Get initial state resolver
235
+
236
+ **Methods:**
237
+ - `register<T>(data, options): () => void` - Register data for sync
238
+ - `setEnabled(state, initial): void` - Enable/disable handler
239
+ - `setInitialState(state): void` - Set initial state
240
+
241
+ ### Transform Helpers
242
+
243
+ All transform helpers support nullable and missable options:
244
+
245
+ ```typescript
246
+ asNumber(value, {
247
+ fallback: 0, // Default value
248
+ nullable: false, // Allow null return
249
+ missable: false, // Allow undefined return
250
+ })
251
+ ```
252
+
253
+ ## TypeScript Support
254
+
255
+ Full TypeScript support with type inference:
256
+
257
+ ```typescript
258
+ import type {
259
+ ContextStorageHandler,
260
+ ContextStorageHandlerConstructor,
261
+ IContextStorageQueryHandler,
262
+ QueryValue,
263
+ SerializeOptions,
264
+ } from 'vue-context-storage'
265
+ ```
266
+
267
+ When using Zod schemas, TypeScript will automatically infer types:
268
+
269
+ ```typescript
270
+ const FiltersSchema = z.object({
271
+ search: z.string().default(''),
272
+ page: z.coerce.number().default(1),
273
+ })
274
+
275
+ type Filters = z.infer<typeof FiltersSchema>
276
+ // Result: { search: string; page: number }
277
+ ```
278
+
279
+ ## Examples
280
+
281
+ ### Pagination with URL Sync
282
+
283
+ ```typescript
284
+ import { ref } from 'vue'
285
+ import { useContextStorageQueryHandler, asNumber } from 'vue-context-storage'
286
+
287
+ const pagination = ref({
288
+ page: 1,
289
+ perPage: 25,
290
+ total: 0,
291
+ })
292
+
293
+ useContextStorageQueryHandler(pagination, {
294
+ prefix: 'page',
295
+ transform: (data, initial) => ({
296
+ page: asNumber(data.page, { fallback: 1 }),
297
+ perPage: asNumber(data.perPage, { fallback: 25 }),
298
+ total: initial.total, // Don't sync total from URL
299
+ })
300
+ })
301
+ ```
302
+
303
+ ## Peer Dependencies
304
+
305
+ - `vue`: ^3.5.0
306
+ - `vue-router`: ^4.0.0
307
+ - `zod`: ^4.0.0 (optional - only if using schema validation)
308
+
309
+ ## License
310
+
311
+ MIT
312
+
313
+ ## Development
314
+
315
+ ### Running Playground Locally
316
+
317
+ ```bash
318
+ # Development mode (hot reload)
319
+ npm run play
320
+
321
+ # Production preview
322
+ npm run build:playground
323
+ npm run preview:playground
324
+ ```
325
+
326
+ ### Building
327
+
328
+ ```bash
329
+ # Build library
330
+ npm run build
331
+
332
+ # Build playground for deployment
333
+ npm run build:playground
334
+ ```
335
+
336
+ ### Testing & Quality
337
+
338
+ ```bash
339
+ # Run all checks
340
+ npm run check
341
+
342
+ # Type checking
343
+ npm run ts:check
344
+
345
+ # Linting
346
+ npm run lint
347
+
348
+ # Formatting
349
+ npm run format
350
+ ```
351
+
352
+ ## Contributing
353
+
354
+ Contributions are welcome! Please feel free to submit a Pull Request.
package/dist/index.d.ts CHANGED
@@ -62,9 +62,9 @@ declare const __VLS_export$1: vue9.DefineComponent<vue9.ExtractPropTypes<{
62
62
  interface Props {
63
63
  handlers?: ContextStorageHandlerConstructor[];
64
64
  }
65
- declare var __VLS_20: {};
65
+ declare var __VLS_14: {};
66
66
  type __VLS_Slots = {} & {
67
- default?: (props: typeof __VLS_20) => any;
67
+ default?: (props: typeof __VLS_14) => any;
68
68
  };
69
69
  declare const __VLS_base: vue9.DefineComponent<Props, {}, {}, {}, {}, vue9.ComponentOptionsMixin, vue9.ComponentOptionsMixin, {}, string, vue9.PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, vue9.ComponentProvideOptions, false, {}, any>;
70
70
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -78,6 +78,32 @@ type __VLS_WithSlots<T, S> = T & {
78
78
  //#region src/plugin.d.ts
79
79
  declare const VueContextStoragePlugin: Plugin;
80
80
  //#endregion
81
+ //#region src/collection.d.ts
82
+ type CollectionManagerItem = {
83
+ key: string;
84
+ handlers: ContextStorageHandler[];
85
+ };
86
+ interface ItemOptions {
87
+ key: string;
88
+ }
89
+ declare class CollectionManager {
90
+ private handlerConstructors;
91
+ active?: CollectionManagerItem;
92
+ private collection;
93
+ private onActiveChangeCallbacks;
94
+ private readonly isReadyPromise;
95
+ private resolveMarkAsReady;
96
+ constructor(handlerConstructors: ContextStorageHandlerConstructor[]);
97
+ isReady(): Promise<void>;
98
+ markAsReady(): void;
99
+ onActiveChange(callback: (item: CollectionManagerItem) => void): void;
100
+ first(): CollectionManagerItem | undefined;
101
+ findItemByKey(key: string): CollectionManagerItem | undefined;
102
+ add(options: ItemOptions): CollectionManagerItem;
103
+ remove(removeItem: CollectionManagerItem): void;
104
+ setActive(activeItem: CollectionManagerItem): void;
105
+ }
106
+ //#endregion
81
107
  //#region src/symbols.d.ts
82
108
  declare const collection: unique symbol;
83
109
  declare const collectionItem: unique symbol;
@@ -247,52 +273,6 @@ declare class ContextStorageQueryHandler implements IContextStorageQueryHandler
247
273
  register<T extends Record<string, unknown>>(data: MaybeRefOrGetter<T>, options: RegisterQueryHandlerOptions<T>): () => void;
248
274
  }
249
275
  //#endregion
250
- //#region src/handlers/query/helpers.d.ts
251
- interface SerializeOptions {
252
- /**
253
- * Custom prefix for serialized keys.
254
- * @example
255
- * - prefix: 'filters' => 'filters[key]'
256
- * - prefix: 'search' => 'search[key]'
257
- * - prefix: '' => 'key' (no prefix)
258
- */
259
- prefix?: string;
260
- }
261
- /**
262
- * Serializes filter parameters into a URL-friendly format.
263
- *
264
- * @param params - Raw parameters object to serialize
265
- * @param options - Serialization options
266
- * @returns Serialized parameters with prefixed keys
267
- *
268
- * @example
269
- * // With default prefix 'filters'
270
- * serializeFiltersParams({ status: 'active', tags: ['a', 'b'] })
271
- * // => { 'filters[status]': 'active', 'filters[tags]': 'a,b' }
272
- *
273
- * @example
274
- * // With custom prefix
275
- * serializeFiltersParams({ name: 'John', all: true }, { prefix: 'search' })
276
- * // => { 'search[name]': 'John', 'search[all]': '1' }
277
- *
278
- * @example
279
- * // Without prefix
280
- * serializeFiltersParams({ page: 1, all: false }, { prefix: '' })
281
- * // => { 'page': '1', 'all': '0' }
282
- */
283
- declare function serializeParams(params: Record<string, unknown>, options?: SerializeOptions): LocationQuery;
284
- /**
285
- * Deserializes query parameters from a URL-friendly format back to an object.
286
- *
287
- * @param params - Serialized parameters object
288
- * @returns Deserialized parameters object
289
- *
290
- * @example
291
- * deserializeParams({ 'filters[status]': 'active', search: 'test' })
292
- * // => { filters: {status: 'active'}, search: 'test' }
293
- */
294
- declare function deserializeParams(params: Record<string, any>): Record<string, any>;
295
- //#endregion
296
276
  //#region src/handlers/query/transform-helpers.d.ts
297
277
  declare function asNumber(value: QueryValue | number | undefined): number;
298
278
  declare function asNumber(value: QueryValue | number | undefined, options: {
@@ -417,35 +397,13 @@ declare const transform: {
417
397
  asBoolean: typeof asBoolean;
418
398
  };
419
399
  //#endregion
420
- //#region src/collection.d.ts
421
- type ContextStorageCollectionItem = {
422
- key: string;
423
- handlers: ContextStorageHandler[];
424
- };
425
- interface ItemOptions {
426
- key: string;
427
- }
428
- declare class ContextStorageCollection {
429
- private handlerConstructors;
430
- active?: ContextStorageCollectionItem;
431
- private collection;
432
- private onActiveChangeCallbacks;
433
- constructor(handlerConstructors: ContextStorageHandlerConstructor[]);
434
- onActiveChange(callback: (item: ContextStorageCollectionItem) => void): void;
435
- first(): ContextStorageCollectionItem | undefined;
436
- findItemByKey(key: string): ContextStorageCollectionItem | undefined;
437
- add(options: ItemOptions): ContextStorageCollectionItem;
438
- remove(removeItem: ContextStorageCollectionItem): void;
439
- setActive(activeItem: ContextStorageCollectionItem): void;
440
- }
441
- //#endregion
442
400
  //#region src/injectionSymbols.d.ts
443
- declare const contextStorageCollectionInjectKey: InjectionKey<ContextStorageCollection>;
444
- declare const contextStorageCollectionItemInjectKey: InjectionKey<ContextStorageCollectionItem>;
445
- declare const contextStorageHandlersInjectKey: InjectionKey<ContextStorageCollectionItem['handlers']>;
401
+ declare const contextStorageCollectionInjectKey: InjectionKey<CollectionManager>;
402
+ declare const contextStorageCollectionItemInjectKey: InjectionKey<CollectionManagerItem>;
403
+ declare const contextStorageHandlersInjectKey: InjectionKey<CollectionManagerItem['handlers']>;
446
404
  declare const contextStorageQueryHandlerInjectKey: InjectionKey<InstanceType<typeof ContextStorageQueryHandler>>;
447
405
  //#endregion
448
406
  //#region src/constants.d.ts
449
407
  declare const defaultHandlers: ContextStorageHandlerConstructor[];
450
408
  //#endregion
451
- export { _default as ContextStorage, _default$1 as ContextStorageActivator, _default$2 as ContextStorageCollection, type ContextStorageHandler, type ContextStorageHandlerConstructor, _default$3 as ContextStorageProvider, ContextStorageQueryHandler, type IContextStorageQueryHandler, type QueryValue, type RegisterBaseOptions, type SerializeOptions, VueContextStoragePlugin, asArray, asBoolean, asNumber, asNumberArray, asString, collection, collectionItem, contextStorageCollectionInjectKey, contextStorageCollectionItemInjectKey, contextStorageHandlersInjectKey, contextStorageQueryHandler, contextStorageQueryHandlerInjectKey, defaultHandlers, deserializeParams, handlers, serializeParams, transform, useContextStorageQueryHandler };
409
+ export { CollectionManager, type CollectionManagerItem, _default as ContextStorage, _default$1 as ContextStorageActivator, _default$2 as ContextStorageCollection, type ContextStorageHandler, type ContextStorageHandlerConstructor, _default$3 as ContextStorageProvider, ContextStorageQueryHandler, type IContextStorageQueryHandler, type QueryValue, type RegisterBaseOptions, VueContextStoragePlugin, asArray, asBoolean, asNumber, asNumberArray, asString, collection, collectionItem, contextStorageCollectionInjectKey, contextStorageCollectionItemInjectKey, contextStorageHandlersInjectKey, contextStorageQueryHandler, contextStorageQueryHandlerInjectKey, defaultHandlers, handlers, transform, useContextStorageQueryHandler };
package/dist/index.js CHANGED
@@ -1,14 +1,25 @@
1
- import { computed, createBlock, createVNode, defineComponent, getCurrentInstance, h, inject, onBeforeUnmount, onUnmounted, openBlock, provide, renderSlot, toValue, watch, withCtx } from "vue";
1
+ import { createBlock, createVNode, defineComponent, getCurrentInstance, h, inject, onBeforeUnmount, onUnmounted, openBlock, provide, renderSlot, toValue, watch, withCtx } from "vue";
2
2
  import { cloneDeep, isEqual, merge, pick } from "lodash";
3
3
  import { useRoute, useRouter } from "vue-router";
4
4
 
5
5
  //#region src/collection.ts
6
- var ContextStorageCollection = class {
6
+ var CollectionManager = class {
7
7
  active = void 0;
8
8
  collection = [];
9
9
  onActiveChangeCallbacks = [];
10
+ isReadyPromise;
11
+ resolveMarkAsReady = void 0;
10
12
  constructor(handlerConstructors) {
11
13
  this.handlerConstructors = handlerConstructors;
14
+ this.isReadyPromise = new Promise((resolve) => {
15
+ this.resolveMarkAsReady = resolve;
16
+ });
17
+ }
18
+ isReady() {
19
+ return this.isReadyPromise;
20
+ }
21
+ markAsReady() {
22
+ this.resolveMarkAsReady?.();
12
23
  }
13
24
  onActiveChange(callback) {
14
25
  this.onActiveChangeCallbacks.push(callback);
@@ -28,7 +39,7 @@ var ContextStorageCollection = class {
28
39
  return item;
29
40
  }
30
41
  remove(removeItem) {
31
- if (this.collection.indexOf(removeItem) === -1) throw new Error("[ContextStorage] Item not found in collection");
42
+ if (this.collection.indexOf(removeItem) === -1) throw new Error("[vue-context-storage] Item not found in collection");
32
43
  this.collection = this.collection.filter((item) => item !== removeItem);
33
44
  if (this.active === removeItem && this.collection.length > 0) this.setActive(this.collection[this.collection.length - 1]);
34
45
  }
@@ -130,7 +141,7 @@ const contextStorageQueryHandler = Symbol("context-storage-query-handler");
130
141
  //#region src/handlers/query/index.ts
131
142
  function useContextStorageQueryHandler(data, options) {
132
143
  const handler = inject(contextStorageQueryHandler);
133
- if (!handler) throw new Error("[ContextStorage] ContextStorageQueryHandler is not provided");
144
+ if (!handler) throw new Error("[vue-context-storage] ContextStorageQueryHandler is not provided");
134
145
  const uid = getCurrentInstance()?.uid || 0;
135
146
  const causer = (/* @__PURE__ */ new Error()).stack?.split("\n")[2]?.trimStart() || "unknown";
136
147
  const stop = handler.register(data, {
@@ -272,9 +283,7 @@ var ContextStorageQueryHandler = class ContextStorageQueryHandler {
272
283
  if (deserializedKeys.length === 1 && deserialized[this.options.emptyPlaceholder] === null) delete deserialized[this.options.emptyPlaceholder];
273
284
  }
274
285
  if (item.options?.schema) {
275
- console.log("[vue-context-storage] 248", deserialized);
276
286
  const result = item.options.schema.safeParse(deserialized);
277
- console.log("[vue-context-storage] 251", result);
278
287
  if (result.success) deserialized = result.data;
279
288
  else console.warn("[vue-context-storage] schema parse failed", result.error);
280
289
  if (item.options?.transform) console.warn("[vue-context-storage] transform is not supported with schema");
@@ -348,14 +357,18 @@ const contextStorageHandlersInjectKey = handlers;
348
357
  const contextStorageQueryHandlerInjectKey = contextStorageQueryHandler;
349
358
 
350
359
  //#endregion
351
- //#region src/components/ContextStorageActivator.vue?vue&type=script&lang.ts
352
- var ContextStorageActivator_vue_vue_type_script_lang_default = defineComponent({ setup(_, { slots }) {
360
+ //#region src/composables/useContextStorageActivator.ts
361
+ function useContextStorageCollection$2() {
353
362
  const collection$1 = inject(contextStorageCollectionInjectKey);
354
363
  const item = inject(contextStorageCollectionItemInjectKey);
355
- const onActivate = () => {
356
- collection$1.setActive(item);
357
- };
358
- return () => h("div", { onMousedown: onActivate }, slots.default?.());
364
+ return { activate: () => collection$1.setActive(item) };
365
+ }
366
+
367
+ //#endregion
368
+ //#region src/components/ContextStorageActivator.vue?vue&type=script&lang.ts
369
+ var ContextStorageActivator_vue_vue_type_script_lang_default = defineComponent({ setup(_, { slots }) {
370
+ const { activate } = useContextStorageCollection$2();
371
+ return () => h("div", { onMousedown: activate }, slots.default?.());
359
372
  } });
360
373
 
361
374
  //#endregion
@@ -366,6 +379,14 @@ var ContextStorageActivator_default = ContextStorageActivator_vue_vue_type_scrip
366
379
  //#region src/constants.ts
367
380
  const defaultHandlers = [ContextStorageQueryHandler];
368
381
 
382
+ //#endregion
383
+ //#region src/composables/useContextStorageCollection.ts
384
+ function useContextStorageCollection$1(handlers$1) {
385
+ const collection$1 = new CollectionManager(handlers$1);
386
+ provide(contextStorageCollectionInjectKey, collection$1);
387
+ return collection$1;
388
+ }
389
+
369
390
  //#endregion
370
391
  //#region src/components/ContextStorageCollection.vue?vue&type=script&lang.ts
371
392
  var ContextStorageCollection_vue_vue_type_script_lang_default = defineComponent({
@@ -374,46 +395,11 @@ var ContextStorageCollection_vue_vue_type_script_lang_default = defineComponent(
374
395
  default: () => defaultHandlers
375
396
  } },
376
397
  setup({ handlers: handlers$1 }, { slots }) {
377
- const lastActive = computed({
378
- get: () => localStorage.getItem("context-storage-last-active") || "main",
379
- set: (value) => localStorage.setItem("context-storage-last-active", value)
380
- });
381
398
  const router = useRouter();
382
- const initialNavigatorState = /* @__PURE__ */ new Map();
383
- const initialNavigatorStateResolvers = /* @__PURE__ */ new Map();
384
- handlers$1.forEach((handler) => {
385
- if (!handler.getInitialStateResolver) return;
386
- initialNavigatorStateResolvers.set(handler, handler.getInitialStateResolver());
387
- });
399
+ const collection$1 = useContextStorageCollection$1(handlers$1);
388
400
  router.isReady().then(() => {
389
- initialNavigatorStateResolvers.forEach((resolver, handler) => {
390
- initialNavigatorState.set(handler, resolver());
391
- });
392
- activateLastActiveItem();
393
- });
394
- const collection$1 = new ContextStorageCollection(handlers$1);
395
- collection$1.onActiveChange((item) => {
396
- lastActive.value = item.key;
401
+ collection$1.markAsReady();
397
402
  });
398
- provide(contextStorageCollectionInjectKey, collection$1);
399
- const activateInitialItem = (item) => {
400
- item.handlers.forEach((handler) => {
401
- const state = initialNavigatorState.get(handler.constructor);
402
- if (!state) return;
403
- handler.setInitialState?.(state);
404
- });
405
- collection$1.setActive(item);
406
- };
407
- const activateLastActiveItem = () => {
408
- const lastActiveItem = collection$1.findItemByKey(lastActive.value);
409
- if (lastActiveItem) {
410
- activateInitialItem(lastActiveItem);
411
- return;
412
- }
413
- const firstItem = collection$1.first();
414
- if (!firstItem) throw new Error("[ContextStorage] Cannot find first item in collection");
415
- activateInitialItem(firstItem);
416
- };
417
403
  return () => {
418
404
  return slots.default?.();
419
405
  };
@@ -424,6 +410,22 @@ var ContextStorageCollection_vue_vue_type_script_lang_default = defineComponent(
424
410
  //#region src/components/ContextStorageCollection.vue
425
411
  var ContextStorageCollection_default = ContextStorageCollection_vue_vue_type_script_lang_default;
426
412
 
413
+ //#endregion
414
+ //#region src/composables/useContextStorageProvider.ts
415
+ function useContextStorageCollection(key) {
416
+ const collection$1 = inject(contextStorageCollectionInjectKey);
417
+ if (!collection$1) throw new Error("[vue-context-storage] Context storage collection not found");
418
+ const item = collection$1.add({ key });
419
+ provide(contextStorageCollectionItemInjectKey, item);
420
+ provide(contextStorageHandlersInjectKey, item.handlers);
421
+ item.handlers.forEach((handler) => {
422
+ provide(handler.getInjectionKey(), handler);
423
+ });
424
+ onUnmounted(() => {
425
+ collection$1.remove(item);
426
+ });
427
+ }
428
+
427
429
  //#endregion
428
430
  //#region src/components/ContextStorageProvider.vue?vue&type=script&lang.ts
429
431
  var ContextStorageProvider_vue_vue_type_script_lang_default = defineComponent({
@@ -432,17 +434,7 @@ var ContextStorageProvider_vue_vue_type_script_lang_default = defineComponent({
432
434
  required: true
433
435
  } },
434
436
  setup(props, { slots }) {
435
- const collection$1 = inject(contextStorageCollectionInjectKey);
436
- if (!collection$1) throw new Error("[ContextStorage] Context storage collection not found");
437
- const item = collection$1.add({ key: props.itemKey });
438
- provide(contextStorageCollectionItemInjectKey, item);
439
- provide(contextStorageHandlersInjectKey, item.handlers);
440
- item.handlers.forEach((handler) => {
441
- provide(handler.getInjectionKey(), handler);
442
- });
443
- onUnmounted(() => {
444
- collection$1.remove(item);
445
- });
437
+ useContextStorageCollection(props.itemKey);
446
438
  return () => slots.default?.();
447
439
  }
448
440
  });
@@ -453,6 +445,7 @@ var ContextStorageProvider_default = ContextStorageProvider_vue_vue_type_script_
453
445
 
454
446
  //#endregion
455
447
  //#region src/components/ContextStorage.vue?vue&type=script&setup=true&lang.ts
448
+ const itemKey = "main";
456
449
  var ContextStorage_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
457
450
  __name: "ContextStorage",
458
451
  props: { handlers: {
@@ -461,17 +454,41 @@ var ContextStorage_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */
461
454
  default: () => defaultHandlers
462
455
  } },
463
456
  setup(__props) {
457
+ const router = useRouter();
458
+ const collection$1 = useContextStorageCollection$1(__props.handlers);
459
+ const initialNavigatorState = /* @__PURE__ */ new Map();
460
+ const initialNavigatorStateResolvers = /* @__PURE__ */ new Map();
461
+ __props.handlers.forEach((handler) => {
462
+ if (!handler.getInitialStateResolver) return;
463
+ initialNavigatorStateResolvers.set(handler, handler.getInitialStateResolver());
464
+ });
465
+ const activateMainItem = () => {
466
+ const item = collection$1.findItemByKey(itemKey);
467
+ if (!item) throw new Error(`[vue-context-storage] Cannot find "${itemKey}" item in collection`);
468
+ item.handlers.forEach((handler) => {
469
+ const state = initialNavigatorState.get(handler.constructor);
470
+ if (!state) return;
471
+ handler.setInitialState?.(state);
472
+ });
473
+ collection$1.setActive(item);
474
+ };
475
+ router.isReady().then(() => {
476
+ collection$1.markAsReady();
477
+ });
478
+ collection$1.isReady().then(() => {
479
+ initialNavigatorStateResolvers.forEach((resolver, handler) => {
480
+ initialNavigatorState.set(handler, resolver());
481
+ });
482
+ activateMainItem();
483
+ });
464
484
  return (_ctx, _cache) => {
465
- return openBlock(), createBlock(ContextStorageCollection_default, { handlers: __props.handlers }, {
466
- default: withCtx(() => [createVNode(ContextStorageProvider_default, { "item-key": "main" }, {
467
- default: withCtx(() => [createVNode(ContextStorageActivator_default, null, {
468
- default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
469
- _: 3
470
- })]),
485
+ return openBlock(), createBlock(ContextStorageProvider_default, { "item-key": itemKey }, {
486
+ default: withCtx(() => [createVNode(ContextStorageActivator_default, null, {
487
+ default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
471
488
  _: 3
472
489
  })]),
473
490
  _: 3
474
- }, 8, ["handlers"]);
491
+ });
475
492
  };
476
493
  }
477
494
  });
@@ -552,4 +569,4 @@ const transform = {
552
569
  };
553
570
 
554
571
  //#endregion
555
- export { ContextStorage_default as ContextStorage, ContextStorageActivator_default as ContextStorageActivator, ContextStorageCollection_default as ContextStorageCollection, ContextStorageProvider_default as ContextStorageProvider, ContextStorageQueryHandler, VueContextStoragePlugin, asArray, asBoolean, asNumber, asNumberArray, asString, collection, collectionItem, contextStorageCollectionInjectKey, contextStorageCollectionItemInjectKey, contextStorageHandlersInjectKey, contextStorageQueryHandler, contextStorageQueryHandlerInjectKey, defaultHandlers, deserializeParams, handlers, serializeParams, transform, useContextStorageQueryHandler };
572
+ export { CollectionManager, ContextStorage_default as ContextStorage, ContextStorageActivator_default as ContextStorageActivator, ContextStorageCollection_default as ContextStorageCollection, ContextStorageProvider_default as ContextStorageProvider, ContextStorageQueryHandler, VueContextStoragePlugin, asArray, asBoolean, asNumber, asNumberArray, asString, collection, collectionItem, contextStorageCollectionInjectKey, contextStorageCollectionItemInjectKey, contextStorageHandlersInjectKey, contextStorageQueryHandler, contextStorageQueryHandlerInjectKey, defaultHandlers, handlers, transform, useContextStorageQueryHandler };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vue-context-storage",
3
3
  "type": "module",
4
- "version": "0.1.13",
4
+ "version": "0.1.15",
5
5
  "description": "Vue 3 context storage system with URL query synchronization support",
6
6
  "author": "",
7
7
  "license": "MIT",