breeze-router 0.3.0 → 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.
package/README.md CHANGED
@@ -2,23 +2,36 @@
2
2
 
3
3
  A lightweight, zero-dependency client-side router for single page applications (SPAs).
4
4
 
5
- **Note: This project is not production ready and is still in development.**
6
-
7
5
  ## Installation
8
6
 
9
7
  To use this router in your project, install the router using npm:
10
8
 
9
+ ### 1. Through NPM
10
+
11
11
  ```bash
12
12
  npm install breeze-router
13
13
  ```
14
14
 
15
+ ### 2. Through CDN link
16
+
17
+ ```javascript
18
+ <script type="module">
19
+ import BreezeRouter from
20
+ "https://unpkg.com/breeze-router@1.0.0/dist/BreezeRouter.min.js";
21
+ </script>
22
+ ```
23
+
15
24
  ## Usage
16
25
 
17
26
  To use the router in your application, you need to import `BreezeRouter` and define routes and handlers using the `Router` class:
18
27
 
19
28
  ```javascript
29
+ // NPM.
20
30
  import BeezeRouter from "breeze-router";
21
31
 
32
+ // CDN.
33
+ import BreezeRouter from "https://unpkg.com/breeze-router@0.4.0/dist/BreezeRouter.min.js";
34
+
22
35
  // Create a new `BreezeRouter` instance.
23
36
  const ROUTER = new BreezeRouter();
24
37
 
@@ -37,6 +50,13 @@ ROUTER.add("/users/:userId", async ({ route, params }) => {
37
50
  const userId = params.userId;
38
51
  });
39
52
 
53
+ // You can define a type for the placeholder param to
54
+ // avoid conflicts with other similar route like "/users/history".
55
+ ROUTER.add("/users/:userId<number>", async ({ route, params }) => {
56
+ // Handle the users route with a dynamic parameter :userId
57
+ const userId = params.userId;
58
+ });
59
+
40
60
  ROUTER.add("/users/:username/posts/:postId", async ({ route, params }) => {
41
61
  // Handle the posts route with a dynamic parameter :username and :userId
42
62
  const { username, postId } = params;
@@ -46,20 +66,6 @@ ROUTER.add("/users/:username/posts/:postId", async ({ route, params }) => {
46
66
  ROUTER.start();
47
67
  ```
48
68
 
49
- ## Util functions
50
-
51
- ### `toggleParam(this, 1)`
52
-
53
- ```html
54
- <input
55
- type="checkbox"
56
- name="freelance"
57
- onclick="window.ROUTER.toggleParam(this, 1)"
58
- />
59
- ```
60
-
61
- 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.
62
-
63
69
  ## License
64
70
 
65
71
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -1,5 +1,4 @@
1
1
  // @ts-check
2
-
3
2
  /**
4
3
  * Check if given param is function
5
4
  * @param {Function} fn
@@ -103,6 +102,24 @@ class BreezeRouter {
103
102
  */
104
103
  #ignoreAttribute = "data-breeze-ignore";
105
104
 
105
+ previousPath = "";
106
+ /**
107
+ * The previous route that was navigated to.
108
+ *
109
+ * @type {import('./types.js').Route|null}
110
+ */
111
+ previousRoute = null;
112
+ previousParams = {};
113
+
114
+ currentPath = "";
115
+ /**
116
+ * The current route that we're on.
117
+ *
118
+ * @type {import('./types.js').Route|null}
119
+ */
120
+ currentRoute = null;
121
+ currentParams = {};
122
+
106
123
  /**
107
124
  * Creates a new BreezeRouter instance.
108
125
  * @param {object} config - Config.
@@ -122,13 +139,6 @@ class BreezeRouter {
122
139
  */
123
140
  this._routes = {};
124
141
 
125
- /**
126
- * The previous route that was navigated to
127
- * @type {import('./types.js').Route|null}
128
- * @private
129
- */
130
- this._previousRoute = null;
131
-
132
142
  // Bind event listeners
133
143
  window.addEventListener("popstate", this._onChanged.bind(this));
134
144
  document.body.addEventListener("click", this._handleClick.bind(this));
@@ -162,8 +172,37 @@ class BreezeRouter {
162
172
  return console.error(`handler of route '${route}' must be a function.`);
163
173
  }
164
174
 
175
+ const types = {};
176
+
177
+ // Route has types defined.
178
+ if (route.includes("<")) {
179
+ // Split the URL segments by '/'.
180
+ const urlSegments = route
181
+ .split("/")
182
+ .filter((segment) => segment.includes("<"));
183
+
184
+ // Loop through each segment and get its type.
185
+ urlSegments.forEach((segment) => {
186
+ // Get the type.
187
+ const type = segment
188
+ .match(/\<.+\>/)?.[0]
189
+ .replace("<", "")
190
+ .replace(">", "");
191
+
192
+ // Get the prop name.
193
+ const propName = segment.replace(/\<.+\>/, "").replace(":", "");
194
+
195
+ // Store the prop name and type.
196
+ types[propName] = type;
197
+ });
198
+ }
199
+
200
+ // Remove all '<type>' definitions.
201
+ route = route.replaceAll(/\<.+\>/g, "");
202
+
165
203
  this._routes[route] = {
166
204
  path: route,
205
+ types,
167
206
  handler,
168
207
  };
169
208
 
@@ -176,6 +215,9 @@ class BreezeRouter {
176
215
  * @returns {void}
177
216
  */
178
217
  navigateTo(url) {
218
+ this.previousPath = this.currentPath;
219
+ this.previousRoute = this.currentRoute;
220
+ this.previousParams = this.currentParams;
179
221
  window.history.pushState({ url }, "", url);
180
222
  this._onChanged();
181
223
  }
@@ -197,10 +239,23 @@ class BreezeRouter {
197
239
  // which has been handled by _matchUrlToRoute already
198
240
  await this._handleRoute({ route, params });
199
241
 
200
- /**
201
- * Update previous route, so application route handler can check and decide if it should re-render whole page.
202
- */
203
- this._previousRoute = route;
242
+ this.currentPath = addTrailingSlash(path);
243
+ this.currentRoute = route;
244
+ this.currentParams = params;
245
+
246
+ document.dispatchEvent(
247
+ new CustomEvent("breeze-route-changed", {
248
+ detail: {
249
+ previousPath: this.previousPath,
250
+ previousRoute: this.previousRoute,
251
+ previousParams: this.previousParams,
252
+ currentPath: this.currentPath,
253
+ currentRoute: this.currentRoute,
254
+ currentParams: this.currentParams,
255
+ router: this,
256
+ },
257
+ }),
258
+ );
204
259
  }
205
260
 
206
261
  /**
@@ -231,39 +286,63 @@ class BreezeRouter {
231
286
  url = addTrailingSlash(url);
232
287
  }
233
288
 
234
- const matchedRoute = Object.keys(this._routes).find((route) => {
235
- if (url.split("/").length !== route.split("/").length) {
236
- return false;
237
- }
289
+ const matchedRoute = Object.entries(this._routes).find(
290
+ ([route, routeObject]) => {
291
+ if (url.split("/").length !== route.split("/").length) {
292
+ return false;
293
+ }
238
294
 
239
- let routeSegments = route.split("/").slice(1);
240
- let urlSegments = url.split("/").slice(1);
295
+ const { types } = routeObject;
241
296
 
242
- // If each segment in the url matches the corresponding segment in the route path,
243
- // or the route path segment starts with a ':' then the route is matched.
244
- const match = routeSegments.every((segment, i) => {
245
- return segment === urlSegments[i] || segment.startsWith(":");
246
- });
297
+ let routeSegments = route.split("/").slice(1);
298
+ let urlSegments = url.split("/").slice(1);
299
+
300
+ // If each segment in the url matches the corresponding segment in the route path,
301
+ // or the route path segment starts with a ':' then the route is matched.
302
+ const match = routeSegments.every((segment, i) => {
303
+ if (!segment.startsWith(":")) {
304
+ return segment === urlSegments[i];
305
+ }
306
+
307
+ // /product/:id => segment/segment
308
+ // /product/123 => urlSegment/urlSegment
309
+ const type = types[segment.replace(":", "")];
247
310
 
248
- if (!match) {
249
- return false;
250
- }
311
+ // const segmentType =
312
+ const isValid =
313
+ type === "number" ? Number.isInteger(Number(urlSegments[i])) : true;
251
314
 
252
- // If the route matches the URL, pull out any params from the URL.
253
- routeSegments.forEach((segment, i) => {
254
- if (segment.startsWith(":")) {
255
- const propName = segment.slice(1);
256
- params[propName] = decodeURIComponent(urlSegments[i]);
315
+ return isValid;
316
+ });
317
+
318
+ if (!match) {
319
+ return false;
257
320
  }
258
- });
259
321
 
260
- return true;
261
- });
322
+ // If the route matches the URL, pull out any params from the URL.
323
+ routeSegments.forEach((segment, i) => {
324
+ if (segment.startsWith(":")) {
325
+ const propName = segment.slice(1);
326
+ params[propName] = decodeURIComponent(urlSegments[i]);
327
+ }
328
+ });
329
+
330
+ return true;
331
+ },
332
+ );
262
333
 
263
334
  if (matchedRoute) {
264
- return { route: this._routes[matchedRoute], params };
335
+ return {
336
+ route: this._routes[matchedRoute[0]],
337
+ params,
338
+ searchParams: new URLSearchParams(window.location.search),
339
+ };
265
340
  } else {
266
- return { route: this._routes[404], params };
341
+ return {
342
+ route: this._routes[404],
343
+ params,
344
+ searchParams: null,
345
+ };
267
346
  }
268
347
  }
269
348
 
@@ -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 if (!fn) return false;\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 if (!fn) return false;\n return fn.constructor.name.toLowerCase() === \"asyncfunction\";\n};\n\n/**\n * Remove trailing slash of a given 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 * Add trailing slash of a given url.\n * @param {string} url\n * @returns {string}\n */\nexport const addTrailingSlash = (url) => {\n url = url.trim();\n if (!url.endsWith(\"/\")) {\n url = url + \"/\";\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 addTrailingSlash,\n findAnchor,\n shouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n /**\n * `data-` attribute to allow users to skip the router behavior,\n * anchor link with this attribute set will behavior as normal link.\n * @type {string}\n */\n #ignoreAttribute = \"data-breeze-ignore\";\n\n /**\n * Creates a new BreezeRouter instance.\n * @param {object} config - Config.\n * @constructor\n */\n constructor(config) {\n const { ignoreAttribute } = config || {};\n\n if (ignoreAttribute) {\n this.#ignoreAttribute = ignoreAttribute;\n }\n\n /**\n * Object containing all registered routes.\n * @type {object}\n * @private\n */\n this._routes = {};\n\n /**\n * The previous route that was navigated to\n * @type {import('./types.js').Route|null}\n * @private\n */\n this._previousRoute = null;\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 = addTrailingSlash(route);\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 of route '${route}' must be 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 = addTrailingSlash(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 /**\n * Allow users to define an `data-` attribute to skip the route handling.\n * Default to `data-breeze-ignore`.\n */\n if (anchor.hasAttribute(this.#ignoreAttribute)) {\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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"}
1
+ {"version":3,"file":"BreezeRouter.js","sources":["../src/utils.js","../src/index.js"],"sourcesContent":["// @ts-check\n/**\n * Check if given param is function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isFunction = (fn) => {\n if (!fn) return false;\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 if (!fn) return false;\n return fn.constructor.name.toLowerCase() === \"asyncfunction\";\n};\n\n/**\n * Remove trailing slash of a given 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 * Add trailing slash of a given url.\n * @param {string} url\n * @returns {string}\n */\nexport const addTrailingSlash = (url) => {\n url = url.trim();\n if (!url.endsWith(\"/\")) {\n url = url + \"/\";\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 addTrailingSlash,\n findAnchor,\n shouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n /**\n * `data-` attribute to allow users to skip the router behavior,\n * anchor link with this attribute set will behavior as normal link.\n * @type {string}\n */\n #ignoreAttribute = \"data-breeze-ignore\";\n\n previousPath = \"\";\n /**\n * The previous route that was navigated to.\n *\n * @type {import('./types.js').Route|null}\n */\n previousRoute = null;\n previousParams = {};\n\n currentPath = \"\";\n /**\n * The current route that we're on.\n *\n * @type {import('./types.js').Route|null}\n */\n currentRoute = null;\n currentParams = {};\n\n /**\n * Creates a new BreezeRouter instance.\n * @param {object} config - Config.\n * @constructor\n */\n constructor(config) {\n const { ignoreAttribute } = config || {};\n\n if (ignoreAttribute) {\n this.#ignoreAttribute = ignoreAttribute;\n }\n\n /**\n * Object containing all registered routes.\n * @type {object}\n * @private\n */\n this._routes = {};\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 = addTrailingSlash(route);\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 of route '${route}' must be a function.`);\n }\n\n const types = {};\n\n // Route has types defined.\n if (route.includes(\"<\")) {\n // Split the URL segments by '/'.\n const urlSegments = route\n .split(\"/\")\n .filter((segment) => segment.includes(\"<\"));\n\n // Loop through each segment and get its type.\n urlSegments.forEach((segment) => {\n // Get the type.\n const type = segment\n .match(/\\<.+\\>/)?.[0]\n .replace(\"<\", \"\")\n .replace(\">\", \"\");\n\n // Get the prop name.\n const propName = segment.replace(/\\<.+\\>/, \"\").replace(\":\", \"\");\n\n // Store the prop name and type.\n types[propName] = type;\n });\n }\n\n // Remove all '<type>' definitions.\n route = route.replaceAll(/\\<.+\\>/g, \"\");\n\n this._routes[route] = {\n path: route,\n types,\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 this.previousPath = this.currentPath;\n this.previousRoute = this.currentRoute;\n this.previousParams = this.currentParams;\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 this.currentPath = addTrailingSlash(path);\n this.currentRoute = route;\n this.currentParams = params;\n\n document.dispatchEvent(\n new CustomEvent(\"breeze-route-changed\", {\n detail: {\n previousPath: this.previousPath,\n previousRoute: this.previousRoute,\n previousParams: this.previousParams,\n currentPath: this.currentPath,\n currentRoute: this.currentRoute,\n currentParams: this.currentParams,\n router: this,\n },\n }),\n );\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 = addTrailingSlash(url);\n }\n\n const matchedRoute = Object.entries(this._routes).find(\n ([route, routeObject]) => {\n if (url.split(\"/\").length !== route.split(\"/\").length) {\n return false;\n }\n\n const { types } = routeObject;\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 if (!segment.startsWith(\":\")) {\n return segment === urlSegments[i];\n }\n\n // /product/:id => segment/segment\n // /product/123 => urlSegment/urlSegment\n const type = types[segment.replace(\":\", \"\")];\n\n // const segmentType =\n const isValid =\n type === \"number\" ? Number.isInteger(Number(urlSegments[i])) : true;\n\n return isValid;\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\n if (matchedRoute) {\n return {\n route: this._routes[matchedRoute[0]],\n params,\n searchParams: new URLSearchParams(window.location.search),\n };\n } else {\n return {\n route: this._routes[404],\n params,\n searchParams: null,\n };\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 /**\n * Allow users to define an `data-` attribute to skip the route handling.\n * Default to `data-breeze-ignore`.\n */\n if (anchor.hasAttribute(this.#ignoreAttribute)) {\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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"}
@@ -1,2 +1,2 @@
1
- const t=t=>((t=t.trim()).endsWith("/")||(t+="/"),t);class e{#t="data-breeze-ignore";constructor(t){const{ignoreAttribute:e}=t||{};e&&(this.#t=e),this._routes={},this._previousRoute=null,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)),this._routes[e]?console.warn(`Route already exists: ${e}`):"function"!=typeof r?console.error(`handler of route '${e}' must be 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}){var r;r=t?.handler,r&&"function"===r.constructor.name.toLowerCase()&&t.handler({route:t,params:e}),(t=>!!t&&"asyncfunction"===t.constructor.name.toLowerCase())(t?.handler)&&await t.handler({route:t,params:e})}_matchUrlToRoute(e){const r={};"/"!==e&&(e=t(e));const n=Object.keys(this._routes).find((t=>{if(e.split("/").length!==t.split("/").length)return!1;let n=t.split("/").slice(1),a=e.split("/").slice(1);return!!n.every(((t,e)=>t===a[e]||t.startsWith(":")))&&(n.forEach(((t,e)=>{if(t.startsWith(":")){const n=t.slice(1);r[n]=decodeURIComponent(a[e])}})),!0)}));return n?{route:this._routes[n],params:r}:{route:this._routes[404],params:r}}_handleClick(t){const e=t.composedPath().find((t=>"A"===t.tagName));if(!e)return;if(e.hasAttribute(this.#t))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),n=t.getAttribute("name");if(!n)return console.warn(`name attribute is not set on ${t.outerHTML}`);t.checked?!r.has(n)&&r.set(n,e):t.checked||r.has(n)&&r.delete(n);const a=r.size?`${location.pathname}?${r.toString()}`:location.pathname;this.navigateTo(a)}}export{e as default};
1
+ const t=t=>((t=t.trim()).endsWith("/")||(t+="/"),t);class e{#t="data-breeze-ignore";previousPath="";previousRoute=null;previousParams={};currentPath="";currentRoute=null;currentParams={};constructor(t){const{ignoreAttribute:e}=t||{};e&&(this.#t=e),this._routes={},window.addEventListener("popstate",this._onChanged.bind(this)),document.body.addEventListener("click",this._handleClick.bind(this))}start(){this._onChanged()}add(e,r){if("/"!==(e=e.trim())&&(e=t(e)),this._routes[e])return console.warn(`Route already exists: ${e}`);if("function"!=typeof r)return console.error(`handler of route '${e}' must be a function.`);const a={};if(e.includes("<")){e.split("/").filter((t=>t.includes("<"))).forEach((t=>{const e=t.match(/\<.+\>/)?.[0].replace("<","").replace(">",""),r=t.replace(/\<.+\>/,"").replace(":","");a[r]=e}))}return e=e.replaceAll(/\<.+\>/g,""),this._routes[e]={path:e,types:a,handler:r},this}navigateTo(t){this.previousPath=this.currentPath,this.previousRoute=this.currentRoute,this.previousParams=this.currentParams,window.history.pushState({url:t},"",t),this._onChanged()}redirect(t){this.navigateTo(t)}async _onChanged(){const e=window.location.pathname,{route:r,params:a}=this._matchUrlToRoute(e);await this._handleRoute({route:r,params:a}),this.currentPath=t(e),this.currentRoute=r,this.currentParams=a,document.dispatchEvent(new CustomEvent("breeze-route-changed",{detail:{previousPath:this.previousPath,previousRoute:this.previousRoute,previousParams:this.previousParams,currentPath:this.currentPath,currentRoute:this.currentRoute,currentParams:this.currentParams,router:this}}))}async _handleRoute({route:t,params:e}){var r;r=t?.handler,r&&"function"===r.constructor.name.toLowerCase()&&t.handler({route:t,params:e}),(t=>!!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.entries(this._routes).find((([t,a])=>{if(e.split("/").length!==t.split("/").length)return!1;const{types:s}=a;let n=t.split("/").slice(1),i=e.split("/").slice(1);return!!n.every(((t,e)=>{if(!t.startsWith(":"))return t===i[e];return"number"!==s[t.replace(":","")]||Number.isInteger(Number(i[e]))}))&&(n.forEach(((t,e)=>{if(t.startsWith(":")){const a=t.slice(1);r[a]=decodeURIComponent(i[e])}})),!0)}));return a?{route:this._routes[a[0]],params:r,searchParams:new URLSearchParams(window.location.search)}:{route:this._routes[404],params:r,searchParams:null}}_handleClick(t){const e=t.composedPath().find((t=>"A"===t.tagName));if(!e)return;if(e.hasAttribute(this.#t))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 s=r.size?`${location.pathname}?${r.toString()}`:location.pathname;this.navigateTo(s)}}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 if (!fn) return false;\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 if (!fn) return false;\n return fn.constructor.name.toLowerCase() === \"asyncfunction\";\n};\n\n/**\n * Remove trailing slash of a given 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 * Add trailing slash of a given url.\n * @param {string} url\n * @returns {string}\n */\nexport const addTrailingSlash = (url) => {\n url = url.trim();\n if (!url.endsWith(\"/\")) {\n url = url + \"/\";\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 addTrailingSlash,\n findAnchor,\n shouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n /**\n * `data-` attribute to allow users to skip the router behavior,\n * anchor link with this attribute set will behavior as normal link.\n * @type {string}\n */\n #ignoreAttribute = \"data-breeze-ignore\";\n\n /**\n * Creates a new BreezeRouter instance.\n * @param {object} config - Config.\n * @constructor\n */\n constructor(config) {\n const { ignoreAttribute } = config || {};\n\n if (ignoreAttribute) {\n this.#ignoreAttribute = ignoreAttribute;\n }\n\n /**\n * Object containing all registered routes.\n * @type {object}\n * @private\n */\n this._routes = {};\n\n /**\n * The previous route that was navigated to\n * @type {import('./types.js').Route|null}\n * @private\n */\n this._previousRoute = null;\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 = addTrailingSlash(route);\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 of route '${route}' must be 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 = addTrailingSlash(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 /**\n * Allow users to define an `data-` attribute to skip the route handling.\n * Default to `data-breeze-ignore`.\n */\n if (anchor.hasAttribute(this.#ignoreAttribute)) {\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":["addTrailingSlash","url","trim","endsWith","BreezeRouter","ignoreAttribute","constructor","config","this","_routes","_previousRoute","window","addEventListener","_onChanged","bind","document","body","_handleClick","start","add","route","handler","console","warn","error","path","navigateTo","history","pushState","redirect","async","location","pathname","params","_matchUrlToRoute","_handleRoute","fn","name","toLowerCase","isAsyncFunction","matchedRoute","Object","keys","find","split","length","routeSegments","slice","urlSegments","every","segment","i","startsWith","forEach","propName","decodeURIComponent","event","anchor","composedPath","elem","tagName","hasAttribute","e","defaultPrevented","metaKey","ctrlKey","shiftKey","target","getAttribute","href","origin","shouldRouterHandleClick","preventDefault","toggleParam","checkbox","value","URLSearchParams","search","outerHTML","checked","has","set","delete","newUrl","size","toString"],"mappings":"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"}
1
+ {"version":3,"file":"BreezeRouter.min.js","sources":["../src/utils.js","../src/index.js"],"sourcesContent":["// @ts-check\n/**\n * Check if given param is function\n * @param {Function} fn\n * @returns {boolean}\n */\nexport const isFunction = (fn) => {\n if (!fn) return false;\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 if (!fn) return false;\n return fn.constructor.name.toLowerCase() === \"asyncfunction\";\n};\n\n/**\n * Remove trailing slash of a given 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 * Add trailing slash of a given url.\n * @param {string} url\n * @returns {string}\n */\nexport const addTrailingSlash = (url) => {\n url = url.trim();\n if (!url.endsWith(\"/\")) {\n url = url + \"/\";\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 addTrailingSlash,\n findAnchor,\n shouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n /**\n * `data-` attribute to allow users to skip the router behavior,\n * anchor link with this attribute set will behavior as normal link.\n * @type {string}\n */\n #ignoreAttribute = \"data-breeze-ignore\";\n\n previousPath = \"\";\n /**\n * The previous route that was navigated to.\n *\n * @type {import('./types.js').Route|null}\n */\n previousRoute = null;\n previousParams = {};\n\n currentPath = \"\";\n /**\n * The current route that we're on.\n *\n * @type {import('./types.js').Route|null}\n */\n currentRoute = null;\n currentParams = {};\n\n /**\n * Creates a new BreezeRouter instance.\n * @param {object} config - Config.\n * @constructor\n */\n constructor(config) {\n const { ignoreAttribute } = config || {};\n\n if (ignoreAttribute) {\n this.#ignoreAttribute = ignoreAttribute;\n }\n\n /**\n * Object containing all registered routes.\n * @type {object}\n * @private\n */\n this._routes = {};\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 = addTrailingSlash(route);\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 of route '${route}' must be a function.`);\n }\n\n const types = {};\n\n // Route has types defined.\n if (route.includes(\"<\")) {\n // Split the URL segments by '/'.\n const urlSegments = route\n .split(\"/\")\n .filter((segment) => segment.includes(\"<\"));\n\n // Loop through each segment and get its type.\n urlSegments.forEach((segment) => {\n // Get the type.\n const type = segment\n .match(/\\<.+\\>/)?.[0]\n .replace(\"<\", \"\")\n .replace(\">\", \"\");\n\n // Get the prop name.\n const propName = segment.replace(/\\<.+\\>/, \"\").replace(\":\", \"\");\n\n // Store the prop name and type.\n types[propName] = type;\n });\n }\n\n // Remove all '<type>' definitions.\n route = route.replaceAll(/\\<.+\\>/g, \"\");\n\n this._routes[route] = {\n path: route,\n types,\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 this.previousPath = this.currentPath;\n this.previousRoute = this.currentRoute;\n this.previousParams = this.currentParams;\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 this.currentPath = addTrailingSlash(path);\n this.currentRoute = route;\n this.currentParams = params;\n\n document.dispatchEvent(\n new CustomEvent(\"breeze-route-changed\", {\n detail: {\n previousPath: this.previousPath,\n previousRoute: this.previousRoute,\n previousParams: this.previousParams,\n currentPath: this.currentPath,\n currentRoute: this.currentRoute,\n currentParams: this.currentParams,\n router: this,\n },\n }),\n );\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 = addTrailingSlash(url);\n }\n\n const matchedRoute = Object.entries(this._routes).find(\n ([route, routeObject]) => {\n if (url.split(\"/\").length !== route.split(\"/\").length) {\n return false;\n }\n\n const { types } = routeObject;\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 if (!segment.startsWith(\":\")) {\n return segment === urlSegments[i];\n }\n\n // /product/:id => segment/segment\n // /product/123 => urlSegment/urlSegment\n const type = types[segment.replace(\":\", \"\")];\n\n // const segmentType =\n const isValid =\n type === \"number\" ? Number.isInteger(Number(urlSegments[i])) : true;\n\n return isValid;\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\n if (matchedRoute) {\n return {\n route: this._routes[matchedRoute[0]],\n params,\n searchParams: new URLSearchParams(window.location.search),\n };\n } else {\n return {\n route: this._routes[404],\n params,\n searchParams: null,\n };\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 /**\n * Allow users to define an `data-` attribute to skip the route handling.\n * Default to `data-breeze-ignore`.\n */\n if (anchor.hasAttribute(this.#ignoreAttribute)) {\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":["addTrailingSlash","url","trim","endsWith","BreezeRouter","ignoreAttribute","previousPath","previousRoute","previousParams","currentPath","currentRoute","currentParams","constructor","config","this","_routes","window","addEventListener","_onChanged","bind","document","body","_handleClick","start","add","route","handler","console","warn","error","types","includes","split","filter","segment","forEach","type","match","replace","propName","replaceAll","path","navigateTo","history","pushState","redirect","async","location","pathname","params","_matchUrlToRoute","_handleRoute","dispatchEvent","CustomEvent","detail","router","fn","name","toLowerCase","isAsyncFunction","matchedRoute","Object","entries","find","routeObject","length","routeSegments","slice","urlSegments","every","i","startsWith","Number","isInteger","decodeURIComponent","searchParams","URLSearchParams","search","event","anchor","composedPath","elem","tagName","hasAttribute","e","defaultPrevented","metaKey","ctrlKey","shiftKey","target","getAttribute","href","origin","shouldRouterHandleClick","preventDefault","toggleParam","checkbox","value","outerHTML","checked","has","set","delete","newUrl","size","toString"],"mappings":"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"}
package/dist/types.js CHANGED
@@ -12,6 +12,7 @@
12
12
  * @typedef {Object} MatchedRoute
13
13
  * @property {Route} route
14
14
  * @property {RouteParams} params
15
+ * @property {URLSearchParams|null} searchParams
15
16
  */
16
17
 
17
18
  export { Route, RouteParams, MatchedRoute };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "breeze-router",
3
- "version": "0.3.0",
3
+ "version": "1.0.0",
4
4
  "description": "A lightweight, zero-dependency client-side router for single page applications (SPAs).",
5
5
  "main": "index.js",
6
6
  "type": "module",