state-in-url 5.0.0 → 5.1.0

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
@@ -102,6 +102,7 @@ This library is a good alternative for NUQS.
102
102
  - [Installation](#installation)
103
103
  - [useUrlState hook](#useurlstate)
104
104
  - - [Next.js](#useurlstate-hook-for-nextjs)
105
+ - - [Remix](#useurlstate-hook-for-remixjs)
105
106
  - - [React-Router](#useurlstate-hook-for-react-router)
106
107
  - [Other helpers](#other-hooks-and-helpers)
107
108
  - - [`useUrlStateBase` for other routers](#useurlstatebase-hook-for-others-routers)
@@ -454,6 +455,93 @@ function SettingsComponent() {
454
455
 
455
456
  </details>
456
457
 
458
+ ### useUrlState hook for Remix.js
459
+
460
+ API is same as for Next.js version, except can pass options from [NavigateOptions](https://github.com/remix-run/react-router/blob/bc693ed9f39170bda13b9e1b282fb8e9d5925f66/packages/react-router/lib/context.ts#L99) type.
461
+
462
+ [API Docs](packages/urlstate/remix/useUrlState)
463
+
464
+ #### Example
465
+
466
+ ```typescript
467
+ export const form: Form = {
468
+ name: '',
469
+ age: undefined,
470
+ agree_to_terms: false,
471
+ tags: [],
472
+ };
473
+
474
+ type Form = {
475
+ name: string;
476
+ age?: number;
477
+ agree_to_terms: boolean;
478
+ tags: { id: string; value: { text: string; time: Date } }[];
479
+ };
480
+
481
+ ```
482
+
483
+ ```typescript
484
+ import { useUrlState } from 'state-in-url/remix';
485
+
486
+ import { form } from './form';
487
+
488
+ function TagsComponent() {
489
+ const { urlState, setUrl, setState } = useUrlState(form);
490
+
491
+ const onChangeTags = React.useCallback(
492
+ (tag: (typeof tags)[number]) => {
493
+ setUrl((curr) => ({
494
+ ...curr,
495
+ tags: curr.tags.find((t) => t.id === tag.id)
496
+ ? curr.tags.filter((t) => t.id !== tag.id)
497
+ : curr.tags.concat(tag),
498
+ }));
499
+ },
500
+ [setUrl],
501
+ );
502
+
503
+ return (
504
+ <div>
505
+ <Field text="Tags">
506
+ <div className="flex flex-wrap gap-2">
507
+ {tags.map((tag) => (
508
+ <Tag
509
+ active={!!urlState.tags.find((t) => t.id === tag.id)}
510
+ text={tag.value.text}
511
+ onClick={() => onChangeTags(tag)}
512
+ key={tag.id}
513
+ />
514
+ ))}
515
+ </div>
516
+ </Field>
517
+
518
+ <input value={urlState.name}
519
+ onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }}
520
+ // Can update state immediately but sync change to url as needed
521
+ onBlur={() => setUrl()}
522
+ />
523
+ </div>
524
+ );
525
+ }
526
+
527
+ const tags = [
528
+ {
529
+ id: '1',
530
+ value: { text: 'React.js', time: new Date('2024-07-17T04:53:17.000Z') },
531
+ },
532
+ {
533
+ id: '2',
534
+ value: { text: 'Next.js', time: new Date('2024-07-18T04:53:17.000Z') },
535
+ },
536
+ {
537
+ id: '3',
538
+ value: { text: 'TailwindCSS', time: new Date('2024-07-19T04:53:17.000Z') },
539
+ },
540
+ ];
541
+ ```
542
+
543
+ [Example code](packages/example-remix2/app/routes/Form-for-test.tsx)
544
+
457
545
  ### useUrlState hook for React-Router
458
546
 
459
547
  API is same as for Next.js version, except can pass options from [NavigateOptions](https://github.com/remix-run/react-router/blob/bc693ed9f39170bda13b9e1b282fb8e9d5925f66/packages/react-router/lib/context.ts#L99) type.
@@ -0,0 +1 @@
1
+ "use strict";var e=require("../utils.js");const i={date:"\u23F2",undefined:"\u2219undefined"};const u=e.isSafari?330:70;exports.SYMBOLS=i;exports.TIMEOUT=u;
@@ -0,0 +1 @@
1
+ "use strict";var r=require("./constants.js");exports.SYMBOLS=r.SYMBOLS;exports.TIMEOUT=r.TIMEOUT;
@@ -0,0 +1 @@
1
+ "use strict";var i=require("../encoder/encoder.js");var s=require("../utils.js");function d(o,t,r){const n=s.getParams(r);for(const[e,c]of Object.entries(o||{})){const a=t?.[e];if(JSON.stringify(c)!==JSON.stringify(a)){n.set(e,i.encode(c))}}return n.toString()}function u(o,t){return{...t||{},...Object.fromEntries([...s.getParams(o).entries()].map(([r,n])=>{const e=t?.[r];return[r,i.decode(n,e)??e]}))}}exports.decodeState=u;exports.encodeState=d;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./encodeState.js");exports.decodeState=e.decodeState;exports.encodeState=e.encodeState;
@@ -0,0 +1 @@
1
+ "use strict";var n=require("../constants/constants.js");var o=require("../utils.js");function S(e){if(typeof e==="function"||typeof e==="symbol")return"";if(v(e)){return e}return JSON.stringify(structuredClone(e),c).replace(/'/g,"%27").replace(/"/g,"'")}function c(e,r){const i=o.typeOf(r);if(i!=="object"&&i!=="array"){return s(r)}if(i==="object"){const t=r;for(const u of Object.keys(t)){t[u]=c(e,t[u])}return t}if(i==="array"){return r.map(t=>c(e,t))}return r}const s=e=>{switch(o.typeOf(e)){case"date":return n.SYMBOLS.date+new Date(e).toISOString();case"undefined":return n.SYMBOLS.undefined;default:return e}};function p(e,r){return d(e.replace(/'/g,'"').replace(/%27/g,"'"),r)}function d(e,r){try{return JSON.parse(e,a)}catch{return r}}function a(e,r){return typeof r==="string"?f(r):r}const f=e=>{if(e===n.SYMBOLS.undefined)return void 0;if(e?.startsWith?.(n.SYMBOLS.date))return new Date(e.slice(1));return e};const O=new RegExp(`^(${n.SYMBOLS.undefined}|${n.SYMBOLS.date})`);const v=e=>O.test(String(e));exports.decode=p;exports.decodePrimitive=f;exports.encode=S;exports.encodePrimitive=s;exports.parseJSON=d;exports.reviver=a;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./encoder.js");exports.decode=e.decode;exports.encode=e.encode;
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./encoder/encoder.js");var r=require("./encodeState/encodeState.js");var a=require("./next/useUrlState/useUrlState.js");var u=require("./useSharedState/useSharedState.js");var S=require("./useUrlEncode/useUrlEncode.js");var d=require("./useUrlStateBase/useUrlStateBase.js");var t=require("./utils.js");exports.decode=e.decode;exports.encode=e.encode;exports.decodeState=r.decodeState;exports.encodeState=r.encodeState;exports.useUrlState=a.useUrlState;exports.useSharedState=u.useSharedState;exports.useUrlEncode=S.useUrlEncode;exports.useUrlStateBase=d.useUrlStateBase;exports.isSSR=t.isSSR;exports.typeOf=t.typeOf;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState/useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -0,0 +1 @@
1
+ "use strict";var h=require("react");var p=require("next/navigation");var S=require("../../parseSPObj.js");var k=require("../../useUrlStateBase/useUrlStateBase.js");var r=require("../../utils.js");function q(e){return e&&e.__esModule?e:{default:e}}var s=q(h);function _(e,a){const u=a?.useHistory;const l=p.useRouter();const d=s.default.useMemo(()=>({push:(...t)=>{if(u===void 0?true:!!u){r.routerHistory.push(...t)}else{l.push(...t)}},replace:(...t)=>{if(u===void 0?true:!!u){r.routerHistory.replace(...t)}else{l.replace(...t)}}}),[l]);const{state:P,updateState:o,updateUrl:n,reset:c,getState:U}=k.useUrlStateBase(e,d,({parse:t})=>{return r.isSSR?S.parseSPObj(r.filterUnknownParams(e,a?.searchParams),e):t(r.filterUnknownParamsClient(e,a?.searchParams))});const i=s.default.useMemo(()=>({...j,scroll:a?.scroll,replace:a?.replace}),[]);const m=s.default.useCallback((t,b)=>n(t,{...i,...b}),[n]);const f=p.useSearchParams();s.default.useLayoutEffect(()=>{o(r.filterUnknownParams(e,S.parseSPObj(Object.fromEntries([...f.entries()]),e)))},[f]);const v=s.default.useCallback(t=>{c({...i,...t})},[c]);return{setState:o,setUrl:m,urlState:P,reset:v,getState:U}}const j={replace:true,scroll:false};exports.useUrlState=_;
@@ -0,0 +1 @@
1
+ "use strict";var c=require("./encoder/encoder.js");function s(r,e){return{...e,...o(JSON.stringify(r),e)}}function o(r,e){try{return JSON.parse(r,u)}catch{return e}}const u=(r,e)=>{const t=typeof e==="string";const n=t&&c.decode(e?.replace?.(/'/g,'"'));return r&&t?n:e};exports.parseSPObj=s;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState/useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -0,0 +1 @@
1
+ "use strict";var P=require("react");var c=require("react-router-dom");var _=require("../../parseSPObj.js");var g=require("../../useUrlStateBase/useUrlStateBase.js");var u=require("../../utils.js");function k(e){return e&&e.__esModule?e:{default:e}}var s=k(P);function R(e,l){const p=l?.useHistory;const a=s.default.useMemo(()=>({...h,replace:l?.replace,preventScrollReset:l?.preventScrollReset}),[]);const n=c.useNavigate();const v=s.default.useMemo(()=>p?u.routerHistory:{replace:(t,r)=>n(t,{...a,...r}),push:(t,r)=>n(t,{...a,...r})},[n]);const[o]=c.useSearchParams();const d=c.useHref("/");const{state:U,updateState:i,updateUrl:f,getState:m,reset:S}=g.useUrlStateBase(e,v,({parse:t})=>t(u.filterUnknownParamsClient(e,o.entries())),d);const b=s.default.useCallback((t,r)=>f(t,{...a,...r}),[f]);s.default.useEffect(()=>{i(u.assignValue(e,u.filterUnknownParams(e,_.parseSPObj(Object.fromEntries([...o.entries()]),e))))},[o]);const q=s.default.useCallback(t=>{S({...a,...t})},[S]);return{setState:i,setUrl:b,urlState:U,reset:q,getState:m}}const h={replace:true,preventScrollReset:true};exports.useUrlState=R;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState/useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -0,0 +1 @@
1
+ "use strict";var P=require("react");var o=require("@remix-run/react");var _=require("../../parseSPObj.js");var g=require("../../useUrlStateBase/useUrlStateBase.js");var u=require("../../utils.js");function k(e){return e&&e.__esModule?e:{default:e}}var s=k(P);function h(e,l){const p=l?.useHistory;const a=s.default.useMemo(()=>({...j,replace:l?.replace,preventScrollReset:l?.preventScrollReset}),[]);const n=o.useNavigate();const v=s.default.useMemo(()=>p?u.routerHistory:{replace:(t,r)=>n(t,{...a,...r}),push:(t,r)=>n(t,{...a,...r})},[n]);const[c]=o.useSearchParams();const d=o.useHref("/");const{state:U,updateState:i,updateUrl:f,getState:m,reset:S}=g.useUrlStateBase(e,v,({parse:t})=>t(u.filterUnknownParamsClient(e,c.entries())),d);const b=s.default.useCallback((t,r)=>f(t,{...a,...r}),[f]);s.default.useEffect(()=>{i(u.assignValue(e,u.filterUnknownParams(e,_.parseSPObj(Object.fromEntries([...c.entries()]),e))))},[c]);const q=s.default.useCallback(t=>{S({...a,...t})},[S]);return{setState:i,setUrl:b,urlState:U,reset:q,getState:m}}const j={replace:true,preventScrollReset:true};exports.useUrlState=h;
@@ -0,0 +1 @@
1
+ "use strict";const r=new WeakMap;const s=new WeakMap;const a={get(t){return s.get(t)||[]},add(t,e){const n=this.get(t);s.set(t,n.concat(e));return()=>this.remove(t,e)},remove(t,e){const n=this.get(t);const c=n.filter(o=>o!==e);if(c.length){s.set(t,c)}else{s.delete(t)}}};const i={get:function(t){return r.get(t)},set:function(t,e){r.set(t,e)}};exports.stateMap=i;exports.subscribers=a;
@@ -0,0 +1 @@
1
+ "use strict";var f=require("react");function u(e){return e&&e.__esModule?e:{default:e}}var t=u(f);const s=t.default.useInsertionEffect||t.default.useEffect;exports.useInsertionEffect=s;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useSharedState.js");exports.useSharedState=e.useSharedState;
@@ -0,0 +1 @@
1
+ "use strict";var b=require("react");var u=require("../subscribers.js");var M=require("../useInsertionEffect.js");var i=require("../utils.js");function g(s){return s&&s.__esModule?s:{default:s}}var n=g(b);function v(s,a){const t=n.default.useRef(s);const[f,o]=n.default.useState(()=>{if(i.isSSR){return a?a?.():t.current}const e=u.stateMap.get(t.current);if(!e){const r=a?.()||t.current;u.stateMap.set(t.current,r);return r}else{return e}});const l=n.default.useCallback(e=>{const r=u.stateMap.get(t.current);const S=typeof e==="function";const c=S?e(r):{...r,...e};if(i.isEqual(r,c))return void 0;u.stateMap.set(t.current,c);u.subscribers.get(t.current).forEach(p=>p())},[]);M.useInsertionEffect(()=>{const e=()=>{o(u.stateMap.get(t.current)||t.current)};const r=u.subscribers.add(t.current,e);return()=>{r()}},[]);const d=n.default.useCallback(()=>u.stateMap.get(t.current)||t.current,[]);return{state:f,getState:d,setState:l}}exports.useSharedState=v;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlEncode.js");exports.useUrlEncode=e.useUrlEncode;
@@ -0,0 +1 @@
1
+ "use strict";var c=require("react");var r=require("../encodeState/encodeState.js");var l=require("../utils.js");function d(e){return e&&e.__esModule?e:{default:e}}var u=d(c);function i(e){const a=u.default.useCallback((t,s)=>{return l.typeOf(t)==="object"?r.encodeState(t,e,s):""},[e]);const n=u.default.useCallback(t=>r.decodeState(t,e),[e]);return{parse:n,stringify:a}}exports.useUrlEncode=i;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlStateBase.js");exports.useUrlStateBase=e.useUrlStateBase;
@@ -0,0 +1 @@
1
+ "use strict";var y=require("react");var R=require("../constants/constants.js");var T=require("../useInsertionEffect.js");var _=require("../useSharedState/useSharedState.js");var b=require("../useUrlEncode/useUrlEncode.js");var B=require("../utils.js");function L(e){return e&&e.__esModule?e:{default:e}}var u=L(y);function P(e,t,i,l){const{parse:s,stringify:o}=b.useUrlEncode(e);const{state:f,getState:a,setState:r}=_.useSharedState(e,()=>i?.({parse:s})||e);const d=u.default.useRef(void 0);T.useInsertionEffect(()=>{const n=()=>{const p=s(B.filterUnknownParamsClient(e,I()));r(p)};window.addEventListener(q,n);return()=>{window.removeEventListener(q,n);clearTimeout(d.current)}},[r]);const w=u.default.useRef([]);const h=u.default.useCallback((n,p)=>{const S=typeof n==="function"?n(a()):{...a(),...n};const v=o(S,C(e));const U=M(window.location.pathname,l);const m=`${U}${v.length?"?":""}${v}${window.location.hash}`;const $=`${U}${window.location.search}${window.location.hash}`;if(m===$)return;r(S);const{replace:g,...k}=p||{};w.current.push([g?"replace":"push",m,k]);clearTimeout(d.current);const c=w.current.filter(Boolean).at(-1);if(c){queueMicrotask(()=>{d.current=setTimeout(()=>{w.current=[];t[c[0]](c[1],c[2])},R.TIMEOUT)})}},[t,o,a]);const E=u.default.useCallback(n=>{r(e);h(e,n)},[h,r]);return{updateState:r,updateUrl:h,state:f,reset:E,getState:a}}function C(e){const t=Object.keys(e);const i=window.location.search;const l=new URLSearchParams(i);const s=new URLSearchParams;for(const[o,f]of l){!t.includes(o)&&s.set(o,f)}return s}const q="popstate";function I(){return typeof window!=="undefined"&&window.location.search||""}function M(e,t){if(!t||t==="/"){return e}return e.slice(t.length)}exports.useUrlStateBase=P;
package/dist/utils.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";const c=n=>{const t=typeof n;const r=n===null;const e=Array.isArray(n);const s=n instanceof Date;const o=!r&&!s&&!e&&t==="object";return r&&"null"||s&&"date"||e&&"array"||o&&"object"||t};const a=typeof window==="undefined";const f=n=>new URLSearchParams(typeof n==="string"?l(n):n?.toString?.()||"");const l=n=>n.split("?")?.[1]||n||"";const u=(n,t)=>JSON.stringify(n)===JSON.stringify(t);function w(n,t){const r=new URLSearchParams;const e=i(n,[...new URLSearchParams(t||"").entries()]);for(const[s,o]of e){r.set(s,o)}return r.toString()}function y(n,t){return Object.fromEntries(i(n,Object.entries(t||{})))}function i(n,t){const r=Object.keys(n);const e=[];for(let s=0;s<t.length;s++){if(r.includes(t[s]?.[0])){e.push([t[s][0].replace(/\+/g," "),t[s][1]])}}return e}function S(n,t){const r=Object.assign({},n);for(const[e]of Object.entries(n)){const s=e;const o=t[s]!==void 0;r[s]=o?t[s]:n[s]}return r}const g={push:n=>{window&&window.history.pushState(null,"",n)},replace:n=>{window&&window.history.replaceState(null,"",n)}};const p=/apple/i.test(!a&&navigator?.userAgent||"");exports.assignValue=S;exports.filterUnknown=i;exports.filterUnknownParams=y;exports.filterUnknownParamsClient=w;exports.getParams=f;exports.isEqual=u;exports.isSSR=a;exports.isSafari=p;exports.routerHistory=g;exports.typeOf=c;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "state-in-url",
3
- "version": "5.0.0",
3
+ "version": "5.1.0",
4
4
  "description": "Store state in URL as in object, types and structure are preserved, with TS validation. Same API as React.useState, wthout any hasssle or boilerplate. Next.js@14-15, react-router@6-7, and remix@2.",
5
5
  "homepage": "https://state-in-url.dev",
6
6
  "repository": {
@@ -36,47 +36,58 @@
36
36
  "files": [
37
37
  "dist"
38
38
  ],
39
- "main": "dist/index.mjs",
39
+ "main": "./dist/index.js",
40
+ "module": "dist/index.mjs",
40
41
  "exports": {
41
42
  ".": {
42
43
  "types": "./dist/index.d.ts",
43
- "default": "./dist/index.mjs"
44
+ "default": "./dist/index.mjs",
45
+ "require": "./dist/index.js"
44
46
  },
45
47
  "./next": {
46
48
  "types": "./dist/next/index.d.ts",
47
- "default": "./dist/next/index.mjs"
49
+ "default": "./dist/next/index.mjs",
50
+ "require": "./dist/next/index.js"
48
51
  },
49
52
  "./react-router": {
50
53
  "types": "./dist/react-router/index.d.ts",
51
- "default": "./dist/react-router/index.mjs"
54
+ "default": "./dist/react-router/index.mjs",
55
+ "require": "./dist/react-router/index.js"
52
56
  },
53
57
  "./remix": {
54
58
  "types": "./dist/remix/index.d.ts",
55
- "default": "./dist/remix/index.mjs"
59
+ "default": "./dist/remix/index.mjs",
60
+ "require": "./dist/remix/index.js"
56
61
  },
57
62
  "./encodeState": {
58
63
  "types": "./dist/encodeState/index.d.ts",
59
- "default": "./dist/encodeState/index.mjs"
64
+ "default": "./dist/encodeState/index.mjs",
65
+ "require": "./dist/encodeState/index.js"
60
66
  },
61
67
  "./encoder": {
62
68
  "types": "./dist/encoder/index.d.ts",
63
- "default": "./dist/encoder/index.mjs"
69
+ "default": "./dist/encoder/index.mjs",
70
+ "require": "./dist/encoder/index.js"
64
71
  },
65
72
  "./useSharedState": {
66
73
  "types": "./dist/useSharedState/index.d.ts",
67
- "default": "./dist/useSharedState/index.mjs"
74
+ "default": "./dist/useSharedState/index.mjs",
75
+ "require": "./dist/useSharedState/index.js"
68
76
  },
69
77
  "./useUrlEncode": {
70
78
  "types": "./dist/useUrlEncode/index.d.ts",
71
- "default": "./dist/useUrlEncode/index.mjs"
79
+ "default": "./dist/useUrlEncode/index.mjs",
80
+ "require": "./dist/useUrlEncode/index.js"
72
81
  },
73
82
  "./useUrlStateBase": {
74
83
  "types": "./dist/useUrlStateBase/index.d.ts",
75
- "default": "./dist/useUrlStateBase/index.mjs"
84
+ "default": "./dist/useUrlStateBase/index.mjs",
85
+ "require": "./dist/useUrlStateBase/index.js"
76
86
  },
77
87
  "./utils": {
78
88
  "types": "./dist/utils.d.ts",
79
- "default": "./dist/utils.mjs"
89
+ "default": "./dist/utils.mjs",
90
+ "require": "./dist/utils.js"
80
91
  }
81
92
  },
82
93
  "types": "./dist/index.d.ts",