state-in-url 2.5.2 → 3.0.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.
Files changed (48) hide show
  1. package/README.md +123 -34
  2. package/dist/constants/constants.mjs +1 -1
  3. package/dist/encodeState/encodeState.mjs +1 -1
  4. package/dist/encoder/encoder.mjs +1 -1
  5. package/dist/index.mjs +1 -1
  6. package/dist/next/index.mjs +1 -1
  7. package/dist/next/useUrlState/useUrlState.mjs +1 -1
  8. package/dist/parseSPObj.mjs +1 -0
  9. package/dist/react-router/index.mjs +1 -0
  10. package/dist/react-router/useUrlState/index.mjs +1 -0
  11. package/dist/react-router/useUrlState/useUrlState.mjs +1 -0
  12. package/dist/useSharedState/useSharedState.mjs +1 -1
  13. package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -1
  14. package/dist/utils.mjs +1 -1
  15. package/package.json +154 -49
  16. package/dist/next/parseSPObj.mjs +0 -1
  17. package/dist/package.json +0 -292
  18. package/dist/shared/components/Button.d.ts +0 -8
  19. package/dist/shared/components/Field.d.ts +0 -7
  20. package/dist/shared/components/Input.d.ts +0 -4
  21. package/dist/shared/components/Refresh.d.ts +0 -3
  22. package/dist/shared/components/Tag.d.ts +0 -5
  23. package/dist/shared/components/index.d.ts +0 -4
  24. package/dist/shared/form.d.ts +0 -15
  25. package/dist/shared/index.d.ts +0 -2
  26. package/dist/shared/tailwind.d.ts +0 -2
  27. package/dist/shared/vite-config.d.ts +0 -17
  28. package/dist/tsconfig.tsbuildinfo +0 -1
  29. package/dist/urlstate/constants/constants.d.ts +0 -8
  30. package/dist/urlstate/constants/index.d.ts +0 -1
  31. package/dist/urlstate/encodeState/encodeState.d.ts +0 -32
  32. package/dist/urlstate/encodeState/index.d.ts +0 -1
  33. package/dist/urlstate/encoder/encoder.d.ts +0 -34
  34. package/dist/urlstate/encoder/index.d.ts +0 -1
  35. package/dist/urlstate/index.d.ts +0 -9
  36. package/dist/urlstate/next/index.d.ts +0 -2
  37. package/dist/urlstate/next/parseSPObj.d.ts +0 -9
  38. package/dist/urlstate/next/useUrlState/index.d.ts +0 -1
  39. package/dist/urlstate/next/useUrlState/useUrlState.d.ts +0 -63
  40. package/dist/urlstate/subscribers.d.ts +0 -11
  41. package/dist/urlstate/useInsertionEffect.d.ts +0 -2
  42. package/dist/urlstate/useSharedState/index.d.ts +0 -1
  43. package/dist/urlstate/useSharedState/useSharedState.d.ts +0 -24
  44. package/dist/urlstate/useUrlEncode/index.d.ts +0 -1
  45. package/dist/urlstate/useUrlEncode/useUrlEncode.d.ts +0 -45
  46. package/dist/urlstate/useUrlStateBase/index.d.ts +0 -1
  47. package/dist/urlstate/useUrlStateBase/useUrlStateBase.d.ts +0 -39
  48. package/dist/urlstate/utils.d.ts +0 -26
package/README.md CHANGED
@@ -3,7 +3,7 @@ English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
3
3
  <div align="center">
4
4
  <img src="/assets/Logo_big.png?raw=true" alt="state-in-url logo"/>
5
5
 
6
- <div>Easily share state between unrelated React components, with IDE autocomplete and TS validation. Without any hasssle or boilerplate. </div>
6
+ <div>Easily share complex state between unrelated React components, with IDE autocomplete and TS validation. Without any hasssle or boilerplate. </div>
7
7
  </div>
8
8
 
9
9
  <div align="center">
@@ -13,6 +13,7 @@ English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
13
13
  ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=main)
14
14
  [![Codacy Badge](https://app.codacy.com/project/badge/Grade/73be54068b7f41b0b74a252579ac09ec)](https://app.codacy.com/gh/asmyshlyaev177/state-in-url/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
15
15
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](https://github.com/asmyshlyaev177/state-in-url/)
16
+ [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)]([https://github.com/semantic-release/semantic-release](https://github.com/asmyshlyaev177/state-in-url))
16
17
  ![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/state-in-url.svg)
17
18
  </div>
18
19
 
@@ -22,7 +23,6 @@ English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
22
23
 
23
24
  ![Demo-gif](https://github.com/asmyshlyaev177/state-in-url/blob/main/assets/Demo-gif.gif)
24
25
 
25
-
26
26
  <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">DEMO</a> |
27
27
  <a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a> |
28
28
  <a href ="https://codesandbox.io/p/github/asmyshlyaev177/state-in-url/main?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clyk5bd9y00062v6jspcfrkx7%2522%252C%2522sizes%2522%253A%255B100%252C0%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clyk5bd9x00022v6jyg71cr9e%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clyk5bd9x00042v6jsos2y043%2522%257D%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clyk5bd9x00052v6j5r632b12%2522%257D%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clyk5bd9x00022v6jyg71cr9e%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clyk5bd9w00012v6j83rq3bvo%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clyk5bd9x00022v6jyg71cr9e%2522%252C%2522activeTabId%2522%253A%2522clyk5bd9w00012v6j83rq3bvo%2522%257D%252C%2522clyk5bd9x00052v6j5r632b12%2522%253A%257B%2522id%2522%253A%2522clyk5bd9x00052v6j5r632b12%2522%252C%2522activeTabId%2522%253A%2522clyk5bdjx000b2v6jfc8ae464%2522%252C%2522tabs%2522%253A%255B%257B%2522type%2522%253A%2522SETUP_TASKS%2522%252C%2522id%2522%253A%2522clyk5bdjx000b2v6jfc8ae464%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A2222%252C%2522id%2522%253A%2522clyk5cjbo004d2v6j3u55k74g%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522ENV_SETUP%2522%252C%2522id%2522%253A%2522clyk5h8dp000r2v6j0r7kc7qq%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%257D%252C%2522clyk5bd9x00042v6jsos2y043%2522%253A%257B%2522id%2522%253A%2522clyk5bd9x00042v6jsos2y043%2522%252C%2522activeTabId%2522%253A%2522clyk5cbuv001q2v6joegrxxv6%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clyk5bd9x00032v6jtfhj316o%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522NEW_TERMINAL%2522%257D%252C%257B%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522dev%2522%252C%2522id%2522%253A%2522clyk5cbuv001q2v6joegrxxv6%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522cleanupDist%2522%252C%2522id%2522%253A%2522clyk5dgvo005i2v6jj950sxft%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D" target="_blank">Demo on Codesandbox</a>
@@ -44,7 +44,7 @@ Add a ⭐️ and <a href="https://github.com/asmyshlyaev177" target="_blank">fol
44
44
  - 🧮 Store unsaved user forms in URL
45
45
  - 🙃 Share the state between different components without changing url, good as alternative to signals and other state management tools
46
46
  - 🧠 Sync data between unrelated client components
47
- - 🔗 Shareable URLs with full application state
47
+ - 🔗 Shareable URLs with application state
48
48
  - 🔄 Easy state persistence across page reloads
49
49
 
50
50
  # Features
@@ -59,20 +59,24 @@ Add a ⭐️ and <a href="https://github.com/asmyshlyaev177" target="_blank">fol
59
59
  ## Table of content
60
60
 
61
61
  - [Installation](#installation)
62
- - [`useUrlState` for Next.js](#useurlstate-hook-for-nextjs)
63
- - [`useUrlStateBase` for other routers](#useurlstatebase-hook-for-others-routers)
64
- - [`useSharedState` hook for React.js/Next.js](#usesharedstate-hook-for-reactjs)
65
- - [`useUrlEncode` for React.js](#useurlencode-hook-for-reactjs)
66
- - [`encodeState` and `decodeState` for pure JS usage](#encodestate-and-decodestate-helpers)
67
- - [auto sync state with url](#auto-sync-state)
68
- - [Low-level `encode` and `decode` functions](#encode-and-decode-helpers)
62
+ - [useUrlState hook](#useurlstate)
63
+ - - [Next.js](#useurlstate-hook-for-nextjs)
64
+ - - [React-Router](#useurlstate-hook-for-react-router)
65
+ - [Other helpers](#other-hooks-and-helpers)
66
+ - - [`useUrlStateBase` for other routers](#useurlstatebase-hook-for-others-routers)
67
+ - - [`useSharedState` hook for React.js/Next.js](#usesharedstate-hook-for-reactjs)
68
+ - - [`useUrlEncode` for React.js](#useurlencode-hook-for-reactjs)
69
+ - - [`encodeState` and `decodeState` for pure JS usage](#encodestate-and-decodestate-helpers)
70
+ - - [Low-level `encode` and `decode` functions](#encode-and-decode-helpers)
69
71
  - [Best practices](#best-practices)
70
- - [Gothas](#gothas)
71
- - [Roadmap](#roadmap)
72
- - [Contact & Support](#contact--support)
73
- - [Changelog](#changelog)
74
- - [License](#license)
75
- - [Inspiration](#inspiration)
72
+ - - [Gothas](#gothas)
73
+ - [Other](#other)
74
+ - - [Roadmap](#roadmap)
75
+ - - [Contributing](#contribute-andor-run-locally)
76
+ - - [Contact & Support](#contact--support)
77
+ - - [Changelog](#changelog)
78
+ - - [License](#license)
79
+ - - [Inspiration](#inspiration)
76
80
 
77
81
  ## installation
78
82
 
@@ -92,12 +96,14 @@ pnpm add state-in-url
92
96
  In `tsconfig.json` in `compilerOptions` set `"moduleResolution": "Bundler"`, or`"moduleResolution": "Node16"`, or `"moduleResolution": "NodeNext"`.
93
97
  Possibly need to set `"module": "ES2022"`, or `"module": "ESNext"`
94
98
 
99
+ # useUrlState
100
+
101
+ `useUrlState` is a custom React hook for Next.js/React-Router applications that make communication between client components easy. It allows you to share any complex state and sync it with the URL search parameters, providing a way to persist state across page reloads and share application state via URLs.
102
+
95
103
  ## useUrlState hook for Next.js
96
104
 
97
105
  [Docs](packages/urlstate/next/useUrlState#api)
98
106
 
99
- `useUrlState` is a custom React hook for Next.js applications that make communication between client components easy. It allows you to share any complex state and sync it with the URL search parameters, providing a way to persist state across page reloads and share application state via URLs.
100
-
101
107
  ### Usage examples
102
108
 
103
109
  #### Basic
@@ -229,7 +235,7 @@ const tags = [
229
235
  ];
230
236
  ```
231
237
 
232
- [Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/main/packages/example-nextjs/src/app/Form.tsx)
238
+ [Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/main/packages/example-nextjs14/src/app/Form.tsx)
233
239
 
234
240
  #### Auto sync state
235
241
 
@@ -334,6 +340,90 @@ function SettingsComponent() {
334
340
  }
335
341
  ```
336
342
 
343
+ ## useUrlState hook for React-Router
344
+
345
+ 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.
346
+
347
+ [Docs](packages/urlstate/react-router/useUrlState#api)
348
+
349
+
350
+ ### Example
351
+
352
+ ```typescript
353
+ export const form: Form = {
354
+ name: '',
355
+ age: undefined,
356
+ 'agree to terms': false,
357
+ tags: [],
358
+ };
359
+
360
+ type Form = {
361
+ name: string;
362
+ age?: number;
363
+ 'agree to terms': boolean;
364
+ tags: { id: string; value: { text: string; time: Date } }[];
365
+ };
366
+
367
+ ```
368
+
369
+ ```typescript
370
+ import { useUrlState } from 'state-in-url/react-router';
371
+
372
+ import { form } from './form';
373
+
374
+ function TagsComponent() {
375
+ const { state, updateUrl } = useUrlState({ defaultState: form });
376
+
377
+ const onChangeTags = React.useCallback(
378
+ (tag: (typeof tags)[number]) => {
379
+ updateUrl((curr) => ({
380
+ ...curr,
381
+ tags: curr.tags.find((t) => t.id === tag.id)
382
+ ? curr.tags.filter((t) => t.id !== tag.id)
383
+ : curr.tags.concat(tag),
384
+ }));
385
+ },
386
+ [updateUrl],
387
+ );
388
+
389
+ return (
390
+ <div>
391
+ <Field text="Tags">
392
+ <div className="flex flex-wrap gap-2">
393
+ {tags.map((tag) => (
394
+ <Tag
395
+ active={!!state.tags.find((t) => t.id === tag.id)}
396
+ text={tag.value.text}
397
+ onClick={() => onChangeTags(tag)}
398
+ key={tag.id}
399
+ />
400
+ ))}
401
+ </div>
402
+ </Field>
403
+ </div>
404
+ );
405
+ }
406
+
407
+ const tags = [
408
+ {
409
+ id: '1',
410
+ value: { text: 'React.js', time: new Date('2024-07-17T04:53:17.000Z') },
411
+ },
412
+ {
413
+ id: '2',
414
+ value: { text: 'Next.js', time: new Date('2024-07-18T04:53:17.000Z') },
415
+ },
416
+ {
417
+ id: '3',
418
+ value: { text: 'TailwindCSS', time: new Date('2024-07-19T04:53:17.000Z') },
419
+ },
420
+ ];
421
+ ```
422
+
423
+ [Example code](packages/example-react-router6/src/Form-for-test.tsx)
424
+
425
+ # Other hooks and helpers
426
+
337
427
  ## `useUrlStateBase` hook for others routers
338
428
 
339
429
  Hooks to create your own `useUrlState` hooks with other routers, e.g. react-router or tanstack router.
@@ -369,9 +459,9 @@ function SettingsComponent() {
369
459
 
370
460
  [Docs](packages/urlstate/encoder/README.md)
371
461
 
372
- ## Best Practices
462
+ # Best Practices
373
463
 
374
- - Define your state shape as a constant to ensure consistency
464
+ - Define your state shape as a constant
375
465
  - Use TypeScript for enhanced type safety and autocomplete
376
466
  - Avoid storing sensitive information in URL parameters (SSN, API keys etc)
377
467
  - Use `updateState` for frequent updates and `updateUrl` to sync changes to url
@@ -380,34 +470,33 @@ function SettingsComponent() {
380
470
 
381
471
  ## Gothas
382
472
 
383
- 1. Can pass only serializable values, `Function`, `BigInt` or `Symbol` won't work, probably things like `ArrayBuffer` neither.
473
+ 1. Can pass only serializable values, `Function`, `BigInt` or `Symbol` won't work, probably things like `ArrayBuffer` neither. Everything that can be serialized to JSON will work.
384
474
  2. Vercel servers limit size of headers (query string and other stuff) to **14KB**, so keep your URL state under ~5000 words. <https://vercel.com/docs/errors/URL_TOO_LONG>
385
- 3. Tested with `next.js` 14 with app router, no plans to support pages.
475
+ 3. Tested with `next.js` 14/15 with app router, no plans to support pages.
386
476
 
387
- ## Run locally
477
+ # Other
388
478
 
389
- Clone this repo, run `npm install` and
479
+ ## Contribute and/or run locally
390
480
 
391
- ```sh
392
- npm run dev
393
- ```
481
+ See [Contributing doc](CONTRIBUTING.md)
394
482
 
395
- Go to [localhost:3000](http://localhost:3000)
396
483
 
397
484
  ## Roadmap
398
- - [x] hook for `Next.js`
399
- - [ ] hook for 'react-router`
400
- - [ ] hook for store state in hash ?
485
+
486
+ - [x] hook for `Next.js`
487
+ - [x] hook for 'react-router`
488
+ - [ ] hook for 'remix`
489
+ - [ ] hook for store state in hash ?
401
490
 
402
491
  ## Contact & Support
403
492
 
404
493
  - Create a [GitHub issue](https://github.com/asmyshlyaev177/state-in-url/issues) for bug reports, feature requests, or questions
405
494
 
406
- ## [Changelog](https://github.com/asmyshlyaev177/state-in-url/blob/main/CHANGELOG.md)
495
+ ## [Changelog](CHANGELOG.md)
407
496
 
408
497
  ## License
409
498
 
410
- This project is licensed under the [MIT license](https://github.com/asmyshlyaev177/state-in-url/blob/main/LICENSE).
499
+ This project is licensed under the [MIT license](LICENSE).
411
500
 
412
501
  ## Inspiration
413
502
 
@@ -1 +1 @@
1
- const n={date:"⏲",undefined:"∙undefined",string:"◖",boolean:"🗵",null:"∙null",number:"∓"};export{n as SYMBOLS};
1
+ const e={date:"⏲",undefined:"∙undefined"};export{e as SYMBOLS};
@@ -1 +1 @@
1
- import{encode as t,decode as r}from"../encoder/encoder.mjs";import{getParams as n}from"../utils.mjs";function e(r,e,o){const i=n(o);return Object.entries(r||{}).forEach((([r,n])=>{const o=e?.[r];JSON.stringify(n)!==JSON.stringify(o)&&i.set(r,t(n))})),i.toString()}function o(t,e){return{...e||{},...Object.fromEntries([...n(t).entries()].map((([t,n])=>{const o=e?.[t];return[t,r(n,o)??o]})))}}export{o as decodeState,e as encodeState};
1
+ import{encode as t,decode as n}from"../encoder/encoder.mjs";import{getParams as o}from"../utils.mjs";function r(t){let n,o=t[0],r=1;for(;r<t.length;){const e=t[r],c=t[r+1];if(r+=2,("optionalAccess"===e||"optionalCall"===e)&&null==o)return;"access"===e||"optionalAccess"===e?(n=o,o=c(o)):"call"!==e&&"optionalCall"!==e||(o=c(((...t)=>o.call(n,...t))),n=void 0)}return o}function e(n,e,c){const s=o(c);return Object.entries(n||{}).forEach((([n,o])=>{const c=r([e,"optionalAccess",t=>t[n]]);JSON.stringify(o)!==JSON.stringify(c)&&s.set(n,t(o))})),s.toString()}function c(t,e){return{...e||{},...Object.fromEntries([...o(t).entries()].map((([t,o])=>{const c=r([e,"optionalAccess",n=>n[t]]);return[t,(s=n(o,c),i=()=>c,null!=s?s:i())];var s,i})))}}export{c as decodeState,e as encodeState};
@@ -1 +1 @@
1
- import{SYMBOLS as t}from"../constants/constants.mjs";import{typeOf as e}from"../utils.mjs";function n(n){if(c(n))return n;switch(e(n)){case"function":case"symbol":return"";case"date":return r=n,t.date+new Date(r).toISOString();case"undefined":return t.undefined;default:return JSON.stringify(n).replaceAll('"',"'")}var r}function r(t,e){return i(t.replaceAll("'",'"'),e)}const s=e=>{if(e!==t.undefined)return e?.startsWith?.(t.date)?new Date(e.slice(1)):e===t.null?null:e?.startsWith?.(t.number)?Number.parseFloat(e.replace(t.number,"")):e?.startsWith?.(t.boolean)?!!e.includes("true"):e?.startsWith?.(t.string)?decodeURIComponent(e).replace(/^◖/,""):u},u=Symbol("isError"),a=(t,e)=>{const n="string"==typeof e,r=n&&s(e);return r===u?e:n?r:e};function i(t,e){try{return JSON.parse(t,a)}catch{const n=s(t);return n!==u?n:e}}const o=new RegExp(`^(${t.string}|${t.boolean}|${t.null}|${t.undefined}|${t.number}|${t.date})`),c=t=>o.test(String(t));export{r as decode,s as decodePrimitive,n as encode,u as errorSym,i as parseJSON,a as reviver};
1
+ import{SYMBOLS as t}from"../constants/constants.mjs";import{typeOf as n}from"../utils.mjs";function e(e){if(l(e))return e;switch(n(e)){case"function":case"symbol":return"";case"date":return r=e,t.date+new Date(r).toISOString();case"undefined":return t.undefined;default:return JSON.stringify(e).replaceAll('"',"'")}var r}function r(t,n){return a(t.replaceAll("'",'"'),n)}const o=n=>{if(n!==t.undefined)return function(t){let n,e=t[0],r=1;for(;r<t.length;){const o=t[r],s=t[r+1];if(r+=2,("optionalAccess"===o||"optionalCall"===o)&&null==e)return;"access"===o||"optionalAccess"===o?(n=e,e=s(e)):"call"!==o&&"optionalCall"!==o||(e=s(((...t)=>e.call(n,...t))),n=void 0)}return e}([n,"optionalAccess",t=>t.startsWith,"optionalCall",n=>n(t.date)])?new Date(n.slice(1)):s},s=Symbol("isError"),c=(t,n)=>{const e="string"==typeof n,r=e&&o(n);return r===s?n:e?r:n};function a(t,n){try{return JSON.parse(t,c)}catch(e){const r=o(t);return r!==s?r:n}}const i=new RegExp(`^(${t.undefined}|${t.date})`),l=t=>i.test(String(t));export{r as decode,o as decodePrimitive,e as encode,s as errorSym,a as parseJSON,c as reviver};
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- export{decode,encode}from"./encoder/encoder.mjs";export{decodeState,encodeState}from"./encodeState/encodeState.mjs";export{parseSPObj}from"./next/parseSPObj.mjs";export{useUrlState}from"./next/useUrlState/useUrlState.mjs";export{useSharedState}from"./useSharedState/useSharedState.mjs";export{useUrlEncode}from"./useUrlEncode/useUrlEncode.mjs";export{useUrlStateBase}from"./useUrlStateBase/useUrlStateBase.mjs";export{isSSR,typeOf}from"./utils.mjs";
1
+ export{decode,encode}from"./encoder/encoder.mjs";export{decodeState,encodeState}from"./encodeState/encodeState.mjs";export{useUrlState}from"./next/useUrlState/useUrlState.mjs";export{useSharedState}from"./useSharedState/useSharedState.mjs";export{useUrlEncode}from"./useUrlEncode/useUrlEncode.mjs";export{useUrlStateBase}from"./useUrlStateBase/useUrlStateBase.mjs";export{isSSR,typeOf}from"./utils.mjs";
@@ -1 +1 @@
1
- export{parseSPObj}from"./parseSPObj.mjs";export{useUrlState}from"./useUrlState/useUrlState.mjs";
1
+ export{useUrlState}from"./useUrlState/useUrlState.mjs";
@@ -1 +1 @@
1
- import{useRouter as e,useSearchParams as t}from"next/navigation";import a from"react";import{parseSPObj as r}from"../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{isSSR as c,filterUnknownParamsClient as l}from"../../utils.mjs";function n(n,o){const{_defaultState:u,_searchParams:i,_replace:m}=function(e,t){const a="defaultState"in e,r=a?e.defaultState:e,s=a?e.searchParams:t,c=!!a&&(e.replace??!0);return{_defaultState:r,_searchParams:s,_replace:c}}(n,o),f=e(),{state:p,updateState:S,updateUrl:d,getState:j}=s(u,f,(({parse:e})=>c()?r(function(e,t){const a=Object.keys(e),r=Object.fromEntries(Object.entries(t||{}).map((([e,t])=>[e.replaceAll("+"," "),t])).filter((([e])=>a.includes(e))));return r}(u,i),u):e(l(u)))),b=a.useCallback(((e,t)=>{const a={scroll:!1,replace:m,...t};d(e,a)}),[d,m]),O=t();return a.useEffect((()=>{const e=Object.keys(u),t=Object.fromEntries([...O.entries()].filter((([t])=>e.includes(t))));S(r(t,u))}),[O]),{updateState:S,updateUrl:b,state:p,getState:j}}export{n as useUrlState};
1
+ import{useRouter as t,useSearchParams as e}from"next/navigation";import a from"react";import{parseSPObj as r}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{filterUnknownParams as o,isSSR as m,filterUnknownParamsClient as p}from"../../utils.mjs";function u({defaultState:u,searchParams:i,...l}){const n=t(),{state:f,updateState:S,updateUrl:d,getState:j}=s(u,n,(({parse:t})=>m()?r(o(u,i),u):t(p(u)))),U=a.useCallback(((t,e)=>{const a={...c,...l,...e};d(t,a)}),[d,l]),b=e();return a.useEffect((()=>{S(o(u,r(Object.fromEntries([...b.entries()]),u)))}),[b]),{updateState:S,updateUrl:U,state:f,getState:j}}const c={replace:!0,scroll:!1};export{u as useUrlState};
@@ -0,0 +1 @@
1
+ import{decodePrimitive as n,errorSym as t,decode as o}from"./encoder/encoder.mjs";function l(n,t){return{...t,...r(JSON.stringify(n),t)}}function r(o,l){try{return JSON.parse(o,c)}catch(r){const c=n(o);return c!==t?c:l}}const c=(n,t)=>{const l="string"==typeof t,r=l&&o(function(n){let t,o=n[0],l=1;for(;l<n.length;){const r=n[l],c=n[l+1];if(l+=2,("optionalAccess"===r||"optionalCall"===r)&&null==o)return;"access"===r||"optionalAccess"===r?(t=o,o=c(o)):"call"!==r&&"optionalCall"!==r||(o=c(((...n)=>o.call(t,...n))),t=void 0)}return o}([t,"optionalAccess",n=>n.replaceAll,"optionalCall",n=>n("'",'"')]));return n&&l?r:t};export{l as parseSPObj};
@@ -0,0 +1 @@
1
+ export{useUrlState}from"./useUrlState/useUrlState.mjs";
@@ -0,0 +1 @@
1
+ export{useUrlState}from"./useUrlState.mjs";
@@ -0,0 +1 @@
1
+ import t from"react";import{useNavigate as e,useSearchParams as r}from"react-router-dom";import{parseSPObj as a}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{assignValue as o,filterUnknownParams as m,filterUnknownParamsClient as p}from"../../utils.mjs";function u({defaultState:u,...l}){const f=e(),i=t.useMemo((()=>({replace:(t,e)=>f(t,{...c,...l,...e}),push:(t,e)=>f(t,{...c,...l,...e})})),[f,l]),{state:n,updateState:S,updateUrl:d,getState:j}=s(u,i,(({parse:t})=>t(p(u)))),U=t.useCallback(((t,e)=>{const r={...c,...l,...e};d(t,r)}),[l]),[b]=r();return t.useEffect((()=>{S(o(u,n,m(u,a(Object.fromEntries([...b.entries()]),u))))}),[b]),{updateState:S,updateUrl:U,state:n,getState:j}}const c={replace:!0,preventScrollReset:!0};export{u as useUrlState};
@@ -1 +1 @@
1
- import t from"react";import{stateMap as r,subscribers as e}from"../subscribers.mjs";import{useInsertionEffect as n}from"../useInsertionEffect.mjs";import{isSSR as u,isEqual as c}from"../utils.mjs";function s(s,o){const f=t.useRef(s),[a,i]=t.useState((()=>{if(u())return o?o?.():f.current;const t=r.get(f.current);if(t)return t;{const t=o?.()||f.current;return r.set(f.current,t),t}})),m=t.useCallback((t=>{const n=r.get(f.current),u="function"==typeof t?t(n):{...n,...t};c(n,u)||(r.set(f.current,u),e.get(f.current).forEach((t=>{t()})))}),[]);n((()=>{const t=e.add(f.current,(()=>{i(r.get(f.current)||f.current)}));return()=>{t()}}),[]);return{state:a,getState:t.useCallback((()=>r.get(f.current)||f.current),[]),setState:m}}export{s as useSharedState};
1
+ import t from"react";import{stateMap as r,subscribers as e}from"../subscribers.mjs";import{useInsertionEffect as n}from"../useInsertionEffect.mjs";import{isSSR as c,isEqual as o}from"../utils.mjs";function u(t){let r,e=t[0],n=1;for(;n<t.length;){const c=t[n],o=t[n+1];if(n+=2,("optionalAccess"===c||"optionalCall"===c)&&null==e)return;"access"===c||"optionalAccess"===c?(r=e,e=o(e)):"call"!==c&&"optionalCall"!==c||(e=o(((...t)=>e.call(r,...t))),r=void 0)}return e}function s(s,l){const a=t.useRef(s),[i,f]=t.useState((()=>{if(c())return l?u([l,"optionalCall",t=>t()]):a.current;const t=r.get(a.current);if(t)return t;{const t=u([l,"optionalCall",t=>t()])||a.current;return r.set(a.current,t),t}})),p=t.useCallback((t=>{const n=r.get(a.current),c="function"==typeof t?t(n):{...n,...t};o(n,c)||(r.set(a.current,c),e.get(a.current).forEach((t=>{t()})))}),[]);n((()=>{const t=e.add(a.current,(()=>{f(r.get(a.current)||a.current)}));return()=>{t()}}),[]);return{state:i,getState:t.useCallback((()=>r.get(a.current)||a.current),[]),setState:p}}export{s as useSharedState};
@@ -1 +1 @@
1
- import t from"react";import{useInsertionEffect as e}from"../useInsertionEffect.mjs";import{useSharedState as o}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as n}from"../useUrlEncode/useUrlEncode.mjs";import{filterUnknownParamsClient as a}from"../utils.mjs";function s(s,c,i){const{parse:m,stringify:p}=n(s),{state:w,getState:d,setState:u}=o(s,(()=>i?.({parse:m})||s));e((()=>{const t=()=>{const t=m(a(s));u(t)};return window.addEventListener(r,t),()=>{window.removeEventListener(r,t)}}),[u]);const l=t.useCallback(((t,e)=>{const o=`${window.location.pathname}${window.location.search}${window.location.hash}`,n="function"==typeof t,a=function(t){const e=Object.keys(t),o=window.location.search,n=new URLSearchParams(o),a=new URLSearchParams;return n.forEach(((t,o)=>!e.includes(o)&&a.set(o,t))),a}(s),r=n?t(d()):t?{...d(),...t}:d(),i=p(r,a);u(r);const m=`${window.location.pathname}${i.length?"?":""}${i}${window.location.hash}`;if(o!==m){const{replace:t,...o}=e||{};c[t?"replace":"push"](m,{...o})}}),[c,p,d]);return{updateState:u,updateUrl:l,state:w,getState:d}}const r="popstate";export{s as useUrlStateBase};
1
+ import t from"react";import{useInsertionEffect as e}from"../useInsertionEffect.mjs";import{useSharedState as o}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as n}from"../useUrlEncode/useUrlEncode.mjs";import{filterUnknownParamsClient as a}from"../utils.mjs";function s(s,c,i){const{parse:l,stringify:p}=n(s),{state:u,getState:m,setState:d}=o(s,(()=>function(t){let e,o=t[0],n=1;for(;n<t.length;){const a=t[n],s=t[n+1];if(n+=2,("optionalAccess"===a||"optionalCall"===a)&&null==o)return;"access"===a||"optionalAccess"===a?(e=o,o=s(o)):"call"!==a&&"optionalCall"!==a||(o=s(((...t)=>o.call(e,...t))),e=void 0)}return o}([i,"optionalCall",t=>t({parse:l})])||s));e((()=>{const t=()=>{const t=l(a(s));d(t)};return window.addEventListener(r,t),()=>{window.removeEventListener(r,t)}}),[d]);const w=t.useCallback(((t,e)=>{const o=`${window.location.pathname}${window.location.search}${window.location.hash}`,n="function"==typeof t,a=function(t){const e=Object.keys(t),o=window.location.search,n=new URLSearchParams(o),a=new URLSearchParams;return n.forEach(((t,o)=>!e.includes(o)&&a.set(o,t))),a}(s),r=n?t(m()):t?{...m(),...t}:m(),i=p(r,a);d(r);const l=`${window.location.pathname}${i.length?"?":""}${i}${window.location.hash}`;if(o!==l){const{replace:t,...o}=e||{};c[t?"replace":"push"](l,{...o})}}),[c,p,m]);return{updateState:d,updateUrl:w,state:u,getState:m}}const r="popstate";export{s as useUrlStateBase};
package/dist/utils.mjs CHANGED
@@ -1 +1 @@
1
- const t=t=>{const e=typeof t,n=null===t,r=Array.isArray(t),a=t instanceof Date;return(n?"null":a&&"date")||r&&"array"||!n&&!a&&!r&&"object"===e&&"object"||e},e=()=>"undefined"==typeof window,n=t=>new URLSearchParams("string"==typeof t?r(t):t?.toString?.()||""),r=t=>t.split("?")?.[1]||t||"",a=(t,e)=>JSON.stringify(t)===JSON.stringify(e);function i(t){const e=new URLSearchParams(window.location.search),n=Object.keys(t),r=new URLSearchParams;return[...e.entries()].filter((([t])=>n.includes(t))).forEach((([t,e])=>r.set(t,e))),r.toString()}export{i as filterUnknownParamsClient,n as getParams,a as isEqual,e as isSSR,t as typeOf};
1
+ function t(t){let n,e=t[0],o=1;for(;o<t.length;){const c=t[o],r=t[o+1];if(o+=2,("optionalAccess"===c||"optionalCall"===c)&&null==e)return;"access"===c||"optionalAccess"===c?(n=e,e=r(e)):"call"!==c&&"optionalCall"!==c||(e=r(((...t)=>e.call(n,...t))),n=void 0)}return e}const n=t=>{const n=typeof t,e=null===t,o=Array.isArray(t),c=t instanceof Date;return(e?"null":c&&"date")||o&&"array"||!e&&!c&&!o&&"object"===n&&"object"||n},e=()=>"undefined"==typeof window,o=n=>new URLSearchParams("string"==typeof n?c(n):t([n,"optionalAccess",t=>t.toString,"optionalCall",t=>t()])||""),c=n=>t([n,"access",t=>t.split,"call",t=>t("?"),"optionalAccess",t=>t[1]])||n||"",r=(t,n)=>JSON.stringify(t)===JSON.stringify(n);function a(t){const n=new URLSearchParams;return i(t,[...new URLSearchParams(window.location.search).entries()]).forEach((([t,e])=>n.set(t,e))),n.toString()}function s(t,n){return Object.fromEntries(i(t,Object.entries(n||{})))}function i(t,n){const e=Object.keys(t);return n.filter((([t])=>e.includes(t))).map((([t,n])=>[t.replaceAll("+"," "),n]))}function l(t,n,e){const o=Object.assign({},t,n);return Object.entries(t).forEach((([n])=>{const c=n,r=void 0!==e[c];o[c]=r?e[c]:t[c]})),o}export{l as assignValue,s as filterUnknownParams,a as filterUnknownParamsClient,o as getParams,r as isEqual,e as isSSR,n as typeOf};