state-in-url 4.1.0 → 4.1.1

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.CN.md CHANGED
@@ -89,7 +89,7 @@ pnpm add state-in-url
89
89
 
90
90
  ## Next.js的useUrlState钩子
91
91
 
92
- [文档](packages/urlstate/next/useUrlState#api)
92
+ [文档](packages/urlstate/next/useUrlState)
93
93
 
94
94
  `useUrlState`是一个为Next.js应用程序设计的自定义React钩子,使客户端组件之间的通信变得简单。它允许你共享任何复杂的状态并将其与URL搜索参数同步,提供了一种在页面重新加载之间持久化状态并通过URL共享应用程序状态的方法。
95
95
 
package/README.KO.md CHANGED
@@ -96,7 +96,7 @@ pnpm add state-in-url
96
96
 
97
97
  ## Next.js용 useUrlState 훅
98
98
 
99
- [문서](packages/urlstate/next/useUrlState#api)
99
+ [문서](packages/urlstate/next/useUrlState)
100
100
 
101
101
  `useUrlState`는 Next.js 애플리케이션을 위한 사용자 정의 React 훅으로, 클라이언트 컴포넌트 간의 통신을 쉽게 만듭니다. 복잡한 상태를 공유하고 URL 검색 매개변수와 동기화할 수 있게 해주어, 페이지 새로고침 시 상태를 유지하고 URL을 통해 애플리케이션 상태를 공유할 수 있습니다.
102
102
 
package/README.md CHANGED
@@ -1,29 +1,25 @@
1
1
  English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
2
2
 
3
3
  <div align="center">
4
- <img src="/assets/Logo_big.png?raw=true" alt="state-in-url logo"/>
5
-
6
- <div><h2> Store any complex state in query parameters, URL synchronization (aka deep links) made easy. Share state between any unrelated components. With support of static types validation and server side rendering.</h2></div>
7
-
4
+ <img src="/packages/example-nextjs14/public/Logo_symbol.png" alt="state-in-url logo" width="120px" />
8
5
 
6
+ # State in url
9
7
  </div>
10
8
 
11
- <hr />
12
-
13
9
  <div align="center">
14
- <a href="https://github.com/asmyshlyaev177/state-in-url/blob/master/Limits.md" target="_blank">URI size limitation, <b>up to 12KB</b> is safe</a>
15
10
  </div>
16
11
 
17
12
  <div align="center">
18
- <hr />
19
13
 
20
14
  [![npm](https://img.shields.io/npm/v/state-in-url.svg)](https://www.npmjs.com/package/state-in-url)
21
- ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=master)
15
+ ![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/state-in-url.svg)
22
16
  [![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)
17
+
18
+ ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=master)
23
19
  [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/73be54068b7f41b0b74a252579ac09ec)](https://app.codacy.com/gh/asmyshlyaev177/state-in-url/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
24
20
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](https://github.com/asmyshlyaev177/state-in-url/)
25
21
  [![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))
26
- ![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/state-in-url.svg)
22
+
27
23
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/asmyshlyaev177/state-in-url/badge)](https://scorecard.dev/viewer/?uri=github.com/asmyshlyaev177/state-in-url)
28
24
  [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9679/badge)](https://www.bestpractices.dev/projects/9679)
29
25
  <!-- or by embedding this in your HTML:
@@ -40,6 +36,8 @@ English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
40
36
  <a href="https://state-in-url.dev" target="_blank">DEMO</a> |
41
37
  <a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a>
42
38
 
39
+ <a href="https://github.com/asmyshlyaev177/state-in-url/blob/master/Limits.md" target="_blank">URI size limitation, <b>up to 12KB</b> is safe</a>
40
+
43
41
  <hr />
44
42
 
45
43
  Add a ⭐️ and <a href="https://github.com/asmyshlyaev177" target="_blank">follow me</a> to support the project!
@@ -56,9 +54,9 @@ Share if it useful for you.
56
54
 
57
55
  </div>
58
56
 
59
- # Why use `state-in-url`?
57
+ ## Why use `state-in-url`?
60
58
 
61
- Store any user state in query parameters; imagine JSON in a browser URL, while keeping types and structure of data, e.g. numbers will be decoded as numbers not strings, Date stay a date, objects and arrays supported.
59
+ Store any user state in query parameters; imagine JSON in a browser URL, while keeping types and structure of data, e.g. numbers will be decoded as numbers not strings, objects and arrays supported.
62
60
  Dead simple, fast, and with static Typescript validation. Deep links, aka URL synchronization, made easy.
63
61
 
64
62
  Contains `useUrlState` hook for Next.js and react-router, and helpers for anything else on JS.
@@ -69,27 +67,27 @@ This library does all mundane stuff for you.
69
67
 
70
68
  This library is a good alternative for NUQS.
71
69
 
72
- # Use cases
70
+ ### Use cases
73
71
 
74
- - 💾 Store unsaved user forms or page filters in URL
75
- - 🧠 Just sync data between unrelated client components without touching URI
76
- - 🔗 Shareable URLs with application state (Deep linking, URL state synchronization)
77
- - 🔄 Easy state persistence across page reloads
72
+ - Store unsaved user forms or page filters in URL
73
+ - Just sync data between unrelated client components without touching URI
74
+ - Shareable URLs with application state (Deep linking, URL state synchronization)
75
+ - Easy state persistence across page reloads
78
76
 
79
- # Features
77
+ ### Features
80
78
 
81
79
  - 🧩 **Simple**: No providers, reducers, boilerplate or new concepts, API similar to `React.useState`
82
- - 📘 **Typescript validation**: State is just an object, automatic static validation in IDE/tests according to Typescript definition
80
+ - 📘 **Typescript validation/autocomplete**: State is just an object, automatic static validation in IDE/tests according to Typescript definition
83
81
  - ✨ **Complex data**: Nested objects, dates and arrays, works same as JSON, but in URL
84
- - 🔵 **Server Side Rendering**: Can use it in Server Components, Next.js 14 and 15 are supported
85
82
  - ☂ **Default values**: Giving you default values if parameter not in url
86
83
  - ⌨ **Organized**: All possible values defined at start, protect you from getting non existing key
87
- - ⚙ **Well tested**: Unit tests and Playwright tests for Chrome/Firefox/Safari
88
- - ⚡ **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 big object
89
- - 🪶 **Lightweight**: Zero dependencies, library less than 2KB
90
- - 👍 **DX**: Good developer experience, documentation, JSDoc comments, and examples
91
- - ⚛️ **Framework Flexibility**: Hooks for `Next.js` and `react-router`, helpers to use it with other frameworks or pure JS
92
- - 📃 **Permissive license**: MIT
84
+ - **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 big object
85
+ - **Server Side Rendering**: Can use it in Server Components, Next.js 14 and 15 are supported
86
+ - **Lightweight**: Zero dependencies, library less than 2KB
87
+ - **DX**: Good developer experience, documentation, JSDoc comments, and examples
88
+ - **Framework Flexibility**: Hooks for `Next.js` and `react-router`, helpers to use it with other frameworks or pure JS
89
+ - **Well tested**: Unit tests and Playwright tests for Chrome/Firefox/Safari
90
+ - **Permissive license**: MIT
93
91
 
94
92
  ## Table of content
95
93
 
@@ -136,26 +134,26 @@ Possibly need to set `"module": "ES2022"`, or `"module": "ESNext"`
136
134
  Main hook that takes initial state as parameter and returns state object, callback to update url, and callback to update only state.
137
135
  All components that use the same `state` object are automatically synchronized.
138
136
 
139
- ## useUrlState hook for Next.js
137
+ ### useUrlState hook for Next.js
140
138
 
141
- [Full API Docs](packages/urlstate/next/useUrlState#api)
139
+ [Full API Docs](packages/urlstate/next/useUrlState)
142
140
 
143
141
  [React-Router example](#useurlstate-hook-for-react-router)
144
142
 
145
- ### Usage examples
143
+ #### Usage examples
146
144
 
147
- #### Basic
145
+ ##### Basic
148
146
 
149
147
  1. Define state shape with default values
150
148
 
151
- ```typescript
152
- // userState.ts
153
- // Only parameters with value different from default will go to the url.
154
- export const userState: UserState = { name: '', age: 0 }
149
+ ```typescript
150
+ // userState.ts
151
+ // Only parameters with value different from default will go to the url.
152
+ export const userState: UserState = { name: '', age: 0 }
155
153
 
156
- // use `Type` not `Interface`!
157
- type UserState = { name: string, age: number }
158
- ```
154
+ // use `Type` not `Interface`!
155
+ type UserState = { name: string, age: number }
156
+ ```
159
157
 
160
158
  2. Import it and use
161
159
 
@@ -196,7 +194,56 @@ function MyComponent() {
196
194
  }
197
195
  ```
198
196
 
199
- #### With complex state shape
197
+ ##### Custom hook to work with slice of state conveniently
198
+ <details>
199
+ <Summary>Example</Summary>
200
+
201
+ ```typescript
202
+ 'use client';
203
+
204
+ import React from 'react';
205
+ import { useUrlState } from 'state-in-url/next';
206
+
207
+ const form: Form = {
208
+ name: '',
209
+ age: undefined,
210
+ agree_to_terms: false,
211
+ tags: [],
212
+ };
213
+
214
+ type Form = {
215
+ name: string;
216
+ age?: number;
217
+ agree_to_terms: boolean;
218
+ tags: {id: string; value: {text: string; time: Date } }[];
219
+ };
220
+
221
+ export const useFormState = ({ searchParams }: { searchParams?: object }) => {
222
+ const { urlState, setUrl: setUrlBase, reset } = useUrlState(form, {
223
+ searchParams,
224
+ });
225
+
226
+ // first navigation will push new history entry
227
+ // all following will just replace that entry
228
+ // this way will have history with only 2 entries - ['/url', '/url?key=param']
229
+
230
+ const replace = React.useRef(false);
231
+ const setUrl = React.useCallback((
232
+ state: Parameters<typeof setUrlBase>[0],
233
+ opts?: Parameters<typeof setUrlBase>[1]
234
+ ) => {
235
+ setUrlBase(state, { replace: replace.current, ...opts });
236
+ replace.current = true;
237
+ }, [setUrlBase]);
238
+
239
+ return { urlState, setUrl, resetUrl: reset };
240
+ };
241
+ ```
242
+ </details>
243
+
244
+ <hr />
245
+
246
+ ##### With complex state shape
200
247
 
201
248
  <details>
202
249
  <Summary>Example</Summary>
@@ -276,7 +323,7 @@ const tags = [
276
323
  [Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/master/packages/example-nextjs14/src/app/Form.tsx)
277
324
  </details>
278
325
 
279
- #### Update state only and sync to URL manually
326
+ ##### Update state only and sync to URL manually
280
327
 
281
328
  <details>
282
329
  <Summary>Example</Summary>
@@ -305,7 +352,7 @@ Syncing state `onBlur` will be more aligned with real world usage.
305
352
 
306
353
  </details>
307
354
 
308
- #### With server side rendering
355
+ ##### With server side rendering
309
356
 
310
357
  <details>
311
358
  <Summary>Example</Summary>
@@ -330,7 +377,7 @@ const Form = ({ searchParams }: { searchParams: object }) => {
330
377
 
331
378
  </details>
332
379
 
333
- #### Using hook in `layout` component
380
+ ##### Using hook in `layout` component
334
381
 
335
382
  <details>
336
383
  <Summary>Example</Summary>
@@ -381,7 +428,7 @@ export default async function Layout({
381
428
 
382
429
  </details>
383
430
 
384
- #### With arbitrary state shape (not recommended)
431
+ ##### With arbitrary state shape (not recommended)
385
432
 
386
433
  <details>
387
434
  <Summary>Example</Summary>
@@ -399,13 +446,13 @@ function SettingsComponent() {
399
446
 
400
447
  </details>
401
448
 
402
- ## useUrlState hook for React-Router
449
+ ### useUrlState hook for React-Router
403
450
 
404
451
  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.
405
452
 
406
- [API Docs](packages/urlstate/react-router/useUrlState#api)
453
+ [API Docs](packages/urlstate/react-router/useUrlState)
407
454
 
408
- ### Example
455
+ #### Example
409
456
 
410
457
  ```typescript
411
458
  export const form: Form = {
@@ -486,15 +533,15 @@ const tags = [
486
533
 
487
534
  [Example code](packages/example-react-router6/src/Form-for-test.tsx)
488
535
 
489
- # Other hooks and helpers
536
+ ## Other hooks and helpers
490
537
 
491
- ## `useUrlStateBase` hook for others routers
538
+ ### `useUrlStateBase` hook for others routers
492
539
 
493
540
  Hooks to create your own `useUrlState` hooks with other routers, e.g. react-router or tanstack router.
494
541
 
495
542
  [API Docs](packages/urlstate/useUrlStateBase)
496
543
 
497
- ## `useSharedState` hook for React.js
544
+ ### `useSharedState` hook for React.js
498
545
 
499
546
  Hook to share state between any React components, tested with Next.js and Vite.
500
547
 
@@ -511,19 +558,19 @@ function SettingsComponent() {
511
558
 
512
559
  [API Docs](packages/urlstate/useSharedState/README.md)
513
560
 
514
- ## `useUrlEncode` hook for React.js
561
+ ### `useUrlEncode` hook for React.js
515
562
 
516
563
  [API Docs](packages/urlstate/useUrlEncode/README.md)
517
564
 
518
- ## `encodeState` and `decodeState` helpers
565
+ ### `encodeState` and `decodeState` helpers
519
566
 
520
567
  [API Docs](packages/urlstate/encodeState/README.md)
521
568
 
522
- ## `encode` and `decode` helpers
569
+ ### `encode` and `decode` helpers
523
570
 
524
571
  [API Docs](packages/urlstate/encoder/README.md)
525
572
 
526
- # Best Practices
573
+ ## Best Practices
527
574
 
528
575
  - Define your state shape as a constant
529
576
  - Use TypeScript for enhanced type safety and autocomplete
@@ -557,9 +604,9 @@ export const useUserState = () => {
557
604
  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>
558
605
  3. Tested with `next.js` 14/15 with app router, no plans to support pages.
559
606
 
560
- # Other
607
+ ## Other
561
608
 
562
- ## Contribute and/or run locally
609
+ ### Contribute and/or run locally
563
610
 
564
611
  See [Contributing doc](CONTRIBUTING.md)
565
612
 
@@ -1,4 +1,4 @@
1
- import { type JSONCompatible } from "../utils";
1
+ import { type JSONCompatible, type Simple } from "../utils";
2
2
  /**
3
3
  * Encode any JSON serializable value to URL friendly string
4
4
  *
@@ -8,7 +8,7 @@ import { type JSONCompatible } from "../utils";
8
8
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/encoder#encode}
9
9
  */
10
10
  export declare function encode(payload: unknown): string;
11
- export type Primitive = Exclude<ReturnType<typeof decodePrimitive>, typeof errorSym>;
11
+ export declare const encodePrimitive: (payload: Simple) => Simple;
12
12
  /**
13
13
  * Decode value part of URLSearchParams back to JS value
14
14
  *
@@ -18,17 +18,17 @@ export type Primitive = Exclude<ReturnType<typeof decodePrimitive>, typeof error
18
18
  *
19
19
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/encoder#decode}
20
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;
21
+ export declare function decode<T>(payload: string, fallback?: T): T;
25
22
  /**
26
23
  * Parses a JSON string into a TypeScript object.
27
24
  *
28
25
  * @param {string} jsonString - The JSON string to parse.
29
26
  * @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.
27
+ * @return {T | CustomDecoded | undefined} - The parsed object or a primitive value, or undefined if parsing fails.
31
28
  *
32
29
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url}
33
30
  */
34
- export declare function parseJSON<T extends JSONCompatible>(jsonString: string, fallbackValue?: T): T | Primitive | undefined;
31
+ export declare function parseJSON<T extends JSONCompatible>(jsonString: string, fallbackValue?: T): T | CustomDecoded | undefined;
32
+ export declare function reviver(_key: string, value: unknown): unknown;
33
+ export declare const decodePrimitive: (str: string) => string | Date | undefined;
34
+ export type CustomDecoded = ReturnType<typeof decodePrimitive>;
@@ -1 +1 @@
1
- import{SYMBOLS as t}from"../constants/constants.mjs";import{typeOf as e}from"../utils.mjs";function n(n){if(u(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).replace(/'/g,"%27").replace(/"/g,"'")}var r}function r(t,e){return a(t.replace(/'/g,'"').replace(/%27/g,"'"),e)}const s=e=>{if(e!==t.undefined)return e?.startsWith?.(t.date)?new Date(e.slice(1)):c},c=Symbol("isError"),i=(t,e)=>{const n="string"==typeof e,r=n&&s(e);return r===c?e:n?r:e};function a(t,e){try{return JSON.parse(t,i)}catch{const n=s(t);return n!==c?n:e}}const o=new RegExp(`^(${t.undefined}|${t.date})`),u=t=>o.test(String(t));export{r as decode,s as decodePrimitive,n as encode,c as errorSym,a as parseJSON,i as reviver};
1
+ import{SYMBOLS as t}from"../constants/constants.mjs";import{typeOf as e}from"../utils.mjs";function n(t){return f(t)?t:JSON.stringify(t,r).replace(/'/g,"%27").replace(/"/g,"'")}function r(t,n){const a=e(n);if("object"!==a&&"array"!==a)return c(n);if("object"===a){const t=n;return Object.keys(t).forEach((e=>{t[e]=r("",t[e])})),t}return"array"===a?n.map((t=>r("",t))):n}const c=n=>{switch(e(n)){case"function":case"symbol":return"";case"date":return t.date+new Date(n).toISOString();case"undefined":return t.undefined;default:return n}};function a(t,e){return s(t.replace(/'/g,'"').replace(/%27/g,"'"),e)}function s(t,e){try{return JSON.parse(t,u)}catch{return e}}function u(t,e){return"string"==typeof e?i(e):e}const i=e=>{if(e!==t.undefined)return e?.startsWith?.(t.date)?new Date(e.slice(1)):e},o=new RegExp(`^(${t.undefined}|${t.date})`),f=t=>o.test(String(t));export{a as decode,i as decodePrimitive,n as encode,c as encodePrimitive,s as parseJSON,u as reviver};
@@ -46,7 +46,7 @@ export declare function useUrlState<T extends JSONCompatible>({ defaultState: T,
46
46
  * // same as setState(form) with setUrl(form)
47
47
  * ```
48
48
  *
49
- * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#api}
49
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState}
50
50
  */
51
51
  export declare function useUrlState<T extends JSONCompatible>(defaultState: T, params?: Params): {
52
52
  urlState: T;
@@ -1 +1 @@
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?.replace?.(/'/g,'"'));return r&&e?o:t};export{e as parseSPObj};
1
+ import{decode as r}from"./encoder/encoder.mjs";function t(r,t){return{...t,...n(JSON.stringify(r),t)}}function n(r,t){try{return JSON.parse(r,e)}catch{return t}}const e=(t,n)=>{const e="string"==typeof n,o=e&&r(n?.replace?.(/'/g,'"'));return t&&e?o:n};export{t as parseSPObj};
@@ -43,7 +43,7 @@ export declare function useUrlState<T extends JSONCompatible>({ defaultState: T,
43
43
  * // same as setState(form) with setUrl(form)
44
44
  * ```
45
45
  *
46
- * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#api}
46
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState}
47
47
  */
48
48
  export declare function useUrlState<T extends JSONCompatible>(defaultState: T, params?: Params): {
49
49
  urlState: T;
@@ -0,0 +1 @@
1
+ export declare const clone: (obj: object) => any;
@@ -17,7 +17,7 @@ import { type JSONCompatible } from "../utils";
17
17
  * parse('name=Tom');
18
18
  * ```
19
19
  *
20
- * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/useUrlEncode#api}
20
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/useUrlEncode}
21
21
  */
22
22
  export declare function useUrlEncode<T extends JSONCompatible>(stateShape: T): {
23
23
  /**
@@ -31,7 +31,7 @@ import { type JSONCompatible, type Router } from "../utils";
31
31
  *
32
32
  * ```
33
33
  *
34
- * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/integrations/packages/urlstate/useUrlStateBase#api}
34
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/integrations/packages/urlstate/useUrlStateBase}
35
35
  */
36
36
  export declare function useUrlStateBase<T extends JSONCompatible>(defaultState: T, router: Router, getInitialState?: ({ parse, }: {
37
37
  parse: ReturnType<typeof useUrlEncode<T>>["parse"];
package/dist/utils.d.ts CHANGED
@@ -8,6 +8,8 @@ export type Type = "string" | "date" | "boolean" | "number" | "bigint" | "undefi
8
8
  * `function`, `symbol`, `array`
9
9
  */
10
10
  export declare const typeOf: (val: unknown) => Type;
11
+ export declare const isPrimitive: (val: unknown) => val is Simple;
12
+ export type Simple = string | Date | boolean | number | null | undefined | Function | symbol;
11
13
  export declare const isSSR: () => boolean;
12
14
  export type JSON = null | boolean | Date | number | string | {
13
15
  [prop: string]: JSON | JSON[];
@@ -22,6 +24,7 @@ export type UnknownObj = object | {
22
24
  export declare const isEqual: (val1: unknown, val2: unknown) => boolean;
23
25
  export declare function filterUnknownParamsClient<T extends object>(shape: T): string;
24
26
  export declare function filterUnknownParams<T extends object>(shape: T, searchParams?: object): T;
27
+ export declare function filterUnknown<T extends object>(shape: T, entries: [key: string, value: string][]): string[][];
25
28
  export declare function assignValue<T extends object>(shape: T, newVal: Partial<T>): T;
26
29
  export interface Router {
27
30
  push: (href: string, opts: object) => void;
package/dist/utils.mjs CHANGED
@@ -1 +1 @@
1
- const t=t=>{const n=typeof t,e=null===t,r=Array.isArray(t),o=t instanceof Date;return(e?"null":o&&"date")||r&&"array"||!e&&!o&&!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||"",o=(t,n)=>JSON.stringify(t)===JSON.stringify(n);function i(t){const n=new URLSearchParams;return s(t,[...new URLSearchParams(window.location.search).entries()]).forEach((([t,e])=>n.set(t,e))),n.toString()}function c(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.replace(/\+/g," "),n]))}function a(t,n){const e=Object.assign({},t);return Object.entries(t).forEach((([r])=>{const o=r,i=void 0!==n[o];e[o]=i?n[o]:t[o]})),e}const u={push:t=>{window&&window.history.pushState(null,"",t)},replace:t=>{window&&window.history.replaceState(null,"",t)}};export{a as assignValue,c as filterUnknownParams,i as filterUnknownParamsClient,e as getParams,o as isEqual,n as isSSR,u as routerHistory,t as typeOf};
1
+ const t=t=>{const n=typeof t,e=null===t,r=Array.isArray(t),o=t instanceof Date;return(e?"null":o&&"date")||r&&"array"||!e&&!o&&!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||"",o=(t,n)=>JSON.stringify(t)===JSON.stringify(n);function i(t){const n=new URLSearchParams;return c(t,[...new URLSearchParams(window.location.search).entries()]).forEach((([t,e])=>n.set(t,e))),n.toString()}function s(t,n){return Object.fromEntries(c(t,Object.entries(n||{})))}function c(t,n){const e=Object.keys(t),r=[];for(let t=0;t<n.length;t++)e.includes(n[t]?.[0])&&r.push([n[t][0].replace(/\+/g," "),n[t][1]]);return r}function a(t,n){const e=Object.assign({},t);return Object.entries(t).forEach((([r])=>{const o=r,i=void 0!==n[o];e[o]=i?n[o]:t[o]})),e}const u={push:t=>{window&&window.history.pushState(null,"",t)},replace:t=>{window&&window.history.replaceState(null,"",t)}};export{a as assignValue,c as filterUnknown,s as filterUnknownParams,i as filterUnknownParamsClient,e as getParams,o as isEqual,n as isSSR,u as routerHistory,t as typeOf};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "state-in-url",
3
- "version": "4.1.0",
3
+ "version": "4.1.1",
4
4
  "description": "Easily share complex state objects between unrelated React components, preserve types and structure, with TS validation. Deep links and url state synchronization wthout any hasssle or boilerplate.",
5
5
  "homepage": "https://state-in-url.dev",
6
6
  "repository": {
@@ -103,7 +103,7 @@
103
103
  "reinstall": "npm run cleanup && npm install",
104
104
  "setup": "playwright install --with-deps",
105
105
  "prepack": "npm run build && cp package.json dist/package.json && npm run build:demo",
106
- "pin-gh-deps": "pin-github-action .github/workflows/tests.yml",
106
+ "pin-gh-deps": "pin-github-action -i .github/workflows/tests.yml",
107
107
  "prepare": "npx husky"
108
108
  },
109
109
  "wireit": {
@@ -342,7 +342,7 @@
342
342
  "@testing-library/react": "^16.0.0",
343
343
  "@testing-library/react-hooks": "^8.0.1",
344
344
  "@types/glob": "^8.1.0",
345
- "@types/jest": "^29.5.13",
345
+ "@types/jest": "^29.5.14",
346
346
  "@types/node": "^20.11.24",
347
347
  "@types/react": "^18.3.3",
348
348
  "@types/react-dom": "^18.3.0",
@@ -374,7 +374,7 @@
374
374
  "rollup-plugin-sourcemaps": "^0.6.3",
375
375
  "semantic-release": "^24.1.0",
376
376
  "standard-version": "^9.5.0",
377
- "ts-jest": "^29.1.5",
377
+ "ts-jest": "^29.2.5",
378
378
  "ts-node": "^10.9.2",
379
379
  "typescript": "^5.3.3",
380
380
  "wait-on": "^8.0.1",