state-in-url 0.2.0 → 0.2.2

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
@@ -2,12 +2,15 @@
2
2
  <h1 align="center">state-in-url</h1>
3
3
 
4
4
 
5
- <p align="center">
5
+ <div align="center">
6
6
  <img src="/assets/logo.svg?raw=true" alt="Library logo"/>
7
- </p>
7
+
8
+ <div>Seamlessly store complex nested state object in URL query parameters with IDE autotomplete and TS validation.</div>
9
+ </div>
8
10
 
9
11
 
10
12
  <div align="center">
13
+ <hr />
11
14
 
12
15
  [![For hire](/assets/hireBadge.svg)](https://www.linkedin.com/in/asmyshlyaev177/)
13
16
 
@@ -19,7 +22,6 @@
19
22
  </div>
20
23
 
21
24
  <div align="center">
22
- Seamlessly store complex React state object in URL query parameters without losing types. Works with Next.js/React.js applications.
23
25
 
24
26
  <h4 align="center">Don't hesitate to open issue if you found a bug</h4>
25
27
 
@@ -46,12 +48,14 @@ Add a ⭐️ to support the project!
46
48
  - 🔗 Shareable URLs with full application state
47
49
  - 🔄 Easy state persistence across page reloads
48
50
  - 🧠 Pass data between unrelated client components
51
+ - 🙃 Can just share the state without changing url
49
52
  - 🧮 Store unsaved user forms in URL
50
53
 
51
54
  # Features
52
55
  - 🧩 **Simple**: Handles complex objects without extra effort
53
- - 📘 **Typescript support and type Safety**: Preserves data types and structure, enhances developer experience with strong typing and JSDoc comments
56
+ - 📘 **Typescript support and type Safety**: Preserves data types and structure, enhances developer experience with IDE suggestions, strong typing and JSDoc comments
54
57
  - ⚛️ **Framework Flexibility**: Separate hooks for Next.js and React.js applications, and functions for pure JS
58
+ - ⚙ **Well tested**: Unit tests and Playwright tests
55
59
  - 🪶 **Lightweight**: Zero dependencies for a smaller footprint
56
60
 
57
61
 
@@ -70,6 +74,7 @@ Add a ⭐️ to support the project!
70
74
  - [Inspiration](#inspiration)
71
75
 
72
76
  ## installation
77
+ ### 1. Install package
73
78
  ```sh
74
79
  # npm
75
80
  npm install --save state-in-url
@@ -78,6 +83,8 @@ yarn add state-in-url
78
83
  # pnpm
79
84
  pnpm add state-in-url
80
85
  ```
86
+ ### 2. Edit tsconfig.json
87
+ set `"moduleResolution": "Node16"` or `"moduleResolution": "NodeNext"` in your `tsconfig.json`
81
88
 
82
89
 
83
90
  ## useUrlState hook for Next.js
@@ -113,11 +120,13 @@ function MyComponent() {
113
120
  Increment (Update URL)
114
121
  </button>
115
122
 
116
- <button onClick={() => updateState({ count: state.count + 1 })}>
123
+ // same api as React.useState
124
+ <button onClick={() => updateState(currState => ({...currState, count: currState.count + 1 }) )}>
117
125
  Increment (Local Only)
118
126
  </button>
119
127
  <button onClick={() => updateUrl()}>
120
128
  Sync changes to url
129
+ // Or don't sync it and just share state
121
130
  </button>
122
131
 
123
132
  <button onClick={() => updateUrl(state)}>
@@ -157,6 +166,20 @@ function SettingsComponent() {
157
166
  }));
158
167
  };
159
168
 
169
+ // sync state to url when idle
170
+ const timer = React.useRef(0 as unknown as NodeJS.Timeout);
171
+ React.useEffect(() => {
172
+ clearTimeout(timer.current);
173
+ timer.current = setTimeout(() => {
174
+ // will compare state by content not by reference and fire update only for new values
175
+ updateUrl(state);
176
+ }, 500);
177
+
178
+ return () => {
179
+ clearTimeout(timer.current);
180
+ };
181
+ }, [state, updateUrl]);
182
+
160
183
  return (
161
184
  <div>
162
185
  <h2>User Settings</h2>
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),t=require("next/navigation");function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=n(e);const c=e=>{const t=null===e,n=Array.isArray(e),r=e instanceof Date;return(t?"null":r&&"date")||n&&"array"||!t&&!r&&!n&&"object"==typeof e&&"object"||typeof e},u=()=>"undefined"==typeof window,o=e=>new URLSearchParams("string"==typeof e?e:e?.toString?.()||""),a=(e,t)=>JSON.stringify(e)===JSON.stringify(t),s={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function i(e){switch(c(e)){case"function":case"symbol":return"";case"date":return s.date+e.toISOString();case"string":return e.match(/^◖/)?encodeURIComponent(e):`${s.string}${encodeURIComponent(e)}`;case"number":return s.number+String(e);case"boolean":return s.boolean+String(e);case"object":case"array":return JSON.stringify(e,f).replaceAll('"',"'");case"null":return s.null;case"undefined":return s.undefined;default:return String(e)}}function l(e,t){return function(e,t){try{return JSON.parse(e,g)}catch{const n=d(e);return n!==p?n:t}}(e.replaceAll("'",'"'),t)}const f=(e,t)=>{const n=c(t);return e&&"object"!==n&&"array"!==n?i(t):t},d=e=>e===s.null?null:e!==s.undefined?e?.startsWith?.(s.number)?Number.parseFloat(e.replace(s.number,"")):e?.startsWith?.(s.boolean)?!!e.includes("true"):e?.startsWith?.(s.date)?new Date(e.slice(1)):e?.startsWith?.(s.string)?decodeURIComponent(e).replace(/^◖/,""):p:void 0,p=Symbol("isError"),g=(e,t)=>{const n="string"==typeof t,r=n&&d(t);return r===p?t:e&&n?r:t};const y=(e,t)=>{const n="string"==typeof t,r=n&&l(t?.replaceAll?.("'",'"'));return r===p?t:e&&n?r:t};function b(e,t){try{return JSON.parse(e,y)}catch{const n=d(e);return n!==p?n:t}}function S(e,t,n){const r=o(n);return Object.entries(e||{}).forEach((([e,n])=>{const c=t?.[e];JSON.stringify(n)!==JSON.stringify(c)&&r.set(e,i(n))})),r.toString()}function h(e,t){return{...t||{},...Object.fromEntries([...o(e).entries()].map((([e,n])=>{const r=t?.[e];return[e,l(n,r)??r]})))}}function m(e){const t=r.default.useCallback((function(t,n){return"object"===c(t)?S(t,e,n):""}),[e]);return{parse:r.default.useCallback((function(t){return h(t,e)}),[e]),stringify:t}}const w=new WeakMap,O=new WeakMap;exports.decode=l,exports.decodeState=h,exports.encode=i,exports.encodeState=S,exports.useUrlEncode=m,exports.useUrlState=function(e,n){const{state:c,getState:o,setState:s,stringify:i}=function(e){const t=r.default.useRef(e),{parse:n,stringify:c}=m(t.current),[o,s]=r.default.useState((()=>{const e=w.get(t.current),r=n(u()?"":window.location.search);return e||w.set(t.current,r),r}));return r.default.useInsertionEffect((()=>{const n=O.get(t.current)||[],r=()=>{s(w.get(t.current)||e)};return O.set(t.current,n.concat(r)),()=>{const e=O.get(t.current)||[];O.set(t.current,e.filter((e=>e!==r)))}}),[]),{state:o,getState:r.default.useCallback((()=>w.get(t.current)||t.current),[]),setState:r.default.useCallback((e=>{const n=w.get(t.current)||t.current;if("function"==typeof e){const r=e(n);if(a(n,r))return!1;w.set(t.current,r),(O.get(t.current)||[]).forEach((e=>{e()}))}else{if(a(n,e))return!1;w.set(t.current,e),(O.get(t.current)||[]).forEach((e=>{e()}))}}),[]),parse:n,stringify:c}}(u()?function(e,t){return{...t,...b(JSON.stringify(e),t)}}(n,e):e),l=t.useRouter();return{updateUrl:r.default.useCallback(((e,t)=>{e&&s(e);const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`,c=i("function"==typeof e?e(o()):o()),u=`${window.location.pathname}${c.length?"?":""}${c}`;if(r!==u){const{replace:e,...n}=t||{};l[e?"replace":"push"](u,{scroll:!1,...n})}}),[l,i,o]),updateState:s,state:c}};
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import t from"react";import{useRouter as e}from"next/navigation";const n=t=>{const e=null===t,n=Array.isArray(t),r=t instanceof Date;return(e?"null":r&&"date")||n&&"array"||!e&&!r&&!n&&"object"==typeof t&&"object"||typeof t},r=()=>"undefined"==typeof window,c=t=>new URLSearchParams("string"==typeof t?t:t?.toString?.()||""),o=(t,e)=>JSON.stringify(t)===JSON.stringify(e),s={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function a(t){switch(n(t)){case"function":case"symbol":return"";case"date":return s.date+t.toISOString();case"string":return t.match(/^◖/)?encodeURIComponent(t):`${s.string}${encodeURIComponent(t)}`;case"number":return s.number+String(t);case"boolean":return s.boolean+String(t);case"object":case"array":return JSON.stringify(t,i).replaceAll('"',"'");case"null":return s.null;case"undefined":return s.undefined;default:return String(t)}}function u(t,e){return function(t,e){try{return JSON.parse(t,g)}catch{const n=l(t);return n!==f?n:e}}(t.replaceAll("'",'"'),e)}const i=(t,e)=>{const r=n(e);return t&&"object"!==r&&"array"!==r?a(e):e},l=t=>t===s.null?null:t!==s.undefined?t?.startsWith?.(s.number)?Number.parseFloat(t.replace(s.number,"")):t?.startsWith?.(s.boolean)?!!t.includes("true"):t?.startsWith?.(s.date)?new Date(t.slice(1)):t?.startsWith?.(s.string)?decodeURIComponent(t).replace(/^◖/,""):f:void 0,f=Symbol("isError"),g=(t,e)=>{const n="string"==typeof e,r=n&&l(e);return r===f?e:t&&n?r:e};const p=(t,e)=>{const n="string"==typeof e,r=n&&u(e?.replaceAll?.("'",'"'));return r===f?e:t&&n?r:e};function d(t,e){try{return JSON.parse(t,p)}catch{const n=l(t);return n!==f?n:e}}function y(t,e,n){const r=c(n);return Object.entries(t||{}).forEach((([t,n])=>{const c=e?.[t];JSON.stringify(n)!==JSON.stringify(c)&&r.set(t,a(n))})),r.toString()}function b(t,e){return{...e||{},...Object.fromEntries([...c(t).entries()].map((([t,n])=>{const r=e?.[t];return[t,u(n,r)??r]})))}}function S(e){const r=t.useCallback((function(t,r){return"object"===n(t)?y(t,e,r):""}),[e]);return{parse:t.useCallback((function(t){return b(t,e)}),[e]),stringify:r}}const m=new WeakMap,h=new WeakMap;function w(n,c){const{state:s,getState:a,setState:u,stringify:i}=function(e){const n=t.useRef(e),{parse:c,stringify:s}=S(n.current),[a,u]=t.useState((()=>{const t=m.get(n.current),e=c(r()?"":window.location.search);return t||m.set(n.current,e),e}));return t.useInsertionEffect((()=>{const t=h.get(n.current)||[],r=()=>{u(m.get(n.current)||e)};return h.set(n.current,t.concat(r)),()=>{const t=h.get(n.current)||[];h.set(n.current,t.filter((t=>t!==r)))}}),[]),{state:a,getState:t.useCallback((()=>m.get(n.current)||n.current),[]),setState:t.useCallback((t=>{const e=m.get(n.current)||n.current;if("function"==typeof t){const r=t(e);if(o(e,r))return!1;m.set(n.current,r),(h.get(n.current)||[]).forEach((t=>{t()}))}else{if(o(e,t))return!1;m.set(n.current,t),(h.get(n.current)||[]).forEach((t=>{t()}))}}),[]),parse:c,stringify:s}}(r()?function(t,e){return{...e,...d(JSON.stringify(t),e)}}(c,n):n),l=e();return{updateUrl:t.useCallback(((t,e)=>{t&&u(t);const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`,c=i("function"==typeof t?t(a()):a()),o=`${window.location.pathname}${c.length?"?":""}${c}`;if(r!==o){const{replace:t,...n}=e||{};l[t?"replace":"push"](o,{scroll:!1,...n})}}),[l,i,a]),updateState:u,state:s}}export{u as decode,b as decodeState,a as encode,y as encodeState,S as useUrlEncode,w as useUrlState};
@@ -2,7 +2,7 @@ import { type JSONCompatible, type DeepReadonly } from './utils';
2
2
  export declare function useState<T>(defaultState: JSONCompatible<T>): {
3
3
  state: JSONCompatible<T>;
4
4
  getState: () => JSONCompatible<T>;
5
- setState: (value: JSONCompatible<T> | Readonly<JSONCompatible<T> extends infer T ? { readonly [P in keyof T]: Readonly<JSONCompatible<T>[P] extends infer T_1 ? { readonly [P_1 in keyof T_1]: Readonly<JSONCompatible<T>[P][P_1] extends infer T_2 ? { readonly [P_2 in keyof T_2]: Readonly<JSONCompatible<T>[P][P_1][P_2] extends infer T_3 ? { readonly [P_3 in keyof T_3]: Readonly<JSONCompatible<T>[P][P_1][P_2][P_3] extends infer T_4 ? { readonly [P_4 in keyof T_4]: Readonly<JSONCompatible<T>[P][P_1][P_2][P_3][P_4] extends infer T_5 ? { readonly [P_5 in keyof T_5]: Readonly<JSONCompatible<T>[P][P_1][P_2][P_3][P_4][P_5] extends infer T_6 ? { readonly [P_6 in keyof T_6]: Readonly<JSONCompatible<T>[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_7 ? { readonly [P_7 in keyof T_7]: Readonly<JSONCompatible<T>[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_8 ? { readonly [P_8 in keyof T_8]: Readonly<JSONCompatible<T>[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_9 ? { readonly [P_9 in keyof T_9]: Readonly<JSONCompatible<T>[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_10 ? { readonly [P_10 in keyof T_10]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never> | ((currState: typeof defaultState) => typeof defaultState)) => false | undefined;
6
- parse: (strOrSearchParams: string | URLSearchParams) => Readonly<NonNullable<JSONCompatible<T>> extends infer T_11 ? { readonly [P_11 in keyof T_11]: Readonly<NonNullable<JSONCompatible<T>>[P_11] extends infer T_12 ? { readonly [P_12 in keyof T_12]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12] extends infer T_13 ? { readonly [P_13 in keyof T_13]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13] extends infer T_14 ? { readonly [P_14 in keyof T_14]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14] extends infer T_15 ? { readonly [P_15 in keyof T_15]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15] extends infer T_16 ? { readonly [P_16 in keyof T_16]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15][P_16] extends infer T_17 ? { readonly [P_17 in keyof T_17]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15][P_16][P_17] extends infer T_18 ? { readonly [P_18 in keyof T_18]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15][P_16][P_17][P_18] extends infer T_19 ? { readonly [P_19 in keyof T_19]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15][P_16][P_17][P_18][P_19] extends infer T_20 ? { readonly [P_20 in keyof T_20]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15][P_16][P_17][P_18][P_19][P_20] extends infer T_21 ? { readonly [P_21 in keyof T_21]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>;
7
- stringify: (state: JSONCompatible<T> | NonNullable<JSONCompatible<T>> | Readonly<NonNullable<JSONCompatible<T>> extends infer T_22 ? { readonly [P_11 in keyof T_22]: Readonly<NonNullable<JSONCompatible<T>>[P_11] extends infer T_23 ? { readonly [P_12 in keyof T_23]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12] extends infer T_24 ? { readonly [P_13 in keyof T_24]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13] extends infer T_25 ? { readonly [P_14 in keyof T_25]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14] extends infer T_26 ? { readonly [P_15 in keyof T_26]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15] extends infer T_27 ? { readonly [P_16 in keyof T_27]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15][P_16] extends infer T_28 ? { readonly [P_17 in keyof T_28]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15][P_16][P_17] extends infer T_29 ? { readonly [P_18 in keyof T_29]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15][P_16][P_17][P_18] extends infer T_30 ? { readonly [P_19 in keyof T_30]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15][P_16][P_17][P_18][P_19] extends infer T_31 ? { readonly [P_20 in keyof T_31]: Readonly<NonNullable<JSONCompatible<T>>[P_11][P_12][P_13][P_14][P_15][P_16][P_17][P_18][P_19][P_20] extends infer T_32 ? { readonly [P_21 in keyof T_32]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>, paramsToKeep?: string | URLSearchParams | undefined) => string;
5
+ setState: (value: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>> | ((currState: typeof defaultState | DeepReadonly<typeof defaultState>) => typeof defaultState | DeepReadonly<typeof defaultState>)) => false | undefined;
6
+ parse: (strOrSearchParams: string | URLSearchParams) => DeepReadonly<JSONCompatible<T>>;
7
+ stringify: (state: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>>, paramsToKeep?: string | URLSearchParams | undefined) => string;
8
8
  };
@@ -20,6 +20,6 @@ import { type JSONCompatible, type DeepReadonly } from './utils';
20
20
  * * Github {@link https://github.com/asmyshlyaev177/state-in-url}
21
21
  */
22
22
  export declare function useUrlEncode<T>(stateShape: JSONCompatible<T>): {
23
- parse: (strOrSearchParams: string | URLSearchParams) => Readonly<NonNullable<JSONCompatible<T>> extends infer T ? { readonly [P in keyof T]: Readonly<NonNullable<JSONCompatible<T>>[P] extends infer T_1 ? { readonly [P_1 in keyof T_1]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1] extends infer T_2 ? { readonly [P_2 in keyof T_2]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2] extends infer T_3 ? { readonly [P_3 in keyof T_3]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3] extends infer T_4 ? { readonly [P_4 in keyof T_4]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4] extends infer T_5 ? { readonly [P_5 in keyof T_5]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4][P_5] extends infer T_6 ? { readonly [P_6 in keyof T_6]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_7 ? { readonly [P_7 in keyof T_7]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_8 ? { readonly [P_8 in keyof T_8]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_9 ? { readonly [P_9 in keyof T_9]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_10 ? { readonly [P_10 in keyof T_10]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>;
24
- stringify: (state: typeof stateShape | NonNullable<typeof stateShape> | DeepReadonly<NonNullable<typeof stateShape>>, paramsToKeep?: string | URLSearchParams) => string;
23
+ parse: (strOrSearchParams: string | URLSearchParams) => DeepReadonly<JSONCompatible<T>>;
24
+ stringify: (state: typeof stateShape | DeepReadonly<typeof stateShape>, paramsToKeep?: string | URLSearchParams) => string;
25
25
  };
@@ -19,9 +19,9 @@ import { DeepReadonly, type JSONCompatible } from './utils';
19
19
  * * Github {@link https://github.com/asmyshlyaev177/state-in-url}
20
20
  */
21
21
  export declare function useUrlState<T>(defaultState: JSONCompatible<T>, sp?: object): {
22
- updateUrl: (value?: NonNullable<JSONCompatible<T>> | Readonly<NonNullable<JSONCompatible<T>> extends infer T ? { readonly [P in keyof T]: Readonly<NonNullable<JSONCompatible<T>>[P] extends infer T_1 ? { readonly [P_1 in keyof T_1]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1] extends infer T_2 ? { readonly [P_2 in keyof T_2]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2] extends infer T_3 ? { readonly [P_3 in keyof T_3]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3] extends infer T_4 ? { readonly [P_4 in keyof T_4]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4] extends infer T_5 ? { readonly [P_5 in keyof T_5]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4][P_5] extends infer T_6 ? { readonly [P_6 in keyof T_6]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_7 ? { readonly [P_7 in keyof T_7]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_8 ? { readonly [P_8 in keyof T_8]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_9 ? { readonly [P_9 in keyof T_9]: Readonly<NonNullable<JSONCompatible<T>>[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_10 ? { readonly [P_10 in keyof T_10]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never> | ((currState: typeof defaultState | DeepReadonly<typeof defaultState>) => typeof defaultState) | undefined, options?: Options) => void;
23
- updateState: (value: JSONCompatible<T> | Readonly<JSONCompatible<T> extends infer T_11 ? { readonly [P_11 in keyof T_11]: Readonly<JSONCompatible<T>[P_11] extends infer T_12 ? { readonly [P_12 in keyof T_12]: Readonly<JSONCompatible<T>[P_11][P_12] extends infer T_13 ? { readonly [P_13 in keyof T_13]: Readonly<JSONCompatible<T>[P_11][P_12][P_13] extends infer T_14 ? { readonly [P_14 in keyof T_14]: Readonly<JSONCompatible<T>[P_11][P_12][P_13][P_14] extends infer T_15 ? { readonly [P_15 in keyof T_15]: Readonly<JSONCompatible<T>[P_11][P_12][P_13][P_14][P_15] extends infer T_16 ? { readonly [P_16 in keyof T_16]: Readonly<JSONCompatible<T>[P_11][P_12][P_13][P_14][P_15][P_16] extends infer T_17 ? { readonly [P_17 in keyof T_17]: Readonly<JSONCompatible<T>[P_11][P_12][P_13][P_14][P_15][P_16][P_17] extends infer T_18 ? { readonly [P_18 in keyof T_18]: Readonly<JSONCompatible<T>[P_11][P_12][P_13][P_14][P_15][P_16][P_17][P_18] extends infer T_19 ? { readonly [P_19 in keyof T_19]: Readonly<JSONCompatible<T>[P_11][P_12][P_13][P_14][P_15][P_16][P_17][P_18][P_19] extends infer T_20 ? { readonly [P_20 in keyof T_20]: Readonly<JSONCompatible<T>[P_11][P_12][P_13][P_14][P_15][P_16][P_17][P_18][P_19][P_20] extends infer T_21 ? { readonly [P_21 in keyof T_21]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never> | ((currState: JSONCompatible<T>) => JSONCompatible<T>)) => false | undefined;
24
- state: JSONCompatible<T>;
22
+ updateUrl: (value?: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>> | ((currState: typeof defaultState | DeepReadonly<typeof defaultState>) => typeof defaultState | DeepReadonly<typeof defaultState>) | undefined, options?: Options) => void;
23
+ updateState: (value: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>> | ((currState: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>>) => JSONCompatible<T> | DeepReadonly<JSONCompatible<T>>)) => false | undefined;
24
+ state: DeepReadonly<JSONCompatible<T>>;
25
25
  };
26
26
  type Router = ReturnType<typeof useRouter>;
27
27
  type RouterOptions = NonNullable<Parameters<Router['push']>[1]>;
@@ -17,9 +17,9 @@ type NotAssignableToJson = bigint | symbol | Function;
17
17
  export type JSONCompatible<T> = unknown extends T ? never : {
18
18
  [P in keyof T]: T[P] extends JSONValue ? T[P] : T[P] extends NotAssignableToJson ? never : JSONCompatible<T[P]>;
19
19
  };
20
- export type DeepReadonly<T> = Readonly<{
21
- readonly [P in keyof T]: DeepReadonly<T[P]>;
22
- }>;
20
+ export type DeepReadonly<T> = T extends Map<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>> : T extends Set<infer S> ? ReadonlySet<DeepReadonly<S>> : T extends object ? {
21
+ readonly [K in keyof T]: DeepReadonly<T[K]>;
22
+ } : T;
23
23
  export declare const getParams: (strOrSearchParams?: string | URLSearchParams) => URLSearchParams;
24
24
  export type UnknownObj = {
25
25
  [key: string]: unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "state-in-url",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Library to store state in browser URL, includes hook for NextJS, hook for pure React, and low level helpers.",
5
5
  "homepage": "https://github.com/asmyshlyaev177/state-in-url#readme",
6
6
  "keywords": [
@@ -41,19 +41,26 @@
41
41
  "files": [
42
42
  "dist"
43
43
  ],
44
- "main": "dist/index.js",
45
- "type": "module",
44
+ "main": "dist/index.cjs",
46
45
  "exports": {
47
46
  "import": {
48
47
  "types": "./dist/index.d.ts",
49
- "default": "./dist/index.js"
48
+ "default": "./dist/index.mjs"
49
+ },
50
+ "node": {
51
+ "types": "./dist/index.d.ts",
52
+ "default": "./dist/index.mjs"
53
+ },
54
+ "require": {
55
+ "types": "./dist/index.d.ts",
56
+ "default": "./dist/index.cjs"
50
57
  },
51
58
  "module": {
52
- "types": "./dist/index.js",
59
+ "types": "./dist/index.mjs",
53
60
  "default": "./dist/index.d.ts"
54
61
  },
55
62
  "default": {
56
- "types": "./dist/index.js",
63
+ "types": "./dist/index.mjs",
57
64
  "default": "./dist/index.d.ts"
58
65
  }
59
66
  },
@@ -80,7 +87,7 @@
80
87
  "cleanup": "rm -rf dist",
81
88
  "setup": "playwright install --with-deps",
82
89
  "cleanupDist": "wireit",
83
- "prepack": "npm run test:unit && npm run build",
90
+ "prepack": "npm run test:unit && npm run build && npm run build:demo",
84
91
  "prepare": "npx husky"
85
92
  },
86
93
  "wireit": {
package/dist/index.js DELETED
@@ -1 +0,0 @@
1
- import t from"react";import{useRouter as e}from"next/navigation";const n=t=>{const e=null===t,n=Array.isArray(t),r=t instanceof Date;return(e?"null":r&&"date")||n&&"array"||!e&&!r&&!n&&"object"==typeof t&&"object"||typeof t},r=()=>"undefined"==typeof window,c=t=>new URLSearchParams("string"==typeof t?t:t?.toString?.()||""),o=(t,e)=>JSON.stringify(t)===JSON.stringify(e),s={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function a(t){switch(n(t)){case"function":case"symbol":return"";case"date":return s.date+t.toISOString();case"string":return t.match(/^◖/)?encodeURIComponent(t):`${s.string}${encodeURIComponent(t)}`;case"number":return s.number+String(t);case"boolean":return s.boolean+String(t);case"object":case"array":return JSON.stringify(t,i).replaceAll('"',"'");case"null":return s.null;case"undefined":return s.undefined;default:return String(t)}}function u(t,e){return function(t,e){try{return JSON.parse(t,g)}catch{const n=l(t);return n!==f?n:e}}(t.replaceAll("'",'"'),e)}const i=(t,e)=>{const r=n(e);return t&&"object"!==r&&"array"!==r?a(e):e},l=t=>t===s.null?null:t!==s.undefined?t?.startsWith?.(s.number)?Number.parseFloat(t.replace(s.number,"")):t?.startsWith?.(s.boolean)?!!t.includes("true"):t?.startsWith?.(s.date)?new Date(t.slice(1)):t?.startsWith?.(s.string)?decodeURIComponent(t).replace(/^◖/,""):f:void 0,f=Symbol("isError"),g=(t,e)=>{const n="string"==typeof e,r=n&&l(e);return r===f?e:t&&n?r:e};function p(t,e){return{...e,...y(JSON.stringify(t),e)}}const d=(t,e)=>{const n="string"==typeof e,r=n&&u(e?.replaceAll?.("'",'"'));return r===f?e:t&&n?r:e};function y(t,e){try{return JSON.parse(t,d)}catch{const n=l(t);return n!==f?n:e}}function b(t,e,n){const r=c(n);return Object.entries(t||{}).forEach((([t,n])=>{const c=e?.[t];JSON.stringify(n)!==JSON.stringify(c)&&r.set(t,a(n))})),r.toString()}function S(t,e){return{...e||{},...Object.fromEntries([...c(t).entries()].map((([t,n])=>{const r=e?.[t];return[t,u(n,r)??r]})))}}function m(e){const r=t.useCallback((function(t,r){return"object"===n(t)?b(t,e,r):""}),[e]);return{parse:t.useCallback((function(t){return S(t,e)}),[e]),stringify:r}}const h=new WeakMap,w=new WeakMap;function O(n,c){const{state:s,getState:a,setState:u,stringify:i}=function(e){const n=t.useRef(e),{parse:c,stringify:s}=m(n.current),[a,u]=t.useState((()=>{const t=h.get(n.current),e=c(r()?"":window.location.search);return t||h.set(n.current,e),e}));return t.useInsertionEffect((()=>{const t=w.get(n.current)||[],r=()=>{u(h.get(n.current)||e)};return w.set(n.current,t.concat(r)),()=>{const t=w.get(n.current)||[];w.set(n.current,t.filter((t=>t!==r)))}}),[]),{state:a,getState:t.useCallback((()=>h.get(n.current)||n.current),[]),setState:t.useCallback((t=>{const e=h.get(n.current)||n.current;if("function"==typeof t){const r=t(e);if(o(e,r))return!1;h.set(n.current,r),(w.get(n.current)||[]).forEach((t=>{t()}))}else{if(o(e,t))return!1;h.set(n.current,t),(w.get(n.current)||[]).forEach((t=>{t()}))}}),[]),parse:c,stringify:s}}(n),l=e();return{updateUrl:t.useCallback(((t,e)=>{const n=window.location.search,r=`${window.location.pathname}${n.length?"?":""}${n}`,c=i("function"==typeof t?t(a()):a()),o=`${window.location.pathname}${c.length?"?":""}${c}`;if(r!==o){const{replace:t,...n}=e||{};l[t?"replace":"push"](o,{scroll:!1,...n})}}),[l,i,a]),updateState:u,state:r()?p(c,n):s}}export{u as decode,S as decodeState,a as encode,b as encodeState,m as useUrlEncode,O as useUrlState};