breeze-router 0.1.0 → 0.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/LICENSE +21 -0
- package/README.md +10 -0
- package/dist/BreezeRouter.js +31 -2
- package/dist/BreezeRouter.js.map +1 -1
- package/dist/BreezeRouter.min.js +1 -1
- package/dist/BreezeRouter.min.js.map +1 -1
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Brandon Zhang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -43,6 +43,16 @@ ROUTER.add("/users/:username/posts/:postId", async ({ route, params }) => {
|
|
|
43
43
|
ROUTER.start();
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
## Util functions
|
|
47
|
+
|
|
48
|
+
### `toggleParam(this, 1)`
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<input type="checkbox" name="freelance" onclick="window.ROUTER.toggleParam(this, 1)">
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If checked, then it will append search params to url like this: `localhost/users?freelance=1`, if you click checkbox again, it will remove that search param from url.
|
|
55
|
+
|
|
46
56
|
## License
|
|
47
57
|
|
|
48
58
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/dist/BreezeRouter.js
CHANGED
|
@@ -179,6 +179,11 @@ class BreezeRouter {
|
|
|
179
179
|
// If no matching route found, route will be '404' route
|
|
180
180
|
// which has been handled by _matchUrlToRoute already
|
|
181
181
|
await this._handleRoute({ route, params });
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Update previous route, so application route handler can check and decide if it should re-render whole page.
|
|
185
|
+
*/
|
|
186
|
+
this._previousRoute = route;
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
/**
|
|
@@ -261,13 +266,37 @@ class BreezeRouter {
|
|
|
261
266
|
}
|
|
262
267
|
|
|
263
268
|
event.preventDefault();
|
|
264
|
-
let href = anchor.getAttribute("href")
|
|
265
|
-
if (!href
|
|
269
|
+
let href = anchor.getAttribute("href")?.trim();
|
|
270
|
+
if (!href?.startsWith("/")) {
|
|
266
271
|
href = "/" + href;
|
|
267
272
|
}
|
|
268
273
|
|
|
269
274
|
this.navigateTo(href);
|
|
270
275
|
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Add or remove search param to current url.
|
|
279
|
+
* @param {HTMLInputElement} checkbox
|
|
280
|
+
* @param {string} value
|
|
281
|
+
* @returns void
|
|
282
|
+
*/
|
|
283
|
+
toggleParam(checkbox, value) {
|
|
284
|
+
const params = new URLSearchParams(location.search);
|
|
285
|
+
const name = checkbox.getAttribute('name');
|
|
286
|
+
if (!name) {
|
|
287
|
+
return console.warn(`name attribute is not set on ${checkbox.outerHTML}`);
|
|
288
|
+
}
|
|
289
|
+
if (checkbox.checked) {
|
|
290
|
+
!params.has(name) && params.set(name, value);
|
|
291
|
+
} else if (!checkbox.checked) {
|
|
292
|
+
params.has(name) && params.delete(name);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const newUrl = !!params.size
|
|
296
|
+
? `${location.pathname}?${params.toString()}`
|
|
297
|
+
: location.pathname;
|
|
298
|
+
this.navigateTo(newUrl);
|
|
299
|
+
}
|
|
271
300
|
}
|
|
272
301
|
|
|
273
302
|
export { BreezeRouter as default };
|
package/dist/BreezeRouter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BreezeRouter.js","sources":["../src/utils.js","../src/index.js"],"sourcesContent":["// @ts-check\n\n/**\n * Check if given param is function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"function\";\n};\n\n/**\n * Check if given param is async function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isAsyncFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"asyncfunction\";\n};\n\n/**\n * Remove trailing slash of a give url\n * @param {string} url\n * @returns {string}\n */\nexport const removeTrailingSlash = (url) => {\n if (url.endsWith(\"/\")) {\n url = url.replace(/\\/$/, \"\");\n }\n\n return url;\n};\n\n/**\n * Find anchor element from click event.\n * @param {Event} e - The click event.\n * @returns {HTMLAnchorElement|undefined}\n */\nexport const findAnchor = (e) => {\n return e.composedPath().find((elem) => {\n return elem.tagName === \"A\";\n });\n};\n\n/**\n * Check if the router should handle a click event on an anchor element.\n * @param {Event} e - The click event.\n * @param {HTMLAnchorElement} anchor - The anchor element.\n * @returns {boolean} - True if the router should handle the event, false otherwise.\n */\nexport const shouldRouterHandleClick = (e, anchor) => {\n // If the event has already been handled by another event listener\n if (e.defaultPrevented) {\n return false;\n }\n\n // If the user is holding down the meta, control, or shift key\n if (e.metaKey || e.ctrlKey || e.shiftKey) {\n return false;\n }\n\n if (!anchor) {\n return false;\n }\n\n if (anchor.target) {\n return false;\n }\n\n if (\n anchor.hasAttribute(\"download\") ||\n anchor.getAttribute(\"rel\") === \"external\"\n ) {\n return false;\n }\n\n const href = anchor.href;\n if (!href || href.startsWith(\"mailto:\")) {\n return false;\n }\n\n // If the href attribute does not start with the same origin\n if (!href.startsWith(location.origin)) {\n return false;\n }\n\n return true;\n};\n","// @ts-check\nimport {\n isFunction,\n isAsyncFunction,\n removeTrailingSlash,\n findAnchor,\n shouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n /**\n * Creates a new BreezeRouter instance.\n * @constructor\n */\n constructor() {\n /**\n * Object containing all registered routes.\n * @type {import('./types.js').Route}\n * @private\n */\n this._routes = {};\n\n /**\n * The previous route that was navigated to\n * @type {import('./types.js').Route}\n * @private\n */\n this._previousRoute = {};\n\n // Bind event listeners\n window.addEventListener(\"popstate\", this._onChanged.bind(this));\n document.body.addEventListener(\"click\", this._handleClick.bind(this));\n }\n\n /**\n * Starts the router.\n * @returns {void}\n */\n start() {\n this._onChanged();\n }\n\n /**\n * Adds a new route to the router.\n * @param {string} route - The route path to add.\n * @param {Function} handler - The async function to handle the route\n * @returns {BreezeRouter|void} The BreezeRouter instance.\n */\n add(route, handler) {\n route = route.trim();\n if (route !== \"/\") {\n route = removeTrailingSlash(route.trim());\n }\n\n if (this._routes[route]) {\n return console.warn(`Route already exists: ${route}`);\n }\n\n if (typeof handler !== \"function\") {\n return console.error(`handler on route '${route}' is not a function.`);\n }\n\n this._routes[route] = {\n path: route,\n handler,\n };\n\n return this;\n }\n\n /**\n * Navigates to the specified URL.\n * @param {string} url - The URL to navigate to\n * @returns {void}\n */\n navigateTo(url) {\n window.history.pushState({ url }, \"\", url);\n this._onChanged();\n }\n\n /**\n * Redirects a URL\n * @param {string} url\n * @returns {void}\n */\n redirect(url) {\n this.navigateTo(url);\n }\n\n async _onChanged() {\n const path = window.location.pathname;\n const { route, params } = this._matchUrlToRoute(path);\n\n // If no matching route found, route will be '404' route\n // which has been handled by _matchUrlToRoute already\n await this._handleRoute({ route, params });\n }\n\n /**\n * Processes route callbacks registered by app\n * @param {import('./types.js').MatchedRoute} options\n * @returns {Promise<void>}\n */\n async _handleRoute({ route, params }) {\n if (isFunction(route.handler)) {\n route.handler({ route, params });\n }\n\n if (isAsyncFunction(route.handler)) {\n await route.handler({ route, params });\n }\n }\n\n /**\n *\n * @param {string} url - Current url users visite or nagivate to.\n * @returns {import('./types.js').MatchedRoute}\n */\n _matchUrlToRoute(url) {\n /** @type {import('./types.js').RouteParams} */\n const params = {};\n\n if (url !== \"/\") {\n url = removeTrailingSlash(url);\n }\n\n const matchedRoute = Object.keys(this._routes).find((route) => {\n if (url.split(\"/\").length !== route.split(\"/\").length) {\n return false;\n }\n\n let routeSegments = route.split(\"/\").slice(1);\n let urlSegments = url.split(\"/\").slice(1);\n\n // If each segment in the url matches the corresponding segment in the route path,\n // or the route path segment starts with a ':' then the route is matched.\n const match = routeSegments.every((segment, i) => {\n return segment === urlSegments[i] || segment.startsWith(\":\");\n });\n\n if (!match) {\n return false;\n }\n\n // If the route matches the URL, pull out any params from the URL.\n routeSegments.forEach((segment, i) => {\n if (segment.startsWith(\":\")) {\n const propName = segment.slice(1);\n params[propName] = decodeURIComponent(urlSegments[i]);\n }\n });\n\n return true;\n });\n\n if (matchedRoute) {\n return { route: this._routes[matchedRoute], params };\n } else {\n return { route: this._routes[404], params };\n }\n }\n\n /**\n * Handles <a> link clicks\n * @param {Event} event\n * @returns {void}\n */\n _handleClick(event) {\n const anchor = findAnchor(event);\n if (!anchor) {\n return;\n }\n\n if (!shouldRouterHandleClick(event, anchor)) {\n return;\n }\n\n event.preventDefault();\n let href = anchor.getAttribute(\"href\").trim();\n if (!href.startsWith(\"/\")) {\n href = \"/\" + href;\n }\n\n this.navigateTo(href);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BreezeRouter.js","sources":["../src/utils.js","../src/index.js"],"sourcesContent":["// @ts-check\n\n/**\n * Check if given param is function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"function\";\n};\n\n/**\n * Check if given param is async function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isAsyncFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"asyncfunction\";\n};\n\n/**\n * Remove trailing slash of a give url\n * @param {string} url\n * @returns {string}\n */\nexport const removeTrailingSlash = (url) => {\n if (url.endsWith(\"/\")) {\n url = url.replace(/\\/$/, \"\");\n }\n\n return url;\n};\n\n/**\n * Find anchor element from click event.\n * @param {Event} e - The click event.\n * @returns {HTMLAnchorElement|undefined}\n */\nexport const findAnchor = (e) => {\n return e.composedPath().find((elem) => {\n return elem.tagName === \"A\";\n });\n};\n\n/**\n * Check if the router should handle a click event on an anchor element.\n * @param {Event} e - The click event.\n * @param {HTMLAnchorElement} anchor - The anchor element.\n * @returns {boolean} - True if the router should handle the event, false otherwise.\n */\nexport const shouldRouterHandleClick = (e, anchor) => {\n // If the event has already been handled by another event listener\n if (e.defaultPrevented) {\n return false;\n }\n\n // If the user is holding down the meta, control, or shift key\n if (e.metaKey || e.ctrlKey || e.shiftKey) {\n return false;\n }\n\n if (!anchor) {\n return false;\n }\n\n if (anchor.target) {\n return false;\n }\n\n if (\n anchor.hasAttribute(\"download\") ||\n anchor.getAttribute(\"rel\") === \"external\"\n ) {\n return false;\n }\n\n const href = anchor.href;\n if (!href || href.startsWith(\"mailto:\")) {\n return false;\n }\n\n // If the href attribute does not start with the same origin\n if (!href.startsWith(location.origin)) {\n return false;\n }\n\n return true;\n};\n","// @ts-check\nimport {\n isFunction,\n isAsyncFunction,\n removeTrailingSlash,\n findAnchor,\n shouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n /**\n * Creates a new BreezeRouter instance.\n * @constructor\n */\n constructor() {\n /**\n * Object containing all registered routes.\n * @type {import('./types.js').Route}\n * @private\n */\n this._routes = {};\n\n /**\n * The previous route that was navigated to\n * @type {import('./types.js').Route}\n * @private\n */\n this._previousRoute = {};\n\n // Bind event listeners\n window.addEventListener(\"popstate\", this._onChanged.bind(this));\n document.body.addEventListener(\"click\", this._handleClick.bind(this));\n }\n\n /**\n * Starts the router.\n * @returns {void}\n */\n start() {\n this._onChanged();\n }\n\n /**\n * Adds a new route to the router.\n * @param {string} route - The route path to add.\n * @param {Function} handler - The async function to handle the route\n * @returns {BreezeRouter|void} The BreezeRouter instance.\n */\n add(route, handler) {\n route = route.trim();\n if (route !== \"/\") {\n route = removeTrailingSlash(route.trim());\n }\n\n if (this._routes[route]) {\n return console.warn(`Route already exists: ${route}`);\n }\n\n if (typeof handler !== \"function\") {\n return console.error(`handler on route '${route}' is not a function.`);\n }\n\n this._routes[route] = {\n path: route,\n handler,\n };\n\n return this;\n }\n\n /**\n * Navigates to the specified URL.\n * @param {string} url - The URL to navigate to\n * @returns {void}\n */\n navigateTo(url) {\n window.history.pushState({ url }, \"\", url);\n this._onChanged();\n }\n\n /**\n * Redirects a URL\n * @param {string} url\n * @returns {void}\n */\n redirect(url) {\n this.navigateTo(url);\n }\n\n async _onChanged() {\n const path = window.location.pathname;\n const { route, params } = this._matchUrlToRoute(path);\n\n // If no matching route found, route will be '404' route\n // which has been handled by _matchUrlToRoute already\n await this._handleRoute({ route, params });\n\n /**\n * Update previous route, so application route handler can check and decide if it should re-render whole page.\n */\n this._previousRoute = route;\n }\n\n /**\n * Processes route callbacks registered by app\n * @param {import('./types.js').MatchedRoute} options\n * @returns {Promise<void>}\n */\n async _handleRoute({ route, params }) {\n if (isFunction(route.handler)) {\n route.handler({ route, params });\n }\n\n if (isAsyncFunction(route.handler)) {\n await route.handler({ route, params });\n }\n }\n\n /**\n *\n * @param {string} url - Current url users visite or nagivate to.\n * @returns {import('./types.js').MatchedRoute}\n */\n _matchUrlToRoute(url) {\n /** @type {import('./types.js').RouteParams} */\n const params = {};\n\n if (url !== \"/\") {\n url = removeTrailingSlash(url);\n }\n\n const matchedRoute = Object.keys(this._routes).find((route) => {\n if (url.split(\"/\").length !== route.split(\"/\").length) {\n return false;\n }\n\n let routeSegments = route.split(\"/\").slice(1);\n let urlSegments = url.split(\"/\").slice(1);\n\n // If each segment in the url matches the corresponding segment in the route path,\n // or the route path segment starts with a ':' then the route is matched.\n const match = routeSegments.every((segment, i) => {\n return segment === urlSegments[i] || segment.startsWith(\":\");\n });\n\n if (!match) {\n return false;\n }\n\n // If the route matches the URL, pull out any params from the URL.\n routeSegments.forEach((segment, i) => {\n if (segment.startsWith(\":\")) {\n const propName = segment.slice(1);\n params[propName] = decodeURIComponent(urlSegments[i]);\n }\n });\n\n return true;\n });\n\n if (matchedRoute) {\n return { route: this._routes[matchedRoute], params };\n } else {\n return { route: this._routes[404], params };\n }\n }\n\n /**\n * Handles <a> link clicks\n * @param {Event} event\n * @returns {void}\n */\n _handleClick(event) {\n const anchor = findAnchor(event);\n if (!anchor) {\n return;\n }\n\n if (!shouldRouterHandleClick(event, anchor)) {\n return;\n }\n\n event.preventDefault();\n let href = anchor.getAttribute(\"href\")?.trim();\n if (!href?.startsWith(\"/\")) {\n href = \"/\" + href;\n }\n\n this.navigateTo(href);\n }\n\n /**\n * Add or remove search param to current url.\n * @param {HTMLInputElement} checkbox \n * @param {string} value\n * @returns void\n */\n toggleParam(checkbox, value) {\n const params = new URLSearchParams(location.search);\n const name = checkbox.getAttribute('name')\n if (!name) {\n return console.warn(`name attribute is not set on ${checkbox.outerHTML}`);\n }\n if (checkbox.checked) {\n !params.has(name) && params.set(name, value);\n } else if (!checkbox.checked) {\n params.has(name) && params.delete(name);\n }\n \n const newUrl = !!params.size\n ? `${location.pathname}?${params.toString()}`\n : location.pathname;\n this.navigateTo(newUrl);\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/BreezeRouter.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const t=t=>(t.endsWith("/")&&(t=t.replace(/\/$/,"")),t);class e{constructor(){this._routes={},this._previousRoute={},window.addEventListener("popstate",this._onChanged.bind(this)),document.body.addEventListener("click",this._handleClick.bind(this))}start(){this._onChanged()}add(e,r){return"/"!==(e=e.trim())&&(e=t(e.trim())),this._routes[e]?console.warn(`Route already exists: ${e}`):"function"!=typeof r?console.error(`handler on route '${e}' is not a function.`):(this._routes[e]={path:e,handler:r},this)}navigateTo(t){window.history.pushState({url:t},"",t),this._onChanged()}redirect(t){this.navigateTo(t)}async _onChanged(){const t=window.location.pathname,{route:e,params:r}=this._matchUrlToRoute(t);await this._handleRoute({route:e,params:r})}async _handleRoute({route:t,params:e}){"function"===t.handler.constructor.name.toLowerCase()&&t.handler({route:t,params:e}),(t=>"asyncfunction"===t.constructor.name.toLowerCase())(t.handler)&&await t.handler({route:t,params:e})}_matchUrlToRoute(e){const r={};"/"!==e&&(e=t(e));const
|
|
1
|
+
const t=t=>(t.endsWith("/")&&(t=t.replace(/\/$/,"")),t);class e{constructor(){this._routes={},this._previousRoute={},window.addEventListener("popstate",this._onChanged.bind(this)),document.body.addEventListener("click",this._handleClick.bind(this))}start(){this._onChanged()}add(e,r){return"/"!==(e=e.trim())&&(e=t(e.trim())),this._routes[e]?console.warn(`Route already exists: ${e}`):"function"!=typeof r?console.error(`handler on route '${e}' is not a function.`):(this._routes[e]={path:e,handler:r},this)}navigateTo(t){window.history.pushState({url:t},"",t),this._onChanged()}redirect(t){this.navigateTo(t)}async _onChanged(){const t=window.location.pathname,{route:e,params:r}=this._matchUrlToRoute(t);await this._handleRoute({route:e,params:r}),this._previousRoute=e}async _handleRoute({route:t,params:e}){"function"===t.handler.constructor.name.toLowerCase()&&t.handler({route:t,params:e}),(t=>"asyncfunction"===t.constructor.name.toLowerCase())(t.handler)&&await t.handler({route:t,params:e})}_matchUrlToRoute(e){const r={};"/"!==e&&(e=t(e));const a=Object.keys(this._routes).find((t=>{if(e.split("/").length!==t.split("/").length)return!1;let a=t.split("/").slice(1),n=e.split("/").slice(1);return!!a.every(((t,e)=>t===n[e]||t.startsWith(":")))&&(a.forEach(((t,e)=>{if(t.startsWith(":")){const a=t.slice(1);r[a]=decodeURIComponent(n[e])}})),!0)}));return a?{route:this._routes[a],params:r}:{route:this._routes[404],params:r}}_handleClick(t){const e=t.composedPath().find((t=>"A"===t.tagName));if(!e)return;if(!((t,e)=>{if(t.defaultPrevented)return!1;if(t.metaKey||t.ctrlKey||t.shiftKey)return!1;if(!e)return!1;if(e.target)return!1;if(e.hasAttribute("download")||"external"===e.getAttribute("rel"))return!1;const r=e.href;return!(!r||r.startsWith("mailto:")||!r.startsWith(location.origin))})(t,e))return;t.preventDefault();let r=e.getAttribute("href")?.trim();r?.startsWith("/")||(r="/"+r),this.navigateTo(r)}toggleParam(t,e){const r=new URLSearchParams(location.search),a=t.getAttribute("name");if(!a)return console.warn(`name attribute is not set on ${t.outerHTML}`);t.checked?!r.has(a)&&r.set(a,e):t.checked||r.has(a)&&r.delete(a);const n=r.size?`${location.pathname}?${r.toString()}`:location.pathname;this.navigateTo(n)}}export{e as default};
|
|
2
2
|
//# sourceMappingURL=BreezeRouter.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BreezeRouter.min.js","sources":["../src/utils.js","../src/index.js"],"sourcesContent":["// @ts-check\n\n/**\n * Check if given param is function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"function\";\n};\n\n/**\n * Check if given param is async function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isAsyncFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"asyncfunction\";\n};\n\n/**\n * Remove trailing slash of a give url\n * @param {string} url\n * @returns {string}\n */\nexport const removeTrailingSlash = (url) => {\n if (url.endsWith(\"/\")) {\n url = url.replace(/\\/$/, \"\");\n }\n\n return url;\n};\n\n/**\n * Find anchor element from click event.\n * @param {Event} e - The click event.\n * @returns {HTMLAnchorElement|undefined}\n */\nexport const findAnchor = (e) => {\n return e.composedPath().find((elem) => {\n return elem.tagName === \"A\";\n });\n};\n\n/**\n * Check if the router should handle a click event on an anchor element.\n * @param {Event} e - The click event.\n * @param {HTMLAnchorElement} anchor - The anchor element.\n * @returns {boolean} - True if the router should handle the event, false otherwise.\n */\nexport const shouldRouterHandleClick = (e, anchor) => {\n // If the event has already been handled by another event listener\n if (e.defaultPrevented) {\n return false;\n }\n\n // If the user is holding down the meta, control, or shift key\n if (e.metaKey || e.ctrlKey || e.shiftKey) {\n return false;\n }\n\n if (!anchor) {\n return false;\n }\n\n if (anchor.target) {\n return false;\n }\n\n if (\n anchor.hasAttribute(\"download\") ||\n anchor.getAttribute(\"rel\") === \"external\"\n ) {\n return false;\n }\n\n const href = anchor.href;\n if (!href || href.startsWith(\"mailto:\")) {\n return false;\n }\n\n // If the href attribute does not start with the same origin\n if (!href.startsWith(location.origin)) {\n return false;\n }\n\n return true;\n};\n","// @ts-check\nimport {\n isFunction,\n isAsyncFunction,\n removeTrailingSlash,\n findAnchor,\n shouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n /**\n * Creates a new BreezeRouter instance.\n * @constructor\n */\n constructor() {\n /**\n * Object containing all registered routes.\n * @type {import('./types.js').Route}\n * @private\n */\n this._routes = {};\n\n /**\n * The previous route that was navigated to\n * @type {import('./types.js').Route}\n * @private\n */\n this._previousRoute = {};\n\n // Bind event listeners\n window.addEventListener(\"popstate\", this._onChanged.bind(this));\n document.body.addEventListener(\"click\", this._handleClick.bind(this));\n }\n\n /**\n * Starts the router.\n * @returns {void}\n */\n start() {\n this._onChanged();\n }\n\n /**\n * Adds a new route to the router.\n * @param {string} route - The route path to add.\n * @param {Function} handler - The async function to handle the route\n * @returns {BreezeRouter|void} The BreezeRouter instance.\n */\n add(route, handler) {\n route = route.trim();\n if (route !== \"/\") {\n route = removeTrailingSlash(route.trim());\n }\n\n if (this._routes[route]) {\n return console.warn(`Route already exists: ${route}`);\n }\n\n if (typeof handler !== \"function\") {\n return console.error(`handler on route '${route}' is not a function.`);\n }\n\n this._routes[route] = {\n path: route,\n handler,\n };\n\n return this;\n }\n\n /**\n * Navigates to the specified URL.\n * @param {string} url - The URL to navigate to\n * @returns {void}\n */\n navigateTo(url) {\n window.history.pushState({ url }, \"\", url);\n this._onChanged();\n }\n\n /**\n * Redirects a URL\n * @param {string} url\n * @returns {void}\n */\n redirect(url) {\n this.navigateTo(url);\n }\n\n async _onChanged() {\n const path = window.location.pathname;\n const { route, params } = this._matchUrlToRoute(path);\n\n // If no matching route found, route will be '404' route\n // which has been handled by _matchUrlToRoute already\n await this._handleRoute({ route, params });\n }\n\n /**\n * Processes route callbacks registered by app\n * @param {import('./types.js').MatchedRoute} options\n * @returns {Promise<void>}\n */\n async _handleRoute({ route, params }) {\n if (isFunction(route.handler)) {\n route.handler({ route, params });\n }\n\n if (isAsyncFunction(route.handler)) {\n await route.handler({ route, params });\n }\n }\n\n /**\n *\n * @param {string} url - Current url users visite or nagivate to.\n * @returns {import('./types.js').MatchedRoute}\n */\n _matchUrlToRoute(url) {\n /** @type {import('./types.js').RouteParams} */\n const params = {};\n\n if (url !== \"/\") {\n url = removeTrailingSlash(url);\n }\n\n const matchedRoute = Object.keys(this._routes).find((route) => {\n if (url.split(\"/\").length !== route.split(\"/\").length) {\n return false;\n }\n\n let routeSegments = route.split(\"/\").slice(1);\n let urlSegments = url.split(\"/\").slice(1);\n\n // If each segment in the url matches the corresponding segment in the route path,\n // or the route path segment starts with a ':' then the route is matched.\n const match = routeSegments.every((segment, i) => {\n return segment === urlSegments[i] || segment.startsWith(\":\");\n });\n\n if (!match) {\n return false;\n }\n\n // If the route matches the URL, pull out any params from the URL.\n routeSegments.forEach((segment, i) => {\n if (segment.startsWith(\":\")) {\n const propName = segment.slice(1);\n params[propName] = decodeURIComponent(urlSegments[i]);\n }\n });\n\n return true;\n });\n\n if (matchedRoute) {\n return { route: this._routes[matchedRoute], params };\n } else {\n return { route: this._routes[404], params };\n }\n }\n\n /**\n * Handles <a> link clicks\n * @param {Event} event\n * @returns {void}\n */\n _handleClick(event) {\n const anchor = findAnchor(event);\n if (!anchor) {\n return;\n }\n\n if (!shouldRouterHandleClick(event, anchor)) {\n return;\n }\n\n event.preventDefault();\n let href = anchor.getAttribute(\"href\").trim();\n if (!href.startsWith(\"/\")) {\n href = \"/\" + href;\n }\n\n this.navigateTo(href);\n }\n}\n"],"names":["removeTrailingSlash","url","endsWith","replace","BreezeRouter","constructor","this","_routes","_previousRoute","window","addEventListener","_onChanged","bind","document","body","_handleClick","start","add","route","handler","trim","console","warn","error","path","navigateTo","history","pushState","redirect","async","location","pathname","params","_matchUrlToRoute","_handleRoute","name","toLowerCase","fn","isAsyncFunction","matchedRoute","Object","keys","find","split","length","routeSegments","slice","urlSegments","every","segment","i","startsWith","forEach","propName","decodeURIComponent","event","anchor","composedPath","elem","tagName","e","defaultPrevented","metaKey","ctrlKey","shiftKey","target","hasAttribute","getAttribute","href","origin","shouldRouterHandleClick","preventDefault"],"mappings":"AAOO,MAkBMA,EAAuBC,IAC9BA,EAAIC,SAAS,OACfD,EAAMA,EAAIE,QAAQ,MAAO,KAGpBF,GClBM,MAAMG,EAKnBC,cAMEC,KAAKC,QAAU,GAOfD,KAAKE,eAAiB,GAGtBC,OAAOC,iBAAiB,WAAYJ,KAAKK,WAAWC,KAAKN,OACzDO,SAASC,KAAKJ,iBAAiB,QAASJ,KAAKS,aAAaH,KAAKN,MAChE,CAMDU,QACEV,KAAKK,YACN,CAQDM,IAAIC,EAAOC,GAMT,MAJc,OADdD,EAAQA,EAAME,UAEZF,EAAQlB,EAAoBkB,EAAME,SAGhCd,KAAKC,QAAQW,GACRG,QAAQC,KAAK,yBAAyBJ,KAGxB,mBAAZC,EACFE,QAAQE,MAAM,qBAAqBL,0BAG5CZ,KAAKC,QAAQW,GAAS,CACpBM,KAAMN,EACNC,WAGKb,KACR,CAODmB,WAAWxB,GACTQ,OAAOiB,QAAQC,UAAU,CAAE1B,OAAO,GAAIA,GACtCK,KAAKK,YACN,CAODiB,SAAS3B,GACPK,KAAKmB,WAAWxB,EACjB,CAED4B,mBACE,MAAML,EAAOf,OAAOqB,SAASC,UACvBb,MAAEA,EAAKc,OAAEA,GAAW1B,KAAK2B,iBAAiBT,SAI1ClB,KAAK4B,aAAa,CAAEhB,QAAOc,UAClC,CAODH,oBAAmBX,MAAEA,EAAKc,OAAEA,IDlGiB,aCmG5Bd,EAAMC,QDnGbd,YAAY8B,KAAKC,eCoGvBlB,EAAMC,QAAQ,CAAED,QAAOc,WD5FE,CAACK,GACe,kBAAtCA,EAAGhC,YAAY8B,KAAKC,cC8FrBE,CAAgBpB,EAAMC,gBAClBD,EAAMC,QAAQ,CAAED,QAAOc,UAEhC,CAODC,iBAAiBhC,GAEf,MAAM+B,EAAS,CAAA,EAEH,MAAR/B,IACFA,EAAMD,EAAoBC,IAG5B,MAAMsC,EAAeC,OAAOC,KAAKnC,KAAKC,SAASmC,MAAMxB,IACnD,GAAIjB,EAAI0C,MAAM,KAAKC,SAAW1B,EAAMyB,MAAM,KAAKC,OAC7C,OAAO,EAGT,IAAIC,EAAgB3B,EAAMyB,MAAM,KAAKG,MAAM,GACvCC,EAAc9C,EAAI0C,MAAM,KAAKG,MAAM,GAQvC,QAJcD,EAAcG,OAAM,CAACC,EAASC,IACnCD,IAAYF,EAAYG,IAAMD,EAAQE,WAAW,SAQ1DN,EAAcO,SAAQ,CAACH,EAASC,KAC9B,GAAID,EAAQE,WAAW,KAAM,CAC3B,MAAME,EAAWJ,EAAQH,MAAM,GAC/Bd,EAAOqB,GAAYC,mBAAmBP,EAAYG,GACnD,MAGI,EAAI,IAGb,OAAIX,EACK,CAAErB,MAAOZ,KAAKC,QAAQgC,GAAeP,UAErC,CAAEd,MAAOZ,KAAKC,QAAQ,KAAMyB,SAEtC,CAODjB,aAAawC,GACX,MAAMC,EAAoBD,EDpInBE,eAAef,MAAMgB,GACJ,MAAjBA,EAAKC,UCoIZ,IAAKH,EACH,OAGF,ID9HmC,EAACI,EAAGJ,KAEzC,GAAII,EAAEC,iBACJ,OAAO,EAIT,GAAID,EAAEE,SAAWF,EAAEG,SAAWH,EAAEI,SAC9B,OAAO,EAGT,IAAKR,EACH,OAAO,EAGT,GAAIA,EAAOS,OACT,OAAO,EAGT,GACET,EAAOU,aAAa,aACW,aAA/BV,EAAOW,aAAa,OAEpB,OAAO,EAGT,MAAMC,EAAOZ,EAAOY,KACpB,SAAKA,GAAQA,EAAKjB,WAAW,aAKxBiB,EAAKjB,WAAWrB,SAASuC,QAInB,EC0FJC,CAAwBf,EAAOC,GAClC,OAGFD,EAAMgB,iBACN,IAAIH,EAAOZ,EAAOW,aAAa,QAAQ/C,OAClCgD,EAAKjB,WAAW,OACnBiB,EAAO,IAAMA,GAGf9D,KAAKmB,WAAW2C,EACjB"}
|
|
1
|
+
{"version":3,"file":"BreezeRouter.min.js","sources":["../src/utils.js","../src/index.js"],"sourcesContent":["// @ts-check\n\n/**\n * Check if given param is function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"function\";\n};\n\n/**\n * Check if given param is async function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isAsyncFunction = (fn) => {\n return fn.constructor.name.toLowerCase() === \"asyncfunction\";\n};\n\n/**\n * Remove trailing slash of a give url\n * @param {string} url\n * @returns {string}\n */\nexport const removeTrailingSlash = (url) => {\n if (url.endsWith(\"/\")) {\n url = url.replace(/\\/$/, \"\");\n }\n\n return url;\n};\n\n/**\n * Find anchor element from click event.\n * @param {Event} e - The click event.\n * @returns {HTMLAnchorElement|undefined}\n */\nexport const findAnchor = (e) => {\n return e.composedPath().find((elem) => {\n return elem.tagName === \"A\";\n });\n};\n\n/**\n * Check if the router should handle a click event on an anchor element.\n * @param {Event} e - The click event.\n * @param {HTMLAnchorElement} anchor - The anchor element.\n * @returns {boolean} - True if the router should handle the event, false otherwise.\n */\nexport const shouldRouterHandleClick = (e, anchor) => {\n // If the event has already been handled by another event listener\n if (e.defaultPrevented) {\n return false;\n }\n\n // If the user is holding down the meta, control, or shift key\n if (e.metaKey || e.ctrlKey || e.shiftKey) {\n return false;\n }\n\n if (!anchor) {\n return false;\n }\n\n if (anchor.target) {\n return false;\n }\n\n if (\n anchor.hasAttribute(\"download\") ||\n anchor.getAttribute(\"rel\") === \"external\"\n ) {\n return false;\n }\n\n const href = anchor.href;\n if (!href || href.startsWith(\"mailto:\")) {\n return false;\n }\n\n // If the href attribute does not start with the same origin\n if (!href.startsWith(location.origin)) {\n return false;\n }\n\n return true;\n};\n","// @ts-check\nimport {\n isFunction,\n isAsyncFunction,\n removeTrailingSlash,\n findAnchor,\n shouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n /**\n * Creates a new BreezeRouter instance.\n * @constructor\n */\n constructor() {\n /**\n * Object containing all registered routes.\n * @type {import('./types.js').Route}\n * @private\n */\n this._routes = {};\n\n /**\n * The previous route that was navigated to\n * @type {import('./types.js').Route}\n * @private\n */\n this._previousRoute = {};\n\n // Bind event listeners\n window.addEventListener(\"popstate\", this._onChanged.bind(this));\n document.body.addEventListener(\"click\", this._handleClick.bind(this));\n }\n\n /**\n * Starts the router.\n * @returns {void}\n */\n start() {\n this._onChanged();\n }\n\n /**\n * Adds a new route to the router.\n * @param {string} route - The route path to add.\n * @param {Function} handler - The async function to handle the route\n * @returns {BreezeRouter|void} The BreezeRouter instance.\n */\n add(route, handler) {\n route = route.trim();\n if (route !== \"/\") {\n route = removeTrailingSlash(route.trim());\n }\n\n if (this._routes[route]) {\n return console.warn(`Route already exists: ${route}`);\n }\n\n if (typeof handler !== \"function\") {\n return console.error(`handler on route '${route}' is not a function.`);\n }\n\n this._routes[route] = {\n path: route,\n handler,\n };\n\n return this;\n }\n\n /**\n * Navigates to the specified URL.\n * @param {string} url - The URL to navigate to\n * @returns {void}\n */\n navigateTo(url) {\n window.history.pushState({ url }, \"\", url);\n this._onChanged();\n }\n\n /**\n * Redirects a URL\n * @param {string} url\n * @returns {void}\n */\n redirect(url) {\n this.navigateTo(url);\n }\n\n async _onChanged() {\n const path = window.location.pathname;\n const { route, params } = this._matchUrlToRoute(path);\n\n // If no matching route found, route will be '404' route\n // which has been handled by _matchUrlToRoute already\n await this._handleRoute({ route, params });\n\n /**\n * Update previous route, so application route handler can check and decide if it should re-render whole page.\n */\n this._previousRoute = route;\n }\n\n /**\n * Processes route callbacks registered by app\n * @param {import('./types.js').MatchedRoute} options\n * @returns {Promise<void>}\n */\n async _handleRoute({ route, params }) {\n if (isFunction(route.handler)) {\n route.handler({ route, params });\n }\n\n if (isAsyncFunction(route.handler)) {\n await route.handler({ route, params });\n }\n }\n\n /**\n *\n * @param {string} url - Current url users visite or nagivate to.\n * @returns {import('./types.js').MatchedRoute}\n */\n _matchUrlToRoute(url) {\n /** @type {import('./types.js').RouteParams} */\n const params = {};\n\n if (url !== \"/\") {\n url = removeTrailingSlash(url);\n }\n\n const matchedRoute = Object.keys(this._routes).find((route) => {\n if (url.split(\"/\").length !== route.split(\"/\").length) {\n return false;\n }\n\n let routeSegments = route.split(\"/\").slice(1);\n let urlSegments = url.split(\"/\").slice(1);\n\n // If each segment in the url matches the corresponding segment in the route path,\n // or the route path segment starts with a ':' then the route is matched.\n const match = routeSegments.every((segment, i) => {\n return segment === urlSegments[i] || segment.startsWith(\":\");\n });\n\n if (!match) {\n return false;\n }\n\n // If the route matches the URL, pull out any params from the URL.\n routeSegments.forEach((segment, i) => {\n if (segment.startsWith(\":\")) {\n const propName = segment.slice(1);\n params[propName] = decodeURIComponent(urlSegments[i]);\n }\n });\n\n return true;\n });\n\n if (matchedRoute) {\n return { route: this._routes[matchedRoute], params };\n } else {\n return { route: this._routes[404], params };\n }\n }\n\n /**\n * Handles <a> link clicks\n * @param {Event} event\n * @returns {void}\n */\n _handleClick(event) {\n const anchor = findAnchor(event);\n if (!anchor) {\n return;\n }\n\n if (!shouldRouterHandleClick(event, anchor)) {\n return;\n }\n\n event.preventDefault();\n let href = anchor.getAttribute(\"href\")?.trim();\n if (!href?.startsWith(\"/\")) {\n href = \"/\" + href;\n }\n\n this.navigateTo(href);\n }\n\n /**\n * Add or remove search param to current url.\n * @param {HTMLInputElement} checkbox \n * @param {string} value\n * @returns void\n */\n toggleParam(checkbox, value) {\n const params = new URLSearchParams(location.search);\n const name = checkbox.getAttribute('name')\n if (!name) {\n return console.warn(`name attribute is not set on ${checkbox.outerHTML}`);\n }\n if (checkbox.checked) {\n !params.has(name) && params.set(name, value);\n } else if (!checkbox.checked) {\n params.has(name) && params.delete(name);\n }\n \n const newUrl = !!params.size\n ? `${location.pathname}?${params.toString()}`\n : location.pathname;\n this.navigateTo(newUrl);\n }\n}\n"],"names":["removeTrailingSlash","url","endsWith","replace","BreezeRouter","constructor","this","_routes","_previousRoute","window","addEventListener","_onChanged","bind","document","body","_handleClick","start","add","route","handler","trim","console","warn","error","path","navigateTo","history","pushState","redirect","async","location","pathname","params","_matchUrlToRoute","_handleRoute","name","toLowerCase","fn","isAsyncFunction","matchedRoute","Object","keys","find","split","length","routeSegments","slice","urlSegments","every","segment","i","startsWith","forEach","propName","decodeURIComponent","event","anchor","composedPath","elem","tagName","e","defaultPrevented","metaKey","ctrlKey","shiftKey","target","hasAttribute","getAttribute","href","origin","shouldRouterHandleClick","preventDefault","toggleParam","checkbox","value","URLSearchParams","search","outerHTML","checked","has","set","delete","newUrl","size","toString"],"mappings":"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"}
|