crossroad 1.2.0 → 1.2.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/index.min.js +1 -1
- package/package.json +1 -1
- package/readme.md +34 -20
package/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t,{createContext as e,useState as r,useCallback as n,useEffect as o,useContext as a}from"react";var i=e();const s=t=>{if("string"!=typeof t)return t;const e={},r=new URL(t,"http://localhost:3000/");e.path=r.pathname.replace(/\/$/,"")||"/",e.query={};for(const[t]of r.searchParams)e.query[t]=(n=r.searchParams.getAll(t)).length>1?n:n[0];var n;return r.hash&&(e.hash=r.hash.replace(/^#/,"")),e},p=t=>{if("string"==typeof t)return t;const{path:e,query:r={},hash:n}=t||{};let o=e||"/";const a=new URLSearchParams(Object.entries(r).map(([t,e])=>(Array.isArray(e)?e:[e]).map(e=>[t,e])).flat().filter(([t,e])=>e)).toString();return a&&(o+="?"+a),n&&(o+="#"+n),o};var u=()=>"undefined"==typeof window;function l(t,e,r={}){if(t=JSON.parse(JSON.stringify(s(t))),(e=JSON.parse(JSON.stringify(s(e)))).path=e.path.replace(/\/$/,"")||"/",t.path=t.path.replace(/\/$/,"")||"/",t.path.endsWith("*")){t.path=t.path.replace(/\/?\*/,"")||"/";const r=t.path.split("/").filter(Boolean).length;e.path="/"+e.path.slice(1).split("/").slice(0,r).join("/")}if(Object.entries(t.query).length)for(let r in t.query){if(!(r in e.query))return!1;if(t.query[r]&&t.query[r]!==e.query[r])return!1}if(!t.path.includes(":"))return t.path===e.path&&r;if(t.path.split("/").length!==e.path.split("/").length)return!1;const n={},o=t.path.split("/").every((t,o)=>{const a=e.path.split("/")[o];return t.startsWith(":")?(n[t.slice(1)]=a,r):a===t});return o&&Object.assign(r,n),o&&r}var c=({path:e="*",scrollUp:r,component:n,render:o,children:s})=>{const p=a(i),u=l(e,p[0]);if(!u)return null;if(r&&window.scrollTo(0,0),n){const e=n;s=t.createElement(e,u)}else if(o)s=o(u);else if(!s)throw new Error("Route needs prop `component`, `render` or `children`");return t.createElement(i.Provider,{value:[{...p[0],params:u},...p.slice(1)]},s)},h=()=>{const t=a(i);if(!t)throw new Error("Wrap your App with <Router>");return t};var f=({redirect:t,children:e})=>{const[r,n]=h(),a=(t=>(Array.isArray(t)||(t=[t]),t.filter(t=>t&&t.props)))(e).find(t=>l(t.props.path||"*",r))||null;return o(()=>{t&&(a||("function"==typeof t&&(t=t(r)),n(p(t))))},[t,a]),a},y=()=>{const[t,e]=h(),r=n((t,r)=>{e(e=>("function"==typeof t&&(t=t(e.path)),"string"!=typeof t&&(t="/"),{...e,path:t}),r)},[]);return[t.path,r]};const d=t=>p({query:t});var w=t=>t?(t=>{const[e,r]=h(),o=n((e,n)=>{r(r=>{const n=r.query[t];if((e="function"==typeof e?e(n):e)===n)return r;if(e)return{...r,query:{...r.query,[t]:e}};{const{[t]:e,...n}=r.query;return{...r,query:n}}},n)},[]);return[e.query[t],o]})(t):(()=>{const[t,e]=h(),r=n((t,r)=>{e(e=>("string"==typeof(t="function"==typeof t?t(e.query):t)&&(t=s("/?"+t.replace(/^\?/,"")).query),t=s(d(t)).query,d(t)===d(e.query)?e:{...e,query:t}),r)},[]);return[t.query,r]})(),
|
|
1
|
+
import t,{createContext as e,useState as r,useCallback as n,useEffect as o,useContext as a}from"react";var i=e();const s=t=>{if("string"!=typeof t)return t;const e={},r=new URL(t,"http://localhost:3000/");e.path=r.pathname.replace(/\/$/,"")||"/",e.query={};for(const[t]of r.searchParams)e.query[t]=(n=r.searchParams.getAll(t)).length>1?n:n[0];var n;return r.hash&&(e.hash=r.hash.replace(/^#/,"")),e},p=t=>{if("string"==typeof t)return t;const{path:e,query:r={},hash:n}=t||{};let o=e||"/";const a=new URLSearchParams(Object.entries(r).map(([t,e])=>(Array.isArray(e)?e:[e]).map(e=>[t,e])).flat().filter(([t,e])=>e)).toString();return a&&(o+="?"+a),n&&(o+="#"+n),o};var u=()=>"undefined"==typeof window;function l(t,e,r={}){if(t=JSON.parse(JSON.stringify(s(t))),(e=JSON.parse(JSON.stringify(s(e)))).path=e.path.replace(/\/$/,"")||"/",t.path=t.path.replace(/\/$/,"")||"/",t.path.endsWith("*")){t.path=t.path.replace(/\/?\*/,"")||"/";const r=t.path.split("/").filter(Boolean).length;e.path="/"+e.path.slice(1).split("/").slice(0,r).join("/")}if(Object.entries(t.query).length)for(let r in t.query){if(!(r in e.query))return!1;if(t.query[r]&&t.query[r]!==e.query[r])return!1}if(!t.path.includes(":"))return t.path===e.path&&r;if(t.path.split("/").length!==e.path.split("/").length)return!1;const n={},o=t.path.split("/").every((t,o)=>{const a=e.path.split("/")[o];return t.startsWith(":")?(n[t.slice(1)]=decodeURIComponent(a),r):a===t});return o&&Object.assign(r,n),o&&r}var c=({path:e="*",scrollUp:r,component:n,render:o,children:s})=>{const p=a(i),u=l(e,p[0]);if(!u)return null;if(r&&window.scrollTo(0,0),n){const e=n;s=t.createElement(e,u)}else if(o)s=o(u);else if(!s)throw new Error("Route needs prop `component`, `render` or `children`");return t.createElement(i.Provider,{value:[{...p[0],params:u},...p.slice(1)]},s)},h=()=>{const t=a(i);if(!t)throw new Error("Wrap your App with <Router>");return t};var f=({redirect:t,children:e})=>{const[r,n]=h(),a=(t=>(Array.isArray(t)||(t=[t]),t.filter(t=>t&&t.props)))(e).find(t=>l(t.props.path||"*",r))||null;return o(()=>{t&&(a||("function"==typeof t&&(t=t(r)),n(p(t))))},[t,a]),a},y=()=>{const[t,e]=h(),r=n((t,r)=>{e(e=>("function"==typeof t&&(t=t(e.path)),"string"!=typeof t&&(t="/"),{...e,path:t}),r)},[]);return[t.path,r]};const d=t=>p({query:t});var w=t=>t?(t=>{const[e,r]=h(),o=n((e,n)=>{r(r=>{const n=r.query[t];if((e="function"==typeof e?e(n):e)===n)return r;if(e)return{...r,query:{...r.query,[t]:e}};{const{[t]:e,...n}=r.query;return{...r,query:n}}},n)},[]);return[e.query[t],o]})(t):(()=>{const[t,e]=h(),r=n((t,r)=>{e(e=>("string"==typeof(t="function"==typeof t?t(e.query):t)&&(t=s("/?"+t.replace(/^\?/,"")).query),t=s(d(t)).query,d(t)===d(e.query)?e:{...e,query:t}),r)},[]);return[t.query,r]})(),m=()=>{const[t,e]=h(),r=n((t,r)=>{e(e=>("function"==typeof t&&(t=t(e.hash)),"string"!=typeof t&&(t=""),t=t.replace(/^#/,""),{...e,hash:t}),r)},[]);return[t.hash,r]},q=t=>{const e=a(i);return t?l(t,e[0].path)||{}:e[0].params};export default({scrollUp:e,url:a,children:l})=>{const c=a||(u()?"/":window.location.href),[h,f]=r(()=>s(c)),y=n((t,{mode:r="push"}={})=>{if(!history[r+"State"])throw new Error(`Invalid mode "${r}"`);t="function"==typeof t?t(h):t,f(n=>p(n)===p(t)?n:(history[r+"State"]({},null,p(t)),e&&window.scrollTo(0,0),s(t)))},[]);return o(()=>{if(u())return;const t=()=>f(s(window.location.href)),e=t=>{const e=(t=>{if(!t)return null;const e=t.getAttribute("href");return e?/^https?:\/\//.test(e)||null!==t.getAttribute("target")?null:e:null})(t.target.closest("a"));if(!e)return;t.preventDefault();const[r,n]=e.split("#");r&&y(r),n&&(window.location.hash="#"+n)};return window.addEventListener("popstate",t),document.addEventListener("click",e),()=>{window.removeEventListener("popstate",t),document.removeEventListener("click",e)}},[y]),t.createElement(i.Provider,{value:[h,y]},l)};export{i as Context,c as Route,f as Switch,m as useHash,q as useParams,y as usePath,w as useQuery,h as useUrl};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crossroad",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "A React library to handle navigation in your WebApp. Built with simple components and React Hooks so your code is cleaner.",
|
|
5
5
|
"homepage": "https://crossroad.page/",
|
|
6
6
|
"repository": "https://github.com/franciscop/crossroad.git",
|
package/readme.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A React library to handle navigation in your WebApp. Built with simple components and React Hooks so you write cleaner code:
|
|
4
4
|
|
|
5
5
|
- `<Router>`, `<Switch>` and `<Route>` inspired by React Router so it's easy to get started.
|
|
6
|
-
- Very useful hooks like [`useUrl`](#useurl), [`useQuery`](#usequery), etc.
|
|
6
|
+
- Very useful hooks like [`useUrl`](#useurl), [`useQuery`](#usequery), etc. Follow [the rules of hooks](https://reactjs.org/docs/hooks-rules.html).
|
|
7
7
|
- Links are plain `<a>` instead of custom components. [Read more](#a).
|
|
8
8
|
- The `<Route>` path is `exact` by default and can match query parameters.
|
|
9
9
|
- It's [just ~1.5kb](https://bundlephobia.com/package/crossroad) (min+gzip) instead of the 17kb of React Router(+Dom).
|
|
@@ -357,6 +357,10 @@ These are the structures of each:
|
|
|
357
357
|
- `setUrl({ path: '/newpath', query: { hello: 'world' } })`: update the path and query (and delete the hash if any)
|
|
358
358
|
- `setUrl(prev => ...)`: use the previous url (object)
|
|
359
359
|
|
|
360
|
+
`useUrl()` is powerful enough for all of your needs, but you might still be interested in other hooks to simplify situations where you do e.g. heavy query manipulation with [`useQuery`](#usequery).
|
|
361
|
+
|
|
362
|
+
#### url
|
|
363
|
+
|
|
360
364
|
The resulting `url` is an object containing each of the parts of the URL:
|
|
361
365
|
|
|
362
366
|
```js
|
|
@@ -367,7 +371,25 @@ console.log(url.query); // { filter: hello }
|
|
|
367
371
|
console.log(url.hash); // world
|
|
368
372
|
```
|
|
369
373
|
|
|
370
|
-
|
|
374
|
+
It is memoized, so that if the url doesn't change then the object will remain the same. The same of course applies to the subelements like `url.path`. It will however change when the url changes, so you want to put it in your dependencies as usual:
|
|
375
|
+
|
|
376
|
+
```js
|
|
377
|
+
// You can put the whole thing if you want to listen to
|
|
378
|
+
// ANY change on the url
|
|
379
|
+
useEffect(() => {
|
|
380
|
+
// ...
|
|
381
|
+
}, [url]);
|
|
382
|
+
|
|
383
|
+
// Or only a part of it. This is useful becase it WON'T trigger
|
|
384
|
+
// when the query or hashtag change
|
|
385
|
+
useEffect(() => {
|
|
386
|
+
// ...
|
|
387
|
+
}, [url.path]);
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### Setter
|
|
391
|
+
|
|
392
|
+
The setter can be invoked directly, or with a callback:
|
|
371
393
|
|
|
372
394
|
```js
|
|
373
395
|
const [url, setUrl] = useUrl();
|
|
@@ -388,27 +410,15 @@ setUrl({ ...url, query: { search: "hello" } });
|
|
|
388
410
|
setUrl({ ...url, query: { ...url.query, safe: "no" } });
|
|
389
411
|
```
|
|
390
412
|
|
|
391
|
-
`useUrl()` is powerful enough for all of your needs, but you might still be interested in other hooks to simplify situations where you do e.g. heavy query manipulation with `useQuery`.
|
|
392
|
-
|
|
393
|
-
#### Setter
|
|
394
|
-
|
|
395
|
-
The setter can be invoked directly, or with a callback:
|
|
396
|
-
|
|
397
|
-
```js
|
|
398
|
-
setUrl("/newurl");
|
|
399
|
-
setUrl((oldUrl) => "/newurl");
|
|
400
|
-
setUrl((oldUrl) => ({ ...oldUrl, path: newPath }));
|
|
401
|
-
```
|
|
402
|
-
|
|
403
413
|
The function `setUrl` is _always_ the same, so it doesn't matter whether you put it as a dependency or not. However the `path` can be updated and change, so you want to depend on it:
|
|
404
414
|
|
|
405
415
|
```js
|
|
406
|
-
const [url, setUrl] =
|
|
416
|
+
const [url, setUrl] = useUrl();
|
|
407
417
|
useEffect(() => {
|
|
408
418
|
if (url.path === "/base") {
|
|
409
419
|
setUrl("/base/deeper");
|
|
410
420
|
}
|
|
411
|
-
}, [url, setUrl]);
|
|
421
|
+
}, [url.path, setUrl]);
|
|
412
422
|
```
|
|
413
423
|
|
|
414
424
|
If you update the url with the current url, it won't trigger a rerender. So the above can also be written as this, removing all dependencies:
|
|
@@ -428,6 +438,7 @@ useEffect(() => {
|
|
|
428
438
|
By default `setUrl()` will create a new entry in the browser history. If you want to instead replace the current url you can pass a second parameter with `{ mode: 'replace' }`:
|
|
429
439
|
|
|
430
440
|
```js
|
|
441
|
+
setUrl("/newurl"); // Default: "push"
|
|
431
442
|
setUrl("/newurl", { mode: "replace" });
|
|
432
443
|
```
|
|
433
444
|
|
|
@@ -451,9 +462,9 @@ const Login = () => {
|
|
|
451
462
|
};
|
|
452
463
|
```
|
|
453
464
|
|
|
454
|
-
The path is always a string equivalent to `window.location.pathname`.
|
|
465
|
+
The path is always a string equivalent to `window.location.pathname`. Why not use `window.location.pathname` then? Because usePath() is a hook that will trigger a re-render when the path changes!
|
|
455
466
|
|
|
456
|
-
> Note:
|
|
467
|
+
> Note: `setPath` _only_ modifies the path(name) and keeps the search query and hash the same, so if you want to modify the full URL you should instead utilize `useUrl()` and `setUrl('/welcome')`
|
|
457
468
|
|
|
458
469
|
#### Setter
|
|
459
470
|
|
|
@@ -492,6 +503,7 @@ useEffect(() => {
|
|
|
492
503
|
By default `setPath()` will create a new entry in the browser history. If you want to instead replace the current url you can pass a second parameter with `{ mode: 'replace' }`:
|
|
493
504
|
|
|
494
505
|
```js
|
|
506
|
+
setPath("/newpath"); // Default: "push"
|
|
495
507
|
setPath("/newpath", { mode: "replace" });
|
|
496
508
|
```
|
|
497
509
|
|
|
@@ -568,6 +580,7 @@ import { useQuery as useSearch } from 'crossroad';
|
|
|
568
580
|
By default `setQuery()` will create a new entry in the browser history. If you want to instead replace the current entry, so that the "Back" button goes to the previous page, you can pass a second parameter with `{ mode: 'replace' }`:
|
|
569
581
|
|
|
570
582
|
```js
|
|
583
|
+
setQuery({ search: "abc" }); // Default: "push"
|
|
571
584
|
setQuery({ search: "abc" }, { mode: "replace" });
|
|
572
585
|
```
|
|
573
586
|
|
|
@@ -600,6 +613,7 @@ If you want to remove the hash, pass a `null` or `undefined` to the setter.
|
|
|
600
613
|
By default `setHash()` will create a new entry in the browser history. If you want to instead replace the current entry, so that the "Back" button goes to the previous page, you can pass a second parameter with `{ mode: 'replace' }`:
|
|
601
614
|
|
|
602
615
|
```js
|
|
616
|
+
setHash("newhash"); // Default: "push"
|
|
603
617
|
setHash("newhash", { mode: "replace" });
|
|
604
618
|
```
|
|
605
619
|
|
|
@@ -682,7 +696,7 @@ That's it, in the [Codesandbox](https://codesandbox.io/s/loving-joana-jikne) we
|
|
|
682
696
|
|
|
683
697
|
These refer to the websites where your username is straight after the domain, like Twitter (https://twitter.com/fpresencia). Of course Twitter has _other_ pages besides the username, so how can we emulate loading the page e.g. `/explore` in this case?
|
|
684
698
|
|
|
685
|
-
The best way is to first define the known, company pages and then use the wildcard for the usernames:
|
|
699
|
+
The best way is to first define the known, company pages and then use the wildcard for the usernames. This **must** be inside a `<Switch>`, otherwise multiple will be rendered:
|
|
686
700
|
|
|
687
701
|
```js
|
|
688
702
|
<Switch>
|
|
@@ -751,7 +765,7 @@ export default function SearchForm() {
|
|
|
751
765
|
}
|
|
752
766
|
```
|
|
753
767
|
|
|
754
|
-
In here we can see that we are treating the output of `useQuery` in the
|
|
768
|
+
In here we can see that we are treating the output of `useQuery` in the same way that we'd treat the output of `useState()`. This is on purpose and it makes things a lot easier for your application to work.
|
|
755
769
|
|
|
756
770
|
### Query routing
|
|
757
771
|
|