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 ADDED
@@ -0,0 +1,175 @@
1
+ # vue-router-query-sync
2
+
3
+ ![Анимация работы](src/assets/demo.gif)
4
+
5
+ > Effortlessly sync Vue Router query parameters with your store or local refs — no boilerplate.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/vue-router-query-sync.svg)](https://www.npmjs.com/package/vue-router-query-sync)
8
+ [![license](https://img.shields.io/npm/l/vue-router-query-sync.svg)](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 { Options } from '../types';
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?: Options): void;
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":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAGjC;;;;;;;;;;;;;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,OAAY,QAyDtB"}
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
@@ -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,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue"),require("vue-router")):typeof define=="function"&&define.amd?define(["exports","vue","vue-router"],e):(n=typeof globalThis<"u"?globalThis:n||self,e(n.VueRouterQuerySync={},n.Vue,n.VueRouter))})(this,(function(n,e,R){"use strict";let f={};const d=new Set;let m=!1;function c(r,s){const a=e.getCurrentInstance();if(!a)throw new Error("useReplaceRouterQueue must be called within a Vue component setup.");const l=a.appContext.config.globalProperties.$router;if(!l)throw new Error("Router not found. Ensure app.use(router) is called in your main.ts.");s===null?r in f||d.add(r):(f[r]=s,d.delete(r)),m||(m=!0,queueMicrotask(()=>{const o={...l.currentRoute.value.query};d.forEach(y=>{delete o[y]});const p={...o,...f};l.replace({query:p}),f={},d.clear(),m=!1}))}function w(r,s,a,l={}){const o=R.useRoute();console.log("!!!!",o);const{deps:p=[],context:y}=l,u=y?`${y}_${r}`:r,q=(t,h=!1)=>{if(typeof t=="string"){const i=isNaN(Number(t))?t:Number(t);if(h||i!==s()){a(i);const Q=s();Q!==i&&Q!==null&&c(u,Q)}}else{const i=s();i!==null&&c(u,i)}};e.onBeforeMount(()=>{p.length>0?e.watch(p,()=>q(o.query[u])):q(o.query[u])}),e.watch(s,t=>{const h=o.query[u];t===null?h!==void 0&&c(u,null):h!==String(t)&&c(u,t)}),e.watch(()=>o.query[u],t=>{q(t)}),e.onBeforeUnmount(()=>{c(u,null)})}n.useQuerySync=w,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}));
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 { getCurrentInstance as h, onBeforeMount as R, watch as q, onBeforeUnmount as w } from "vue";
2
- import { useRoute as b } from "vue-router";
3
- let p = {};
4
- const d = /* @__PURE__ */ new Set();
5
- let Q = !1;
6
- function s(u, n) {
7
- const c = h();
8
- if (!c)
9
- throw new Error("useReplaceRouterQueue must be called within a Vue component setup.");
10
- const l = c.appContext.config.globalProperties.$router;
11
- if (!l)
12
- throw new Error("Router not found. Ensure app.use(router) is called in your main.ts.");
13
- n === null ? u in p || d.add(u) : (p[u] = n, d.delete(u)), Q || (Q = !0, queueMicrotask(() => {
14
- const o = { ...l.currentRoute.value.query };
15
- d.forEach((f) => {
16
- delete o[f];
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 i = {
19
- ...o,
20
- ...p
23
+ const R = {
24
+ ...c,
25
+ ...i
21
26
  };
22
- l.replace({ query: i }), p = {}, d.clear(), Q = !1;
27
+ s.replace({ query: R }), i = {}, a.clear(), q = !1;
23
28
  }));
24
29
  }
25
- function N(u, n, c, l = {}) {
26
- const o = b();
27
- console.log("!!!!", o);
28
- const { deps: i = [], context: f } = l, t = f ? `${f}_${u}` : u, m = (e, a = !1) => {
29
- if (typeof e == "string") {
30
- const r = isNaN(Number(e)) ? e : Number(e);
31
- if (a || r !== n()) {
32
- c(r);
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 r = n();
38
- r !== null && s(t, r);
40
+ const n = u();
41
+ n !== null && l(r, n);
39
42
  }
40
43
  };
41
- R(() => {
42
- i.length > 0 ? q(i, () => m(o.query[t])) : m(o.query[t]);
43
- }), q(n, (e) => {
44
- const a = o.query[t];
45
- e === null ? a !== void 0 && s(t, null) : a !== String(e) && s(t, e);
46
- }), q(
47
- () => o.query[t],
48
- (e) => {
49
- m(e);
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
- ), w(() => {
52
- s(t, null);
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
- N as useQuerySync
64
+ w as default,
65
+ b as getRouter,
66
+ x as setRouter,
67
+ g as useQuerySync
57
68
  };
@@ -0,0 +1,4 @@
1
+ import { Router } from 'vue-router';
2
+ export declare function setRouter(router: Router): void;
3
+ export declare function getRouter(): Router;
4
+ //# sourceMappingURL=router.d.ts.map
@@ -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"}
@@ -1,5 +1,5 @@
1
1
  import { Ref } from 'vue';
2
- export type Options = {
2
+ export type QuerySyncOptions = {
3
3
  deps?: Ref<unknown>[];
4
4
  context?: string;
5
5
  };
@@ -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,OAAO,GAAG;IACpB,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA"}
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":"AAOA,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,QAyCpF"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-router-query-sync",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Vue composable for syncing router query params with store",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",