@fruit-ui/router 1.0.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 (3) hide show
  1. package/README.md +11 -0
  2. package/package.json +30 -0
  3. package/src/router.js +102 -0
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ ## FRUIT Router
2
+
3
+ This is a basic router component for [FRUIT](https://www.npmjs.com/package/@fruit-ui/core). It uses search params for routes, which minimizes server-side requirements. Its basic principles can be extended to RegExp-matched routes and could be applied to create a path-based router.
4
+
5
+ This router provides four components:
6
+ - the Router component. This takes in `routes`, an object mapping from paths (strings) to Routes, objects with a `route` method to generate the appropriate Template, Component etc. It also allows one 'wildcard' (*) route, whose function can take in the name of the route as a parameter. The Route can also have a `title`, which will set the window's title for that route.
7
+ - the `navigate` function, which takes in a path and navigates to that path. Navigation is done with `history.pushState` so it is compatible with the browser forward/back methods.
8
+ - the `pagechange` event. Elements with `data-receive-page-changes` enabled will receive the `pagechange` event which can be listened for like any other event. This event is broadcast when the `navigate` function is called or forward/back methods are used in the browser. (It is *not*, however, broadcast on page load.) Within the listener, the new route can be accessed using `event.detail.page`.
9
+ - the `getPage` function, which returns the current path.
10
+
11
+ More thorough documentation for FRUIT Router will be available shortly [here](https://asantagata.github.io/fruit-ui/?page=router-index).
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@fruit-ui/router",
3
+ "version": "1.0.0",
4
+ "description": "A basic router component for FRUIT",
5
+ "main": "src/router.js",
6
+ "homepage": "https://asantagata.github.io/fruit-ui/?page=router-index",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1",
9
+ "build": "terser .\\src\\router.js -o .\\dist\\router.js"
10
+ },
11
+ "keywords": [
12
+ "fruit",
13
+ "core",
14
+ "js",
15
+ "json",
16
+ "vanilla",
17
+ "ui",
18
+ "dom",
19
+ "react",
20
+ "reactive",
21
+ "router"
22
+ ],
23
+ "author": "asantagata",
24
+ "license": "ISC",
25
+ "type": "module",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/asantagata/fruit-ui.git"
29
+ }
30
+ }
package/src/router.js ADDED
@@ -0,0 +1,102 @@
1
+ import { replaceWith } from "https://cdn.jsdelivr.net/npm/@fruit-ui/core@latest/src/index.js";
2
+
3
+ const PARAM_NAME = 'page';
4
+
5
+ /**
6
+ * Get the current page.
7
+ * @returns {string}
8
+ */
9
+ function getPage() {
10
+ const params = new URLSearchParams(window.location.search);
11
+ return params.get(PARAM_NAME) ?? '';
12
+ }
13
+
14
+ /**
15
+ * Get the correct route for a page.
16
+ * @param {Object.<string, *>} routes - the collection of routes.
17
+ * @param {string} page - the page name to index the routes.
18
+ * @returns {object} the corresponding route.
19
+ */
20
+ function getRoute(routes, page) {
21
+ if (routes[page]) return routes[page];
22
+ if (routes[`/${page}`]) return routes[`/${page}`];
23
+ if (routes['*']) return routes['*'];
24
+ if (routes['/*']) return routes['/*'];
25
+ return {};
26
+ }
27
+
28
+ /**
29
+ * A basic router component.
30
+ * @param {Object.<string, {route: () => object, title?: string}>} routes - the collection of routes.
31
+ * @returns {object} a component.
32
+ */
33
+ function Router(routes) {
34
+ return {
35
+ render() {
36
+ const page = getPage();
37
+ const route = getRoute(routes, page);
38
+ let element = (() => {
39
+ if (Object.getPrototypeOf(route.route).constructor.name === "AsyncFunction") {
40
+ route.route(page).then(v => {
41
+ replaceWith(this.element.firstChild, v);
42
+ });
43
+ return {};
44
+ } else {
45
+ return route.route(page);
46
+ }
47
+ })();
48
+ return {
49
+ children: typeof element === 'object' ? {...element, key: page} : element,
50
+ id: 'router',
51
+ dataset: { receivePageChanges: true },
52
+ on: {
53
+ pagechange() {
54
+ const page = getPage();
55
+ const route = getRoute(routes, page);
56
+ if (Object.getPrototypeOf(route.route).constructor.name === "AsyncFunction") {
57
+ route.route(page).then(v => {
58
+ this.element.scrollTo(0, 0);
59
+ replaceWith(this.element.firstChild, v);
60
+ });
61
+ } else {
62
+ this.element.scrollTo(0, 0);
63
+ this.rerender();
64
+ }
65
+ document.title = getRoute(routes, getPage()).title ?? document.title;
66
+ },
67
+ mount() {
68
+ window.onpopstate = () => broadcastPageChange(getPage());
69
+ document.title = route.title ?? document.title;
70
+ }
71
+ }
72
+ }
73
+ },
74
+ memo: {page: getPage()}
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Navigates to a page.
80
+ * @param {string} page - the page.
81
+ */
82
+ function navigate(page) {
83
+ if (page.startsWith('/')) page = page.slice(1);
84
+ if (page === getPage()) return;
85
+ const url = new URL(window.location.href);
86
+ url.searchParams.set(PARAM_NAME, page);
87
+ window.history.pushState({}, '', url);
88
+ broadcastPageChange(page);
89
+ }
90
+
91
+ /**
92
+ * Broadcasts that a page updated.
93
+ * @param {string} page - the page.
94
+ */
95
+ function broadcastPageChange(page) {
96
+ const event = new CustomEvent('pagechange', {detail: {page}});
97
+ Array.from(document.querySelectorAll(`[data-receive-page-changes]`)).forEach(target => {
98
+ target.dispatchEvent(event);
99
+ });
100
+ }
101
+
102
+ export { navigate, Router, getPage }