vue-context-storage 0.1.33 → 0.1.35
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 +186 -83
- package/dist/index.d.ts +38 -44
- package/dist/index.js +67 -74
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ In Vue applications, reactive state often needs to live beyond a single componen
|
|
|
52
52
|
- **URL query parameters** stay in sync with your data automatically - users can bookmark or share a page and get the exact same state back.
|
|
53
53
|
- **localStorage and sessionStorage** are kept up to date without manual `getItem`/`setItem` calls, including cross-tab synchronization.
|
|
54
54
|
- **Type safety** is preserved end-to-end: URL strings are coerced back to numbers, booleans, and arrays via transform helpers or Zod schemas.
|
|
55
|
-
- **Multiple independent contexts** (e.g. two data tables on the same page) are supported out of the box through the
|
|
55
|
+
- **Multiple independent contexts** (e.g. two data tables on the same page) are supported out of the box through the key pattern, so query parameters never collide.
|
|
56
56
|
|
|
57
57
|
The goal is a single, declarative API - `useContextStorage('query', data, options)` - that replaces scattered watchers, router guards, and storage listeners with one composable call per piece of state.
|
|
58
58
|
|
|
@@ -118,7 +118,7 @@ const filters = reactive({
|
|
|
118
118
|
|
|
119
119
|
// Sync with URL query
|
|
120
120
|
useContextStorage('query', filters, {
|
|
121
|
-
|
|
121
|
+
key: 'filters',
|
|
122
122
|
})
|
|
123
123
|
|
|
124
124
|
// Sync with localStorage
|
|
@@ -141,7 +141,7 @@ You can also pass an injection key directly instead of a string:
|
|
|
141
141
|
import { contextStorageQueryHandlerInjectKey } from 'vue-context-storage'
|
|
142
142
|
|
|
143
143
|
useContextStorage(contextStorageQueryHandlerInjectKey, filters, {
|
|
144
|
-
|
|
144
|
+
key: 'filters',
|
|
145
145
|
})
|
|
146
146
|
```
|
|
147
147
|
|
|
@@ -181,11 +181,11 @@ The `<ContextStoragePrefix>` component adds a prefix to all `useContextStorage`
|
|
|
181
181
|
</template>
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
Inside `MyTable`, any `useContextStorage('query', data)` call will automatically get `
|
|
184
|
+
Inside `MyTable`, any `useContextStorage('query', data)` call will automatically get `key: 'tables'`. If the composable also specifies its own key, they are combined:
|
|
185
185
|
|
|
186
186
|
```typescript
|
|
187
|
-
// Inside MyTable — effective
|
|
188
|
-
useContextStorage('query', filters, {
|
|
187
|
+
// Inside MyTable — effective key becomes 'table[filters]'
|
|
188
|
+
useContextStorage('query', filters, { key: 'filters' })
|
|
189
189
|
// URL: ?table[filters][search]=...
|
|
190
190
|
```
|
|
191
191
|
|
|
@@ -248,21 +248,11 @@ const filters = reactive<Filters>({
|
|
|
248
248
|
|
|
249
249
|
// Automatically syncs filters with URL query
|
|
250
250
|
useContextStorage('query', filters, {
|
|
251
|
-
|
|
251
|
+
key: 'filters', // URL will be: ?filters[search]=...&filters[status]=...
|
|
252
252
|
})
|
|
253
253
|
</script>
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
-
Also available as a dedicated composable:
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
import { useContextStorageQueryHandler } from 'vue-context-storage'
|
|
260
|
-
|
|
261
|
-
useContextStorageQueryHandler(filters, {
|
|
262
|
-
prefix: 'filters',
|
|
263
|
-
})
|
|
264
|
-
```
|
|
265
|
-
|
|
266
256
|
## Advanced Usage
|
|
267
257
|
|
|
268
258
|
### Using Transform Helpers
|
|
@@ -286,7 +276,7 @@ const state = ref<TableState>({
|
|
|
286
276
|
})
|
|
287
277
|
|
|
288
278
|
useContextStorage('query', state, {
|
|
289
|
-
|
|
279
|
+
key: 'table',
|
|
290
280
|
transform: (deserialized, initial) => ({
|
|
291
281
|
page: transform.asNumber(deserialized.page, { fallback: 1 }),
|
|
292
282
|
search: transform.asString(deserialized.search, { fallback: '' }),
|
|
@@ -302,6 +292,7 @@ useContextStorage('query', state, {
|
|
|
302
292
|
- `asBoolean(value, options)` - Convert to boolean
|
|
303
293
|
- `asArray(value, options)` - Convert to array
|
|
304
294
|
- `asNumberArray(value, options)` - Convert to number array
|
|
295
|
+
- `asObjectArray(value, options)` - Convert indexed object to array of objects (see [Arrays of Objects](#arrays-of-objects))
|
|
305
296
|
|
|
306
297
|
### Using Zod Schemas
|
|
307
298
|
|
|
@@ -322,7 +313,7 @@ const filters = ref(FiltersSchema.parse({}))
|
|
|
322
313
|
|
|
323
314
|
// Use schema for automatic validation
|
|
324
315
|
useContextStorage('query', filters, {
|
|
325
|
-
|
|
316
|
+
key: 'filters',
|
|
326
317
|
schema: FiltersSchema,
|
|
327
318
|
})
|
|
328
319
|
```
|
|
@@ -335,25 +326,104 @@ useContextStorage('query', filters, {
|
|
|
335
326
|
- Less boilerplate code
|
|
336
327
|
- Single source of truth for structure and validation
|
|
337
328
|
|
|
329
|
+
### Arrays of Objects
|
|
330
|
+
|
|
331
|
+
The query handler supports arrays of objects. They are serialized as indexed query parameters:
|
|
332
|
+
|
|
333
|
+
```
|
|
334
|
+
?items[0][product]=Apple&items[0][quantity]=5&items[1][product]=Banana&items[1][quantity]=10
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
After deserialization, URL parameters produce indexed objects (`{ '0': {...}, '1': {...} }`) rather than arrays. Use `transform.asObjectArray` or the Zod helper `zObjectArray` to convert them back.
|
|
338
|
+
|
|
339
|
+
**With transform helpers:**
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { reactive } from 'vue'
|
|
343
|
+
import { useContextStorage, transform } from 'vue-context-storage'
|
|
344
|
+
|
|
345
|
+
const data = reactive({
|
|
346
|
+
title: '',
|
|
347
|
+
items: [] as { product: string; quantity: number }[],
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
useContextStorage('query', data, {
|
|
351
|
+
transform: (value) => ({
|
|
352
|
+
title: transform.asString(value.title),
|
|
353
|
+
items: transform.asObjectArray(value.items, (entry) => ({
|
|
354
|
+
product: transform.asString(entry.product),
|
|
355
|
+
quantity: transform.asNumber(entry.quantity),
|
|
356
|
+
})),
|
|
357
|
+
}),
|
|
358
|
+
})
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
`asObjectArray` also supports a callback shorthand — pass a function as the second argument instead of an options object.
|
|
362
|
+
|
|
363
|
+
**With Zod schema:**
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
import { z } from 'zod'
|
|
367
|
+
import { zObjectArray } from 'vue-context-storage/zod'
|
|
368
|
+
|
|
369
|
+
const ItemSchema = z.object({
|
|
370
|
+
product: z.string().default(''),
|
|
371
|
+
quantity: z.coerce.number().default(0),
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
const DataSchema = z.object({
|
|
375
|
+
title: z.string().default(''),
|
|
376
|
+
items: zObjectArray(ItemSchema),
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
useContextStorage('query', data, { schema: DataSchema })
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
See [Zod Helpers](#zod-helpers-vue-context-storagezod) for more details.
|
|
383
|
+
|
|
338
384
|
### Preserve Empty State
|
|
339
385
|
|
|
340
386
|
Keep empty state in URL to prevent resetting on reload:
|
|
341
387
|
|
|
342
388
|
```typescript
|
|
343
389
|
useContextStorage('query', filters, {
|
|
344
|
-
|
|
390
|
+
key: 'filters',
|
|
345
391
|
preserveEmptyState: true,
|
|
346
392
|
// Empty filters will show as: ?filters
|
|
347
393
|
// Without this option, empty filters would clear the URL completely
|
|
348
394
|
})
|
|
349
395
|
```
|
|
350
396
|
|
|
397
|
+
### Additional Default Data
|
|
398
|
+
|
|
399
|
+
When `onlyChanges` is enabled (the default), a key is omitted from the URL if its current value matches the initial snapshot. `additionalDefaultData` lets you specify extra values that should also be treated as defaults and excluded from the URL.
|
|
400
|
+
|
|
401
|
+
This is useful when the initial reactive data starts with `undefined` (e.g. before an API response), but you also want a specific value (like `1`) to be considered a default:
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
const data = ref({ page: undefined as number | undefined })
|
|
405
|
+
|
|
406
|
+
useContextStorage('query', data, {
|
|
407
|
+
key: 'filters',
|
|
408
|
+
onlyChanges: true,
|
|
409
|
+
additionalDefaultData: { page: 1 },
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
// page=undefined → not in query (matches initial)
|
|
413
|
+
// page=1 → not in query (matches additionalDefaultData)
|
|
414
|
+
// page=2 → appears in query as ?filters[page]=2
|
|
415
|
+
```
|
|
416
|
+
|
|
351
417
|
### Configure Query Handler
|
|
352
418
|
|
|
353
419
|
Customize behavior by passing options to the factory:
|
|
354
420
|
|
|
355
421
|
```typescript
|
|
356
|
-
import {
|
|
422
|
+
import {
|
|
423
|
+
createQueryHandler,
|
|
424
|
+
createLocalStorageHandler,
|
|
425
|
+
createSessionStorageHandler,
|
|
426
|
+
} from 'vue-context-storage'
|
|
357
427
|
|
|
358
428
|
const customHandlers = [
|
|
359
429
|
createQueryHandler({
|
|
@@ -391,16 +461,6 @@ useContextStorage('localStorage', settings, {
|
|
|
391
461
|
</script>
|
|
392
462
|
```
|
|
393
463
|
|
|
394
|
-
Also available as a dedicated composable:
|
|
395
|
-
|
|
396
|
-
```typescript
|
|
397
|
-
import { useContextStorageLocalStorage } from 'vue-context-storage'
|
|
398
|
-
|
|
399
|
-
useContextStorageLocalStorage(settings, {
|
|
400
|
-
key: 'app-settings',
|
|
401
|
-
})
|
|
402
|
-
```
|
|
403
|
-
|
|
404
464
|
### Configure localStorage Handler
|
|
405
465
|
|
|
406
466
|
```typescript
|
|
@@ -433,36 +493,26 @@ useContextStorage('sessionStorage', formDraft, {
|
|
|
433
493
|
</script>
|
|
434
494
|
```
|
|
435
495
|
|
|
436
|
-
|
|
496
|
+
### Multiple Registrations Under One Root Key
|
|
437
497
|
|
|
438
|
-
|
|
439
|
-
import { useContextStorageSessionStorage } from 'vue-context-storage'
|
|
440
|
-
|
|
441
|
-
useContextStorageSessionStorage(formDraft, {
|
|
442
|
-
key: 'contact-form-draft',
|
|
443
|
-
})
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
### Using Prefix
|
|
447
|
-
|
|
448
|
-
The prefix is appended to the storage key in bracket notation, so each prefixed registration gets its own storage entry:
|
|
498
|
+
Use bracket notation in `key` to store multiple data objects under a common root:
|
|
449
499
|
|
|
450
500
|
```typescript
|
|
451
501
|
const filters = reactive({ search: '', status: 'active' })
|
|
452
502
|
|
|
453
503
|
useContextStorage('sessionStorage', filters, {
|
|
454
|
-
key: 'app-state',
|
|
455
|
-
prefix: 'filters', // Storage key: 'app-state[filters]', value: { search: '', status: 'active' }
|
|
504
|
+
key: 'app-state[filters]', // Storage key: 'app-state[filters]'
|
|
456
505
|
})
|
|
457
506
|
|
|
458
507
|
const pagination = reactive({ page: 1, perPage: 25 })
|
|
459
508
|
|
|
460
509
|
useContextStorage('sessionStorage', pagination, {
|
|
461
|
-
key: 'app-state',
|
|
462
|
-
prefix: 'pagination', // Storage key: 'app-state[pagination]', value: { page: 1, perPage: 25 }
|
|
510
|
+
key: 'app-state[pagination]', // Storage key: 'app-state[pagination]'
|
|
463
511
|
})
|
|
464
512
|
```
|
|
465
513
|
|
|
514
|
+
Or use `<ContextStoragePrefix>` for automatic scoping (see [Prefix Scoping](#prefix-scoping-with-contextstorageprefix)).
|
|
515
|
+
|
|
466
516
|
### Using Transform with Storage Handlers
|
|
467
517
|
|
|
468
518
|
Convert stored values to proper types when reading from storage:
|
|
@@ -488,7 +538,7 @@ useContextStorage('localStorage', settings, {
|
|
|
488
538
|
|
|
489
539
|
```typescript
|
|
490
540
|
import { z } from 'zod'
|
|
491
|
-
import {
|
|
541
|
+
import { useContextStorage } from 'vue-context-storage'
|
|
492
542
|
|
|
493
543
|
const SettingsSchema = z.object({
|
|
494
544
|
theme: z.enum(['light', 'dark']).default('light'),
|
|
@@ -542,19 +592,6 @@ Unified composable that delegates to the correct handler based on `type`.
|
|
|
542
592
|
- `defineContextStorageHandler(name, injectionKey)` - Register a custom handler
|
|
543
593
|
- `resolveHandlerInjectionKey(type)` - Look up an injection key by name
|
|
544
594
|
|
|
545
|
-
#### `useContextStorageQueryHandler<T>(data, options)`
|
|
546
|
-
|
|
547
|
-
Registers reactive data for URL query synchronization.
|
|
548
|
-
|
|
549
|
-
**Parameters:**
|
|
550
|
-
|
|
551
|
-
- `data: MaybeRefOrGetter<T>` - Reactive reference to sync
|
|
552
|
-
- `options?: RegisterQueryHandlerOptions<T>`
|
|
553
|
-
- `prefix?: string` - Query parameter prefix
|
|
554
|
-
- `transform?: (deserialized, initial) => T` - Transform function
|
|
555
|
-
- `preserveEmptyState?: boolean` - Keep empty state in URL
|
|
556
|
-
- `mergeOnlyExistingKeysWithoutTransform?: boolean` - Only merge existing keys (default: true)
|
|
557
|
-
|
|
558
595
|
### Handler Factories
|
|
559
596
|
|
|
560
597
|
#### `createQueryHandler(options?)`
|
|
@@ -585,25 +622,6 @@ Creates a sessionStorage handler factory.
|
|
|
585
622
|
|
|
586
623
|
- `listenToStorageEvents?: boolean` - Listen to storage events (default: `false`)
|
|
587
624
|
|
|
588
|
-
#### `useContextStorageLocalStorage<T>(data, options)`
|
|
589
|
-
|
|
590
|
-
Registers reactive data for localStorage synchronization.
|
|
591
|
-
|
|
592
|
-
**Parameters:**
|
|
593
|
-
|
|
594
|
-
- `data: MaybeRefOrGetter<T>` - Reactive reference to sync
|
|
595
|
-
- `options: RegisterWebStorageHandlerBaseOptions<T>`
|
|
596
|
-
- `key: string` - Storage key (required)
|
|
597
|
-
- `prefix?: string` - Appended to the storage key in bracket notation (e.g. key `'app'` + prefix `'filters'` = storage key `'app[filters]'`)
|
|
598
|
-
- `transform?: (deserialized, initial) => T` - Transform function
|
|
599
|
-
- `schema?: ZodSchema` - Zod schema for validation
|
|
600
|
-
- `serializer?: (data: T) => string` - Custom serializer (default: `JSON.stringify`)
|
|
601
|
-
- `deserializer?: (str: string) => unknown` - Custom deserializer (default: `JSON.parse`)
|
|
602
|
-
|
|
603
|
-
#### `useContextStorageSessionStorage<T>(data, options)`
|
|
604
|
-
|
|
605
|
-
Registers reactive data for sessionStorage synchronization. Same options as `useContextStorageLocalStorage`.
|
|
606
|
-
|
|
607
625
|
### Components
|
|
608
626
|
|
|
609
627
|
#### `<ContextStoragePrefix>`
|
|
@@ -628,6 +646,91 @@ transform.asNumber(value, {
|
|
|
628
646
|
})
|
|
629
647
|
```
|
|
630
648
|
|
|
649
|
+
## Zod Helpers (`vue-context-storage/zod`)
|
|
650
|
+
|
|
651
|
+
The library provides a separate entry point with Zod-specific helpers. Since `zod` is an optional peer dependency, these helpers are isolated in `vue-context-storage/zod` to avoid importing Zod in the main bundle.
|
|
652
|
+
|
|
653
|
+
```bash
|
|
654
|
+
npm install zod
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### `zObjectArray(itemSchema)`
|
|
658
|
+
|
|
659
|
+
Creates a Zod schema for arrays of objects serialized as indexed query parameters. Wraps `z.record()` + `.transform()` to convert indexed objects back to sorted arrays.
|
|
660
|
+
|
|
661
|
+
```typescript
|
|
662
|
+
import { z } from 'zod'
|
|
663
|
+
import { zObjectArray } from 'vue-context-storage/zod'
|
|
664
|
+
|
|
665
|
+
const ItemSchema = z.object({
|
|
666
|
+
product: z.string().default(''),
|
|
667
|
+
quantity: z.coerce.number().default(0),
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
const DataSchema = z.object({
|
|
671
|
+
title: z.string().default(''),
|
|
672
|
+
items: zObjectArray(ItemSchema),
|
|
673
|
+
})
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### `zUrlBoolean(defaultValue?)`
|
|
677
|
+
|
|
678
|
+
Creates a Zod schema for booleans serialized as URL query parameters. Standard `z.coerce.boolean()` cannot be used because `Boolean('0')` is `true` in JavaScript. This helper correctly handles `'1'`, `'true'`, `'0'`, `'false'`, and native booleans.
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
import { z } from 'zod'
|
|
682
|
+
import { zUrlBoolean } from 'vue-context-storage/zod'
|
|
683
|
+
|
|
684
|
+
const Schema = z.object({
|
|
685
|
+
active: zUrlBoolean(), // defaults to false
|
|
686
|
+
enabled: zUrlBoolean(true), // defaults to true
|
|
687
|
+
})
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### `createSchemaObject(schema, options?)`
|
|
691
|
+
|
|
692
|
+
Creates a plain object with empty/default values based on a Zod schema. Useful for initializing reactive data from a schema definition.
|
|
693
|
+
|
|
694
|
+
```typescript
|
|
695
|
+
import { z } from 'zod'
|
|
696
|
+
import { createSchemaObject } from 'vue-context-storage/zod'
|
|
697
|
+
|
|
698
|
+
const FiltersSchema = z.object({
|
|
699
|
+
search: z.string().default(''),
|
|
700
|
+
page: z.coerce.number().default(1),
|
|
701
|
+
active: z.boolean().default(false),
|
|
702
|
+
score: z.number().nullable(),
|
|
703
|
+
})
|
|
704
|
+
|
|
705
|
+
const filters = reactive(createSchemaObject(FiltersSchema))
|
|
706
|
+
// Result: { search: '', page: 1, active: false, score: null }
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
**Options:**
|
|
710
|
+
|
|
711
|
+
- `useDefaults` (default: `true`) — When `true`, uses `.default()` values from the schema. When `false`, uses type-based empty values (`''` for strings, `0` for numbers, `false` for booleans, etc.).
|
|
712
|
+
- `withSchema` (default: `false`) — When `true`, attaches the schema to the result object via `SCHEMA_SYMBOL` (wrapped with `markRaw`). Nested objects also receive their respective schemas.
|
|
713
|
+
|
|
714
|
+
```typescript
|
|
715
|
+
import { createSchemaObject, SCHEMA_SYMBOL } from 'vue-context-storage/zod'
|
|
716
|
+
|
|
717
|
+
const data = createSchemaObject(FiltersSchema, { withSchema: true })
|
|
718
|
+
data[SCHEMA_SYMBOL] // → FiltersSchema
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
**Type-based defaults** (when `useDefaults: false` or no `.default()` is set):
|
|
722
|
+
|
|
723
|
+
| Zod type | Default value |
|
|
724
|
+
| ------------- | -------------------------------------------- |
|
|
725
|
+
| `z.string()` | `''` |
|
|
726
|
+
| `z.number()` | `0` (respects `.min()` / `.positive()`) |
|
|
727
|
+
| `z.boolean()` | `false` |
|
|
728
|
+
| `z.array()` | `[]` |
|
|
729
|
+
| `z.object()` | Recursively created via `createSchemaObject` |
|
|
730
|
+
| `z.date()` | `null` |
|
|
731
|
+
| `.nullable()` | `null` |
|
|
732
|
+
| `.optional()` | `undefined` |
|
|
733
|
+
|
|
631
734
|
## TypeScript Support
|
|
632
735
|
|
|
633
736
|
Full TypeScript support with type inference:
|
|
@@ -658,7 +761,7 @@ type Filters = z.infer<typeof FiltersSchema>
|
|
|
658
761
|
|
|
659
762
|
```typescript
|
|
660
763
|
import { ref } from 'vue'
|
|
661
|
-
import {
|
|
764
|
+
import { useContextStorage, transform } from 'vue-context-storage'
|
|
662
765
|
|
|
663
766
|
const pagination = ref({
|
|
664
767
|
page: 1,
|
|
@@ -666,8 +769,8 @@ const pagination = ref({
|
|
|
666
769
|
total: 0,
|
|
667
770
|
})
|
|
668
771
|
|
|
669
|
-
|
|
670
|
-
|
|
772
|
+
useContextStorage('query', pagination, {
|
|
773
|
+
key: 'page',
|
|
671
774
|
transform: (data, initial) => ({
|
|
672
775
|
page: transform.asNumber(data.page, { fallback: 1 }),
|
|
673
776
|
perPage: transform.asNumber(data.perPage, { fallback: 25 }),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as vue29 from "vue";
|
|
2
2
|
import { ComputedRef, InjectionKey, MaybeRefOrGetter, Plugin, PropType, UnwrapNestedRefs } from "vue";
|
|
3
3
|
import { LocationQuery, LocationQueryValue } from "vue-router";
|
|
4
4
|
|
|
5
5
|
//#region src/components/ContextStorageActivator.vue.d.ts
|
|
6
6
|
declare const _default$1: typeof __VLS_export$4;
|
|
7
|
-
declare const __VLS_export$4:
|
|
7
|
+
declare const __VLS_export$4: vue29.DefineComponent<{}, () => vue29.VNode<vue29.RendererNode, vue29.RendererElement, {
|
|
8
8
|
[key: string]: any;
|
|
9
|
-
}>, {}, {}, {},
|
|
9
|
+
}>, {}, {}, {}, vue29.ComponentOptionsMixin, vue29.ComponentOptionsMixin, {}, string, vue29.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue29.ComponentProvideOptions, true, {}, any>;
|
|
10
10
|
//#endregion
|
|
11
11
|
//#region src/handlers/types.d.ts
|
|
12
12
|
interface HandlerSchema<T> {
|
|
@@ -43,8 +43,8 @@ interface RegisterBaseOptions<T> {
|
|
|
43
43
|
* status: z.enum(['active', 'inactive']).default('active'),
|
|
44
44
|
* })
|
|
45
45
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
46
|
+
* useContextStorage('query', filters, {
|
|
47
|
+
* key: 'filters',
|
|
48
48
|
* schema: FiltersSchema,
|
|
49
49
|
* })
|
|
50
50
|
* ```
|
|
@@ -65,55 +65,55 @@ interface ContextStorageHandler<T, O> {
|
|
|
65
65
|
//#endregion
|
|
66
66
|
//#region src/components/ContextStorageCollection.vue.d.ts
|
|
67
67
|
declare const _default$2: typeof __VLS_export$3;
|
|
68
|
-
declare const __VLS_export$3:
|
|
68
|
+
declare const __VLS_export$3: vue29.DefineComponent<vue29.ExtractPropTypes<{
|
|
69
69
|
handlers: {
|
|
70
70
|
type: PropType<ContextStorageHandlerFactory[]>;
|
|
71
71
|
default: () => ContextStorageHandlerFactory<{}, RegisterOptions<{}>>[];
|
|
72
72
|
};
|
|
73
|
-
}>, () =>
|
|
73
|
+
}>, () => vue29.VNode<vue29.RendererNode, vue29.RendererElement, {
|
|
74
74
|
[key: string]: any;
|
|
75
|
-
}>[] | undefined, {}, {}, {},
|
|
75
|
+
}>[] | undefined, {}, {}, {}, vue29.ComponentOptionsMixin, vue29.ComponentOptionsMixin, {}, string, vue29.PublicProps, Readonly<vue29.ExtractPropTypes<{
|
|
76
76
|
handlers: {
|
|
77
77
|
type: PropType<ContextStorageHandlerFactory[]>;
|
|
78
78
|
default: () => ContextStorageHandlerFactory<{}, RegisterOptions<{}>>[];
|
|
79
79
|
};
|
|
80
80
|
}>> & Readonly<{}>, {
|
|
81
81
|
handlers: ContextStorageHandlerFactory<{}, RegisterOptions<{}>>[];
|
|
82
|
-
}, {}, {}, {}, string,
|
|
82
|
+
}, {}, {}, {}, string, vue29.ComponentProvideOptions, true, {}, any>;
|
|
83
83
|
//#endregion
|
|
84
84
|
//#region src/components/ContextStorageProvider.vue.d.ts
|
|
85
85
|
declare const _default$4: typeof __VLS_export$2;
|
|
86
|
-
declare const __VLS_export$2:
|
|
86
|
+
declare const __VLS_export$2: vue29.DefineComponent<vue29.ExtractPropTypes<{
|
|
87
87
|
itemKey: {
|
|
88
88
|
type: StringConstructor;
|
|
89
89
|
required: true;
|
|
90
90
|
};
|
|
91
|
-
}>, () =>
|
|
91
|
+
}>, () => vue29.VNode<vue29.RendererNode, vue29.RendererElement, {
|
|
92
92
|
[key: string]: any;
|
|
93
|
-
}>[] | undefined, {}, {}, {},
|
|
93
|
+
}>[] | undefined, {}, {}, {}, vue29.ComponentOptionsMixin, vue29.ComponentOptionsMixin, {}, string, vue29.PublicProps, Readonly<vue29.ExtractPropTypes<{
|
|
94
94
|
itemKey: {
|
|
95
95
|
type: StringConstructor;
|
|
96
96
|
required: true;
|
|
97
97
|
};
|
|
98
|
-
}>> & Readonly<{}>, {}, {}, {}, {}, string,
|
|
98
|
+
}>> & Readonly<{}>, {}, {}, {}, {}, string, vue29.ComponentProvideOptions, true, {}, any>;
|
|
99
99
|
//#endregion
|
|
100
100
|
//#region src/components/ContextStorage.vue.d.ts
|
|
101
101
|
declare const _default: typeof __VLS_export$1;
|
|
102
|
-
declare const __VLS_export$1:
|
|
102
|
+
declare const __VLS_export$1: vue29.DefineComponent<vue29.ExtractPropTypes<{
|
|
103
103
|
handlers: {
|
|
104
104
|
type: PropType<ContextStorageHandlerFactory[]>;
|
|
105
105
|
default: () => ContextStorageHandlerFactory<{}, RegisterOptions<{}>>[];
|
|
106
106
|
};
|
|
107
|
-
}>, () =>
|
|
107
|
+
}>, () => vue29.VNode<vue29.RendererNode, vue29.RendererElement, {
|
|
108
108
|
[key: string]: any;
|
|
109
|
-
}>[] | undefined, {}, {}, {},
|
|
109
|
+
}>[] | undefined, {}, {}, {}, vue29.ComponentOptionsMixin, vue29.ComponentOptionsMixin, {}, string, vue29.PublicProps, Readonly<vue29.ExtractPropTypes<{
|
|
110
110
|
handlers: {
|
|
111
111
|
type: PropType<ContextStorageHandlerFactory[]>;
|
|
112
112
|
default: () => ContextStorageHandlerFactory<{}, RegisterOptions<{}>>[];
|
|
113
113
|
};
|
|
114
114
|
}>> & Readonly<{}>, {
|
|
115
115
|
handlers: ContextStorageHandlerFactory<{}, RegisterOptions<{}>>[];
|
|
116
|
-
}, {}, {}, {}, string,
|
|
116
|
+
}, {}, {}, {}, string, vue29.ComponentProvideOptions, true, {}, any>;
|
|
117
117
|
//#endregion
|
|
118
118
|
//#region src/prefix.d.ts
|
|
119
119
|
/**
|
|
@@ -129,26 +129,25 @@ type ContextStoragePrefixSegment = string | Partial<Record<string, string>>;
|
|
|
129
129
|
* bracket notation (`a[b][c]`).
|
|
130
130
|
*
|
|
131
131
|
* @param segments — array provided via inject (stacked by nested ContextStoragePrefix components)
|
|
132
|
-
* @param
|
|
133
|
-
* @param knownHandlerKeys — mapping from injection key to handler type name
|
|
132
|
+
* @param handlerType — handler type name (e.g. 'query', 'localStorage')
|
|
134
133
|
*/
|
|
135
134
|
declare const contextStoragePrefixSegmentsInjectKey: InjectionKey<MaybeRefOrGetter<ContextStoragePrefixSegment[]>>;
|
|
136
135
|
//#endregion
|
|
137
136
|
//#region src/components/ContextStoragePrefix.vue.d.ts
|
|
138
137
|
declare const _default$3: typeof __VLS_export;
|
|
139
|
-
declare const __VLS_export:
|
|
138
|
+
declare const __VLS_export: vue29.DefineComponent<vue29.ExtractPropTypes<{
|
|
140
139
|
name: {
|
|
141
140
|
type: PropType<ContextStoragePrefixSegment>;
|
|
142
141
|
required: true;
|
|
143
142
|
};
|
|
144
|
-
}>, () =>
|
|
143
|
+
}>, () => vue29.VNode<vue29.RendererNode, vue29.RendererElement, {
|
|
145
144
|
[key: string]: any;
|
|
146
|
-
}>, {}, {}, {},
|
|
145
|
+
}>, {}, {}, {}, vue29.ComponentOptionsMixin, vue29.ComponentOptionsMixin, {}, string, vue29.PublicProps, Readonly<vue29.ExtractPropTypes<{
|
|
147
146
|
name: {
|
|
148
147
|
type: PropType<ContextStoragePrefixSegment>;
|
|
149
148
|
required: true;
|
|
150
149
|
};
|
|
151
|
-
}>> & Readonly<{}>, {}, {}, {}, {}, string,
|
|
150
|
+
}>> & Readonly<{}>, {}, {}, {}, {}, string, vue29.ComponentProvideOptions, true, {}, any>;
|
|
152
151
|
//#endregion
|
|
153
152
|
//#region src/plugin.d.ts
|
|
154
153
|
declare const VueContextStoragePlugin: Plugin;
|
|
@@ -205,7 +204,7 @@ interface QueryHandlerSharedOptions {
|
|
|
205
204
|
* Useful, when you have default values, and want to preserve empty state in query.
|
|
206
205
|
* @example
|
|
207
206
|
* ```
|
|
208
|
-
* Options: {preserveEmptyState: true,
|
|
207
|
+
* Options: {preserveEmptyState: true, key: 'filters'}
|
|
209
208
|
*
|
|
210
209
|
* When filters are empty we will get this in query string:
|
|
211
210
|
*
|
|
@@ -216,7 +215,7 @@ interface QueryHandlerSharedOptions {
|
|
|
216
215
|
*
|
|
217
216
|
* @example
|
|
218
217
|
* ```
|
|
219
|
-
* Options: {preserveEmptyState: false,
|
|
218
|
+
* Options: {preserveEmptyState: false, key: 'filters'}
|
|
220
219
|
*
|
|
221
220
|
* When filters are empty we will get this in query string:
|
|
222
221
|
*
|
|
@@ -270,14 +269,14 @@ interface QueryHandlerBaseOptions extends QueryHandlerSharedOptions {
|
|
|
270
269
|
}
|
|
271
270
|
interface RegisterQueryHandlerBaseOptions<T> extends QueryHandlerSharedOptions {
|
|
272
271
|
/**
|
|
273
|
-
*
|
|
272
|
+
* Key in query string.
|
|
274
273
|
*
|
|
275
274
|
* @example
|
|
276
275
|
* ```
|
|
277
276
|
* filters, table-1[filters], table-2[filters]
|
|
278
277
|
* ```
|
|
279
278
|
*/
|
|
280
|
-
|
|
279
|
+
key?: string;
|
|
281
280
|
/**
|
|
282
281
|
* Transform function to convert deserialized query parameters to the expected type.
|
|
283
282
|
*
|
|
@@ -298,8 +297,8 @@ interface RegisterQueryHandlerBaseOptions<T> extends QueryHandlerSharedOptions {
|
|
|
298
297
|
* ```ts
|
|
299
298
|
* const data = ref({ page: undefined as number | undefined })
|
|
300
299
|
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
300
|
+
* useContextStorage('query', data, {
|
|
301
|
+
* key: 'filters',
|
|
303
302
|
* onlyChanges: true,
|
|
304
303
|
* additionalDefaultData: { page: 1 },
|
|
305
304
|
* })
|
|
@@ -325,11 +324,6 @@ interface WebStorageHandlerBaseOptions {
|
|
|
325
324
|
}
|
|
326
325
|
interface RegisterWebStorageHandlerBaseOptions<T> {
|
|
327
326
|
key?: string;
|
|
328
|
-
/**
|
|
329
|
-
* Optional prefix for nested data within the storage key.
|
|
330
|
-
* When provided, data will be stored under this prefix within the main storage object.
|
|
331
|
-
*/
|
|
332
|
-
prefix?: string;
|
|
333
327
|
/**
|
|
334
328
|
* Transform function to convert deserialized storage data to the expected type.
|
|
335
329
|
*
|
|
@@ -365,7 +359,10 @@ interface ContextStorageHandlerMap<T> {
|
|
|
365
359
|
localStorage: RegisterWebStorageHandlerOptions<T>;
|
|
366
360
|
sessionStorage: RegisterWebStorageHandlerOptions<T>;
|
|
367
361
|
}
|
|
368
|
-
declare function defineContextStorageHandler<T, O extends object>(name: string, injectionKey: InjectionKey<ContextStorageHandler<T, O
|
|
362
|
+
declare function defineContextStorageHandler<T, O extends object>(name: string, injectionKey: InjectionKey<ContextStorageHandler<T, O>>, options?: {
|
|
363
|
+
prefixProperty?: string;
|
|
364
|
+
prefixMergeStrategy?: 'prepend' | 'append';
|
|
365
|
+
}): void;
|
|
369
366
|
declare function resolveHandlerInjectionKey<K$1 extends keyof ContextStorageHandlerMap<T>, T>(type: K$1): InjectionKey<ContextStorageHandler<T, ContextStorageHandlerMap<T>[K$1]>> | undefined;
|
|
370
367
|
//#endregion
|
|
371
368
|
//#region src/composables/types.d.ts
|
|
@@ -401,16 +398,13 @@ declare function useContextStorageCollection(handlers?: ContextStorageHandlerFac
|
|
|
401
398
|
declare function useContextStorageProvider(key: string): void;
|
|
402
399
|
//#endregion
|
|
403
400
|
//#region src/handlers/query/index.d.ts
|
|
404
|
-
declare function useContextStorageQueryHandler<T extends Record<string, unknown>>(data: MaybeRefOrGetter<T>, options?: RegisterQueryHandlerOptions<T>): UseContextStorageResult<T>;
|
|
405
401
|
declare function createQueryHandler(baseOptions?: QueryHandlerBaseOptions): ContextStorageHandlerFactory;
|
|
406
402
|
//#endregion
|
|
407
403
|
//#region src/handlers/local-storage/index.d.ts
|
|
408
404
|
declare function createLocalStorageHandler(customOptions?: WebStorageHandlerBaseOptions): ContextStorageHandlerFactory;
|
|
409
|
-
declare const useContextStorageLocalStorage: (data: vue9.MaybeRefOrGetter<Record<string, unknown>>, options: RegisterWebStorageHandlerOptions<Record<string, unknown>>) => UseContextStorageResult<Record<string, unknown>>;
|
|
410
405
|
//#endregion
|
|
411
406
|
//#region src/handlers/session-storage/index.d.ts
|
|
412
407
|
declare function createSessionStorageHandler(customOptions?: WebStorageHandlerBaseOptions): ContextStorageHandlerFactory;
|
|
413
|
-
declare const useContextStorageSessionStorage: (data: vue9.MaybeRefOrGetter<Record<string, unknown>>, options: RegisterWebStorageHandlerOptions<Record<string, unknown>>) => UseContextStorageResult<Record<string, unknown>>;
|
|
414
408
|
//#endregion
|
|
415
409
|
//#region src/handlers/query/transform-helpers.d.ts
|
|
416
410
|
declare function asNumber(value: QueryValue | number | undefined): number;
|
|
@@ -562,13 +556,13 @@ declare const transform: {
|
|
|
562
556
|
//#region src/handlers/query/helpers.d.ts
|
|
563
557
|
interface SerializeOptions {
|
|
564
558
|
/**
|
|
565
|
-
* Custom prefix for serialized keys.
|
|
559
|
+
* Custom key prefix for serialized keys.
|
|
566
560
|
* @example
|
|
567
|
-
* -
|
|
568
|
-
* -
|
|
569
|
-
* -
|
|
561
|
+
* - key: 'filters' => 'filters[field]'
|
|
562
|
+
* - key: 'search' => 'search[field]'
|
|
563
|
+
* - key: '' => 'field' (no prefix)
|
|
570
564
|
*/
|
|
571
|
-
|
|
565
|
+
key?: string;
|
|
572
566
|
}
|
|
573
567
|
/**
|
|
574
568
|
* Serializes filter parameters into a URL-friendly format.
|
|
@@ -616,4 +610,4 @@ declare const contextStorageSessionStorageHandlerInjectKey: InjectionKey<Context
|
|
|
616
610
|
//#region src/constants.d.ts
|
|
617
611
|
declare const defaultHandlers: ContextStorageHandlerFactory[];
|
|
618
612
|
//#endregion
|
|
619
|
-
export { type CollectionManager, type CollectionManagerItem, _default as ContextStorage, _default$1 as ContextStorageActivator, _default$2 as ContextStorageCollection, type ContextStorageHandler, type ContextStorageHandlerFactory, type ContextStorageHandlerMap, _default$3 as ContextStoragePrefix, type ContextStoragePrefixSegment, _default$4 as ContextStorageProvider, type WebStorageHandlerBaseOptions as LocalStorageHandlerBaseOptions, type QueryValue, type RegisterBaseOptions, type RegisterWebStorageHandlerBaseOptions as RegisterLocalStorageHandlerBaseOptions, VueContextStoragePlugin, asArray, asBoolean, asNumber, asNumberArray, asObjectArray, asString, contextStorageCollectionInjectKey, contextStorageCollectionItemInjectKey, contextStorageHandlersInjectKey, contextStorageLocalStorageHandlerInjectKey, contextStoragePrefixSegmentsInjectKey, contextStorageQueryHandlerInjectKey, contextStorageSessionStorageHandlerInjectKey, createCollectionManager, createLocalStorageHandler, createQueryHandler, createSessionStorageHandler, defaultHandlers, defineContextStorageHandler, deserializeParams as deserializeQueryParams, resolveHandlerInjectionKey, serializeParams as serializeQueryParams, transform, useContextStorage, useContextStorageActivator, useContextStorageCollection,
|
|
613
|
+
export { type CollectionManager, type CollectionManagerItem, _default as ContextStorage, _default$1 as ContextStorageActivator, _default$2 as ContextStorageCollection, type ContextStorageHandler, type ContextStorageHandlerFactory, type ContextStorageHandlerMap, _default$3 as ContextStoragePrefix, type ContextStoragePrefixSegment, _default$4 as ContextStorageProvider, type WebStorageHandlerBaseOptions as LocalStorageHandlerBaseOptions, type QueryValue, type RegisterBaseOptions, type RegisterWebStorageHandlerBaseOptions as RegisterLocalStorageHandlerBaseOptions, VueContextStoragePlugin, asArray, asBoolean, asNumber, asNumberArray, asObjectArray, asString, contextStorageCollectionInjectKey, contextStorageCollectionItemInjectKey, contextStorageHandlersInjectKey, contextStorageLocalStorageHandlerInjectKey, contextStoragePrefixSegmentsInjectKey, contextStorageQueryHandlerInjectKey, contextStorageSessionStorageHandlerInjectKey, createCollectionManager, createLocalStorageHandler, createQueryHandler, createSessionStorageHandler, defaultHandlers, defineContextStorageHandler, deserializeParams as deserializeQueryParams, resolveHandlerInjectionKey, serializeParams as serializeQueryParams, transform, useContextStorage, useContextStorageActivator, useContextStorageCollection, useContextStorageProvider };
|
package/dist/index.js
CHANGED
|
@@ -19,8 +19,7 @@ function joinPrefix(left, right) {
|
|
|
19
19
|
return `${left}[${right}]`;
|
|
20
20
|
}
|
|
21
21
|
const contextStoragePrefixSegmentsInjectKey = contextStoragePrefixSegments;
|
|
22
|
-
function resolvePrefixSegments(segments,
|
|
23
|
-
const handlerType = knownHandlerKeys$1.get(handlerInjectionKey);
|
|
22
|
+
function resolvePrefixSegments(segments, handlerType) {
|
|
24
23
|
let combined = "";
|
|
25
24
|
for (const segment of segments) {
|
|
26
25
|
let value;
|
|
@@ -62,7 +61,7 @@ var ContextStorageActivator_default = ContextStorageActivator_vue_vue_type_scrip
|
|
|
62
61
|
//#endregion
|
|
63
62
|
//#region src/handlers/query/helpers.ts
|
|
64
63
|
function serializeParams(params, options = {}) {
|
|
65
|
-
const { prefix = "" } = options;
|
|
64
|
+
const { key: prefix = "" } = options;
|
|
66
65
|
const result = {};
|
|
67
66
|
Object.keys(params).forEach((key) => {
|
|
68
67
|
const value = params[key];
|
|
@@ -80,12 +79,12 @@ function serializeParams(params, options = {}) {
|
|
|
80
79
|
});
|
|
81
80
|
Object.assign(result, serializeParams(indexed, {
|
|
82
81
|
...options,
|
|
83
|
-
|
|
82
|
+
key: formattedKey
|
|
84
83
|
}));
|
|
85
84
|
} else result[formattedKey] = value.map(String);
|
|
86
85
|
else Object.assign(result, serializeParams(value, {
|
|
87
86
|
...options,
|
|
88
|
-
|
|
87
|
+
key: formattedKey
|
|
89
88
|
}));
|
|
90
89
|
else if (typeof value === "boolean") result[formattedKey] = value ? "1" : "0";
|
|
91
90
|
else result[formattedKey] = String(value);
|
|
@@ -145,8 +144,17 @@ function applyTransform(input) {
|
|
|
145
144
|
};
|
|
146
145
|
}
|
|
147
146
|
const knownHandlerKeys = /* @__PURE__ */ new Map();
|
|
148
|
-
function registerKnownHandlerKey(injectionKey, handlerType) {
|
|
149
|
-
knownHandlerKeys.set(injectionKey,
|
|
147
|
+
function registerKnownHandlerKey(injectionKey, handlerType, prefixProperty = "key", prefixMergeStrategy = "prepend") {
|
|
148
|
+
knownHandlerKeys.set(injectionKey, {
|
|
149
|
+
handlerType,
|
|
150
|
+
prefixProperty,
|
|
151
|
+
prefixMergeStrategy
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function appendBracketNotation(base, suffix) {
|
|
155
|
+
const bracketIdx = suffix.indexOf("[");
|
|
156
|
+
if (bracketIdx === -1) return `${base}[${suffix}]`;
|
|
157
|
+
return `${base}[${suffix.slice(0, bracketIdx)}]${suffix.slice(bracketIdx)}`;
|
|
150
158
|
}
|
|
151
159
|
function buildContextStorageHandler(handler, data, options) {
|
|
152
160
|
const uid = getCurrentInstance()?.uid || 0;
|
|
@@ -158,11 +166,16 @@ function buildContextStorageHandler(handler, data, options) {
|
|
|
158
166
|
const rawPrefixSegments = inject(contextStoragePrefixSegmentsInjectKey, void 0);
|
|
159
167
|
const prefixSegments = rawPrefixSegments ? toValue(rawPrefixSegments) : void 0;
|
|
160
168
|
if (prefixSegments && prefixSegments.length > 0) {
|
|
161
|
-
const
|
|
169
|
+
const handlerInjectionKey = handler.getInjectionKey();
|
|
170
|
+
const handlerInfo = knownHandlerKeys.get(handlerInjectionKey);
|
|
171
|
+
const resolvedPrefix = resolvePrefixSegments(prefixSegments, handlerInfo?.handlerType);
|
|
162
172
|
if (resolvedPrefix) {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
173
|
+
const prefixProp = handlerInfo?.prefixProperty ?? "key";
|
|
174
|
+
const strategy = handlerInfo?.prefixMergeStrategy ?? "prepend";
|
|
175
|
+
const optionsValue = mergedOptions[prefixProp];
|
|
176
|
+
if (optionsValue) if (strategy === "append") mergedOptions[prefixProp] = appendBracketNotation(optionsValue, resolvedPrefix);
|
|
177
|
+
else mergedOptions[prefixProp] = `${resolvedPrefix}[${optionsValue}]`;
|
|
178
|
+
else mergedOptions[prefixProp] = resolvedPrefix;
|
|
166
179
|
}
|
|
167
180
|
}
|
|
168
181
|
const { stop, reset, wasChanged } = handler.register(data, mergedOptions);
|
|
@@ -199,23 +212,23 @@ function buildQuery(input) {
|
|
|
199
212
|
const warnings = [];
|
|
200
213
|
const newQueryRaw = {};
|
|
201
214
|
input.items.forEach((item) => {
|
|
202
|
-
const {
|
|
215
|
+
const { key, onlyChanges = input.onlyChanges } = item;
|
|
203
216
|
let preserveEmptyState = item.preserveEmptyState ?? input.preserveEmptyState;
|
|
204
|
-
const patch = serializeParams(item.data, {
|
|
217
|
+
const patch = serializeParams(item.data, { key });
|
|
205
218
|
if (onlyChanges) {
|
|
206
219
|
if (preserveEmptyState) {
|
|
207
220
|
preserveEmptyState = false;
|
|
208
221
|
warnings.push("[vue-context-storage] preserveEmptyState is not supported with onlyChanges");
|
|
209
222
|
}
|
|
210
|
-
Object.keys(patch).forEach((
|
|
211
|
-
if (isEqual(patch[
|
|
223
|
+
Object.keys(patch).forEach((key2) => {
|
|
224
|
+
if (isEqual(patch[key2], item.initialQueryData[key2]) || item.additionalDefaultQueryData && isEqual(patch[key2], item.additionalDefaultQueryData[key2])) delete patch[key2];
|
|
212
225
|
});
|
|
213
226
|
}
|
|
214
227
|
const patchKeys = Object.keys(patch);
|
|
215
|
-
patchKeys.forEach((
|
|
216
|
-
if (Object.hasOwn(newQueryRaw,
|
|
228
|
+
patchKeys.forEach((key2) => {
|
|
229
|
+
if (Object.hasOwn(newQueryRaw, key2)) warnings.push(`[vue-context-storage] Key ${key2} is already present, overriding ` + (item.causer || ""));
|
|
217
230
|
});
|
|
218
|
-
if (!patchKeys.length && preserveEmptyState) patch[
|
|
231
|
+
if (!patchKeys.length && preserveEmptyState) patch[key || input.emptyPlaceholder] = null;
|
|
219
232
|
Object.assign(newQueryRaw, patch);
|
|
220
233
|
});
|
|
221
234
|
let newQuery = { ...newQueryRaw };
|
|
@@ -239,8 +252,8 @@ function buildQuery(input) {
|
|
|
239
252
|
//#region src/handlers/query/compute-sync-state.ts
|
|
240
253
|
function computeSyncState(input) {
|
|
241
254
|
let state = input.deserializedState;
|
|
242
|
-
if (typeof input.
|
|
243
|
-
const parts = input.
|
|
255
|
+
if (typeof input.key === "string" && input.key.length > 0) {
|
|
256
|
+
const parts = input.key.split(/[\[\]]/).filter(Boolean);
|
|
244
257
|
for (const part of parts) {
|
|
245
258
|
if (state === void 0 || state === null) break;
|
|
246
259
|
state = state[part];
|
|
@@ -262,11 +275,6 @@ function computeSyncState(input) {
|
|
|
262
275
|
|
|
263
276
|
//#endregion
|
|
264
277
|
//#region src/handlers/query/index.ts
|
|
265
|
-
function useContextStorageQueryHandler(data, options) {
|
|
266
|
-
const handler = inject(contextStorageQueryHandler);
|
|
267
|
-
if (!handler) throw new Error("[vue-context-storage] ContextStorageQueryHandler is not provided");
|
|
268
|
-
return buildContextStorageHandler(handler, data, options);
|
|
269
|
-
}
|
|
270
278
|
function createQueryHandler(baseOptions) {
|
|
271
279
|
const factory = () => {
|
|
272
280
|
const route = useRoute();
|
|
@@ -358,13 +366,13 @@ function createQueryHandler(baseOptions) {
|
|
|
358
366
|
registered.forEach((item) => syncInitialStateToRegisteredItem(item));
|
|
359
367
|
}
|
|
360
368
|
function syncInitialStateToRegisteredItem(item) {
|
|
361
|
-
const
|
|
369
|
+
const key = item.options?.key;
|
|
362
370
|
const { mergeOnlyExistingKeysWithoutTransform = options.mergeOnlyExistingKeysWithoutTransform } = item.options || {};
|
|
363
371
|
const itemState = toValue(item.data);
|
|
364
372
|
const result = computeSyncState({
|
|
365
373
|
deserializedState: deserializeParams(route.query),
|
|
366
374
|
initialData: item.initialData,
|
|
367
|
-
|
|
375
|
+
key,
|
|
368
376
|
emptyPlaceholder: options.emptyPlaceholder
|
|
369
377
|
});
|
|
370
378
|
if (result.type === "none") return;
|
|
@@ -383,45 +391,45 @@ function createQueryHandler(baseOptions) {
|
|
|
383
391
|
});
|
|
384
392
|
transformed.warnings.forEach((w) => console.warn(w.message, ...w.args));
|
|
385
393
|
const finalData = { ...transformed.data };
|
|
386
|
-
for (const
|
|
394
|
+
for (const key2 of Object.keys(itemState)) if (!urlKeys.has(key2)) finalData[key2] = itemState[key2];
|
|
387
395
|
if (isEqual(itemState, finalData)) return;
|
|
388
396
|
({ ...itemState });
|
|
389
397
|
syncReactive(itemState, finalData);
|
|
390
398
|
}
|
|
391
399
|
function register(data, registerOptions) {
|
|
392
400
|
const resolvedData = toValue(data);
|
|
393
|
-
if (registeredDataObjects.has(resolvedData)) console.warn("[vue-context-storage] The same data object is already registered in ContextStorageQueryHandler.", {
|
|
401
|
+
if (registeredDataObjects.has(resolvedData)) console.warn("[vue-context-storage] The same data object is already registered in ContextStorageQueryHandler.", { key: registerOptions?.key });
|
|
394
402
|
registeredDataObjects.add(resolvedData);
|
|
395
403
|
hasAnyRegistered = true;
|
|
396
404
|
const watchHandle = watch(data, () => {
|
|
397
|
-
registerOptions.
|
|
405
|
+
registerOptions.key;
|
|
398
406
|
scheduleSyncToQuery();
|
|
399
407
|
}, { deep: true });
|
|
400
408
|
const initialData = cloneDeep(resolvedData);
|
|
401
409
|
const item = {
|
|
402
410
|
data,
|
|
403
411
|
initialData,
|
|
404
|
-
initialQueryData: serializeParams(initialData, {
|
|
405
|
-
additionalDefaultQueryData: registerOptions.additionalDefaultData ? serializeParams(registerOptions.additionalDefaultData, {
|
|
412
|
+
initialQueryData: serializeParams(initialData, { key: registerOptions.key }),
|
|
413
|
+
additionalDefaultQueryData: registerOptions.additionalDefaultData ? serializeParams(registerOptions.additionalDefaultData, { key: registerOptions.key }) : void 0,
|
|
406
414
|
options: registerOptions,
|
|
407
415
|
watchHandle
|
|
408
416
|
};
|
|
409
417
|
registered.push(item);
|
|
410
418
|
const syncCallback = () => {
|
|
411
|
-
registerOptions.
|
|
419
|
+
registerOptions.key;
|
|
412
420
|
syncInitialStateToRegisteredItem(item);
|
|
413
421
|
scheduleSyncToQuery();
|
|
414
422
|
};
|
|
415
423
|
if (preventAfterEachRouteCallsWhileCallingRouter) {
|
|
416
|
-
registerOptions.
|
|
424
|
+
registerOptions.key;
|
|
417
425
|
setTimeout(syncCallback);
|
|
418
426
|
} else {
|
|
419
|
-
registerOptions.
|
|
427
|
+
registerOptions.key;
|
|
420
428
|
syncCallback();
|
|
421
429
|
}
|
|
422
430
|
return {
|
|
423
431
|
stop: () => {
|
|
424
|
-
registerOptions.
|
|
432
|
+
registerOptions.key;
|
|
425
433
|
item.watchHandle.stop();
|
|
426
434
|
const index = registered.indexOf(item);
|
|
427
435
|
if (index !== -1) registered.splice(index, 1);
|
|
@@ -430,7 +438,7 @@ function createQueryHandler(baseOptions) {
|
|
|
430
438
|
currentQuery = void 0;
|
|
431
439
|
},
|
|
432
440
|
reset: () => {
|
|
433
|
-
registerOptions.
|
|
441
|
+
registerOptions.key;
|
|
434
442
|
syncReactive(toValue(data), cloneDeep(initialData));
|
|
435
443
|
},
|
|
436
444
|
wasChanged: computed(() => !isEqual(toValue(data), initialData))
|
|
@@ -442,7 +450,7 @@ function createQueryHandler(baseOptions) {
|
|
|
442
450
|
data: toValue(item.data),
|
|
443
451
|
initialQueryData: item.initialQueryData,
|
|
444
452
|
additionalDefaultQueryData: item.additionalDefaultQueryData,
|
|
445
|
-
|
|
453
|
+
key: item.options?.key,
|
|
446
454
|
onlyChanges: item.options?.onlyChanges,
|
|
447
455
|
preserveEmptyState: item.options?.preserveEmptyState,
|
|
448
456
|
causer: item.options?.causer
|
|
@@ -485,19 +493,10 @@ function createWebStorageHandlerInstance(config) {
|
|
|
485
493
|
window.removeEventListener("storage", handler);
|
|
486
494
|
});
|
|
487
495
|
}
|
|
488
|
-
function resolveStorageKey(key, prefix) {
|
|
489
|
-
if (prefix) {
|
|
490
|
-
const bracketIdx = prefix.indexOf("[");
|
|
491
|
-
if (bracketIdx === -1) return `${key}[${prefix}]`;
|
|
492
|
-
return `${key}[${prefix.slice(0, bracketIdx)}]${prefix.slice(bracketIdx)}`;
|
|
493
|
-
}
|
|
494
|
-
return key;
|
|
495
|
-
}
|
|
496
496
|
function handleStorageEvent(event) {
|
|
497
497
|
if (!enabled) return;
|
|
498
498
|
registered.forEach((item) => {
|
|
499
|
-
|
|
500
|
-
if (event.key === effectiveKey) syncStorageToRegisteredItem(item);
|
|
499
|
+
if (event.key === item.options.key) syncStorageToRegisteredItem(item);
|
|
501
500
|
});
|
|
502
501
|
}
|
|
503
502
|
function getInjectionKey() {
|
|
@@ -514,23 +513,23 @@ function createWebStorageHandlerInstance(config) {
|
|
|
514
513
|
function syncRegisteredToStorage() {
|
|
515
514
|
if (!enabled) return;
|
|
516
515
|
registered.forEach((item) => {
|
|
517
|
-
const
|
|
516
|
+
const storageKey = item.options.key;
|
|
518
517
|
const data = toValue(item.data);
|
|
519
518
|
const { serializer } = item.options;
|
|
520
519
|
try {
|
|
521
|
-
if (serializer) config.storage.setItem(
|
|
522
|
-
else config.storage.setItem(
|
|
520
|
+
if (serializer) config.storage.setItem(storageKey, serializer(data));
|
|
521
|
+
else config.storage.setItem(storageKey, JSON.stringify(data));
|
|
523
522
|
} catch (e) {
|
|
524
523
|
console.error("[vue-context-storage] Error writing to storage", e);
|
|
525
524
|
}
|
|
526
525
|
});
|
|
527
526
|
}
|
|
528
527
|
function syncStorageToRegisteredItem(item) {
|
|
529
|
-
const { key,
|
|
530
|
-
const
|
|
528
|
+
const { key, deserializer } = item.options;
|
|
529
|
+
const storageKey = key;
|
|
531
530
|
let stored = null;
|
|
532
531
|
try {
|
|
533
|
-
stored = config.storage.getItem(
|
|
532
|
+
stored = config.storage.getItem(storageKey);
|
|
534
533
|
} catch {
|
|
535
534
|
return;
|
|
536
535
|
}
|
|
@@ -540,7 +539,7 @@ function createWebStorageHandlerInstance(config) {
|
|
|
540
539
|
if (deserializer) deserialized = deserializer(stored);
|
|
541
540
|
else deserialized = JSON.parse(stored);
|
|
542
541
|
} catch {
|
|
543
|
-
console.warn("[vue-context-storage] Failed to parse storage data for key:",
|
|
542
|
+
console.warn("[vue-context-storage] Failed to parse storage data for key:", storageKey);
|
|
544
543
|
return;
|
|
545
544
|
}
|
|
546
545
|
if (deserialized === void 0 || deserialized === null) return;
|
|
@@ -562,10 +561,7 @@ function createWebStorageHandlerInstance(config) {
|
|
|
562
561
|
function register(data, options) {
|
|
563
562
|
if (!options.key) throw new Error("[vue-context-storage] Storage handler requires a key option");
|
|
564
563
|
const resolvedData = toValue(data);
|
|
565
|
-
if (registeredDataObjects.has(resolvedData)) console.warn(`[vue-context-storage] The same data object is already registered in ${config.handlerName}.`, {
|
|
566
|
-
key: options.key,
|
|
567
|
-
prefix: options.prefix
|
|
568
|
-
});
|
|
564
|
+
if (registeredDataObjects.has(resolvedData)) console.warn(`[vue-context-storage] The same data object is already registered in ${config.handlerName}.`, { key: options.key });
|
|
569
565
|
registeredDataObjects.add(resolvedData);
|
|
570
566
|
hasAnyRegistered = true;
|
|
571
567
|
const watchHandle = watch(data, () => syncRegisteredToStorage(), { deep: true });
|
|
@@ -597,13 +593,6 @@ function createWebStorageHandlerInstance(config) {
|
|
|
597
593
|
getInjectionKey
|
|
598
594
|
};
|
|
599
595
|
}
|
|
600
|
-
function createWebStorageComposable(injectionKey, handlerName) {
|
|
601
|
-
return function useContextStorageWebStorage(data, options) {
|
|
602
|
-
const handler = inject(injectionKey);
|
|
603
|
-
if (!handler) throw new Error(`[vue-context-storage] ${handlerName} is not provided`);
|
|
604
|
-
return buildContextStorageHandler(handler, data, options);
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
596
|
|
|
608
597
|
//#endregion
|
|
609
598
|
//#region src/handlers/local-storage/index.ts
|
|
@@ -619,7 +608,6 @@ function createLocalStorageHandler(customOptions) {
|
|
|
619
608
|
});
|
|
620
609
|
return factory;
|
|
621
610
|
}
|
|
622
|
-
const useContextStorageLocalStorage = createWebStorageComposable(contextStorageLocalStorageHandler, "ContextStorageLocalStorageHandler");
|
|
623
611
|
|
|
624
612
|
//#endregion
|
|
625
613
|
//#region src/handlers/session-storage/index.ts
|
|
@@ -635,7 +623,6 @@ function createSessionStorageHandler(customOptions) {
|
|
|
635
623
|
});
|
|
636
624
|
return factory;
|
|
637
625
|
}
|
|
638
|
-
const useContextStorageSessionStorage = createWebStorageComposable(contextStorageSessionStorageHandler, "ContextStorageSessionStorageHandler");
|
|
639
626
|
|
|
640
627
|
//#endregion
|
|
641
628
|
//#region src/constants.ts
|
|
@@ -847,16 +834,22 @@ const VueContextStoragePlugin = { install(app) {
|
|
|
847
834
|
//#endregion
|
|
848
835
|
//#region src/registry.ts
|
|
849
836
|
const handlerRegistry = /* @__PURE__ */ new Map();
|
|
850
|
-
function defineContextStorageHandler(name, injectionKey) {
|
|
837
|
+
function defineContextStorageHandler(name, injectionKey, options) {
|
|
851
838
|
handlerRegistry.set(name, injectionKey);
|
|
852
|
-
registerKnownHandlerKey(injectionKey, name);
|
|
839
|
+
registerKnownHandlerKey(injectionKey, name, options?.prefixProperty, options?.prefixMergeStrategy);
|
|
853
840
|
}
|
|
854
841
|
function resolveHandlerInjectionKey(type) {
|
|
855
842
|
return handlerRegistry.get(type);
|
|
856
843
|
}
|
|
857
|
-
defineContextStorageHandler("query", contextStorageQueryHandlerInjectKey);
|
|
858
|
-
defineContextStorageHandler("localStorage", contextStorageLocalStorageHandlerInjectKey
|
|
859
|
-
|
|
844
|
+
defineContextStorageHandler("query", contextStorageQueryHandlerInjectKey, { prefixProperty: "key" });
|
|
845
|
+
defineContextStorageHandler("localStorage", contextStorageLocalStorageHandlerInjectKey, {
|
|
846
|
+
prefixProperty: "key",
|
|
847
|
+
prefixMergeStrategy: "append"
|
|
848
|
+
});
|
|
849
|
+
defineContextStorageHandler("sessionStorage", contextStorageSessionStorageHandlerInjectKey, {
|
|
850
|
+
prefixProperty: "key",
|
|
851
|
+
prefixMergeStrategy: "append"
|
|
852
|
+
});
|
|
860
853
|
|
|
861
854
|
//#endregion
|
|
862
855
|
//#region src/composables/useContextStorage.ts
|
|
@@ -945,4 +938,4 @@ const transform = {
|
|
|
945
938
|
};
|
|
946
939
|
|
|
947
940
|
//#endregion
|
|
948
|
-
export { ContextStorage_default as ContextStorage, ContextStorageActivator_default as ContextStorageActivator, ContextStorageCollection_default as ContextStorageCollection, ContextStoragePrefix_default as ContextStoragePrefix, ContextStorageProvider_default as ContextStorageProvider, VueContextStoragePlugin, asArray, asBoolean, asNumber, asNumberArray, asObjectArray, asString, contextStorageCollectionInjectKey, contextStorageCollectionItemInjectKey, contextStorageHandlersInjectKey, contextStorageLocalStorageHandlerInjectKey, contextStoragePrefixSegmentsInjectKey, contextStorageQueryHandlerInjectKey, contextStorageSessionStorageHandlerInjectKey, createCollectionManager, createLocalStorageHandler, createQueryHandler, createSessionStorageHandler, defaultHandlers, defineContextStorageHandler, deserializeParams as deserializeQueryParams, resolveHandlerInjectionKey, serializeParams as serializeQueryParams, transform, useContextStorage, useContextStorageActivator, useContextStorageCollection,
|
|
941
|
+
export { ContextStorage_default as ContextStorage, ContextStorageActivator_default as ContextStorageActivator, ContextStorageCollection_default as ContextStorageCollection, ContextStoragePrefix_default as ContextStoragePrefix, ContextStorageProvider_default as ContextStorageProvider, VueContextStoragePlugin, asArray, asBoolean, asNumber, asNumberArray, asObjectArray, asString, contextStorageCollectionInjectKey, contextStorageCollectionItemInjectKey, contextStorageHandlersInjectKey, contextStorageLocalStorageHandlerInjectKey, contextStoragePrefixSegmentsInjectKey, contextStorageQueryHandlerInjectKey, contextStorageSessionStorageHandlerInjectKey, createCollectionManager, createLocalStorageHandler, createQueryHandler, createSessionStorageHandler, defaultHandlers, defineContextStorageHandler, deserializeParams as deserializeQueryParams, resolveHandlerInjectionKey, serializeParams as serializeQueryParams, transform, useContextStorage, useContextStorageActivator, useContextStorageCollection, useContextStorageProvider };
|