import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue"; import type { RouteParamValueRaw, Router } from "vue-router"; import { tryOnScopeDispose } from "@vueuse/core"; import { customRef, nextTick, toValue, watch } from "vue"; import { useRoute, useRouter } from "vue-router"; const _queue = new WeakMap>(); export type RouteQueryValueRaw = RouteParamValueRaw | string[]; export type RouteHashValueRaw = string | null | undefined; export interface ReactiveRouteOptions { /** * Mode to update the router query, ref is also acceptable * * @default 'replace' */ mode?: MaybeRef<"replace" | "push">; /** * Route instance, use `useRoute()` if not given */ route?: ReturnType; /** * Router instance, use `useRouter()` if not given */ router?: ReturnType; } export interface ReactiveRouteOptionsWithTransform extends ReactiveRouteOptions { /** * Function to transform data before return, or an object with one or both functions: * `get` to transform data before returning, and `set` to transform data before setting */ transform?: | ((val: V) => R) | ({ get?: (value: V) => R; set?: (value: R) => V; }); } export function useRouteQueryWithoutParam( name: string, ): Ref; export function useRouteQueryWithoutParam< T extends RouteQueryValueRaw = RouteQueryValueRaw, K = T, >( name: string, defaultValue?: MaybeRefOrGetter, options?: ReactiveRouteOptionsWithTransform, ): Ref; export function useRouteQueryWithoutParam< T extends RouteQueryValueRaw = RouteQueryValueRaw, K = T, >( name: string, defaultValue?: MaybeRefOrGetter, options: ReactiveRouteOptionsWithTransform = {}, ): Ref { const { mode = "replace", route = useRoute(), router = useRouter(), transform, } = options; let transformGet = (value: T) => value as unknown as K; let transformSet = (value: K) => value as unknown as T; if (typeof transform === "function") { transformGet = transform; } else if (transform) { if (transform.get) { transformGet = transform.get; } if (transform.set) { transformSet = transform.set; } } if (!_queue.has(router)) { _queue.set(router, new Map()); } const _queriesQueue = _queue.get(router)!; let query = route.query[name] as any; tryOnScopeDispose(() => { query = undefined; }); let _trigger: () => void; const proxy = customRef((track, trigger) => { _trigger = trigger; return { get() { track(); return transformGet(query !== undefined ? query : toValue(defaultValue)); }, set(v) { v = transformSet(v); if (query === v) { return; } query = (v === toValue(defaultValue)) ? undefined : v; _queriesQueue.set(name, (v === toValue(defaultValue)) ? undefined : v); trigger(); nextTick(() => { if (_queriesQueue.size === 0) { return; } const newQueries = Object.fromEntries(_queriesQueue.entries()); _queriesQueue.clear(); const { path, query, hash } = route; router[toValue(mode)]({ path, // replace params with path could fix %2F query: { ...query, ...newQueries }, hash, }); }); }, }; }); watch( () => route.query[name], (v) => { if (query === transformGet(v as T)) { return; } query = v; _trigger(); }, { flush: "sync" }, ); return proxy as Ref; }