vue-router-query-sync 0.0.9 → 0.0.11
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 +175 -0
- package/dist/composables/useQuerySync.d.ts +2 -2
- package/dist/composables/useQuerySync.d.ts.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +55 -44
- package/dist/router.d.ts +4 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/replaceRouterQueue.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# vue-router-query-sync
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
> Effortlessly sync Vue Router query parameters with your store or local refs — no boilerplate.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/vue-router-query-sync)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ✨ What is this?
|
|
13
|
+
|
|
14
|
+
`vue-router-query-sync` is a tiny, fully-typed Vue 3 utility that keeps your reactive state
|
|
15
|
+
(Pinia store fields or local refs) in sync with the current URL query string — both ways.
|
|
16
|
+
|
|
17
|
+
- Changes in your store update the URL query.
|
|
18
|
+
- Changes in the URL query update your store.
|
|
19
|
+
- Batched updates to avoid excessive router.replace calls.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## ✅ Requirements
|
|
24
|
+
|
|
25
|
+
- Vue 3 (`vue@^3.3.13`)
|
|
26
|
+
- Vue Router 4 (`vue-router@^4.0.3`)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 🚀 Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install vue-router-query-sync
|
|
34
|
+
# or
|
|
35
|
+
yarn add vue-router-query-sync
|
|
36
|
+
# or
|
|
37
|
+
pnpm add vue-router-query-sync
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## ⚙️ Setup
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// main.ts
|
|
46
|
+
import { createApp } from 'vue'
|
|
47
|
+
import { createRouter, createWebHistory } from 'vue-router'
|
|
48
|
+
import routerQuerySync from 'vue-router-query-sync'
|
|
49
|
+
import App from './App.vue'
|
|
50
|
+
|
|
51
|
+
const router = createRouter({
|
|
52
|
+
history: createWebHistory(),
|
|
53
|
+
routes: []
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
createApp(App)
|
|
57
|
+
.use(router)
|
|
58
|
+
.use(routerQuerySync, { router })
|
|
59
|
+
.mount('#app')
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 🧩 Basic Usage
|
|
65
|
+
|
|
66
|
+
Sync a value with a query param named `tab`:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { useQuerySync } from 'vue-router-query-sync'
|
|
70
|
+
import { ref } from 'vue'
|
|
71
|
+
|
|
72
|
+
const tab = ref<'favorite' | 'all'>('all')
|
|
73
|
+
|
|
74
|
+
useQuerySync(
|
|
75
|
+
'tab',
|
|
76
|
+
() => tab.value,
|
|
77
|
+
(val) => (tab.value = val)
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Now:
|
|
82
|
+
- Visiting `?tab=favorite` sets `tab.value = 'favorite'`.
|
|
83
|
+
- Changing `tab.value = 'all'` updates the URL to `?tab=all`.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 🔧 API
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
function useQuerySync<T extends string | number>(
|
|
91
|
+
key: string,
|
|
92
|
+
get: () => T | null,
|
|
93
|
+
set: (val: T) => void,
|
|
94
|
+
options?: QuerySyncOptions
|
|
95
|
+
): void
|
|
96
|
+
|
|
97
|
+
type QuerySyncOptions = {
|
|
98
|
+
deps?: Ref<unknown>[]
|
|
99
|
+
context?: string
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- **key**: Query parameter name, e.g., `'tab'`.
|
|
104
|
+
- **get**: Function returning the current value from your store/ref. Return `null` to indicate “no value”.
|
|
105
|
+
- **set**: Function that writes the value to your store/ref.
|
|
106
|
+
- **options.deps**: Array of refs. If provided, the initial sync runs after any of these deps change (useful when state is populated asynchronously).
|
|
107
|
+
- **options.context**: If the same query key may be used multiple times on the same page, provide a unique context to avoid collisions. The actual query key becomes `${context}_${key}`.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## ❗ Important
|
|
112
|
+
|
|
113
|
+
If a page can have multiple components with the same query key, pass a unique `context` to avoid conflicts.
|
|
114
|
+
The real key used in the URL will be `${context}_${key}`. Otherwise, use unique keys per component.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 🏷️ Context Example
|
|
119
|
+
|
|
120
|
+
If two widgets on the same page need a `tab` param, use unique contexts:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
useQuerySync('tab', () => usersStore.tab, (v) => (usersStore.tab = v), { context: 'users' })
|
|
124
|
+
useQuerySync('tab', () => ordersStore.tab, (v) => (ordersStore.tab = v), { context: 'orders' })
|
|
125
|
+
// URL keys will be: users_tab and orders_tab
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## ⏳ Delayed/Dependent Initialization
|
|
131
|
+
|
|
132
|
+
If your state becomes available later (e.g., after a request), delay the initial sync with `deps`:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const isReady = ref(false)
|
|
136
|
+
|
|
137
|
+
useQuerySync('sort', () => store.sort, (v) => (store.sort = v), { deps: [isReady] })
|
|
138
|
+
|
|
139
|
+
// Later when data is loaded:
|
|
140
|
+
isReady.value = true
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 📦 Exports
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import routerQuerySync, { useQuerySync, replaceRouterQueue } from 'vue-router-query-sync'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- **default**: Vue plugin to register with `app.use(routerQuerySync, { router })`.
|
|
152
|
+
- **useQuerySync**: The composable described above.
|
|
153
|
+
- **replaceRouterQueue**: Internal helper that batches query updates (exported in case you need manual batching).
|
|
154
|
+
|
|
155
|
+
Note: `setRouter/getRouter` are internal; prefer using the plugin install.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 🔒 TypeScript
|
|
160
|
+
|
|
161
|
+
`useQuerySync` is fully typed. You can annotate the expected type if needed:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
useQuerySync<number>('page', () => page.value, (v) => (page.value = v))
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Values must be `string` or `number`.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 📄 License
|
|
172
|
+
|
|
173
|
+
MIT © Ivan Chikachev
|
|
174
|
+
|
|
175
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QuerySyncOptions } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* Синхронизирует значение из стора с query-параметром в адресной строке
|
|
4
4
|
*
|
|
@@ -13,5 +13,5 @@ import { Options } from '../types';
|
|
|
13
13
|
* обязательно передавайте уникальный `context`, чтобы не было конфликтов.
|
|
14
14
|
* Или всегда использовать уникальные ключи (например вместо "page" - "usersPage").
|
|
15
15
|
*/
|
|
16
|
-
export declare function useQuerySync<T extends string | number>(key: string, get: () => T | null, set: (val: T) => void, options?:
|
|
16
|
+
export declare function useQuerySync<T extends string | number>(key: string, get: () => T | null, set: (val: T) => void, options?: QuerySyncOptions): void;
|
|
17
17
|
//# sourceMappingURL=useQuerySync.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useQuerySync.d.ts","sourceRoot":"","sources":["../../src/composables/useQuerySync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useQuerySync.d.ts","sourceRoot":"","sources":["../../src/composables/useQuerySync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI1C;;;;;;;;;;;;;GAaG;AAEH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EACpD,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,EACnB,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EACrB,OAAO,GAAE,gBAAqB,QAyD/B"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
import { App } from 'vue';
|
|
2
|
+
import { Router } from 'vue-router';
|
|
1
3
|
export * from './composables/useQuerySync';
|
|
2
4
|
export * from './utils/replaceRouterQueue';
|
|
5
|
+
export * from './router';
|
|
3
6
|
export * from './types/index';
|
|
7
|
+
declare const _default: {
|
|
8
|
+
install(app: App, options: {
|
|
9
|
+
router: Router;
|
|
10
|
+
}): void;
|
|
11
|
+
};
|
|
12
|
+
export default _default;
|
|
4
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,eAAe,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAC9B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAGxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA;;iBAGd,GAAG,WAAW;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE;;AAD/C,wBAIC"}
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(n
|
|
1
|
+
(function(e,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],n):(e=typeof globalThis<"u"?globalThis:e||self,n(e.VueRouterQuerySync={},e.Vue))})(this,(function(e,n){"use strict";let p=null;function m(t){p=t}function R(){if(!p)throw new Error("[vue-router-query-sync] Router instance not set. Call setRouter(router) before using composables.");return p}let c={};const f=new Set;let h=!1;function i(t,o){const a=R();o===null?t in c||f.add(t):(c[t]=o,f.delete(t)),h||(h=!0,queueMicrotask(()=>{const d={...a.currentRoute.value.query};f.forEach(l=>{delete d[l]});const v={...d,...c};a.replace({query:v}),c={},f.clear(),h=!1}))}function g(t,o,a,d={}){const l=R().currentRoute,{deps:S=[],context:b}=d,r=b?`${b}_${t}`:t,q=(u,y=!1)=>{if(typeof u=="string"){const s=isNaN(Number(u))?u:Number(u);if(y||s!==o()){a(s);const Q=o();Q!==s&&Q!==null&&i(r,Q)}}else{const s=o();s!==null&&i(r,s)}};n.onBeforeMount(()=>{S.length>0?n.watch(S,()=>q(l.value.query[r])):q(l.value.query[r])}),n.watch(o,u=>{const y=l.value.query[r];u===null?y!==void 0&&i(r,null):y!==String(u)&&i(r,u)}),n.watch(()=>l.value.query[r],u=>{q(u)}),n.onBeforeUnmount(()=>{i(r,null)})}const w={install(t,o){m(o.router)}};e.default=w,e.getRouter=R,e.setRouter=m,e.useQuerySync=g,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|
package/dist/index.mjs
CHANGED
|
@@ -1,57 +1,68 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { onBeforeMount as h, watch as y, onBeforeUnmount as N } from "vue";
|
|
2
|
+
let v = null;
|
|
3
|
+
function x(e) {
|
|
4
|
+
v = e;
|
|
5
|
+
}
|
|
6
|
+
function b() {
|
|
7
|
+
if (!v)
|
|
8
|
+
throw new Error(
|
|
9
|
+
"[vue-router-query-sync] Router instance not set. Call setRouter(router) before using composables."
|
|
10
|
+
);
|
|
11
|
+
return v;
|
|
12
|
+
}
|
|
13
|
+
let i = {};
|
|
14
|
+
const a = /* @__PURE__ */ new Set();
|
|
15
|
+
let q = !1;
|
|
16
|
+
function l(e, u) {
|
|
17
|
+
const s = b();
|
|
18
|
+
u === null ? e in i || a.add(e) : (i[e] = u, a.delete(e)), q || (q = !0, queueMicrotask(() => {
|
|
19
|
+
const c = { ...s.currentRoute.value.query };
|
|
20
|
+
a.forEach((o) => {
|
|
21
|
+
delete c[o];
|
|
17
22
|
});
|
|
18
|
-
const
|
|
19
|
-
...
|
|
20
|
-
...
|
|
23
|
+
const R = {
|
|
24
|
+
...c,
|
|
25
|
+
...i
|
|
21
26
|
};
|
|
22
|
-
|
|
27
|
+
s.replace({ query: R }), i = {}, a.clear(), q = !1;
|
|
23
28
|
}));
|
|
24
29
|
}
|
|
25
|
-
function
|
|
26
|
-
const o = b()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const y = n();
|
|
34
|
-
y !== r && y !== null && s(t, y);
|
|
30
|
+
function g(e, u, s, c = {}) {
|
|
31
|
+
const o = b().currentRoute, { deps: m = [], context: Q } = c, r = Q ? `${Q}_${e}` : e, d = (t, f = !1) => {
|
|
32
|
+
if (typeof t == "string") {
|
|
33
|
+
const n = isNaN(Number(t)) ? t : Number(t);
|
|
34
|
+
if (f || n !== u()) {
|
|
35
|
+
s(n);
|
|
36
|
+
const p = u();
|
|
37
|
+
p !== n && p !== null && l(r, p);
|
|
35
38
|
}
|
|
36
39
|
} else {
|
|
37
|
-
const
|
|
38
|
-
|
|
40
|
+
const n = u();
|
|
41
|
+
n !== null && l(r, n);
|
|
39
42
|
}
|
|
40
43
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}),
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
}),
|
|
47
|
-
() => o.query[
|
|
48
|
-
(
|
|
49
|
-
|
|
44
|
+
h(() => {
|
|
45
|
+
m.length > 0 ? y(m, () => d(o.value.query[r])) : d(o.value.query[r]);
|
|
46
|
+
}), y(u, (t) => {
|
|
47
|
+
const f = o.value.query[r];
|
|
48
|
+
t === null ? f !== void 0 && l(r, null) : f !== String(t) && l(r, t);
|
|
49
|
+
}), y(
|
|
50
|
+
() => o.value.query[r],
|
|
51
|
+
(t) => {
|
|
52
|
+
d(t);
|
|
50
53
|
}
|
|
51
|
-
),
|
|
52
|
-
|
|
54
|
+
), N(() => {
|
|
55
|
+
l(r, null);
|
|
53
56
|
});
|
|
54
57
|
}
|
|
58
|
+
const w = {
|
|
59
|
+
install(e, u) {
|
|
60
|
+
x(u.router);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
55
63
|
export {
|
|
56
|
-
|
|
64
|
+
w as default,
|
|
65
|
+
b as getRouter,
|
|
66
|
+
x as setRouter,
|
|
67
|
+
g as useQuerySync
|
|
57
68
|
};
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAIxC,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,QAEvC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAOlC"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replaceRouterQueue.d.ts","sourceRoot":"","sources":["../../src/utils/replaceRouterQueue.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"replaceRouterQueue.d.ts","sourceRoot":"","sources":["../../src/utils/replaceRouterQueue.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,QAiCpF"}
|