state-in-url 5.0.0 → 5.2.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 (35) hide show
  1. package/README.md +284 -169
  2. package/dist/constants/constants.js +1 -0
  3. package/dist/constants/index.js +1 -0
  4. package/dist/encodeState/encodeState.js +1 -0
  5. package/dist/encodeState/index.js +1 -0
  6. package/dist/encoder/encoder.js +1 -0
  7. package/dist/encoder/index.js +1 -0
  8. package/dist/index.js +1 -0
  9. package/dist/next/index.js +1 -0
  10. package/dist/next/useUrlState/index.js +1 -0
  11. package/dist/next/useUrlState/useUrlState.d.ts +8 -5
  12. package/dist/next/useUrlState/useUrlState.js +1 -0
  13. package/dist/parseSPObj.js +1 -0
  14. package/dist/react-router/index.js +1 -0
  15. package/dist/react-router/useUrlState/index.js +1 -0
  16. package/dist/react-router/useUrlState/useUrlState.d.ts +7 -6
  17. package/dist/react-router/useUrlState/useUrlState.js +1 -0
  18. package/dist/remix/index.js +1 -0
  19. package/dist/remix/useUrlState/index.js +1 -0
  20. package/dist/remix/useUrlState/useUrlState.d.ts +7 -6
  21. package/dist/remix/useUrlState/useUrlState.js +1 -0
  22. package/dist/subscribers.js +1 -0
  23. package/dist/useInsertionEffect.js +1 -0
  24. package/dist/useSharedState/index.js +1 -0
  25. package/dist/useSharedState/useSharedState.d.ts +2 -2
  26. package/dist/useSharedState/useSharedState.js +1 -0
  27. package/dist/useSharedState/useSharedState.mjs +1 -1
  28. package/dist/useUrlEncode/index.js +1 -0
  29. package/dist/useUrlEncode/useUrlEncode.js +1 -0
  30. package/dist/useUrlStateBase/index.js +1 -0
  31. package/dist/useUrlStateBase/useUrlStateBase.d.ts +3 -3
  32. package/dist/useUrlStateBase/useUrlStateBase.js +1 -0
  33. package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -1
  34. package/dist/utils.js +1 -0
  35. package/package.json +23 -12
package/README.md CHANGED
@@ -99,25 +99,46 @@ This library is a good alternative for NUQS.
99
99
 
100
100
  ## Table of content
101
101
 
102
- - [Installation](#installation)
103
- - [useUrlState hook](#useurlstate)
104
- - - [Next.js](#useurlstate-hook-for-nextjs)
105
- - - [React-Router](#useurlstate-hook-for-react-router)
106
- - [Other helpers](#other-hooks-and-helpers)
107
- - - [`useUrlStateBase` for other routers](#useurlstatebase-hook-for-others-routers)
108
- - - [`useSharedState` hook for React.js/Next.js](#usesharedstate-hook-for-reactjs)
109
- - - [`useUrlEncode` for React.js](#useurlencode-hook-for-reactjs)
110
- - - [`encodeState` and `decodeState` for pure JS usage](#encodestate-and-decodestate-helpers)
111
- - - [Low-level `encode` and `decode` functions](#encode-and-decode-helpers)
112
- - [Best practices](#best-practices)
113
- - - [Gotchas](#gotchas)
114
- - [Other](#other)
115
- - - [Roadmap](#roadmap)
116
- - - [Contributing](#contribute-andor-run-locally)
117
- - - [Contact & Support](#contact--support)
118
- - - [Changelog](#changelog)
119
- - - [License](#license)
120
- - - [Inspiration](#inspiration)
102
+ - [State in url](#state-in-url)
103
+ - [Demo](#demo)
104
+ - [Why use `state-in-url`?](#why-use-state-in-url)
105
+ - [Use cases](#use-cases)
106
+ - [Features](#features)
107
+ - [Table of content](#table-of-content)
108
+ - [installation](#installation)
109
+ - [1. Install package](#1-install-package)
110
+ - [2. Edit tsconfig.json](#2-edit-tsconfigjson)
111
+ - [useUrlState](#useurlstate)
112
+ - [useUrlState hook for Next.js](#useurlstate-hook-for-nextjs)
113
+ - [Usage examples](#usage-examples)
114
+ - [Basic](#basic)
115
+ - [With server side rendering](#with-server-side-rendering)
116
+ - [Using hook in `layout` component](#using-hook-in-layout-component)
117
+ - [With arbitrary state shape (not recommended)](#with-arbitrary-state-shape-not-recommended)
118
+ - [useUrlState hook for Remix.js](#useurlstate-hook-for-remixjs)
119
+ - [Example](#example)
120
+ - [useUrlState hook for React-Router](#useurlstate-hook-for-react-router)
121
+ - [Example](#example-1)
122
+ - [Recipes](#recipes)
123
+ - [Custom hook to work with slice of state conveniently](#custom-hook-to-work-with-slice-of-state-conveniently)
124
+ - [With complex state shape](#with-complex-state-shape)
125
+ - [Update state only and sync to URL manually](#update-state-only-and-sync-to-url-manually)
126
+ - [Other hooks and helpers](#other-hooks-and-helpers)
127
+ - [`useUrlStateBase` hook for others routers](#useurlstatebase-hook-for-others-routers)
128
+ - [`useSharedState` hook for React.js](#usesharedstate-hook-for-reactjs)
129
+ - [`useUrlEncode` hook for React.js](#useurlencode-hook-for-reactjs)
130
+ - [`encodeState` and `decodeState` helpers](#encodestate-and-decodestate-helpers)
131
+ - [`encode` and `decode` helpers](#encode-and-decode-helpers)
132
+ - [Best Practices](#best-practices)
133
+ - [Gotchas](#gotchas)
134
+ - [Other](#other)
135
+ - [Contribute and/or run locally](#contribute-andor-run-locally)
136
+ - [Roadmap](#roadmap)
137
+ - [Contact \& Support](#contact--support)
138
+ - [Changelog](#changelog)
139
+ - [Mentions](#mentions)
140
+ - [License](#license)
141
+ - [Inspiration](#inspiration)
121
142
 
122
143
  ## installation
123
144
 
@@ -174,7 +195,7 @@ import { userState } from './userState';
174
195
  function MyComponent() {
175
196
  // can pass `replace` arg, it's control will `setUrl` will use `rounter.push` or `router.replace`, default replace=true
176
197
  // can pass `searchParams` from server components, pass `useHistory: false` if you need to fetch smt in the server component
177
- const { urlState, setUrl, setState, reset } = useUrlState(userState);
198
+ const { urlState, setUrl, setState } = useUrlState(userState);
178
199
 
179
200
  return (
180
201
  <div>
@@ -193,7 +214,7 @@ function MyComponent() {
193
214
  onBlur={() => setUrl()}
194
215
  />
195
216
 
196
- <button onClick={reset}>
217
+ <button onClick={() => setUrl((_, initial) => initial)}>
197
218
  Reset
198
219
  </button>
199
220
 
@@ -202,60 +223,108 @@ function MyComponent() {
202
223
  }
203
224
  ```
204
225
 
205
- ##### Custom hook to work with slice of state conveniently
226
+ ##### With server side rendering
227
+
206
228
  <details>
207
229
  <Summary>Example</Summary>
208
230
 
209
- ```typescript
210
- 'use client';
231
+ ```typescript
232
+ export default async function Home({ searchParams }: { searchParams: object }) {
233
+ return (
234
+ <Form searchParams={searchParams} />
235
+ )
236
+ }
211
237
 
238
+ // Form.tsx
239
+ 'use client'
212
240
  import React from 'react';
213
241
  import { useUrlState } from 'state-in-url/next';
242
+ import { form } from './form';
214
243
 
215
- const form: Form = {
216
- name: '',
217
- age: undefined,
218
- agree_to_terms: false,
219
- tags: [],
220
- };
244
+ const Form = ({ searchParams }: { searchParams: object }) => {
245
+ const { urlState, setState, setUrl } = useUrlState(form, { searchParams });
246
+ }
247
+ ```
221
248
 
222
- type Form = {
223
- name: string;
224
- age?: number;
225
- agree_to_terms: boolean;
226
- tags: {id: string; value: {text: string; time: Date } }[];
227
- };
249
+ </details>
228
250
 
229
- export const useFormState = ({ searchParams }: { searchParams?: object }) => {
230
- const { urlState, setUrl: setUrlBase, reset } = useUrlState(form, {
231
- searchParams,
232
- });
251
+ ##### Using hook in `layout` component
233
252
 
234
- // first navigation will push new history entry
235
- // all following will just replace that entry
236
- // this way will have history with only 2 entries - ['/url', '/url?key=param']
253
+ <details>
254
+ <Summary>Example</Summary>
255
+ That a tricky part, since nextjs with app router doesn't allow to access searchParams from server side. There is workaround with using middleware, but it isn't pretty and can stop working after nextjs update.
237
256
 
238
- const replace = React.useRef(false);
239
- const setUrl = React.useCallback((
240
- state: Parameters<typeof setUrlBase>[0],
241
- opts?: Parameters<typeof setUrlBase>[1]
242
- ) => {
243
- setUrlBase(state, { replace: replace.current, ...opts });
244
- replace.current = true;
245
- }, [setUrlBase]);
257
+ ```typescript
258
+ // add to appropriate `layout.tsc`
259
+ export const runtime = 'edge';
246
260
 
247
- return { urlState, setUrl, resetUrl: reset };
248
- };
249
- ```
250
- </details>
261
+ // middleware.ts
262
+ import type { NextRequest } from 'next/server';
263
+ import { NextResponse } from 'next/server';
251
264
 
252
- <hr />
265
+ export function middleware(request: NextRequest) {
266
+ const url = request.url?.includes('_next') ? null : request.url;
267
+ const sp = url?.split?.('?')?.[1] || '';
253
268
 
254
- ##### With complex state shape
269
+ const response = NextResponse.next();
270
+
271
+ if (url !== null) {
272
+ response.headers.set('searchParams', sp);
273
+ }
274
+
275
+ return response;
276
+ }
277
+
278
+ // Target layout component
279
+ import { headers } from 'next/headers';
280
+ import { decodeState } from 'state-in-url/encodeState';
281
+
282
+ export default async function Layout({
283
+ children,
284
+ }: {
285
+ children: React.ReactNode;
286
+ }) {
287
+ const sp = headers().get('searchParams') || '';
288
+
289
+ return (
290
+ <div>
291
+ <Comp1 searchParams={decodeState(sp, stateShape)} />
292
+ {children}
293
+ </div>
294
+ );
295
+ }
296
+
297
+
298
+ ```
299
+
300
+ </details>
301
+
302
+ ##### With arbitrary state shape (not recommended)
255
303
 
256
304
  <details>
257
305
  <Summary>Example</Summary>
258
306
 
307
+ ```typescript
308
+ 'use client'
309
+ import { useUrlState } from 'state-in-url/next';
310
+
311
+ const someObj = {};
312
+
313
+ function SettingsComponent() {
314
+ const { urlState, setUrl, setState } = useUrlState<object>(someObj);
315
+ }
316
+ ```
317
+
318
+ </details>
319
+
320
+ ### useUrlState hook for Remix.js
321
+
322
+ 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.
323
+
324
+ [API Docs](packages/urlstate/remix/useUrlState)
325
+
326
+ #### Example
327
+
259
328
  ```typescript
260
329
  export const form: Form = {
261
330
  name: '',
@@ -270,17 +339,16 @@ type Form = {
270
339
  agree_to_terms: boolean;
271
340
  tags: { id: string; value: { text: string; time: Date } }[];
272
341
  };
342
+
273
343
  ```
274
344
 
275
345
  ```typescript
276
- 'use client'
277
- import { useUrlState } from 'state-in-url/next';
346
+ import { useUrlState } from 'state-in-url/remix';
278
347
 
279
348
  import { form } from './form';
280
349
 
281
350
  function TagsComponent() {
282
- // `urlState` will infer from Form type!
283
- const { urlState, setUrl } = useUrlState(form);
351
+ const { urlState, setUrl, setState } = useUrlState(form);
284
352
 
285
353
  const onChangeTags = React.useCallback(
286
354
  (tag: (typeof tags)[number]) => {
@@ -308,6 +376,12 @@ function TagsComponent() {
308
376
  ))}
309
377
  </div>
310
378
  </Field>
379
+
380
+ <input value={urlState.name}
381
+ onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }}
382
+ // Can update state immediately but sync change to url as needed
383
+ onBlur={() => setUrl()}
384
+ />
311
385
  </div>
312
386
  );
313
387
  }
@@ -328,139 +402,149 @@ const tags = [
328
402
  ];
329
403
  ```
330
404
 
331
- [Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/master/packages/example-nextjs14/src/app/Form.tsx)
332
- </details>
333
-
334
- ##### Update state only and sync to URL manually
335
-
336
- <details>
337
- <Summary>Example</Summary>
338
-
339
- ```typescript
405
+ [Example code](packages/example-remix2/app/routes/Form-for-test.tsx)
340
406
 
341
- const timer = React.useRef(0 as unknown as NodeJS.Timeout);
342
- React.useEffect(() => {
343
- clearTimeout(timer.current);
344
- timer.current = setTimeout(() => {
345
- // will compare state by content not by reference and fire update only for new values
346
- setUrl(urlState);
347
- }, 500);
348
-
349
- return () => {
350
- clearTimeout(timer.current);
351
- };
352
- }, [urlState, setUrl]);
353
- ```
354
-
355
- Syncing state `onBlur` will be more aligned with real world usage.
356
-
357
- ```typescript
358
- <input onBlur={() => updateUrl()} .../>
359
- ```
407
+ ### useUrlState hook for React-Router
360
408
 
361
- </details>
409
+ 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.
362
410
 
363
- ##### With server side rendering
411
+ [API Docs](packages/urlstate/react-router/useUrlState)
364
412
 
365
- <details>
366
- <Summary>Example</Summary>
413
+ #### Example
367
414
 
368
415
  ```typescript
369
- export default async function Home({ searchParams }: { searchParams: object }) {
370
- return (
371
- <Form searchParams={searchParams} />
372
- )
373
- }
416
+ export const form: Form = {
417
+ name: '',
418
+ age: undefined,
419
+ agree_to_terms: false,
420
+ tags: [],
421
+ };
374
422
 
375
- // Form.tsx
376
- 'use client'
377
- import React from 'react';
378
- import { useUrlState } from 'state-in-url/next';
379
- import { form } from './form';
423
+ type Form = {
424
+ name: string;
425
+ age?: number;
426
+ agree_to_terms: boolean;
427
+ tags: { id: string; value: { text: string; time: Date } }[];
428
+ };
380
429
 
381
- const Form = ({ searchParams }: { searchParams: object }) => {
382
- const { urlState, setState, setUrl } = useUrlState(form, { searchParams });
383
- }
384
430
  ```
385
431
 
386
- </details>
387
-
388
- ##### Using hook in `layout` component
389
-
390
- <details>
391
- <Summary>Example</Summary>
392
- That a tricky part, since nextjs with app router doesn't allow to access searchParams from server side. There is workaround with using middleware, but it isn't pretty and can stop working after nextjs update.
393
-
394
432
  ```typescript
395
- // add to appropriate `layout.tsc`
396
- export const runtime = 'edge';
397
-
398
- // middleware.ts
399
- import type { NextRequest } from 'next/server';
400
- import { NextResponse } from 'next/server';
401
-
402
- export function middleware(request: NextRequest) {
403
- const url = request.url?.includes('_next') ? null : request.url;
404
- const sp = url?.split?.('?')?.[1] || '';
405
-
406
- const response = NextResponse.next();
407
-
408
- if (url !== null) {
409
- response.headers.set('searchParams', sp);
410
- }
433
+ import { useUrlState } from 'state-in-url/react-router';
411
434
 
412
- return response;
413
- }
435
+ import { form } from './form';
414
436
 
415
- // Target layout component
416
- import { headers } from 'next/headers';
417
- import { decodeState } from 'state-in-url/encodeState';
437
+ function TagsComponent() {
438
+ const { urlState, setUrl, setState } = useUrlState(form);
418
439
 
419
- export default async function Layout({
420
- children,
421
- }: {
422
- children: React.ReactNode;
423
- }) {
424
- const sp = headers().get('searchParams') || '';
440
+ const onChangeTags = React.useCallback(
441
+ (tag: (typeof tags)[number]) => {
442
+ setUrl((curr) => ({
443
+ ...curr,
444
+ tags: curr.tags.find((t) => t.id === tag.id)
445
+ ? curr.tags.filter((t) => t.id !== tag.id)
446
+ : curr.tags.concat(tag),
447
+ }));
448
+ },
449
+ [setUrl],
450
+ );
425
451
 
426
452
  return (
427
453
  <div>
428
- <Comp1 searchParams={decodeState(sp, stateShape)} />
429
- {children}
454
+ <Field text="Tags">
455
+ <div className="flex flex-wrap gap-2">
456
+ {tags.map((tag) => (
457
+ <Tag
458
+ active={!!urlState.tags.find((t) => t.id === tag.id)}
459
+ text={tag.value.text}
460
+ onClick={() => onChangeTags(tag)}
461
+ key={tag.id}
462
+ />
463
+ ))}
464
+ </div>
465
+ </Field>
466
+
467
+ <input value={urlState.name}
468
+ onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }}
469
+ // Can update state immediately but sync change to url as needed
470
+ onBlur={() => setUrl()}
471
+ />
430
472
  </div>
431
473
  );
432
474
  }
433
475
 
434
-
476
+ const tags = [
477
+ {
478
+ id: '1',
479
+ value: { text: 'React.js', time: new Date('2024-07-17T04:53:17.000Z') },
480
+ },
481
+ {
482
+ id: '2',
483
+ value: { text: 'Next.js', time: new Date('2024-07-18T04:53:17.000Z') },
484
+ },
485
+ {
486
+ id: '3',
487
+ value: { text: 'TailwindCSS', time: new Date('2024-07-19T04:53:17.000Z') },
488
+ },
489
+ ];
435
490
  ```
436
491
 
437
- </details>
438
-
439
- ##### With arbitrary state shape (not recommended)
492
+ [Example code](packages/example-react-router6/src/Form-for-test.tsx)
440
493
 
494
+ ## Recipes
495
+ ##### Custom hook to work with slice of state conveniently
441
496
  <details>
442
497
  <Summary>Example</Summary>
443
498
 
444
- ```typescript
445
- 'use client'
499
+ ```typescript
500
+ 'use client';
501
+
502
+ import React from 'react';
446
503
  import { useUrlState } from 'state-in-url/next';
447
504
 
448
- const someObj = {};
505
+ const form: Form = {
506
+ name: '',
507
+ age: undefined,
508
+ agree_to_terms: false,
509
+ tags: [],
510
+ };
449
511
 
450
- function SettingsComponent() {
451
- const { urlState, setUrl, setState } = useUrlState<object>(someObj);
452
- }
453
- ```
512
+ type Form = {
513
+ name: string;
514
+ age?: number;
515
+ agree_to_terms: boolean;
516
+ tags: {id: string; value: {text: string; time: Date } }[];
517
+ };
454
518
 
455
- </details>
519
+ export const useFormState = ({ searchParams }: { searchParams?: object }) => {
520
+ const { urlState, setUrl: setUrlBase, reset } = useUrlState(form, {
521
+ searchParams,
522
+ });
456
523
 
457
- ### useUrlState hook for React-Router
524
+ // first navigation will push new history entry
525
+ // all following will just replace that entry
526
+ // this way will have history with only 2 entries - ['/url', '/url?key=param']
458
527
 
459
- 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.
528
+ const replace = React.useRef(false);
529
+ const setUrl = React.useCallback((
530
+ state: Parameters<typeof setUrlBase>[0],
531
+ opts?: Parameters<typeof setUrlBase>[1]
532
+ ) => {
533
+ setUrlBase(state, { replace: replace.current, ...opts });
534
+ replace.current = true;
535
+ }, [setUrlBase]);
460
536
 
461
- [API Docs](packages/urlstate/react-router/useUrlState)
537
+ return { urlState, setUrl, resetUrl: reset };
538
+ };
539
+ ```
540
+ </details>
462
541
 
463
- #### Example
542
+ <hr />
543
+
544
+ ##### With complex state shape
545
+
546
+ <details>
547
+ <Summary>Example</Summary>
464
548
 
465
549
  ```typescript
466
550
  export const form: Form = {
@@ -476,16 +560,17 @@ type Form = {
476
560
  agree_to_terms: boolean;
477
561
  tags: { id: string; value: { text: string; time: Date } }[];
478
562
  };
479
-
480
563
  ```
481
564
 
482
565
  ```typescript
483
- import { useUrlState } from 'state-in-url/react-router';
566
+ 'use client'
567
+ import { useUrlState } from 'state-in-url/next';
484
568
 
485
569
  import { form } from './form';
486
570
 
487
571
  function TagsComponent() {
488
- const { urlState, setUrl, setState } = useUrlState(form);
572
+ // `urlState` will infer from Form type!
573
+ const { urlState, setUrl } = useUrlState(form);
489
574
 
490
575
  const onChangeTags = React.useCallback(
491
576
  (tag: (typeof tags)[number]) => {
@@ -513,12 +598,6 @@ function TagsComponent() {
513
598
  ))}
514
599
  </div>
515
600
  </Field>
516
-
517
- <input value={urlState.name}
518
- onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }}
519
- // Can update state immediately but sync change to url as needed
520
- onBlur={() => setUrl()}
521
- />
522
601
  </div>
523
602
  );
524
603
  }
@@ -539,7 +618,37 @@ const tags = [
539
618
  ];
540
619
  ```
541
620
 
542
- [Example code](packages/example-react-router6/src/Form-for-test.tsx)
621
+ [Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/master/packages/example-nextjs14/src/app/Form.tsx)
622
+ </details>
623
+
624
+ ##### Update state only and sync to URL manually
625
+
626
+ <details>
627
+ <Summary>Example</Summary>
628
+
629
+ ```typescript
630
+
631
+ const timer = React.useRef(0 as unknown as NodeJS.Timeout);
632
+ React.useEffect(() => {
633
+ clearTimeout(timer.current);
634
+ timer.current = setTimeout(() => {
635
+ // will compare state by content not by reference and fire update only for new values
636
+ setUrl(urlState);
637
+ }, 500);
638
+
639
+ return () => {
640
+ clearTimeout(timer.current);
641
+ };
642
+ }, [urlState, setUrl]);
643
+ ```
644
+
645
+ Syncing state `onBlur` will be more aligned with real world usage.
646
+
647
+ ```typescript
648
+ <input onBlur={() => updateUrl()} .../>
649
+ ```
650
+
651
+ </details>
543
652
 
544
653
  ## Other hooks and helpers
545
654
 
@@ -640,6 +749,12 @@ See [Contributing doc](CONTRIBUTING.md)
640
749
 
641
750
  ## [Changelog](CHANGELOG.md)
642
751
 
752
+ ## Mentions
753
+
754
+ - [This Week in React 209](https://thisweekinreact.com/newsletter/209)
755
+ - [JavaScript Weekly](https://javascriptweekly.com/issues/741)
756
+ - [This Week in React 240](https://thisweekinreact.com/newsletter/240)
757
+
643
758
  ## License
644
759
 
645
760
  This project is licensed under the [MIT license](LICENSE).
@@ -0,0 +1 @@
1
+ "use strict";var e=require("../utils.js");const i={date:"\u23F2",undefined:"\u2219undefined"};const u=e.isSafari?330:70;exports.SYMBOLS=i;exports.TIMEOUT=u;
@@ -0,0 +1 @@
1
+ "use strict";var r=require("./constants.js");exports.SYMBOLS=r.SYMBOLS;exports.TIMEOUT=r.TIMEOUT;
@@ -0,0 +1 @@
1
+ "use strict";var i=require("../encoder/encoder.js");var s=require("../utils.js");function d(o,t,r){const n=s.getParams(r);for(const[e,c]of Object.entries(o||{})){const a=t?.[e];if(JSON.stringify(c)!==JSON.stringify(a)){n.set(e,i.encode(c))}}return n.toString()}function u(o,t){return{...t||{},...Object.fromEntries([...s.getParams(o).entries()].map(([r,n])=>{const e=t?.[r];return[r,i.decode(n,e)??e]}))}}exports.decodeState=u;exports.encodeState=d;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./encodeState.js");exports.decodeState=e.decodeState;exports.encodeState=e.encodeState;
@@ -0,0 +1 @@
1
+ "use strict";var n=require("../constants/constants.js");var o=require("../utils.js");function S(e){if(typeof e==="function"||typeof e==="symbol")return"";if(v(e)){return e}return JSON.stringify(structuredClone(e),c).replace(/'/g,"%27").replace(/"/g,"'")}function c(e,r){const i=o.typeOf(r);if(i!=="object"&&i!=="array"){return s(r)}if(i==="object"){const t=r;for(const u of Object.keys(t)){t[u]=c(e,t[u])}return t}if(i==="array"){return r.map(t=>c(e,t))}return r}const s=e=>{switch(o.typeOf(e)){case"date":return n.SYMBOLS.date+new Date(e).toISOString();case"undefined":return n.SYMBOLS.undefined;default:return e}};function p(e,r){return d(e.replace(/'/g,'"').replace(/%27/g,"'"),r)}function d(e,r){try{return JSON.parse(e,a)}catch{return r}}function a(e,r){return typeof r==="string"?f(r):r}const f=e=>{if(e===n.SYMBOLS.undefined)return void 0;if(e?.startsWith?.(n.SYMBOLS.date))return new Date(e.slice(1));return e};const O=new RegExp(`^(${n.SYMBOLS.undefined}|${n.SYMBOLS.date})`);const v=e=>O.test(String(e));exports.decode=p;exports.decodePrimitive=f;exports.encode=S;exports.encodePrimitive=s;exports.parseJSON=d;exports.reviver=a;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./encoder.js");exports.decode=e.decode;exports.encode=e.encode;
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./encoder/encoder.js");var r=require("./encodeState/encodeState.js");var a=require("./next/useUrlState/useUrlState.js");var u=require("./useSharedState/useSharedState.js");var S=require("./useUrlEncode/useUrlEncode.js");var d=require("./useUrlStateBase/useUrlStateBase.js");var t=require("./utils.js");exports.decode=e.decode;exports.encode=e.encode;exports.decodeState=r.decodeState;exports.encodeState=r.encodeState;exports.useUrlState=a.useUrlState;exports.useSharedState=u.useSharedState;exports.useUrlEncode=S.useUrlEncode;exports.useUrlStateBase=d.useUrlStateBase;exports.isSSR=t.isSSR;exports.typeOf=t.typeOf;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState/useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -26,10 +26,11 @@ import { type JSONCompatible } from "../../utils";
26
26
  * setState({ name: 'test' });
27
27
  * setUrl({ name: 'test' }, { replace: true, scroll: true });
28
28
  * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: true });
29
- * // reset state and url
29
+ * // RESET state and url
30
+ * setUrl((_curr, initialState) => initialState);
31
+ * // Or
30
32
  * reset();
31
33
  * reset({ replace: true });
32
- * // same as setState(form) with setUrl(form)
33
34
  * ```
34
35
  *
35
36
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState}
@@ -49,18 +50,20 @@ export declare function useUrlState<T extends JSONCompatible>(defaultState: T, p
49
50
  *
50
51
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updatestate}
51
52
  */
52
- setState: (value: Partial<T> | ((currState: T) => T)) => void;
53
+ setState: (value: Partial<T> | ((currState: T, initial: T) => T)) => void;
53
54
  /**
54
55
  * * Example:
55
56
  * ```ts
56
57
  * setUrl({ name: 'test' });
57
58
  * // or
58
- * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: false } );
59
+ * setUrl((curr) => ({ ...curr, name: 'test' }), { replace: true, scroll: false } );
60
+ * * Reset
61
+ * setUrl((_curr, initialState) => initialState, { replace: true, scroll: false } );
59
62
  * ```
60
63
  *
61
64
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updateurl}
62
65
  */
63
- setUrl: (value?: Partial<T> | ((currState: T) => T), options?: Options) => void;
66
+ setUrl: (value?: Partial<T> | ((currState: T, initialState: T) => T), options?: Options) => void;
64
67
  /**
65
68
  * * Example:
66
69
  * ```ts
@@ -0,0 +1 @@
1
+ "use strict";var h=require("react");var p=require("next/navigation");var S=require("../../parseSPObj.js");var k=require("../../useUrlStateBase/useUrlStateBase.js");var r=require("../../utils.js");function q(e){return e&&e.__esModule?e:{default:e}}var s=q(h);function _(e,a){const u=a?.useHistory;const l=p.useRouter();const d=s.default.useMemo(()=>({push:(...t)=>{if(u===void 0?true:!!u){r.routerHistory.push(...t)}else{l.push(...t)}},replace:(...t)=>{if(u===void 0?true:!!u){r.routerHistory.replace(...t)}else{l.replace(...t)}}}),[l]);const{state:P,updateState:o,updateUrl:n,reset:c,getState:U}=k.useUrlStateBase(e,d,({parse:t})=>{return r.isSSR?S.parseSPObj(r.filterUnknownParams(e,a?.searchParams),e):t(r.filterUnknownParamsClient(e,a?.searchParams))});const i=s.default.useMemo(()=>({...j,scroll:a?.scroll,replace:a?.replace}),[]);const m=s.default.useCallback((t,b)=>n(t,{...i,...b}),[n]);const f=p.useSearchParams();s.default.useLayoutEffect(()=>{o(r.filterUnknownParams(e,S.parseSPObj(Object.fromEntries([...f.entries()]),e)))},[f]);const v=s.default.useCallback(t=>{c({...i,...t})},[c]);return{setState:o,setUrl:m,urlState:P,reset:v,getState:U}}const j={replace:true,scroll:false};exports.useUrlState=_;
@@ -0,0 +1 @@
1
+ "use strict";var c=require("./encoder/encoder.js");function s(r,e){return{...e,...o(JSON.stringify(r),e)}}function o(r,e){try{return JSON.parse(r,u)}catch{return e}}const u=(r,e)=>{const t=typeof e==="string";const n=t&&c.decode(e?.replace?.(/'/g,'"'));return r&&t?n:e};exports.parseSPObj=s;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState/useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -23,10 +23,11 @@ import { type JSONCompatible } from "../../utils";
23
23
  * setUrl({ name: 'test' }, { replace: true });
24
24
  * // similar to React.useState
25
25
  * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true });
26
- * // reset state and url
26
+ * // RESET state and url
27
+ * setUrl((_curr, initialState) => initialState);
28
+ * // Or
27
29
  * reset();
28
30
  * reset({ replace: true });
29
- * // same as setState(form) with setUrl(form)
30
31
  * ```
31
32
  *
32
33
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState}
@@ -41,25 +42,25 @@ export declare function useUrlState<T extends JSONCompatible>(defaultState: T, p
41
42
  * ```ts
42
43
  * setState({ name: 'test' });
43
44
  * // or
44
- * setState(curr => ({ ...curr, name: 'test' }) );
45
+ * setState((curr, initial) => ({ ...curr, name: 'test' }) );
45
46
  * ```
46
47
  *
47
48
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updatestate}
48
49
  */
49
- setState: (value: Partial<T> | ((currState: T) => T)) => void;
50
+ setState: (value: Partial<T> | ((currState: T, initial: T) => T)) => void;
50
51
  /**
51
52
  * * Example:
52
53
  * ```ts
53
54
  * setUrl({ name: 'test' });
54
55
  * // or
55
- * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true } );
56
+ * setUrl((curr, initial) => ({ ...curr, name: 'test' }), { replace: true } );
56
57
  * // can pass optional React-Router `NavigateOptions`
57
58
  * setUrl(curr => ({ ...curr, name: 'test' }), { preventScrollReset: false } );
58
59
  * ```
59
60
  *
60
61
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updateurl}
61
62
  */
62
- setUrl: (value?: Partial<T> | ((currState: T) => T), options?: Params) => void;
63
+ setUrl: (value?: Partial<T> | ((currState: T, initial: T) => T), options?: Params) => void;
63
64
  /**
64
65
  * * Example:
65
66
  * ```ts
@@ -0,0 +1 @@
1
+ "use strict";var P=require("react");var c=require("react-router-dom");var _=require("../../parseSPObj.js");var g=require("../../useUrlStateBase/useUrlStateBase.js");var u=require("../../utils.js");function k(e){return e&&e.__esModule?e:{default:e}}var s=k(P);function R(e,l){const p=l?.useHistory;const a=s.default.useMemo(()=>({...h,replace:l?.replace,preventScrollReset:l?.preventScrollReset}),[]);const n=c.useNavigate();const v=s.default.useMemo(()=>p?u.routerHistory:{replace:(t,r)=>n(t,{...a,...r}),push:(t,r)=>n(t,{...a,...r})},[n]);const[o]=c.useSearchParams();const d=c.useHref("/");const{state:U,updateState:i,updateUrl:f,getState:m,reset:S}=g.useUrlStateBase(e,v,({parse:t})=>t(u.filterUnknownParamsClient(e,o.entries())),d);const b=s.default.useCallback((t,r)=>f(t,{...a,...r}),[f]);s.default.useEffect(()=>{i(u.assignValue(e,u.filterUnknownParams(e,_.parseSPObj(Object.fromEntries([...o.entries()]),e))))},[o]);const q=s.default.useCallback(t=>{S({...a,...t})},[S]);return{setState:i,setUrl:b,urlState:U,reset:q,getState:m}}const h={replace:true,preventScrollReset:true};exports.useUrlState=R;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState/useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -23,10 +23,11 @@ import { type JSONCompatible } from "../../utils";
23
23
  * setUrl({ name: 'test' }, { replace: true });
24
24
  * // similar to React.useState
25
25
  * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true });
26
- * // reset state and url
26
+ * // RESET state and url
27
+ * setUrl((_curr, initialState) => initialState);
28
+ * // Or
27
29
  * reset();
28
30
  * reset({ replace: true });
29
- * // same as setState(form) with setUrl(form)
30
31
  * ```
31
32
  *
32
33
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/remix/useUrlState}
@@ -41,25 +42,25 @@ export declare function useUrlState<T extends JSONCompatible>(defaultState: T, p
41
42
  * ```ts
42
43
  * setState({ name: 'test' });
43
44
  * // or
44
- * setState(curr => ({ ...curr, name: 'test' }) );
45
+ * setState((curr, initial) => ({ ...curr, name: 'test' }) );
45
46
  * ```
46
47
  *
47
48
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/remix/useUrlState#updatestate}
48
49
  */
49
- setState: (value: Partial<T> | ((currState: T) => T)) => void;
50
+ setState: (value: Partial<T> | ((currState: T, initial: T) => T)) => void;
50
51
  /**
51
52
  * * Example:
52
53
  * ```ts
53
54
  * setUrl({ name: 'test' });
54
55
  * // or
55
- * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true } );
56
+ * setUrl((curr, initial) => ({ ...curr, name: 'test' }), { replace: true } );
56
57
  * // can pass optional React-Router `NavigateOptions`
57
58
  * setUrl(curr => ({ ...curr, name: 'test' }), { preventScrollReset: false } );
58
59
  * ```
59
60
  *
60
61
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/remix/useUrlState#updateurl}
61
62
  */
62
- setUrl: (value?: Partial<T> | ((currState: T) => T), options?: Params) => void;
63
+ setUrl: (value?: Partial<T> | ((currState: T, initial: T) => T), options?: Params) => void;
63
64
  /**
64
65
  * * Example:
65
66
  * ```ts
@@ -0,0 +1 @@
1
+ "use strict";var P=require("react");var o=require("@remix-run/react");var _=require("../../parseSPObj.js");var g=require("../../useUrlStateBase/useUrlStateBase.js");var u=require("../../utils.js");function k(e){return e&&e.__esModule?e:{default:e}}var s=k(P);function h(e,l){const p=l?.useHistory;const a=s.default.useMemo(()=>({...j,replace:l?.replace,preventScrollReset:l?.preventScrollReset}),[]);const n=o.useNavigate();const v=s.default.useMemo(()=>p?u.routerHistory:{replace:(t,r)=>n(t,{...a,...r}),push:(t,r)=>n(t,{...a,...r})},[n]);const[c]=o.useSearchParams();const d=o.useHref("/");const{state:U,updateState:i,updateUrl:f,getState:m,reset:S}=g.useUrlStateBase(e,v,({parse:t})=>t(u.filterUnknownParamsClient(e,c.entries())),d);const b=s.default.useCallback((t,r)=>f(t,{...a,...r}),[f]);s.default.useEffect(()=>{i(u.assignValue(e,u.filterUnknownParams(e,_.parseSPObj(Object.fromEntries([...c.entries()]),e))))},[c]);const q=s.default.useCallback(t=>{S({...a,...t})},[S]);return{setState:i,setUrl:b,urlState:U,reset:q,getState:m}}const j={replace:true,preventScrollReset:true};exports.useUrlState=h;
@@ -0,0 +1 @@
1
+ "use strict";const r=new WeakMap;const s=new WeakMap;const a={get(t){return s.get(t)||[]},add(t,e){const n=this.get(t);s.set(t,n.concat(e));return()=>this.remove(t,e)},remove(t,e){const n=this.get(t);const c=n.filter(o=>o!==e);if(c.length){s.set(t,c)}else{s.delete(t)}}};const i={get:function(t){return r.get(t)},set:function(t,e){r.set(t,e)}};exports.stateMap=i;exports.subscribers=a;
@@ -0,0 +1 @@
1
+ "use strict";var f=require("react");function u(e){return e&&e.__esModule?e:{default:e}}var t=u(f);const s=t.default.useInsertionEffect||t.default.useEffect;exports.useInsertionEffect=s;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useSharedState.js");exports.useSharedState=e.useSharedState;
@@ -13,12 +13,12 @@ import { type JSONCompatible } from "../utils";
13
13
  *
14
14
  * setState({ name: 'test' });
15
15
  * // OR
16
- * setState(curr => ({ ...curr, name: 'test' }))
16
+ * setState((curr, initialState) => ({ ...curr, name: 'test' }))
17
17
  * ```
18
18
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/useSharedState}
19
19
  */
20
20
  export declare function useSharedState<T extends JSONCompatible>(defaultState: T, _getInitial?: () => T): {
21
21
  state: T;
22
22
  getState: () => T;
23
- setState: (value: Partial<T> | ((currState: T) => T)) => void;
23
+ setState: (value: Partial<T> | ((currState: T, defaultState: T) => T)) => void;
24
24
  };
@@ -0,0 +1 @@
1
+ "use strict";var b=require("react");var s=require("../subscribers.js");var M=require("../useInsertionEffect.js");var i=require("../utils.js");function g(u){return u&&u.__esModule?u:{default:u}}var n=g(b);function v(u,a){const t=n.default.useRef(u);const[o,f]=n.default.useState(()=>{if(i.isSSR){return a?a?.():t.current}const e=s.stateMap.get(t.current);if(!e){const r=a?.()||t.current;s.stateMap.set(t.current,r);return r}else{return e}});const l=n.default.useCallback(e=>{const r=s.stateMap.get(t.current);const S=typeof e==="function";const c=S?e(r,u):{...r,...e};if(i.isEqual(r,c))return void 0;s.stateMap.set(t.current,c);s.subscribers.get(t.current).forEach(p=>p())},[]);M.useInsertionEffect(()=>{const e=()=>{f(s.stateMap.get(t.current)||t.current)};const r=s.subscribers.add(t.current,e);return()=>{r()}},[]);const d=n.default.useCallback(()=>s.stateMap.get(t.current)||t.current,[]);return{state:o,getState:d,setState:l}}exports.useSharedState=v;
@@ -1 +1 @@
1
- import c from"react";import{stateMap as n,subscribers as o}from"../subscribers.mjs";import{useInsertionEffect as l}from"../useInsertionEffect.mjs";import{isSSR as b,isEqual as d}from"../utils.mjs";function E(a,u){const t=c.useRef(a);const[f,i]=c.useState(()=>{if(b){return u?u?.():t.current}const e=n.get(t.current);if(!e){const r=u?.()||t.current;n.set(t.current,r);return r}else{return e}});const m=c.useCallback(e=>{const r=n.get(t.current);const S=typeof e==="function";const s=S?e(r):{...r,...e};if(d(r,s))return void 0;n.set(t.current,s);o.get(t.current).forEach(g=>g())},[]);l(()=>{const e=()=>{i(n.get(t.current)||t.current)};const r=o.add(t.current,e);return()=>{r()}},[]);const p=c.useCallback(()=>n.get(t.current)||t.current,[]);return{state:f,getState:p,setState:m}}export{E as useSharedState};
1
+ import c from"react";import{stateMap as n,subscribers as a}from"../subscribers.mjs";import{useInsertionEffect as l}from"../useInsertionEffect.mjs";import{isSSR as b,isEqual as d}from"../utils.mjs";function E(o,s){const t=c.useRef(o);const[f,i]=c.useState(()=>{if(b){return s?s?.():t.current}const e=n.get(t.current);if(!e){const r=s?.()||t.current;n.set(t.current,r);return r}else{return e}});const m=c.useCallback(e=>{const r=n.get(t.current);const S=typeof e==="function";const u=S?e(r,o):{...r,...e};if(d(r,u))return void 0;n.set(t.current,u);a.get(t.current).forEach(g=>g())},[]);l(()=>{const e=()=>{i(n.get(t.current)||t.current)};const r=a.add(t.current,e);return()=>{r()}},[]);const p=c.useCallback(()=>n.get(t.current)||t.current,[]);return{state:f,getState:p,setState:m}}export{E as useSharedState};
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlEncode.js");exports.useUrlEncode=e.useUrlEncode;
@@ -0,0 +1 @@
1
+ "use strict";var c=require("react");var r=require("../encodeState/encodeState.js");var l=require("../utils.js");function d(e){return e&&e.__esModule?e:{default:e}}var u=d(c);function i(e){const a=u.default.useCallback((t,s)=>{return l.typeOf(t)==="object"?r.encodeState(t,e,s):""},[e]);const n=u.default.useCallback(t=>r.decodeState(t,e),[e]);return{parse:n,stringify:a}}exports.useUrlEncode=i;
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlStateBase.js");exports.useUrlStateBase=e.useUrlStateBase;
@@ -23,7 +23,7 @@ import { type JSONCompatible, type Router } from "../utils";
23
23
  * );
24
24
  *
25
25
  * updateState({ name: 'John' });
26
- * updateState(curr => ({ ...curr, name: 'John' }));
26
+ * updateState((curr, initialState) => ({ ...curr, name: 'John' }));
27
27
  * updateUrl({ name: 'John' }, { replace: true });
28
28
  * updateUrl(curr => ({ ...curr, name: 'John' }), { replace: true });
29
29
  * reset()
@@ -36,8 +36,8 @@ import { type JSONCompatible, type Router } from "../utils";
36
36
  export declare function useUrlStateBase<T extends JSONCompatible>(defaultState: T, router: Router, getInitialState?: ({ parse, }: {
37
37
  parse: ReturnType<typeof useUrlEncode<T>>["parse"];
38
38
  }) => T, basename?: string): {
39
- updateState: (value: Partial<T> | ((currState: T) => T)) => void;
40
- updateUrl: (value?: Parameters<(value: Partial<T> | ((currState: T) => T)) => void>[0], options?: Options) => void;
39
+ updateState: (value: Partial<T> | ((currState: T, defaultState: T) => T)) => void;
40
+ updateUrl: (value?: Parameters<(value: Partial<T> | ((currState: T, defaultState: T) => T)) => void>[0], options?: Options) => void;
41
41
  state: T;
42
42
  reset: (options?: Options) => void;
43
43
  getState: () => T;
@@ -0,0 +1 @@
1
+ "use strict";var y=require("react");var R=require("../constants/constants.js");var T=require("../useInsertionEffect.js");var _=require("../useSharedState/useSharedState.js");var B=require("../useUrlEncode/useUrlEncode.js");var L=require("../utils.js");function P(e){return e&&e.__esModule?e:{default:e}}var u=P(y);function b(e,t,i,l){const{parse:s,stringify:o}=B.useUrlEncode(e);const{state:f,getState:a,setState:r}=_.useSharedState(e,()=>i?.({parse:s})||e);const d=u.default.useRef(void 0);T.useInsertionEffect(()=>{const n=()=>{const p=s(L.filterUnknownParamsClient(e,I()));r(p)};window.addEventListener(q,n);return()=>{window.removeEventListener(q,n);clearTimeout(d.current)}},[r]);const w=u.default.useRef([]);const h=u.default.useCallback((n,p)=>{const S=typeof n==="function"?n(a(),e):{...a(),...n};const v=o(S,C(e));const m=M(window.location.pathname,l);const U=`${m}${v.length?"?":""}${v}${window.location.hash}`;const $=`${m}${window.location.search}${window.location.hash}`;if(U===$)return;r(S);const{replace:g,...k}=p||{};w.current.push([g?"replace":"push",U,k]);clearTimeout(d.current);const c=w.current.filter(Boolean).at(-1);if(c){queueMicrotask(()=>{d.current=setTimeout(()=>{w.current=[];t[c[0]](c[1],c[2])},R.TIMEOUT)})}},[t,o,a]);const E=u.default.useCallback(n=>{r(e);h(e,n)},[h,r]);return{updateState:r,updateUrl:h,state:f,reset:E,getState:a}}function C(e){const t=Object.keys(e);const i=window.location.search;const l=new URLSearchParams(i);const s=new URLSearchParams;for(const[o,f]of l){!t.includes(o)&&s.set(o,f)}return s}const q="popstate";function I(){return typeof window!=="undefined"&&window.location.search||""}function M(e,t){if(!t||t==="/"){return e}return e.slice(t.length)}exports.useUrlStateBase=b;
@@ -1 +1 @@
1
- import i from"react";import{TIMEOUT as T}from"../constants/constants.mjs";import{useInsertionEffect as b}from"../useInsertionEffect.mjs";import{useSharedState as v}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as L}from"../useUrlEncode/useUrlEncode.mjs";import{filterUnknownParamsClient as P}from"../utils.mjs";function B(t,e,u,f){const{parse:r,stringify:s}=L(t);const{state:l,getState:c,setState:o}=v(t,()=>u?.({parse:r})||t);const w=i.useRef(void 0);b(()=>{const n=()=>{const d=r(P(t,q()));o(d)};window.addEventListener(g,n);return()=>{window.removeEventListener(g,n);clearTimeout(w.current)}},[o]);const m=i.useRef([]);const p=i.useCallback((n,d)=>{const h=typeof n==="function"?n(c()):{...c(),...n};const S=s(h,C(t));const $=I(window.location.pathname,f);const U=`${$}${S.length?"?":""}${S}${window.location.hash}`;const E=`${$}${window.location.search}${window.location.hash}`;if(U===E)return;o(h);const{replace:y,...R}=d||{};m.current.push([y?"replace":"push",U,R]);clearTimeout(w.current);const a=m.current.filter(Boolean).at(-1);if(a){queueMicrotask(()=>{w.current=setTimeout(()=>{m.current=[];e[a[0]](a[1],a[2])},T)})}},[e,s,c]);const k=i.useCallback(n=>{o(t);p(t,n)},[p,o]);return{updateState:o,updateUrl:p,state:l,reset:k,getState:c}}function C(t){const e=Object.keys(t);const u=window.location.search;const f=new URLSearchParams(u);const r=new URLSearchParams;for(const[s,l]of f){!e.includes(s)&&r.set(s,l)}return r}const g="popstate";function q(){return typeof window!=="undefined"&&window.location.search||""}function I(t,e){if(!e||e==="/"){return t}return t.slice(e.length)}export{B as useUrlStateBase};
1
+ import i from"react";import{TIMEOUT as T}from"../constants/constants.mjs";import{useInsertionEffect as v}from"../useInsertionEffect.mjs";import{useSharedState as L}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as P}from"../useUrlEncode/useUrlEncode.mjs";import{filterUnknownParamsClient as b}from"../utils.mjs";function B(t,e,u,f){const{parse:r,stringify:s}=P(t);const{state:l,getState:c,setState:o}=L(t,()=>u?.({parse:r})||t);const m=i.useRef(void 0);v(()=>{const n=()=>{const d=r(b(t,O()));o(d)};window.addEventListener(g,n);return()=>{window.removeEventListener(g,n);clearTimeout(m.current)}},[o]);const w=i.useRef([]);const p=i.useCallback((n,d)=>{const h=typeof n==="function"?n(c(),t):{...c(),...n};const S=s(h,C(t));const $=q(window.location.pathname,f);const U=`${$}${S.length?"?":""}${S}${window.location.hash}`;const E=`${$}${window.location.search}${window.location.hash}`;if(U===E)return;o(h);const{replace:y,...R}=d||{};w.current.push([y?"replace":"push",U,R]);clearTimeout(m.current);const a=w.current.filter(Boolean).at(-1);if(a){queueMicrotask(()=>{m.current=setTimeout(()=>{w.current=[];e[a[0]](a[1],a[2])},T)})}},[e,s,c]);const k=i.useCallback(n=>{o(t);p(t,n)},[p,o]);return{updateState:o,updateUrl:p,state:l,reset:k,getState:c}}function C(t){const e=Object.keys(t);const u=window.location.search;const f=new URLSearchParams(u);const r=new URLSearchParams;for(const[s,l]of f){!e.includes(s)&&r.set(s,l)}return r}const g="popstate";function O(){return typeof window!=="undefined"&&window.location.search||""}function q(t,e){if(!e||e==="/"){return t}return t.slice(e.length)}export{B as useUrlStateBase};
package/dist/utils.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";const c=n=>{const t=typeof n;const r=n===null;const e=Array.isArray(n);const s=n instanceof Date;const o=!r&&!s&&!e&&t==="object";return r&&"null"||s&&"date"||e&&"array"||o&&"object"||t};const a=typeof window==="undefined";const f=n=>new URLSearchParams(typeof n==="string"?l(n):n?.toString?.()||"");const l=n=>n.split("?")?.[1]||n||"";const u=(n,t)=>JSON.stringify(n)===JSON.stringify(t);function w(n,t){const r=new URLSearchParams;const e=i(n,[...new URLSearchParams(t||"").entries()]);for(const[s,o]of e){r.set(s,o)}return r.toString()}function y(n,t){return Object.fromEntries(i(n,Object.entries(t||{})))}function i(n,t){const r=Object.keys(n);const e=[];for(let s=0;s<t.length;s++){if(r.includes(t[s]?.[0])){e.push([t[s][0].replace(/\+/g," "),t[s][1]])}}return e}function S(n,t){const r=Object.assign({},n);for(const[e]of Object.entries(n)){const s=e;const o=t[s]!==void 0;r[s]=o?t[s]:n[s]}return r}const g={push:n=>{window&&window.history.pushState(null,"",n)},replace:n=>{window&&window.history.replaceState(null,"",n)}};const p=/apple/i.test(!a&&navigator?.userAgent||"");exports.assignValue=S;exports.filterUnknown=i;exports.filterUnknownParams=y;exports.filterUnknownParamsClient=w;exports.getParams=f;exports.isEqual=u;exports.isSSR=a;exports.isSafari=p;exports.routerHistory=g;exports.typeOf=c;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "state-in-url",
3
- "version": "5.0.0",
3
+ "version": "5.2.0",
4
4
  "description": "Store state in URL as in object, types and structure are preserved, with TS validation. Same API as React.useState, wthout any hasssle or boilerplate. Next.js@14-15, react-router@6-7, and remix@2.",
5
5
  "homepage": "https://state-in-url.dev",
6
6
  "repository": {
@@ -36,47 +36,58 @@
36
36
  "files": [
37
37
  "dist"
38
38
  ],
39
- "main": "dist/index.mjs",
39
+ "main": "./dist/index.js",
40
+ "module": "dist/index.mjs",
40
41
  "exports": {
41
42
  ".": {
42
43
  "types": "./dist/index.d.ts",
43
- "default": "./dist/index.mjs"
44
+ "default": "./dist/index.mjs",
45
+ "require": "./dist/index.js"
44
46
  },
45
47
  "./next": {
46
48
  "types": "./dist/next/index.d.ts",
47
- "default": "./dist/next/index.mjs"
49
+ "default": "./dist/next/index.mjs",
50
+ "require": "./dist/next/index.js"
48
51
  },
49
52
  "./react-router": {
50
53
  "types": "./dist/react-router/index.d.ts",
51
- "default": "./dist/react-router/index.mjs"
54
+ "default": "./dist/react-router/index.mjs",
55
+ "require": "./dist/react-router/index.js"
52
56
  },
53
57
  "./remix": {
54
58
  "types": "./dist/remix/index.d.ts",
55
- "default": "./dist/remix/index.mjs"
59
+ "default": "./dist/remix/index.mjs",
60
+ "require": "./dist/remix/index.js"
56
61
  },
57
62
  "./encodeState": {
58
63
  "types": "./dist/encodeState/index.d.ts",
59
- "default": "./dist/encodeState/index.mjs"
64
+ "default": "./dist/encodeState/index.mjs",
65
+ "require": "./dist/encodeState/index.js"
60
66
  },
61
67
  "./encoder": {
62
68
  "types": "./dist/encoder/index.d.ts",
63
- "default": "./dist/encoder/index.mjs"
69
+ "default": "./dist/encoder/index.mjs",
70
+ "require": "./dist/encoder/index.js"
64
71
  },
65
72
  "./useSharedState": {
66
73
  "types": "./dist/useSharedState/index.d.ts",
67
- "default": "./dist/useSharedState/index.mjs"
74
+ "default": "./dist/useSharedState/index.mjs",
75
+ "require": "./dist/useSharedState/index.js"
68
76
  },
69
77
  "./useUrlEncode": {
70
78
  "types": "./dist/useUrlEncode/index.d.ts",
71
- "default": "./dist/useUrlEncode/index.mjs"
79
+ "default": "./dist/useUrlEncode/index.mjs",
80
+ "require": "./dist/useUrlEncode/index.js"
72
81
  },
73
82
  "./useUrlStateBase": {
74
83
  "types": "./dist/useUrlStateBase/index.d.ts",
75
- "default": "./dist/useUrlStateBase/index.mjs"
84
+ "default": "./dist/useUrlStateBase/index.mjs",
85
+ "require": "./dist/useUrlStateBase/index.js"
76
86
  },
77
87
  "./utils": {
78
88
  "types": "./dist/utils.d.ts",
79
- "default": "./dist/utils.mjs"
89
+ "default": "./dist/utils.mjs",
90
+ "require": "./dist/utils.js"
80
91
  }
81
92
  },
82
93
  "types": "./dist/index.d.ts",