state-in-url 3.0.0 → 3.0.3

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 (35) hide show
  1. package/README.CN.md +3 -4
  2. package/README.KO.md +3 -4
  3. package/README.md +14 -7
  4. package/dist/constants/constants.d.ts +4 -0
  5. package/dist/constants/index.d.ts +1 -0
  6. package/dist/encodeState/encodeState.d.ts +32 -0
  7. package/dist/encodeState/encodeState.mjs +1 -1
  8. package/dist/encodeState/index.d.ts +1 -0
  9. package/dist/encoder/encoder.d.ts +34 -0
  10. package/dist/encoder/encoder.mjs +1 -1
  11. package/dist/encoder/index.d.ts +1 -0
  12. package/dist/index.d.ts +9 -0
  13. package/dist/next/index.d.ts +1 -0
  14. package/dist/next/useUrlState/index.d.ts +1 -0
  15. package/dist/next/useUrlState/useUrlState.d.ts +61 -0
  16. package/dist/next/useUrlState/useUrlState.mjs +1 -1
  17. package/dist/parseSPObj.d.ts +13 -0
  18. package/dist/parseSPObj.mjs +1 -1
  19. package/dist/react-router/index.d.ts +1 -0
  20. package/dist/react-router/useUrlState/index.d.ts +1 -0
  21. package/dist/react-router/useUrlState/useUrlState.d.ts +52 -0
  22. package/dist/react-router/useUrlState/useUrlState.mjs +1 -1
  23. package/dist/subscribers.d.ts +11 -0
  24. package/dist/useInsertionEffect.d.ts +2 -0
  25. package/dist/useSharedState/index.d.ts +1 -0
  26. package/dist/useSharedState/useSharedState.d.ts +24 -0
  27. package/dist/useSharedState/useSharedState.mjs +1 -1
  28. package/dist/useUrlEncode/index.d.ts +1 -0
  29. package/dist/useUrlEncode/useUrlEncode.d.ts +45 -0
  30. package/dist/useUrlStateBase/index.d.ts +1 -0
  31. package/dist/useUrlStateBase/useUrlStateBase.d.ts +42 -0
  32. package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -1
  33. package/dist/utils.d.ts +28 -0
  34. package/dist/utils.mjs +1 -1
  35. package/package.json +10 -5
package/README.CN.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <hr />
11
11
 
12
12
  [![npm](https://img.shields.io/npm/v/state-in-url.svg)](https://www.npmjs.com/package/state-in-url)
13
- ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=main)
13
+ ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=master)
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
16
  ![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/state-in-url.svg)
@@ -24,7 +24,6 @@
24
24
 
25
25
  <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">演示</a> |
26
26
  <a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a> |
27
- <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>
28
27
 
29
28
  <hr />
30
29
 
@@ -373,11 +372,11 @@ npm run dev
373
372
 
374
373
  - 创建[GitHub issue](https://github.com/asmyshlyaev177/state-in-url/issues)报告bug、提出功能请求或提问
375
374
 
376
- ## [更新日志](https://github.com/asmyshlyaev177/state-in-url/blob/main/CHANGELOG.md)
375
+ ## [更新日志](https://github.com/asmyshlyaev177/state-in-url/blob/master/CHANGELOG.md)
377
376
 
378
377
  ## 许可证
379
378
 
380
- 本项目采用[MIT许可证](https://github.com/asmyshlyaev177/state-in-url/blob/main/LICENSE)。
379
+ 本项目采用[MIT许可证](https://github.com/asmyshlyaev177/state-in-url/blob/master/LICENSE)。
381
380
 
382
381
  ## 灵感来源
383
382
 
package/README.KO.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
 
15
15
  [![npm](https://img.shields.io/npm/v/state-in-url.svg)](https://www.npmjs.com/package/state-in-url)
16
- ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=main)
16
+ ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=master)
17
17
  [![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)
18
18
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](https://github.com/asmyshlyaev177/state-in-url/)
19
19
  ![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/state-in-url.svg)
@@ -28,7 +28,6 @@
28
28
 
29
29
  <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">데모</a> |
30
30
  <a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a> |
31
- <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>
32
31
 
33
32
 
34
33
  <hr />
@@ -381,11 +380,11 @@ npm run dev
381
380
 
382
381
  - 버그 리포트, 기능 요청 또는 질문을 위해 [GitHub 이슈](https://github.com/asmyshlyaev177/state-in-url/issues)를 생성하세요
383
382
 
384
- ## [변경 로그](https://github.com/asmyshlyaev177/state-in-url/blob/main/CHANGELOG.md)
383
+ ## [변경 로그](https://github.com/asmyshlyaev177/state-in-url/blob/master/CHANGELOG.md)
385
384
 
386
385
  ## 라이선스
387
386
 
388
- 이 프로젝트는 [MIT 라이선스](https://github.com/asmyshlyaev177/state-in-url/blob/main/LICENSE)에 따라 라이선스가 부여됩니다.
387
+ 이 프로젝트는 [MIT 라이선스](https://github.com/asmyshlyaev177/state-in-url/blob/master/LICENSE)에 따라 라이선스가 부여됩니다.
389
388
 
390
389
  ## 영감
391
390
 
package/README.md CHANGED
@@ -10,41 +10,48 @@ English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
10
10
  <hr />
11
11
 
12
12
  [![npm](https://img.shields.io/npm/v/state-in-url.svg)](https://www.npmjs.com/package/state-in-url)
13
- ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=main)
13
+ ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=master)
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
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))
17
17
  ![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/state-in-url.svg)
18
+
19
+ [![StandWithUkraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/master/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua/)
18
20
  </div>
19
21
 
20
22
  <div align="center">
21
23
 
22
24
  <h4 align="center">Don't hesitate to open an issue if you found a bug, or for requesting features</h4>
23
25
 
24
- ![Demo-gif](https://github.com/asmyshlyaev177/state-in-url/blob/main/assets/Demo-gif.gif)
26
+ ![Demo-gif](https://github.com/asmyshlyaev177/state-in-url/blob/master/assets/Demo-gif.gif)
25
27
 
26
28
  <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">DEMO</a> |
27
29
  <a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a> |
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>
29
30
 
30
31
  <hr />
31
32
 
32
33
  Add a ⭐️ and <a href="https://github.com/asmyshlyaev177" target="_blank">follow me</a> to support the project!
33
34
 
35
+ Share if it useful for you.
36
+ [FB](https://www.facebook.com/sharer.php?u=https://github.com/asmyshlyaev177/state-in-url)
37
+ [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://github.com/asmyshlyaev177/state-in-url)
38
+ [X.com](https://twitter.com/intent/tweet?&url=https://github.com/asmyshlyaev177/state-in-url)
39
+ [VK](http://vk.com/share.php?url=https://github.com/asmyshlyaev177/state-in-url)
40
+
34
41
  <hr />
35
42
 
36
43
  </div>
37
44
 
38
45
  ## Why use state-in-url?
39
46
 
40
- `state-in-url` Simple state management with optional URL sync. Share complex states between unrelated React components, TS-friendly, NextJS compatible. Most of users don't care about URL, so, can use it to store your app state.
47
+ `state-in-url` Simple state management with optional URL sync. Good for implementing deep links. Share complex states between unrelated React components, TS-friendly, NextJS compatible. Most of users don't care about URL, so, can use it to store your app state.
41
48
 
42
49
  # Use cases
43
50
 
44
51
  - 🧮 Store unsaved user forms in URL
45
52
  - 🙃 Share the state between different components without changing url, good as alternative to signals and other state management tools
46
53
  - 🧠 Sync data between unrelated client components
47
- - 🔗 Shareable URLs with application state
54
+ - 🔗 Shareable URLs with application state (Deep linking, URL state synchronization)
48
55
  - 🔄 Easy state persistence across page reloads
49
56
 
50
57
  # Features
@@ -53,7 +60,7 @@ Add a ⭐️ and <a href="https://github.com/asmyshlyaev177" target="_blank">fol
53
60
  - 📘 **Typescript support and type Safety**: Preserves data types and structure, good developer experience with IDE suggestions, strong typing and JSDoc comments
54
61
  - ⚛️ **Framework Flexibility**: Separate hooks for Next.js and React.js applications, and functions for pure JS
55
62
  - ⚙ **Well tested**: Unit tests and Playwright tests, high quality and support
56
- - ⚡ **Fast**: Minimal rerenders
63
+ - ⚡ **Fast**: Minimal rerenders, less than [1ms](https://github.com/asmyshlyaev177/state-in-url/blob/87c8c7c995c5cd7d9e7aa039c30bfe64b24abe4b/packages/urlstate/encoder/encoder.test.ts#L185) to encode and decode an object
57
64
  - 🪶 **Lightweight**: Zero dependencies for a smaller footprint
58
65
 
59
66
  ## Table of content
@@ -235,7 +242,7 @@ const tags = [
235
242
  ];
236
243
  ```
237
244
 
238
- [Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/main/packages/example-nextjs14/src/app/Form.tsx)
245
+ [Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/master/packages/example-nextjs14/src/app/Form.tsx)
239
246
 
240
247
  #### Auto sync state
241
248
 
@@ -0,0 +1,4 @@
1
+ export declare const SYMBOLS: {
2
+ readonly date: "⏲";
3
+ readonly undefined: "∙undefined";
4
+ };
@@ -0,0 +1 @@
1
+ export * from "./constants";
@@ -0,0 +1,32 @@
1
+ import { type JSONCompatible, type UnknownObj } from "../utils";
2
+ /**
3
+ * Encodes the state object into a URL query string.
4
+ *
5
+ * @param {JSONCompatible<T>} state - The state object to encode.
6
+ * @param {JSONCompatible<T>} [defaults] - Optional default values for the state object.
7
+ * @return {string} The encoded URL query string representing the state object.
8
+ *
9
+ * * Example:
10
+ * ```ts
11
+ * export const form = { name: '' };
12
+ * encodeState({ name: 'test' }, form, 'someExistingParam=123');
13
+ * ```
14
+ *
15
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/encodeState#encodestate}
16
+ */
17
+ export declare function encodeState<T extends JSONCompatible>(state: T, defaults?: T, paramsToKeep?: string | URLSearchParams): string;
18
+ /**
19
+ * Decodes a URI string into an object of type T.
20
+ *
21
+ * @param {string} uriString - The URI string to decode.
22
+ * @return {T} The decoded object of type T.
23
+ *
24
+ * * Example:
25
+ * ```ts
26
+ * export const form = { name: '' };
27
+ * decodeState('key=value&name=Alex', form);
28
+ * ```
29
+ *
30
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/encodeState#decodestate}
31
+ */
32
+ export declare function decodeState<T extends JSONCompatible>(uriString: string | URLSearchParams, defaults?: T): undefined extends T ? UnknownObj : T;
@@ -1 +1 @@
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
+ 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};
@@ -0,0 +1 @@
1
+ export { decodeState, encodeState } from "./encodeState";
@@ -0,0 +1,34 @@
1
+ import { type JSONCompatible } from "../utils";
2
+ /**
3
+ * Encode any JSON serializable value to URL friendly string
4
+ *
5
+ * @param {unknown} payload any value to serialize
6
+ * @returns {string} URLSearchParams compatible value string
7
+ *
8
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/encoder#encode}
9
+ */
10
+ export declare function encode(payload: unknown): string;
11
+ export type Primitive = Exclude<ReturnType<typeof decodePrimitive>, typeof errorSym>;
12
+ /**
13
+ * Decode value part of URLSearchParams back to JS value
14
+ *
15
+ * @param {string} payload URLSearchParams compatible value string
16
+ * @param {any} fallback optional fallback value
17
+ * @returns {unknown} decoded object
18
+ *
19
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/encoder#decode}
20
+ */
21
+ export declare function decode<T>(payload: string, fallback?: T): JSONCompatible | Primitive;
22
+ export declare const decodePrimitive: (str: string) => Date | typeof errorSym | undefined;
23
+ export declare const errorSym: unique symbol;
24
+ export declare const reviver: (_key: string, value: unknown) => unknown;
25
+ /**
26
+ * Parses a JSON string into a TypeScript object.
27
+ *
28
+ * @param {string} jsonString - The JSON string to parse.
29
+ * @param {T} [fallbackValue] - The fallback value to use if parsing fails.
30
+ * @return {T | Primitive | undefined} - The parsed object or a primitive value, or undefined if parsing fails.
31
+ *
32
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url}
33
+ */
34
+ export declare function parseJSON<T extends JSONCompatible>(jsonString: string, fallbackValue?: T): T | Primitive | undefined;
@@ -1 +1 @@
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};
1
+ import{SYMBOLS as t}from"../constants/constants.mjs";import{typeOf as e}from"../utils.mjs";function n(n){if(a(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 o(t.replaceAll("'",'"'),e)}const s=e=>{if(e!==t.undefined)return e?.startsWith?.(t.date)?new Date(e.slice(1)):i},i=Symbol("isError"),c=(t,e)=>{const n="string"==typeof e,r=n&&s(e);return r===i?e:n?r:e};function o(t,e){try{return JSON.parse(t,c)}catch{const n=s(t);return n!==i?n:e}}const u=new RegExp(`^(${t.undefined}|${t.date})`),a=t=>u.test(String(t));export{r as decode,s as decodePrimitive,n as encode,i as errorSym,o as parseJSON,c as reviver};
@@ -0,0 +1 @@
1
+ export { decode, encode } from "./encoder";
@@ -0,0 +1,9 @@
1
+ import { decode, encode } from "./encoder";
2
+ import { decodeState, encodeState } from "./encodeState";
3
+ import { useUrlState } from "./next";
4
+ import { useSharedState } from "./useSharedState";
5
+ import { useUrlEncode } from "./useUrlEncode";
6
+ import { useUrlStateBase } from "./useUrlStateBase";
7
+ import { isSSR, type JSONCompatible, type Type, typeOf } from "./utils";
8
+ export { decode, decodeState, encode, encodeState, isSSR, typeOf, useSharedState, useUrlEncode, useUrlState, useUrlStateBase, };
9
+ export type { JSONCompatible, Type };
@@ -0,0 +1 @@
1
+ export { useUrlState } from "./useUrlState";
@@ -0,0 +1 @@
1
+ export * from "./useUrlState";
@@ -0,0 +1,61 @@
1
+ import { useRouter } from "next/navigation";
2
+ import { type DeepReadonly, type JSONCompatible } from "../../utils";
3
+ /**
4
+ * NextJS hook. Returns `state`, `updateState`, and `updateUrl` functions
5
+ *
6
+ * @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
7
+ * @param {?SearchParams<T>} [searchParams] searchParams from Next server component
8
+ *
9
+ * * Example:
10
+ * ```ts
11
+ * export const form = { name: '', age: 0 };
12
+ * const { state, updateState, updateUrl } = useUrlState({ defaultState: form });
13
+ * // for nextjs seerver components
14
+ * // const { state, updateState, updateUrl } = useUrlState({ defaultState: form, searchParams });
15
+ *
16
+ * updateState({ name: 'test' });
17
+ * // by default it's uses router.push with scroll: false
18
+ * updateUrl({ name: 'test' }, { replace: true, scroll: true });
19
+ * // similar to React.useState
20
+ * updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: true });
21
+ * ```
22
+ *
23
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#api}
24
+ */
25
+ export declare function useUrlState<T extends JSONCompatible>({ defaultState, searchParams, ...opts }: {
26
+ defaultState: T;
27
+ searchParams?: object;
28
+ replace?: boolean;
29
+ scroll?: boolean;
30
+ }): {
31
+ /**
32
+ * * Example:
33
+ * ```ts
34
+ * updateState({ name: 'test' });
35
+ * // or
36
+ * updateState(curr => ({ ...curr, name: 'test' }) );
37
+ * ```
38
+ *
39
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updatestate}
40
+ */
41
+ updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
42
+ /**
43
+ * * Example:
44
+ * ```ts
45
+ * updateUrl({ name: 'test' });
46
+ * // or
47
+ * updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: false } );
48
+ * ```
49
+ *
50
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updateurl}
51
+ */
52
+ updateUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: Options) => void;
53
+ state: DeepReadonly<DeepReadonly<T>>;
54
+ getState: () => T;
55
+ };
56
+ type Router = ReturnType<typeof useRouter>;
57
+ type RouterOptions = NonNullable<Parameters<Router["push"]>[1] | Parameters<Router["replace"]>[1]>;
58
+ interface Options extends RouterOptions {
59
+ replace?: boolean;
60
+ }
61
+ export {};
@@ -1 +1 @@
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};
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{isSSR as o,filterUnknownParams 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})=>o()?r(m(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(m(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,13 @@
1
+ import { type JSONCompatible } from "./utils";
2
+ /**
3
+ * Parses the Next.js server-side `searchParams` into an object.
4
+ *
5
+ * Or with react-router
6
+ * {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState/useUrlState.ts}
7
+ *
8
+ *
9
+ * @param {object | undefined} sp - The server-side searchParams object to parse.
10
+ * @param {T} defaults - The default values to use if parsing fails.
11
+ * @return {T} - The parsed object or the default values.
12
+ */
13
+ export declare function parseSPObj<T extends JSONCompatible>(sp: object | undefined, defaults: T): T;
@@ -1 +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};
1
+ import{decodePrimitive as r,errorSym as t,decode as n}from"./encoder/encoder.mjs";function e(r,t){return{...t,...o(JSON.stringify(r),t)}}function o(n,e){try{return JSON.parse(n,c)}catch{const o=r(n);return o!==t?o:e}}const c=(r,t)=>{const e="string"==typeof t,o=e&&n(t?.replaceAll?.("'",'"'));return r&&e?o:t};export{e as parseSPObj};
@@ -0,0 +1 @@
1
+ export { useUrlState } from "./useUrlState";
@@ -0,0 +1 @@
1
+ export { useUrlState } from "./useUrlState";
@@ -0,0 +1,52 @@
1
+ import { type NavigateOptions } from "react-router-dom";
2
+ import { type DeepReadonly, type JSONCompatible } from "../../utils";
3
+ /**
4
+ * React-router hook. Returns `state`, `updateState`, and `updateUrl` functions
5
+ *
6
+ * @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
7
+ * @param {NavigateOptions} [NavigateOptions] See type from `react-router-dom`
8
+ * * Example:
9
+ * ```ts
10
+ * export const form = { name: '', age: 0 };
11
+ * const { state, updateState, updateUrl } = useUrlState({ defaultState: form, replace: false, preventScrollReset: false });
12
+ *
13
+ * updateState({ name: 'test' });
14
+ * updateUrl({ name: 'test' }, { replace: true });
15
+ * // similar to React.useState
16
+ * updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true });
17
+ * ```
18
+ *
19
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#api}
20
+ */
21
+ export declare function useUrlState<T extends JSONCompatible>({ defaultState, ...initOpts }: {
22
+ defaultState: T;
23
+ } & NavigateOptions): {
24
+ /**
25
+ * * Example:
26
+ * ```ts
27
+ * updateState({ name: 'test' });
28
+ * // or
29
+ * updateState(curr => ({ ...curr, name: 'test' }) );
30
+ * // can pass optional React-Router `NavigateOptions`
31
+ * updateState(curr => ({ ...curr, name: 'test', preventScrollReset: false }) );
32
+ * ```
33
+ *
34
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updatestate}
35
+ */
36
+ updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
37
+ /**
38
+ * * Example:
39
+ * ```ts
40
+ * updateUrl({ name: 'test' });
41
+ * // or
42
+ * updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true } );
43
+ * // can pass optional React-Router `NavigateOptions`
44
+ * updateState(curr => ({ ...curr, name: 'test', preventScrollReset: false }) );
45
+ * ```
46
+ *
47
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updateurl}
48
+ */
49
+ updateUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: NavigateOptions) => void;
50
+ state: DeepReadonly<DeepReadonly<T>>;
51
+ getState: () => T;
52
+ };
@@ -1 +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
+ 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{filterUnknownParamsClient as o,assignValue as m,filterUnknownParams 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(o(u)))),U=t.useCallback(((t,e)=>{const r={...c,...l,...e};d(t,r)}),[l]),[b]=r();return t.useEffect((()=>{S(m(u,n,p(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};
@@ -0,0 +1,11 @@
1
+ import { type JSONCompatible } from "./utils";
2
+ export declare const subscribers: {
3
+ get(obj: JSONCompatible): Cb[];
4
+ add(obj: JSONCompatible, cb: Cb): () => void;
5
+ remove(obj: JSONCompatible, cb: Cb): void;
6
+ };
7
+ export declare const stateMap: {
8
+ get: <T extends JSONCompatible>(objKey: T) => T;
9
+ set: <T extends JSONCompatible>(objKey: T, value: T) => void;
10
+ };
11
+ export type Cb = () => void;
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare const useInsertionEffect: typeof React.useInsertionEffect;
@@ -0,0 +1 @@
1
+ export { useSharedState } from "./useSharedState";
@@ -0,0 +1,24 @@
1
+ import { type DeepReadonly, type JSONCompatible } from "../utils";
2
+ /**
3
+ * Custom React hook for sharing state between unrelated components.
4
+ *
5
+ * @param {T} defaultState - The default state object
6
+ * @param {() => T} [_getInitial] - Optional function to get initial state
7
+ * @returns {Object} Object containing `state`, `getState`, and `setState` functions
8
+ *
9
+ * * Example:
10
+ * ```ts
11
+ * export const form = { name: '', age: 0 };
12
+ * const { state, setState } = useSharedState(form);
13
+ *
14
+ * setState({ name: 'test' });
15
+ * // OR
16
+ * setState(curr => ({ ...curr, name: 'test' }))
17
+ * ```
18
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/useSharedState}
19
+ */
20
+ export declare function useSharedState<T extends JSONCompatible>(defaultState: T, _getInitial?: () => T): {
21
+ state: T;
22
+ getState: () => T;
23
+ setState: (value: (Partial<T> | Partial<DeepReadonly<T>>) | ((currState: T) => T)) => void;
24
+ };
@@ -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 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
+ 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};
@@ -0,0 +1 @@
1
+ export { useUrlEncode } from "./useUrlEncode";
@@ -0,0 +1,45 @@
1
+ import { type JSONCompatible } from "../utils";
2
+ /**
3
+ * A hook that returns stringify and parse functions for encoding and decoding state
4
+ * to and from URL search parameters.
5
+ *
6
+ * @template T - The type of the state shape
7
+ * @param {JSONCompatible<T>} [stateShape] Fallback values for state
8
+ * @returns {} `stringify` and `parse` functions
9
+ *
10
+ * * Example:
11
+ * ```ts
12
+ * export const form = { name: '' };
13
+ * const { parse, stringify } = useUrlEncode(form);
14
+ *
15
+ * stringify({ name: 'John' }, 'someExistingParamToKeep=123');
16
+ * // by default it's uses router.push with scroll: false
17
+ * parse('name=Tom');
18
+ * ```
19
+ *
20
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/useUrlEncode#api}
21
+ */
22
+ export declare function useUrlEncode<T extends JSONCompatible>(stateShape: T): {
23
+ /**
24
+ Parse URL query string to state object
25
+ * * Example:
26
+ * ```ts
27
+ const state = parse("name='Tom'");
28
+ console.log(state); // Output: { name: 'Tom' }
29
+ * ```
30
+ *
31
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/useUrlEncode#parse}
32
+ */
33
+ parse: (strOrSearchParams: string | URLSearchParams) => typeof stateShape;
34
+ /**
35
+ Converts a state object to a URL query string.
36
+ * * Example:
37
+ * ```ts
38
+ const queryString = stringify({ name: 'John' }, 'someExistingParamToKeep=123');
39
+ console.log(queryString); // Output: name='John'&someExistingParamToKeep=123
40
+ * ```
41
+ *
42
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/useUrlEncode#stringify}
43
+ */
44
+ stringify: (state: typeof stateShape, paramsToKeep?: string | URLSearchParams) => string;
45
+ };
@@ -0,0 +1 @@
1
+ export { useUrlStateBase } from "./useUrlStateBase";
@@ -0,0 +1,42 @@
1
+ import { useUrlEncode } from "../useUrlEncode";
2
+ import { type DeepReadonly, type JSONCompatible } from "../utils";
3
+ /**
4
+ * A custom React hook to create custom hooks.
5
+ *
6
+ * @template T extends JSONCompatible
7
+ * @param {T} defaultState - An object representing the default state values.
8
+ * @param {Router} router - Router object with [push] and [replace] methods.
9
+ * @param {Function} [getInitialState] - Optional function to get the initial state, passes `parse`, and `filterUnknownParams` helpers
10
+ * @returns {Object} An object containing `state`, `getState`, `updateState`, and `updateUrl` properties.
11
+ *
12
+ *
13
+ * * Example:
14
+ * ```ts
15
+ * export const form = { name: '' };
16
+ * const router = { push: () => {}, replace: () => {} };
17
+ * const { state, updateState, updateUrl } = useUrlStateBase(form, router, ({ parse, filterClientSP }) =>
18
+ * isSSR() ? getServerState() : getClientState()
19
+ * );
20
+ * ```
21
+ *
22
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/integrations/packages/urlstate/useUrlStateBase#api}
23
+ */
24
+ export declare function useUrlStateBase<T extends JSONCompatible>(defaultState: T, router: Router, getInitialState?: ({ parse, }: {
25
+ parse: ReturnType<typeof useUrlEncode<T>>["parse"];
26
+ }) => T): {
27
+ updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
28
+ updateUrl: (value?: Parameters<(value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void>[0], options?: Options) => void;
29
+ state: DeepReadonly<T>;
30
+ getState: () => T;
31
+ };
32
+ interface OptionsObject {
33
+ [key: string]: unknown;
34
+ }
35
+ export interface Options extends OptionsObject {
36
+ replace?: boolean;
37
+ }
38
+ type Router = {
39
+ push: (href: string, opts: object) => void;
40
+ replace: (href: string, opts: object) => void;
41
+ };
42
+ export {};
@@ -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: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};
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};
@@ -0,0 +1,28 @@
1
+ export type Type = "string" | "date" | "boolean" | "number" | "bigint" | "undefined" | "object" | "null" | "function" | "symbol" | "array";
2
+ /**
3
+ * A better replacement for `typeof`
4
+ *
5
+ * @param {unknown} val value to detect type
6
+ * @returns {Type} Type of val - `string`, `date`, `boolean`,
7
+ * `number`, `bigint`, `undefined`, `null`, `object`,
8
+ * `function`, `symbol`, `array`
9
+ */
10
+ export declare const typeOf: (val: unknown) => Type;
11
+ export declare const isSSR: () => boolean;
12
+ export type JSON = null | boolean | Date | number | string | {
13
+ [prop: string]: JSON | JSON[];
14
+ };
15
+ export type JSONCompatible = {
16
+ [prop: string]: JSON | JSON[];
17
+ };
18
+ 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 ? {
19
+ readonly [K in keyof T]: DeepReadonly<T[K]>;
20
+ } : T;
21
+ export declare const getParams: (strOrSearchParams?: string | URLSearchParams) => URLSearchParams;
22
+ export type UnknownObj = object | {
23
+ [key: string]: unknown;
24
+ };
25
+ export declare const isEqual: (val1: unknown, val2: unknown) => boolean;
26
+ export declare function filterUnknownParamsClient<T extends object>(shape: T): string;
27
+ export declare function filterUnknownParams<T extends object>(shape: T, searchParams?: object): T;
28
+ export declare function assignValue<T extends object>(shape: T, curr: Partial<T>, newVal: Partial<T>): T;
package/dist/utils.mjs CHANGED
@@ -1 +1 @@
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};
1
+ const t=t=>{const n=typeof t,e=null===t,r=Array.isArray(t),c=t instanceof Date;return(e?"null":c&&"date")||r&&"array"||!e&&!c&&!r&&"object"===n&&"object"||n},n=()=>"undefined"==typeof window,e=t=>new URLSearchParams("string"==typeof t?r(t):t?.toString?.()||""),r=t=>t.split("?")?.[1]||t||"",c=(t,n)=>JSON.stringify(t)===JSON.stringify(n);function o(t){const n=new URLSearchParams;return s(t,[...new URLSearchParams(window.location.search).entries()]).forEach((([t,e])=>n.set(t,e))),n.toString()}function i(t,n){return Object.fromEntries(s(t,Object.entries(n||{})))}function s(t,n){const e=Object.keys(t);return n.filter((([t])=>e.includes(t))).map((([t,n])=>[t.replaceAll("+"," "),n]))}function a(t,n,e){const r=Object.assign({},t,n);return Object.entries(t).forEach((([n])=>{const c=n,o=void 0!==e[c];r[c]=o?e[c]:t[c]})),r}export{a as assignValue,i as filterUnknownParams,o as filterUnknownParamsClient,e as getParams,c as isEqual,n as isSSR,t as typeOf};
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "state-in-url",
3
- "version": "3.0.0",
4
- "description": "Library to store complex state in browser URL, includes hook for NextJS, hook for pure React, and low level helpers. https://state-in-url-asmyshlyaev177.vercel.app/",
5
- "homepage": "https://github.com/asmyshlyaev177/state-in-url",
3
+ "version": "3.0.3",
4
+ "description": "Library to store complex state in browser URL, includes hook for NextJS, hook for pure React, and low level helpers.",
5
+ "homepage": "https://state-in-url-asmyshlyaev177.vercel.app",
6
6
  "keywords": [
7
7
  "front-end",
8
8
  "state-management",
9
9
  "state management",
10
+ "deep links",
11
+ "deep linking",
10
12
  "Next.js",
11
13
  "useUrlState",
12
14
  "client components communication",
@@ -348,8 +350,8 @@
348
350
  "@commitlint/config-conventional": "^19.0.3",
349
351
  "@playwright/test": "^1.45.3",
350
352
  "@rollup/plugin-node-resolve": "^15.3.0",
351
- "@rollup/plugin-sucrase": "^5.0.2",
352
353
  "@rollup/plugin-terser": "^0.4.4",
354
+ "@rollup/plugin-typescript": "^12.1.0",
353
355
  "@semantic-release/changelog": "^6.0.3",
354
356
  "@semantic-release/commit-analyzer": "^13.0.0",
355
357
  "@semantic-release/git": "^10.0.1",
@@ -373,7 +375,9 @@
373
375
  "cz": "^1.8.2",
374
376
  "cz-conventional-changelog": "^3.3.0",
375
377
  "eslint": "^8.57.0",
378
+ "eslint-config-prettier": "^9.1.0",
376
379
  "eslint-plugin-maintainable": "^0.4.4",
380
+ "eslint-plugin-prettier": "^5.2.1",
377
381
  "eslint-plugin-react-hooks": "^4.6.2",
378
382
  "husky": "^9.0.11",
379
383
  "jest": "^29.7.0",
@@ -381,8 +385,9 @@
381
385
  "lint-staged": "^15.2.9",
382
386
  "playwright": "^1.45.3",
383
387
  "prettier": "^3.2.5",
388
+ "prettier-plugin-tailwindcss": "^0.6.8",
384
389
  "release-it": "^17.6.0",
385
- "rollup": "^3.29.5",
390
+ "rollup": "^4.23.0",
386
391
  "rollup-plugin-filesize": "^10.0.0",
387
392
  "rollup-plugin-sourcemaps": "^0.6.3",
388
393
  "semantic-release": "^24.1.0",