@ossy/router-react 1.40.2 → 1.40.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.
package/README.md CHANGED
@@ -8,19 +8,20 @@ The components that want to trigger a navigation only need to be aware of the pa
8
8
 
9
9
  Loading of appropriate data is still the responsibility of the component.
10
10
 
11
- This Router is aimed towards websites that want to prerender as much as possible on the server and therefore doesn't include many client side functionality that you might want for SPAs, like setting search params without doing a full navigation.
11
+ Route changes use full navigation (`window.location`). Query-string state on the **same pathname** can be updated client-side via `useSearchParams()` without remounting the page tree.
12
12
 
13
13
  ## Features
14
14
  - Easy to use
15
15
  - Supports single language websites
16
16
  - Supports multi language websites with localized paths
17
17
  - Supports prerendering (API is only hooks based ATM, API that works for server components is comming)
18
+ - Client-side search param updates on the current page (`useSearchParams`) via `history.pushState`
18
19
 
19
20
  ## Roadmap
20
21
  - [ ] API that works on server components (non hooks API)
21
- - [ ] Trigger navigation programaticly on the client (`router.navigate()`)
22
- - [ ] Update search params without doing full navigation, for times you keep state in the url
23
- - [ ] Redirects
22
+ - [x] Trigger navigation programmatically on the client (`router.navigate()`)
23
+ - [x] Update search params without full navigation (`useSearchParams().set` / `.delete`)
24
+ - [x] Redirects (page `redirect` field)
24
25
 
25
26
  ## Table of Contents
26
27
 
@@ -39,6 +40,7 @@ This Router is aimed towards websites that want to prerender as much as possible
39
40
  - [Switching language (same page)](#switching-language-same-page)
40
41
  - [Navigating back](#navigating-back)
41
42
  - [Redirects](#redirects)
43
+ - [Search params (same page)](#search-params-same-page)
42
44
 
43
45
  ## What problem does this package solve?
44
46
  When creating links in your app it's not uncommon to hard code the path you want to navigate to.
@@ -293,6 +295,42 @@ const pages = [
293
295
  ]
294
296
  ```
295
297
 
298
+ ### Search params (same page)
299
+
300
+ Read and update the query string on the **current pathname** without a full navigation. Use `useSearchParams()` when you only care about query state (storage folders, selected resource `?r=`, auth tokens). Use `useRouter().searchParams` for the same values when you already depend on the full router object.
301
+
302
+ ```tsx
303
+ import { useSearchParams, useRouter } from '@ossy/router-react'
304
+
305
+ function StorageBrowser () {
306
+ const searchParams = useSearchParams()
307
+ const location = searchParams.location ?? '/'
308
+
309
+ const openFolder = (path: string) => {
310
+ searchParams.set('location', path.endsWith('/') ? path : `${path}/`)
311
+ }
312
+
313
+ const clearSelection = () => {
314
+ searchParams.delete('r')
315
+ }
316
+
317
+ return (
318
+ <div>
319
+ <p>Current folder: {location}</p>
320
+ <button type="button" onClick={() => openFolder('/docs/')}>Open docs</button>
321
+ <button type="button" onClick={clearSelection}>Clear selection</button>
322
+ </div>
323
+ )
324
+ }
325
+ ```
326
+
327
+ - `set(name, value)` — updates the query via `pushState` and re-renders subscribers. Empty values remove the key.
328
+ - `delete(name)` — removes a query key.
329
+ - Browser back/forward syncs via `popstate`.
330
+ - SSR: pass `url` on `<Router url={requestUrl} />` so the initial query matches the request; on the client, `window.location.search` is followed after hydration.
331
+
332
+ `router.href` reflects the URL at mount; it is not updated on query-only changes. Read query keys from `useSearchParams()` or `router.searchParams`.
333
+
296
334
  ## Why not open source?
297
335
 
298
336
  TLDR: It might be in the future.
package/build/index.js CHANGED
@@ -1 +1 @@
1
- import e,{createContext as n,useCallback as a,useContext as r}from"react";import{Router as i,isCatchAllRoutePath as o}from"@ossy/router";var t=function(){return t=Object.assign||function(e){for(var n,a=1,r=arguments.length;a<r;a++)for(var i in n=arguments[a])Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i]);return e},t.apply(this,arguments)};"function"==typeof SuppressedError&&SuppressedError;var u=n({pages:[],defaultLanguage:void 0,supportedLanguages:[]}),l=function(){return r(u)},d=function(n){var r,l,d,s,c,f=n.url,p=n.pages,g=void 0===p?[]:p,v=n.defaultLanguage,w=void 0===v?void 0:v,h=n.supportedLanguages,m=void 0===h?[]:h,y=n.children;if(f){var L=f.match(/([^?#]*)(\?[^#]*)?(#.*)?/);l=f,d=(null==L?void 0:L[1])||"",s=(null==L?void 0:L[3])||"",c=(null==L?void 0:L[2])||""}else{if("undefined"==typeof window)return e.createElement(e.Fragment,null);l=window.location.href,d=window.location.pathname,c=(null==(s=window.location.hash)?void 0:s.includes("?"))?s.split("?")[1]:window.location.search}var P=m.length>1&&!!w,S=d.split("/")[1]||"",b=m.includes(S)?S:w,E=i.of({pages:g,defaultLanguage:w,supportedLanguages:m}),R=E.getParamsFromUrl(l),k=new URLSearchParams(c),U=E.getPageByUrl(l);if(U||(P&&"/"===d&&"undefined"!=typeof window&&(window.location.href="/"+w),U=P?g.find(function(e){var n,a=e.path,r=null!==(n=null==a?void 0:a[b])&&void 0!==n?n:null==a?void 0:a[w];return o(r)}):g.find(function(e){return o(e.path)})),!U)return console.warn("[RouterReact] No active page found for",d),e.createElement(e.Fragment,null);a(function(e,n){if("undefined"!=typeof window){window.history.pushState({},"","".concat(d,"?").concat(k.toString()));var a=new URLSearchParams(k);a.set(e,n),history.pushState({},"","?".concat(a.toString()))}},[k]);var F=a(function(e){var n,a,r,i,o,t={};if("string"==typeof e?(n=e,t=R,a=b):(o=e).id||void 0===o.language?(n=(null==e?void 0:e.id)||U.id,t=(null==e?void 0:e.params)||R,a=(null==e?void 0:e.language)||b):(n=U.id,t=R,a=e.language),n.includes("#")){var u=n.split("#");if(n=u[0],(r=u[1]).includes("?")){var l=n.split("?");r=l[0],i=l[1]}}else if(n.includes("?")){var d=n.split("?");n=d[0],i=d[1]}var s=E.getPathname({id:n.replace("@",""),params:t,language:a});return r&&(s=s+"#"+r),i&&(s=s+"?"+i),s},[E,b]),O=a(function(e){if("undefined"!=typeof window&&e)if("@back"!==e){var n=F(e);n&&(window.location.href=n)}else window.history.back()},[F]),j=a(function(){"undefined"!=typeof window&&window.history.back()},[]),x={href:l,pages:g,language:b,defaultLanguage:w,supportedLanguages:m,params:R,searchParams:t({},Array.from(k.entries()).reduce(function(e,n){var a,r=n[0],i=n[1];return t(t({},e),((a={})[r]=i,a))},{})),navigate:O,getHref:F,back:j};(null==U?void 0:U.redirect)&&O({id:U.redirect});var A=(null==U?void 0:U.element)||(null===(r=null==U?void 0:U.render)||void 0===r?void 0:r.call(U));return e.createElement(u.Provider,{value:x},y||A)};export{d as Router,u as RouterContext,l as useRouter};
1
+ import n,{createContext as e,useMemo as t,useState as o,useEffect as i,useCallback as r,useContext as a}from"react";import{Router as u,isCatchAllRoutePath as l}from"@ossy/router";var d=function(){return d=Object.assign||function(n){for(var e,t=1,o=arguments.length;t<o;t++)for(var i in e=arguments[t])Object.prototype.hasOwnProperty.call(e,i)&&(n[i]=e[i]);return n},d.apply(this,arguments)};"function"==typeof SuppressedError&&SuppressedError;var c=e({href:"",pages:[],language:void 0,defaultLanguage:void 0,supportedLanguages:[],params:{},navigate:function(){},getHref:function(){return""},back:function(){}}),f=e({set:function(){},delete:function(){}}),s=function(){return a(f)},v=function(){var n=a(c),e=a(f);return t(function(){return d(d({},n),{searchParams:e})},[n,e])},p=function(e){var a,s,v,p,w=e.url,g=e.pages,h=void 0===g?[]:g,m=e.defaultLanguage,y=void 0===m?void 0:m,L=e.supportedLanguages,E=void 0===L?[]:L,P=e.children;if(w){var b=w.match(/([^?#]*)(\?[^#]*)?(#.*)?/);a=w,s=(null==b?void 0:b[1])||"",v=(null==b?void 0:b[3])||"",p=(null==b?void 0:b[2])||""}else{if("undefined"==typeof window)return n.createElement(n.Fragment,null);a=window.location.href,s=window.location.pathname,v=window.location.hash,p=(null==v?void 0:v.includes("?"))?v.split("?")[1]:window.location.search}var S=E.length>1&&!!y,k=s.split("/")[1]||"",R=E.includes(k)?k:y,O=t(function(){return u.of({pages:h,defaultLanguage:y,supportedLanguages:E})},[h,y,E]),U=t(function(){return O.getParamsFromUrl(a)},[O,a]),j=o(function(){return"undefined"==typeof window?p:window.location.search}),F=j[0],H=j[1];i(function(){if("undefined"!=typeof window){var n=function(){return H(window.location.search)};return window.addEventListener("popstate",n),function(){return window.removeEventListener("popstate",n)}}},[]);var x="undefined"==typeof window&&w?p:F,B=new URLSearchParams(x.replace(/^\?/,"")),N=r(function(n){if("undefined"!=typeof window){var e=new URLSearchParams(window.location.search);n(e);var t=e.toString(),o=t?"".concat(window.location.pathname,"?").concat(t):window.location.pathname;window.history.pushState({},"",o),H(window.location.search)}},[]),q=r(function(n,e){N(function(t){null==e||""===e?t.delete(n):t.set(n,e)})},[N]),z=r(function(n){N(function(e){e.delete(n)})},[N]),A=O.getPageByUrl(a);A||(S&&"/"===s&&"undefined"!=typeof window&&(window.location.href="/"+y),A=S?h.find(function(n){var e,t=n.path,o=null!==(e=null==t?void 0:t[R])&&void 0!==e?e:null==t?void 0:t[y];return l(o)}):h.find(function(n){return l(n.path)})),A||console.warn("[RouterReact] No active page found for",s);var C=r(function(n){var e,t,o,i,r,a,u={};if("string"==typeof n?(t=n,u=U,o=R):(a=n).id||void 0===a.language?(t=(null==n?void 0:n.id)||(null==A?void 0:A.id)||"",u=(null==n?void 0:n.params)||U,o=(null==n?void 0:n.language)||R):(t=null!==(e=null==A?void 0:A.id)&&void 0!==e?e:"",u=U,o=n.language),t.includes("#")){var l=t.split("#");if(t=l[0],(i=l[1]).includes("?")){var d=t.split("?");i=d[0],r=d[1]}}else if(t.includes("?")){var c=t.split("?");t=c[0],r=c[1]}var f=O.getPathname({id:t.replace("@",""),params:u,language:o});return i&&(f=f+"#"+i),r&&(f=f+"?"+r),null!=f?f:""},[O,R,U,A]),D=r(function(n){if("undefined"!=typeof window&&n)if("@back"!==n){var e=C(n);e&&(window.location.href=e)}else window.history.back()},[C]),G=r(function(){"undefined"!=typeof window&&window.history.back()},[]),I=t(function(){var n=Object.fromEntries(B.entries());return d(d({},n),{set:q,delete:z})},[F,q,z]),J=t(function(){return{href:a,pages:h,language:R,defaultLanguage:y,supportedLanguages:E,params:U,navigate:D,getHref:C,back:G}},[a,h,R,y,E,U,D,C,G]);(null==A?void 0:A.redirect)&&D({id:A.redirect});var K=t(function(){var n,e;return null!==(n=null==A?void 0:A.element)&&void 0!==n?n:null===(e=null==A?void 0:A.render)||void 0===e?void 0:e.call(A)},[A]);return A?n.createElement(c.Provider,{value:J},n.createElement(f.Provider,{value:I},P||K)):n.createElement(n.Fragment,null)};export{p as Router,c as RouterContext,f as SearchParamsContext,v as useRouter,s as useSearchParams};
@@ -1,14 +1,37 @@
1
1
  import React, { PropsWithChildren } from 'react';
2
2
  import { MultiLanguagePage, RouterOptions, SingleLanguagePage } from '@ossy/router';
3
- export declare const RouterContext: React.Context<{
3
+ export interface RouterSearchParams {
4
+ set: (name: string, value: string) => void;
5
+ delete: (name: string) => void;
6
+ }
7
+ type RouterNavigationContextValue = {
8
+ href: string;
4
9
  pages: RouterPage[];
10
+ language: string | undefined;
5
11
  defaultLanguage: string | undefined;
6
12
  supportedLanguages: string[];
7
- }>;
13
+ params: Record<string, string>;
14
+ navigate: (navigationRequest: string | '@back' | GetHrefRequest | GetHrefOfActivePageByLanguageRequest) => void;
15
+ getHref: (getHrefRequest: string | GetHrefRequest | GetHrefOfActivePageByLanguageRequest) => string;
16
+ back: () => void;
17
+ };
18
+ export declare const RouterContext: React.Context<RouterNavigationContextValue>;
19
+ export type RouterSearchParamsState = RouterSearchParams & Record<string, string | undefined>;
20
+ export declare const SearchParamsContext: React.Context<RouterSearchParamsState>;
21
+ /** Query string state — updates on pushState/popstate without remounting the page tree. */
22
+ export declare const useSearchParams: () => RouterSearchParamsState;
23
+ /** Navigation + route params. Stable across query-only URL changes when used without `searchParams`. */
8
24
  export declare const useRouter: () => {
25
+ searchParams: RouterSearchParamsState;
26
+ href: string;
9
27
  pages: RouterPage[];
28
+ language: string | undefined;
10
29
  defaultLanguage: string | undefined;
11
30
  supportedLanguages: string[];
31
+ params: Record<string, string>;
32
+ navigate: (navigationRequest: string | "@back" | GetHrefRequest | GetHrefOfActivePageByLanguageRequest) => void;
33
+ getHref: (getHrefRequest: string | GetHrefRequest | GetHrefOfActivePageByLanguageRequest) => string;
34
+ back: () => void;
12
35
  };
13
36
  export declare const Router: <T extends RouterPage>({ url, pages, defaultLanguage, supportedLanguages, children, }: PropsWithChildren<RouterProps<T>>) => React.JSX.Element;
14
37
  export interface RouterProps<T extends RouterPage> extends RouterOptions<T> {
@@ -49,3 +72,4 @@ export interface GetHrefRequest {
49
72
  export interface GetHrefOfActivePageByLanguageRequest {
50
73
  language: string;
51
74
  }
75
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/router-react",
3
- "version": "1.40.2",
3
+ "version": "1.40.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ossy-se/packages.git"
@@ -65,5 +65,5 @@
65
65
  "/build",
66
66
  "README.md"
67
67
  ],
68
- "gitHead": "2b8745d57fee8b6c08787e2755b4df489291a5cd"
68
+ "gitHead": "a0d89185a17f8de8ce328c3a648c108ff1d61d8f"
69
69
  }