crossroad 1.0.0 → 1.1.2
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 +10 -8
- package/readme.md +96 -3
package/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t,{createContext as e,useState as r,
|
|
1
|
+
import t,{createContext as e,useState as r,useCallback as n,useEffect as a,useContext as o}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 a=e||"/";const o=new URLSearchParams(Object.entries(r).map(([t,e])=>(Array.isArray(e)?e:[e]).map(e=>[t,e])).flat().filter(([t,e])=>e)).toString();return o&&(a+="?"+o),n&&(a+="#"+n),a};var u=()=>"undefined"==typeof window;function c(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={},a=t.path.split("/").every((t,a)=>{const o=e.path.split("/")[a];return t.startsWith(":")?(n[t.slice(1)]=o,r):o===t});return a&&Object.assign(r,n),a&&r}var l=({path:e="*",exact:r=!0,component:n,render:a,children:s})=>{const p=o(i),u=c(e,p[0]);if(!u)return null;if(n){const e=n;s=t.createElement(e,u)}else if(a)s=a(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=o(i);if(!t)throw new Error("Wrap your App with <Router>");return t};var f=({redirect:t,children:e})=>{const[r,n]=h(),o=(t=>(Array.isArray(t)||(t=[t]),t.filter(t=>t&&t.props)))(e).find(t=>c(t.props.path||"*",r))||null;return a(()=>{t&&(o||("function"==typeof t&&(t=t(r)),n(p(t))))},[t,o]),o},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 q=t=>t?(t=>{const[e,r]=h(),a=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],a]})(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]},g=t=>{const e=o(i);return t?c(t,e[0].path)||{}:e[0].params};export default({url:e,children:o})=>{const c=e||(u()?"/":window.location.href),[l,h]=r(()=>s(c)),f=n((t,{mode:e="push"}={})=>{if(!history[e+"State"])throw new Error(`Invalid mode "${e}"`);t="function"==typeof t?t(l):t,h(r=>p(r)===p(t)?r:(history[e+"State"]({},null,p(t)),s(t)))},[]);return a(()=>{if(u())return;const t=()=>h(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"));e&&("#"!==e[0]&&t.preventDefault(),f(e))};return window.addEventListener("popstate",t),document.addEventListener("click",e),()=>{window.removeEventListener("popstate",t),document.removeEventListener("click",e)}},[f]),t.createElement(i.Provider,{value:[l,f]},o)};export{i as Context,l as Route,f as Switch,m as useHash,g as useParams,y as usePath,q as useQuery,h as useUrl};
|
package/package.json
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crossroad",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
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",
|
|
7
7
|
"bugs": "https://github.com/franciscop/crossroad/issues",
|
|
8
|
-
"funding":
|
|
9
|
-
"url": "https://www.paypal.me/franciscopresencia/19"
|
|
10
|
-
},
|
|
8
|
+
"funding": "https://www.paypal.me/franciscopresencia/19",
|
|
11
9
|
"author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
|
|
12
10
|
"license": "MIT",
|
|
13
11
|
"type": "module",
|
|
@@ -33,20 +31,24 @@
|
|
|
33
31
|
"@babel/preset-react": "^7.14.5",
|
|
34
32
|
"babel-loader": "^8.2.2",
|
|
35
33
|
"babel-polyfill": "^6.26.0",
|
|
36
|
-
"jest": "^
|
|
37
|
-
"
|
|
38
|
-
"react
|
|
34
|
+
"jest": "^28.1.0",
|
|
35
|
+
"jest-environment-jsdom": "^28.1.0",
|
|
36
|
+
"react": ">=16.8.0",
|
|
37
|
+
"react-test": "^0.13.1",
|
|
39
38
|
"rollup": "^1.32.1",
|
|
40
39
|
"rollup-plugin-babel": "^4.4.0",
|
|
41
40
|
"rollup-plugin-terser": "^5.2.0"
|
|
42
41
|
},
|
|
43
42
|
"peerDependencies": {
|
|
44
|
-
"react": "
|
|
43
|
+
"react": ">=16.8.0"
|
|
45
44
|
},
|
|
46
45
|
"babel": {
|
|
47
46
|
"presets": [
|
|
48
47
|
"@babel/preset-env",
|
|
49
48
|
"@babel/preset-react"
|
|
50
49
|
]
|
|
50
|
+
},
|
|
51
|
+
"jest": {
|
|
52
|
+
"testEnvironment": "jsdom"
|
|
51
53
|
}
|
|
52
54
|
}
|
package/readme.md
CHANGED
|
@@ -125,6 +125,30 @@ export default function App() {
|
|
|
125
125
|
|
|
126
126
|
You would normally setup this Router straight on your App, along things like [Statux](https://statux.dev/)'s or [Redux](https://redux.js.org/)'s Store, error handling, translations, etc.
|
|
127
127
|
|
|
128
|
+
An example for a simple app:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
|
|
132
|
+
// App.js
|
|
133
|
+
import Router, { Switch, Route} from "crossroad";
|
|
134
|
+
|
|
135
|
+
import Home from './pages/Home';
|
|
136
|
+
import Dashboard from './pages/Dashboard';
|
|
137
|
+
import Profile from './pages/Profile';
|
|
138
|
+
|
|
139
|
+
export default function App() {
|
|
140
|
+
return (
|
|
141
|
+
<Router>
|
|
142
|
+
<Switch>
|
|
143
|
+
<Route path="/" component={Home} />
|
|
144
|
+
<Route path="/dashboard" component={Dashboard} />
|
|
145
|
+
<Route path="/:username" component={Profile} />
|
|
146
|
+
</Switch>
|
|
147
|
+
</Router>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
128
152
|
### `<Switch />`
|
|
129
153
|
|
|
130
154
|
A component that will only render the first of its children that matches the current URL. This is very useful to handle 404s, multiple routes matching, etc. For example, if you have a username system like `"/:username"` but want to have a help page, you can make it work easily with the switch:
|
|
@@ -356,11 +380,45 @@ setUrl({ ...url, path: "/" });
|
|
|
356
380
|
setUrl({ ...url, query: { search: "hello" } });
|
|
357
381
|
|
|
358
382
|
// Modify only one query param
|
|
359
|
-
setUrl({ ...url, query: { ...url.query, safe:
|
|
383
|
+
setUrl({ ...url, query: { ...url.query, safe: "no" } });
|
|
360
384
|
```
|
|
361
385
|
|
|
362
386
|
`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`.
|
|
363
387
|
|
|
388
|
+
#### Setter
|
|
389
|
+
|
|
390
|
+
The setter can be invoked directly, or with a callback:
|
|
391
|
+
|
|
392
|
+
```js
|
|
393
|
+
setUrl('/newurl');
|
|
394
|
+
setUrl(oldUrl => '/newurl');
|
|
395
|
+
setUrl(oldUrl => ({ ...oldUrl, path: newPath }));
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
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:
|
|
399
|
+
|
|
400
|
+
```js
|
|
401
|
+
const [url, setUrl] = useurl();
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
if (url.path === '/base') {
|
|
404
|
+
setUrl('/base/deeper');
|
|
405
|
+
}
|
|
406
|
+
}, [url, setUrl]);
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
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:
|
|
410
|
+
|
|
411
|
+
```js
|
|
412
|
+
const [url, setUrl] = useUrl();
|
|
413
|
+
useEffect(() => {
|
|
414
|
+
setUrl(old => {
|
|
415
|
+
if (old.path === '/base') return '/base/deeper';
|
|
416
|
+
return old;
|
|
417
|
+
});
|
|
418
|
+
}, []);
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
|
|
364
422
|
#### New history entry
|
|
365
423
|
|
|
366
424
|
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' }`:
|
|
@@ -369,7 +427,7 @@ By default `setUrl()` will create a new entry in the browser history. If you wan
|
|
|
369
427
|
setUrl("/newurl", { mode: "replace" });
|
|
370
428
|
```
|
|
371
429
|
|
|
372
|
-
- `push` (default): creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(push)> `/c` and then click on the back button, the browser will go back to `/b`. This is because `/b` and `/
|
|
430
|
+
- `push` (default): creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(push)> `/c` and then click on the back button, the browser will go back to `/b`. This is because `/b` and `/c` are both independent entries in your history.
|
|
373
431
|
- `replace`: creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(replace)> `/c` and then click on the back button, it'll go back to `/a`. This is because `/c` is overwriting `/b`, instead of adding a new entry.
|
|
374
432
|
|
|
375
433
|
### `usePath()`
|
|
@@ -389,8 +447,43 @@ const Login = () => {
|
|
|
389
447
|
};
|
|
390
448
|
```
|
|
391
449
|
|
|
450
|
+
The path is always a string equivalent to `window.location.pathname`.
|
|
451
|
+
|
|
392
452
|
> Note: this _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')`
|
|
393
453
|
|
|
454
|
+
#### Setter
|
|
455
|
+
|
|
456
|
+
The setter can be invoked directly, or with a callback:
|
|
457
|
+
|
|
458
|
+
```js
|
|
459
|
+
setPath('/newpath');
|
|
460
|
+
setPath(oldPath => '/newpath');
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
The function `setPath` is _always_ the same, so it doesn't matter whether you put it as a dependency or not. However the `path` can be updated, so you might want to put that:
|
|
464
|
+
|
|
465
|
+
```js
|
|
466
|
+
const [path, setPath] = usePath();
|
|
467
|
+
useEffect(() => {
|
|
468
|
+
if (path === '/base') {
|
|
469
|
+
setPath('/base/deeper');
|
|
470
|
+
}
|
|
471
|
+
}, [path, setPath]);
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
If you update the path with the current path, it won't trigger a rerender. So the above can also be written as this, removing all dependencies:
|
|
475
|
+
|
|
476
|
+
```js
|
|
477
|
+
const [path, setPath] = usePath();
|
|
478
|
+
useEffect(() => {
|
|
479
|
+
setPath(old => {
|
|
480
|
+
if (old === '/base') return '/base/deeper';
|
|
481
|
+
return old;
|
|
482
|
+
});
|
|
483
|
+
}, []);
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
|
|
394
487
|
#### New history entry
|
|
395
488
|
|
|
396
489
|
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' }`:
|
|
@@ -839,7 +932,7 @@ export default function Mock({ url, children }) {
|
|
|
839
932
|
// Undo the setup when the component unmounts
|
|
840
933
|
useEffect(() => {
|
|
841
934
|
return () => Object.defineProperty(window, "location", oldLocation);
|
|
842
|
-
});
|
|
935
|
+
}, []);
|
|
843
936
|
return <div>{children}</div>;
|
|
844
937
|
}
|
|
845
938
|
```
|