breeze-router 1.0.0 → 1.0.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/README.md CHANGED
@@ -17,7 +17,7 @@ npm install breeze-router
17
17
  ```javascript
18
18
  <script type="module">
19
19
  import BreezeRouter from
20
- "https://unpkg.com/breeze-router@1.0.0/dist/BreezeRouter.min.js";
20
+ "https://unpkg.com/breeze-router@1.0.1/dist/BreezeRouter.min.js";
21
21
  </script>
22
22
  ```
23
23
 
@@ -64,6 +64,13 @@ ROUTER.add("/users/:username/posts/:postId", async ({ route, params }) => {
64
64
 
65
65
  // Call the `start` method to start the router.
66
66
  ROUTER.start();
67
+
68
+ // You can register the callbacks to run when the router is ready.
69
+ // This is a good time to do some initial work like injecting some
70
+ // global headers or footers into the page.
71
+ ROUTER.ready(() => {
72
+ console.log("ROUTER is ready now.");
73
+ });
67
74
  ```
68
75
 
69
76
  ## License
@@ -95,312 +95,330 @@ const shouldRouterHandleClick = (e, anchor) => {
95
95
  * Class representing a router.
96
96
  */
97
97
  class BreezeRouter {
98
- /**
99
- * `data-` attribute to allow users to skip the router behavior,
100
- * anchor link with this attribute set will behavior as normal link.
101
- * @type {string}
102
- */
103
- #ignoreAttribute = "data-breeze-ignore";
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
-
123
- /**
124
- * Creates a new BreezeRouter instance.
125
- * @param {object} config - Config.
126
- * @constructor
127
- */
128
- constructor(config) {
129
- const { ignoreAttribute } = config || {};
130
-
131
- if (ignoreAttribute) {
132
- this.#ignoreAttribute = ignoreAttribute;
133
- }
134
-
135
- /**
136
- * Object containing all registered routes.
137
- * @type {object}
138
- * @private
139
- */
140
- this._routes = {};
141
-
142
- // Bind event listeners
143
- window.addEventListener("popstate", this._onChanged.bind(this));
144
- document.body.addEventListener("click", this._handleClick.bind(this));
145
- }
146
-
147
- /**
148
- * Starts the router.
149
- * @returns {void}
150
- */
151
- start() {
152
- this._onChanged();
153
- }
154
-
155
- /**
156
- * Adds a new route to the router.
157
- * @param {string} route - The route path to add.
158
- * @param {Function} handler - The async function to handle the route
159
- * @returns {BreezeRouter|void} The BreezeRouter instance.
160
- */
161
- add(route, handler) {
162
- route = route.trim();
163
- if (route !== "/") {
164
- route = addTrailingSlash(route);
165
- }
166
-
167
- if (this._routes[route]) {
168
- return console.warn(`Route already exists: ${route}`);
169
- }
170
-
171
- if (typeof handler !== "function") {
172
- return console.error(`handler of route '${route}' must be a function.`);
173
- }
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
-
203
- this._routes[route] = {
204
- path: route,
205
- types,
206
- handler,
207
- };
208
-
209
- return this;
210
- }
211
-
212
- /**
213
- * Navigates to the specified URL.
214
- * @param {string} url - The URL to navigate to
215
- * @returns {void}
216
- */
217
- navigateTo(url) {
218
- this.previousPath = this.currentPath;
219
- this.previousRoute = this.currentRoute;
220
- this.previousParams = this.currentParams;
221
- window.history.pushState({ url }, "", url);
222
- this._onChanged();
223
- }
224
-
225
- /**
226
- * Redirects a URL
227
- * @param {string} url
228
- * @returns {void}
229
- */
230
- redirect(url) {
231
- this.navigateTo(url);
232
- }
233
-
234
- async _onChanged() {
235
- const path = window.location.pathname;
236
- const { route, params } = this._matchUrlToRoute(path);
237
-
238
- // If no matching route found, route will be '404' route
239
- // which has been handled by _matchUrlToRoute already
240
- await this._handleRoute({ route, params });
241
-
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
- );
259
- }
260
-
261
- /**
262
- * Processes route callbacks registered by app
263
- * @param {import('./types.js').MatchedRoute} options
264
- * @returns {Promise<void>}
265
- */
266
- async _handleRoute({ route, params }) {
267
- if (isFunction(route?.handler)) {
268
- route.handler({ route, params });
269
- }
270
-
271
- if (isAsyncFunction(route?.handler)) {
272
- await route.handler({ route, params });
273
- }
274
- }
275
-
276
- /**
277
- *
278
- * @param {string} url - Current url users visite or nagivate to.
279
- * @returns {import('./types.js').MatchedRoute}
280
- */
281
- _matchUrlToRoute(url) {
282
- /** @type {import('./types.js').RouteParams} */
283
- const params = {};
284
-
285
- if (url !== "/") {
286
- url = addTrailingSlash(url);
287
- }
288
-
289
- const matchedRoute = Object.entries(this._routes).find(
290
- ([route, routeObject]) => {
291
- if (url.split("/").length !== route.split("/").length) {
292
- return false;
293
- }
294
-
295
- const { types } = routeObject;
296
-
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(":", "")];
310
-
311
- // const segmentType =
312
- const isValid =
313
- type === "number" ? Number.isInteger(Number(urlSegments[i])) : true;
314
-
315
- return isValid;
316
- });
317
-
318
- if (!match) {
319
- return false;
320
- }
321
-
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
- );
333
-
334
- if (matchedRoute) {
335
- return {
336
- route: this._routes[matchedRoute[0]],
337
- params,
338
- searchParams: new URLSearchParams(window.location.search),
339
- };
340
- } else {
341
- return {
342
- route: this._routes[404],
343
- params,
344
- searchParams: null,
345
- };
346
- }
347
- }
348
-
349
- /**
350
- * Handles <a> link clicks
351
- * @param {Event} event
352
- * @returns {void}
353
- */
354
- _handleClick(event) {
355
- const anchor = findAnchor(event);
356
- if (!anchor) {
357
- return;
358
- }
359
-
360
- /**
361
- * Allow users to define an `data-` attribute to skip the route handling.
362
- * Default to `data-breeze-ignore`.
363
- */
364
- if (anchor.hasAttribute(this.#ignoreAttribute)) {
365
- return;
366
- }
367
-
368
- if (!shouldRouterHandleClick(event, anchor)) {
369
- return;
370
- }
371
-
372
- event.preventDefault();
373
- let href = anchor.getAttribute("href")?.trim();
374
- if (!href?.startsWith("/")) {
375
- href = "/" + href;
376
- }
377
-
378
- this.navigateTo(href);
379
- }
380
-
381
- /**
382
- * Add or remove search param to current url.
383
- * @param {HTMLInputElement} checkbox
384
- * @param {string} value
385
- * @returns void
386
- */
387
- toggleParam(checkbox, value) {
388
- const params = new URLSearchParams(location.search);
389
- const name = checkbox.getAttribute("name");
390
- if (!name) {
391
- return console.warn(`name attribute is not set on ${checkbox.outerHTML}`);
392
- }
393
- if (checkbox.checked) {
394
- !params.has(name) && params.set(name, value);
395
- } else if (!checkbox.checked) {
396
- params.has(name) && params.delete(name);
397
- }
398
-
399
- const newUrl = !!params.size
400
- ? `${location.pathname}?${params.toString()}`
401
- : location.pathname;
402
- this.navigateTo(newUrl);
403
- }
98
+ /**
99
+ * `data-` attribute to allow users to skip the router behavior,
100
+ * anchor link with this attribute set will behavior as normal link.
101
+ * @type {string}
102
+ */
103
+ #ignoreAttribute = "data-breeze-ignore";
104
+ #isReady = false;
105
+ #isReadyCallbacks = [];
106
+
107
+ previousPath = "";
108
+ /**
109
+ * The previous route that was navigated to.
110
+ *
111
+ * @type {import('./types.js').Route|null}
112
+ */
113
+ previousRoute = null;
114
+ previousParams = {};
115
+
116
+ currentPath = "";
117
+ /**
118
+ * The current route that we're on.
119
+ *
120
+ * @type {import('./types.js').Route|null}
121
+ */
122
+ currentRoute = null;
123
+ currentParams = {};
124
+
125
+ /**
126
+ * Creates a new BreezeRouter instance.
127
+ * @param {object} config - Config.
128
+ * @constructor
129
+ */
130
+ constructor(config) {
131
+ const { ignoreAttribute } = config || {};
132
+
133
+ if (ignoreAttribute) {
134
+ this.#ignoreAttribute = ignoreAttribute;
135
+ }
136
+
137
+ /**
138
+ * Object containing all registered routes.
139
+ * @type {object}
140
+ * @private
141
+ */
142
+ this._routes = {};
143
+
144
+ // Bind event listeners
145
+ window.addEventListener("popstate", this._onChanged.bind(this));
146
+ document.body.addEventListener("click", this._handleClick.bind(this));
147
+ }
148
+
149
+ /**
150
+ * Starts the router.
151
+ * @returns {void}
152
+ */
153
+ start() {
154
+ this._onChanged();
155
+ }
156
+
157
+ /**
158
+ * Register the callback when the router is ready.
159
+ *
160
+ * @param {Function} fn - Callback function.
161
+ */
162
+ ready(fn) {
163
+ if (typeof fn === 'function') {
164
+ this.#isReadyCallbacks.push(fn);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Adds a new route to the router.
170
+ * @param {string} route - The route path to add.
171
+ * @param {Function} handler - The async function to handle the route
172
+ * @returns {BreezeRouter|void} The BreezeRouter instance.
173
+ */
174
+ add(route, handler) {
175
+ route = route.trim();
176
+ if (route !== "/") {
177
+ route = addTrailingSlash(route);
178
+ }
179
+
180
+ if (this._routes[route]) {
181
+ return console.warn(`Route already exists: ${route}`);
182
+ }
183
+
184
+ if (typeof handler !== "function") {
185
+ return console.error(`handler of route '${route}' must be a function.`);
186
+ }
187
+
188
+ const types = {};
189
+
190
+ // Route has types defined.
191
+ if (route.includes("<")) {
192
+ // Split the URL segments by '/'.
193
+ const urlSegments = route
194
+ .split("/")
195
+ .filter((segment) => segment.includes("<"));
196
+
197
+ // Loop through each segment and get its type.
198
+ urlSegments.forEach((segment) => {
199
+ // Get the type.
200
+ const type = segment
201
+ .match(/\<.+\>/)?.[0]
202
+ .replace("<", "")
203
+ .replace(">", "");
204
+
205
+ // Get the prop name.
206
+ const propName = segment.replace(/\<.+\>/, "").replace(":", "");
207
+
208
+ // Store the prop name and type.
209
+ types[propName] = type;
210
+ });
211
+ }
212
+
213
+ // Remove all '<type>' definitions.
214
+ route = route.replaceAll(/\<.+\>/g, "");
215
+
216
+ this._routes[route] = {
217
+ path: route,
218
+ types,
219
+ handler,
220
+ };
221
+
222
+ return this;
223
+ }
224
+
225
+ /**
226
+ * Navigates to the specified URL.
227
+ * @param {string} url - The URL to navigate to
228
+ * @returns {void}
229
+ */
230
+ navigateTo(url) {
231
+ this.previousPath = this.currentPath;
232
+ this.previousRoute = this.currentRoute;
233
+ this.previousParams = this.currentParams;
234
+ window.history.pushState({ url }, "", url);
235
+ this._onChanged();
236
+ }
237
+
238
+ /**
239
+ * Redirects a URL
240
+ * @param {string} url
241
+ * @returns {void}
242
+ */
243
+ redirect(url) {
244
+ this.navigateTo(url);
245
+ }
246
+
247
+ async _onChanged() {
248
+ const path = window.location.pathname;
249
+ const { route, params } = this._matchUrlToRoute(path);
250
+
251
+ this.currentPath = addTrailingSlash(path);
252
+ this.currentRoute = route;
253
+ this.currentParams = params;
254
+
255
+ // If no matching route found, route will be '404' route
256
+ // which has been handled by _matchUrlToRoute already.
257
+ await this._handleRoute({ route, params });
258
+
259
+ if (!this.#isReady) {
260
+ this.#isReady = true;
261
+ this.#isReadyCallbacks.forEach(fn => fn(this));
262
+ }
263
+
264
+ document.dispatchEvent(
265
+ new CustomEvent("breeze-route-changed", {
266
+ detail: {
267
+ previousPath: this.previousPath,
268
+ previousRoute: this.previousRoute,
269
+ previousParams: this.previousParams,
270
+ currentPath: this.currentPath,
271
+ currentRoute: this.currentRoute,
272
+ currentParams: this.currentParams,
273
+ router: this,
274
+ },
275
+ }),
276
+ );
277
+ }
278
+
279
+ /**
280
+ * Processes route callbacks registered by app
281
+ * @param {import('./types.js').MatchedRoute} options
282
+ * @returns {Promise<void>}
283
+ */
284
+ async _handleRoute({ route, params }) {
285
+ if (isFunction(route?.handler)) {
286
+ route.handler({ route, params });
287
+ }
288
+
289
+ if (isAsyncFunction(route?.handler)) {
290
+ await route.handler({ route, params });
291
+ }
292
+ }
293
+
294
+ /**
295
+ *
296
+ * @param {string} url - Current url users visite or nagivate to.
297
+ * @returns {import('./types.js').MatchedRoute}
298
+ */
299
+ _matchUrlToRoute(url) {
300
+ /** @type {import('./types.js').RouteParams} */
301
+ const params = {};
302
+
303
+ if (url !== "/") {
304
+ url = addTrailingSlash(url);
305
+ }
306
+
307
+ const matchedRoute = Object.entries(this._routes).find(
308
+ ([route, routeObject]) => {
309
+ if (url.split("/").length !== route.split("/").length) {
310
+ return false;
311
+ }
312
+
313
+ const { types } = routeObject;
314
+
315
+ let routeSegments = route.split("/").slice(1);
316
+ let urlSegments = url.split("/").slice(1);
317
+
318
+ // If each segment in the url matches the corresponding segment in the route path,
319
+ // or the route path segment starts with a ':' then the route is matched.
320
+ const match = routeSegments.every((segment, i) => {
321
+ if (!segment.startsWith(":")) {
322
+ return segment === urlSegments[i];
323
+ }
324
+
325
+ // /product/:id => segment/segment
326
+ // /product/123 => urlSegment/urlSegment
327
+ const type = types[segment.replace(":", "")];
328
+
329
+ // const segmentType =
330
+ const isValid =
331
+ type === "number" ? Number.isInteger(Number(urlSegments[i])) : true;
332
+
333
+ return isValid;
334
+ });
335
+
336
+ if (!match) {
337
+ return false;
338
+ }
339
+
340
+ // If the route matches the URL, pull out any params from the URL.
341
+ routeSegments.forEach((segment, i) => {
342
+ if (segment.startsWith(":")) {
343
+ const propName = segment.slice(1);
344
+ params[propName] = decodeURIComponent(urlSegments[i]);
345
+ }
346
+ });
347
+
348
+ return true;
349
+ },
350
+ );
351
+
352
+ if (matchedRoute) {
353
+ return {
354
+ route: this._routes[matchedRoute[0]],
355
+ params,
356
+ searchParams: new URLSearchParams(window.location.search),
357
+ };
358
+ } else {
359
+ return {
360
+ route: this._routes[404],
361
+ params,
362
+ searchParams: null,
363
+ };
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Handles <a> link clicks
369
+ * @param {Event} event
370
+ * @returns {void}
371
+ */
372
+ _handleClick(event) {
373
+ const anchor = findAnchor(event);
374
+ if (!anchor) {
375
+ return;
376
+ }
377
+
378
+ /**
379
+ * Allow users to define an `data-` attribute to skip the route handling.
380
+ * Default to `data-breeze-ignore`.
381
+ */
382
+ if (anchor.hasAttribute(this.#ignoreAttribute)) {
383
+ return;
384
+ }
385
+
386
+ if (!shouldRouterHandleClick(event, anchor)) {
387
+ return;
388
+ }
389
+
390
+ event.preventDefault();
391
+ let href = anchor.getAttribute("href")?.trim();
392
+ if (!href?.startsWith("/")) {
393
+ href = "/" + href;
394
+ }
395
+
396
+ this.navigateTo(href);
397
+ }
398
+
399
+ /**
400
+ * Add or remove search param to current url.
401
+ * @param {HTMLInputElement} checkbox
402
+ * @param {string} value
403
+ * @returns void
404
+ */
405
+ toggleParam(checkbox, value) {
406
+ const params = new URLSearchParams(location.search);
407
+ const name = checkbox.getAttribute("name");
408
+ if (!name) {
409
+ return console.warn(`name attribute is not set on ${checkbox.outerHTML}`);
410
+ }
411
+ if (checkbox.checked) {
412
+ !params.has(name) && params.set(name, value);
413
+ } else if (!checkbox.checked) {
414
+ params.has(name) && params.delete(name);
415
+ }
416
+
417
+ const newUrl = !!params.size
418
+ ? `${location.pathname}?${params.toString()}`
419
+ : location.pathname;
420
+ this.navigateTo(newUrl);
421
+ }
404
422
  }
405
423
 
406
424
  export { BreezeRouter as default };
@@ -1 +1 @@
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
+ {"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\tisFunction,\n\tisAsyncFunction,\n\tremoveTrailingSlash,\n\taddTrailingSlash,\n\tfindAnchor,\n\tshouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n\t/**\n\t * `data-` attribute to allow users to skip the router behavior,\n\t * anchor link with this attribute set will behavior as normal link.\n\t * @type {string}\n\t */\n\t#ignoreAttribute = \"data-breeze-ignore\";\n\t#isReady = false;\n\t#isReadyCallbacks = [];\n\n\tpreviousPath = \"\";\n\t/**\n\t * The previous route that was navigated to.\n\t *\n\t * @type {import('./types.js').Route|null}\n\t */\n\tpreviousRoute = null;\n\tpreviousParams = {};\n\n\tcurrentPath = \"\";\n\t/**\n\t * The current route that we're on.\n\t *\n\t * @type {import('./types.js').Route|null}\n\t */\n\tcurrentRoute = null;\n\tcurrentParams = {};\n\n\t/**\n\t * Creates a new BreezeRouter instance.\n\t * @param {object} config - Config.\n\t * @constructor\n\t */\n\tconstructor(config) {\n\t\tconst { ignoreAttribute } = config || {};\n\n\t\tif (ignoreAttribute) {\n\t\t\tthis.#ignoreAttribute = ignoreAttribute;\n\t\t}\n\n\t\t/**\n\t\t * Object containing all registered routes.\n\t\t * @type {object}\n\t\t * @private\n\t\t */\n\t\tthis._routes = {};\n\n\t\t// Bind event listeners\n\t\twindow.addEventListener(\"popstate\", this._onChanged.bind(this));\n\t\tdocument.body.addEventListener(\"click\", this._handleClick.bind(this));\n\t}\n\n\t/**\n\t * Starts the router.\n\t * @returns {void}\n\t */\n\tstart() {\n\t\tthis._onChanged();\n\t}\n\n\t/**\n\t * Register the callback when the router is ready.\n\t * \n\t * @param {Function} fn - Callback function.\n\t */\n\tready(fn) {\n\t\tif (typeof fn === 'function') {\n\t\t\tthis.#isReadyCallbacks.push(fn);\n\t\t}\n\t}\n\n\t/**\n\t * Adds a new route to the router.\n\t * @param {string} route - The route path to add.\n\t * @param {Function} handler - The async function to handle the route\n\t * @returns {BreezeRouter|void} The BreezeRouter instance.\n\t */\n\tadd(route, handler) {\n\t\troute = route.trim();\n\t\tif (route !== \"/\") {\n\t\t\troute = addTrailingSlash(route);\n\t\t}\n\n\t\tif (this._routes[route]) {\n\t\t\treturn console.warn(`Route already exists: ${route}`);\n\t\t}\n\n\t\tif (typeof handler !== \"function\") {\n\t\t\treturn console.error(`handler of route '${route}' must be a function.`);\n\t\t}\n\n\t\tconst types = {};\n\n\t\t// Route has types defined.\n\t\tif (route.includes(\"<\")) {\n\t\t\t// Split the URL segments by '/'.\n\t\t\tconst urlSegments = route\n\t\t\t\t.split(\"/\")\n\t\t\t\t.filter((segment) => segment.includes(\"<\"));\n\n\t\t\t// Loop through each segment and get its type.\n\t\t\turlSegments.forEach((segment) => {\n\t\t\t\t// Get the type.\n\t\t\t\tconst type = segment\n\t\t\t\t\t.match(/\\<.+\\>/)?.[0]\n\t\t\t\t\t.replace(\"<\", \"\")\n\t\t\t\t\t.replace(\">\", \"\");\n\n\t\t\t\t// Get the prop name.\n\t\t\t\tconst propName = segment.replace(/\\<.+\\>/, \"\").replace(\":\", \"\");\n\n\t\t\t\t// Store the prop name and type.\n\t\t\t\ttypes[propName] = type;\n\t\t\t});\n\t\t}\n\n\t\t// Remove all '<type>' definitions.\n\t\troute = route.replaceAll(/\\<.+\\>/g, \"\");\n\n\t\tthis._routes[route] = {\n\t\t\tpath: route,\n\t\t\ttypes,\n\t\t\thandler,\n\t\t};\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Navigates to the specified URL.\n\t * @param {string} url - The URL to navigate to\n\t * @returns {void}\n\t */\n\tnavigateTo(url) {\n\t\tthis.previousPath = this.currentPath;\n\t\tthis.previousRoute = this.currentRoute;\n\t\tthis.previousParams = this.currentParams;\n\t\twindow.history.pushState({ url }, \"\", url);\n\t\tthis._onChanged();\n\t}\n\n\t/**\n\t * Redirects a URL\n\t * @param {string} url\n\t * @returns {void}\n\t */\n\tredirect(url) {\n\t\tthis.navigateTo(url);\n\t}\n\n\tasync _onChanged() {\n\t\tconst path = window.location.pathname;\n\t\tconst { route, params } = this._matchUrlToRoute(path);\n\n\t\tthis.currentPath = addTrailingSlash(path);\n\t\tthis.currentRoute = route;\n\t\tthis.currentParams = params;\n\n\t\t// If no matching route found, route will be '404' route\n\t\t// which has been handled by _matchUrlToRoute already.\n\t\tawait this._handleRoute({ route, params });\n\n\t\tif (!this.#isReady) {\n\t\t\tthis.#isReady = true;\n\t\t\tthis.#isReadyCallbacks.forEach(fn => fn(this));\n\t\t}\n\n\t\tdocument.dispatchEvent(\n\t\t\tnew CustomEvent(\"breeze-route-changed\", {\n\t\t\t\tdetail: {\n\t\t\t\t\tpreviousPath: this.previousPath,\n\t\t\t\t\tpreviousRoute: this.previousRoute,\n\t\t\t\t\tpreviousParams: this.previousParams,\n\t\t\t\t\tcurrentPath: this.currentPath,\n\t\t\t\t\tcurrentRoute: this.currentRoute,\n\t\t\t\t\tcurrentParams: this.currentParams,\n\t\t\t\t\trouter: this,\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t}\n\n\t/**\n\t * Processes route callbacks registered by app\n\t * @param {import('./types.js').MatchedRoute} options\n\t * @returns {Promise<void>}\n\t */\n\tasync _handleRoute({ route, params }) {\n\t\tif (isFunction(route?.handler)) {\n\t\t\troute.handler({ route, params });\n\t\t}\n\n\t\tif (isAsyncFunction(route?.handler)) {\n\t\t\tawait route.handler({ route, params });\n\t\t}\n\t}\n\n\t/**\n\t *\n\t * @param {string} url - Current url users visite or nagivate to.\n\t * @returns {import('./types.js').MatchedRoute}\n\t */\n\t_matchUrlToRoute(url) {\n\t\t/** @type {import('./types.js').RouteParams} */\n\t\tconst params = {};\n\n\t\tif (url !== \"/\") {\n\t\t\turl = addTrailingSlash(url);\n\t\t}\n\n\t\tconst matchedRoute = Object.entries(this._routes).find(\n\t\t\t([route, routeObject]) => {\n\t\t\t\tif (url.split(\"/\").length !== route.split(\"/\").length) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tconst { types } = routeObject;\n\n\t\t\t\tlet routeSegments = route.split(\"/\").slice(1);\n\t\t\t\tlet urlSegments = url.split(\"/\").slice(1);\n\n\t\t\t\t// If each segment in the url matches the corresponding segment in the route path,\n\t\t\t\t// or the route path segment starts with a ':' then the route is matched.\n\t\t\t\tconst match = routeSegments.every((segment, i) => {\n\t\t\t\t\tif (!segment.startsWith(\":\")) {\n\t\t\t\t\t\treturn segment === urlSegments[i];\n\t\t\t\t\t}\n\n\t\t\t\t\t// /product/:id => segment/segment\n\t\t\t\t\t// /product/123 => urlSegment/urlSegment\n\t\t\t\t\tconst type = types[segment.replace(\":\", \"\")];\n\n\t\t\t\t\t// const segmentType =\n\t\t\t\t\tconst isValid =\n\t\t\t\t\t\ttype === \"number\" ? Number.isInteger(Number(urlSegments[i])) : true;\n\n\t\t\t\t\treturn isValid;\n\t\t\t\t});\n\n\t\t\t\tif (!match) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// If the route matches the URL, pull out any params from the URL.\n\t\t\t\trouteSegments.forEach((segment, i) => {\n\t\t\t\t\tif (segment.startsWith(\":\")) {\n\t\t\t\t\t\tconst propName = segment.slice(1);\n\t\t\t\t\t\tparams[propName] = decodeURIComponent(urlSegments[i]);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn true;\n\t\t\t},\n\t\t);\n\n\t\tif (matchedRoute) {\n\t\t\treturn {\n\t\t\t\troute: this._routes[matchedRoute[0]],\n\t\t\t\tparams,\n\t\t\t\tsearchParams: new URLSearchParams(window.location.search),\n\t\t\t};\n\t\t} else {\n\t\t\treturn {\n\t\t\t\troute: this._routes[404],\n\t\t\t\tparams,\n\t\t\t\tsearchParams: null,\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Handles <a> link clicks\n\t * @param {Event} event\n\t * @returns {void}\n\t */\n\t_handleClick(event) {\n\t\tconst anchor = findAnchor(event);\n\t\tif (!anchor) {\n\t\t\treturn;\n\t\t}\n\n\t\t/**\n\t\t * Allow users to define an `data-` attribute to skip the route handling.\n\t\t * Default to `data-breeze-ignore`.\n\t\t */\n\t\tif (anchor.hasAttribute(this.#ignoreAttribute)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!shouldRouterHandleClick(event, anchor)) {\n\t\t\treturn;\n\t\t}\n\n\t\tevent.preventDefault();\n\t\tlet href = anchor.getAttribute(\"href\")?.trim();\n\t\tif (!href?.startsWith(\"/\")) {\n\t\t\thref = \"/\" + href;\n\t\t}\n\n\t\tthis.navigateTo(href);\n\t}\n\n\t/**\n\t * Add or remove search param to current url.\n\t * @param {HTMLInputElement} checkbox\n\t * @param {string} value\n\t * @returns void\n\t */\n\ttoggleParam(checkbox, value) {\n\t\tconst params = new URLSearchParams(location.search);\n\t\tconst name = checkbox.getAttribute(\"name\");\n\t\tif (!name) {\n\t\t\treturn console.warn(`name attribute is not set on ${checkbox.outerHTML}`);\n\t\t}\n\t\tif (checkbox.checked) {\n\t\t\t!params.has(name) && params.set(name, value);\n\t\t} else if (!checkbox.checked) {\n\t\t\tparams.has(name) && params.delete(name);\n\t\t}\n\n\t\tconst newUrl = !!params.size\n\t\t\t? `${location.pathname}?${params.toString()}`\n\t\t\t: location.pathname;\n\t\tthis.navigateTo(newUrl);\n\t}\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";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};
1
+ const t=t=>((t=t.trim()).endsWith("/")||(t+="/"),t);class e{#t="data-breeze-ignore";#e=!1;#r=[];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()}ready(t){"function"==typeof t&&this.#r.push(t)}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);this.currentPath=t(e),this.currentRoute=r,this.currentParams=a,await this._handleRoute({route:r,params:a}),this.#e||(this.#e=!0,this.#r.forEach((t=>t(this)))),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 i=t.split("/").slice(1),n=e.split("/").slice(1);return!!i.every(((t,e)=>{if(!t.startsWith(":"))return t===n[e];return"number"!==s[t.replace(":","")]||Number.isInteger(Number(n[e]))}))&&(i.forEach(((t,e)=>{if(t.startsWith(":")){const a=t.slice(1);r[a]=decodeURIComponent(n[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 * 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":"AAMO,MAiCMA,EAAoBC,KAC/BA,EAAMA,EAAIC,QACDC,SAAS,OAChBF,GAAY,KAGPA,GChCM,MAAMG,EAMnBC,GAAmB,qBAEnBC,aAAe,GAMfC,cAAgB,KAChBC,eAAiB,CAAA,EAEjBC,YAAc,GAMdC,aAAe,KACfC,cAAgB,CAAA,EAOhBC,YAAYC,GACV,MAAMR,gBAAEA,GAAoBQ,GAAU,GAElCR,IACFS,MAAKT,EAAmBA,GAQ1BS,KAAKC,QAAU,GAGfC,OAAOC,iBAAiB,WAAYH,KAAKI,WAAWC,KAAKL,OACzDM,SAASC,KAAKJ,iBAAiB,QAASH,KAAKQ,aAAaH,KAAKL,MAChE,CAMDS,QACET,KAAKI,YACN,CAQDM,IAAIC,EAAOC,GAMT,GAJc,OADdD,EAAQA,EAAMvB,UAEZuB,EAAQzB,EAAiByB,IAGvBX,KAAKC,QAAQU,GACf,OAAOE,QAAQC,KAAK,yBAAyBH,KAG/C,GAAuB,mBAAZC,EACT,OAAOC,QAAQE,MAAM,qBAAqBJ,0BAG5C,MAAMK,EAAQ,CAAA,EAGd,GAAIL,EAAMM,SAAS,KAAM,CAEHN,EACjBO,MAAM,KACNC,QAAQC,GAAYA,EAAQH,SAAS,OAG5BI,SAASD,IAEnB,MAAME,EAAOF,EACVG,MAAM,YAAY,GAClBC,QAAQ,IAAK,IACbA,QAAQ,IAAK,IAGVC,EAAWL,EAAQI,QAAQ,SAAU,IAAIA,QAAQ,IAAK,IAG5DR,EAAMS,GAAYH,CAAI,GAEzB,CAWD,OARAX,EAAQA,EAAMe,WAAW,UAAW,IAEpC1B,KAAKC,QAAQU,GAAS,CACpBgB,KAAMhB,EACNK,QACAJ,WAGKZ,IACR,CAOD4B,WAAWzC,GACTa,KAAKR,aAAeQ,KAAKL,YACzBK,KAAKP,cAAgBO,KAAKJ,aAC1BI,KAAKN,eAAiBM,KAAKH,cAC3BK,OAAO2B,QAAQC,UAAU,CAAE3C,OAAO,GAAIA,GACtCa,KAAKI,YACN,CAOD2B,SAAS5C,GACPa,KAAK4B,WAAWzC,EACjB,CAED6C,mBACE,MAAML,EAAOzB,OAAO+B,SAASC,UACvBvB,MAAEA,EAAKwB,OAAEA,GAAWnC,KAAKoC,iBAAiBT,SAI1C3B,KAAKqC,aAAa,CAAE1B,QAAOwB,WAEjCnC,KAAKL,YAAcT,EAAiByC,GACpC3B,KAAKJ,aAAee,EACpBX,KAAKH,cAAgBsC,EAErB7B,SAASgC,cACP,IAAIC,YAAY,uBAAwB,CACtCC,OAAQ,CACNhD,aAAcQ,KAAKR,aACnBC,cAAeO,KAAKP,cACpBC,eAAgBM,KAAKN,eACrBC,YAAaK,KAAKL,YAClBC,aAAcI,KAAKJ,aACnBC,cAAeG,KAAKH,cACpB4C,OAAQzC,QAIf,CAODgC,oBAAmBrB,MAAEA,EAAKwB,OAAEA,IDhLJ,IAACO,ICiLR/B,GAAOC,QDhLnB8B,GACwC,aAAtCA,EAAG5C,YAAY6C,KAAKC,eCgLvBjC,EAAMC,QAAQ,CAAED,QAAOwB,WDxKE,CAACO,KACzBA,GACwC,kBAAtCA,EAAG5C,YAAY6C,KAAKC,cCyKrBC,CAAgBlC,GAAOC,gBACnBD,EAAMC,QAAQ,CAAED,QAAOwB,UAEhC,CAODC,iBAAiBjD,GAEf,MAAMgD,EAAS,CAAA,EAEH,MAARhD,IACFA,EAAMD,EAAiBC,IAGzB,MAAM2D,EAAeC,OAAOC,QAAQhD,KAAKC,SAASgD,MAChD,EAAEtC,EAAOuC,MACP,GAAI/D,EAAI+B,MAAM,KAAKiC,SAAWxC,EAAMO,MAAM,KAAKiC,OAC7C,OAAO,EAGT,MAAMnC,MAAEA,GAAUkC,EAElB,IAAIE,EAAgBzC,EAAMO,MAAM,KAAKmC,MAAM,GACvCC,EAAcnE,EAAI+B,MAAM,KAAKmC,MAAM,GAoBvC,QAhBcD,EAAcG,OAAM,CAACnC,EAASoC,KAC1C,IAAKpC,EAAQqC,WAAW,KACtB,OAAOrC,IAAYkC,EAAYE,GAWjC,MAFW,WAJExC,EAAMI,EAAQI,QAAQ,IAAK,MAIlBkC,OAAOC,UAAUD,OAAOJ,EAAYE,IAE5C,MAQhBJ,EAAc/B,SAAQ,CAACD,EAASoC,KAC9B,GAAIpC,EAAQqC,WAAW,KAAM,CAC3B,MAAMhC,EAAWL,EAAQiC,MAAM,GAC/BlB,EAAOV,GAAYmC,mBAAmBN,EAAYE,GACnD,MAGI,EAAI,IAIf,OAAIV,EACK,CACLnC,MAAOX,KAAKC,QAAQ6C,EAAa,IACjCX,SACA0B,aAAc,IAAIC,gBAAgB5D,OAAO+B,SAAS8B,SAG7C,CACLpD,MAAOX,KAAKC,QAAQ,KACpBkC,SACA0B,aAAc,KAGnB,CAODrD,aAAawD,GACX,MAAMC,EAAoBD,EDzNnBE,eAAejB,MAAMkB,GACJ,MAAjBA,EAAKC,UCyNZ,IAAKH,EACH,OAOF,GAAIA,EAAOI,aAAarE,MAAKT,GAC3B,OAGF,ID3NmC,EAAC+E,EAAGL,KAEzC,GAAIK,EAAEC,iBACJ,OAAO,EAIT,GAAID,EAAEE,SAAWF,EAAEG,SAAWH,EAAEI,SAC9B,OAAO,EAGT,IAAKT,EACH,OAAO,EAGT,GAAIA,EAAOU,OACT,OAAO,EAGT,GACEV,EAAOI,aAAa,aACW,aAA/BJ,EAAOW,aAAa,OAEpB,OAAO,EAGT,MAAMC,EAAOZ,EAAOY,KACpB,SAAKA,GAAQA,EAAKpB,WAAW,aAKxBoB,EAAKpB,WAAWxB,SAAS6C,QAInB,ECuLJC,CAAwBf,EAAOC,GAClC,OAGFD,EAAMgB,iBACN,IAAIH,EAAOZ,EAAOW,aAAa,SAASxF,OACnCyF,GAAMpB,WAAW,OACpBoB,EAAO,IAAMA,GAGf7E,KAAK4B,WAAWiD,EACjB,CAQDI,YAAYC,EAAUC,GACpB,MAAMhD,EAAS,IAAI2B,gBAAgB7B,SAAS8B,QACtCpB,EAAOuC,EAASN,aAAa,QACnC,IAAKjC,EACH,OAAO9B,QAAQC,KAAK,gCAAgCoE,EAASE,aAE3DF,EAASG,SACVlD,EAAOmD,IAAI3C,IAASR,EAAOoD,IAAI5C,EAAMwC,GAC5BD,EAASG,SACnBlD,EAAOmD,IAAI3C,IAASR,EAAOqD,OAAO7C,GAGpC,MAAM8C,EAAWtD,EAAOuD,KACpB,GAAGzD,SAASC,YAAYC,EAAOwD,aAC/B1D,SAASC,SACblC,KAAK4B,WAAW6D,EACjB"}
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\tisFunction,\n\tisAsyncFunction,\n\tremoveTrailingSlash,\n\taddTrailingSlash,\n\tfindAnchor,\n\tshouldRouterHandleClick,\n} from \"./utils.js\";\n\n/**\n * Class representing a router.\n */\nexport default class BreezeRouter {\n\t/**\n\t * `data-` attribute to allow users to skip the router behavior,\n\t * anchor link with this attribute set will behavior as normal link.\n\t * @type {string}\n\t */\n\t#ignoreAttribute = \"data-breeze-ignore\";\n\t#isReady = false;\n\t#isReadyCallbacks = [];\n\n\tpreviousPath = \"\";\n\t/**\n\t * The previous route that was navigated to.\n\t *\n\t * @type {import('./types.js').Route|null}\n\t */\n\tpreviousRoute = null;\n\tpreviousParams = {};\n\n\tcurrentPath = \"\";\n\t/**\n\t * The current route that we're on.\n\t *\n\t * @type {import('./types.js').Route|null}\n\t */\n\tcurrentRoute = null;\n\tcurrentParams = {};\n\n\t/**\n\t * Creates a new BreezeRouter instance.\n\t * @param {object} config - Config.\n\t * @constructor\n\t */\n\tconstructor(config) {\n\t\tconst { ignoreAttribute } = config || {};\n\n\t\tif (ignoreAttribute) {\n\t\t\tthis.#ignoreAttribute = ignoreAttribute;\n\t\t}\n\n\t\t/**\n\t\t * Object containing all registered routes.\n\t\t * @type {object}\n\t\t * @private\n\t\t */\n\t\tthis._routes = {};\n\n\t\t// Bind event listeners\n\t\twindow.addEventListener(\"popstate\", this._onChanged.bind(this));\n\t\tdocument.body.addEventListener(\"click\", this._handleClick.bind(this));\n\t}\n\n\t/**\n\t * Starts the router.\n\t * @returns {void}\n\t */\n\tstart() {\n\t\tthis._onChanged();\n\t}\n\n\t/**\n\t * Register the callback when the router is ready.\n\t * \n\t * @param {Function} fn - Callback function.\n\t */\n\tready(fn) {\n\t\tif (typeof fn === 'function') {\n\t\t\tthis.#isReadyCallbacks.push(fn);\n\t\t}\n\t}\n\n\t/**\n\t * Adds a new route to the router.\n\t * @param {string} route - The route path to add.\n\t * @param {Function} handler - The async function to handle the route\n\t * @returns {BreezeRouter|void} The BreezeRouter instance.\n\t */\n\tadd(route, handler) {\n\t\troute = route.trim();\n\t\tif (route !== \"/\") {\n\t\t\troute = addTrailingSlash(route);\n\t\t}\n\n\t\tif (this._routes[route]) {\n\t\t\treturn console.warn(`Route already exists: ${route}`);\n\t\t}\n\n\t\tif (typeof handler !== \"function\") {\n\t\t\treturn console.error(`handler of route '${route}' must be a function.`);\n\t\t}\n\n\t\tconst types = {};\n\n\t\t// Route has types defined.\n\t\tif (route.includes(\"<\")) {\n\t\t\t// Split the URL segments by '/'.\n\t\t\tconst urlSegments = route\n\t\t\t\t.split(\"/\")\n\t\t\t\t.filter((segment) => segment.includes(\"<\"));\n\n\t\t\t// Loop through each segment and get its type.\n\t\t\turlSegments.forEach((segment) => {\n\t\t\t\t// Get the type.\n\t\t\t\tconst type = segment\n\t\t\t\t\t.match(/\\<.+\\>/)?.[0]\n\t\t\t\t\t.replace(\"<\", \"\")\n\t\t\t\t\t.replace(\">\", \"\");\n\n\t\t\t\t// Get the prop name.\n\t\t\t\tconst propName = segment.replace(/\\<.+\\>/, \"\").replace(\":\", \"\");\n\n\t\t\t\t// Store the prop name and type.\n\t\t\t\ttypes[propName] = type;\n\t\t\t});\n\t\t}\n\n\t\t// Remove all '<type>' definitions.\n\t\troute = route.replaceAll(/\\<.+\\>/g, \"\");\n\n\t\tthis._routes[route] = {\n\t\t\tpath: route,\n\t\t\ttypes,\n\t\t\thandler,\n\t\t};\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Navigates to the specified URL.\n\t * @param {string} url - The URL to navigate to\n\t * @returns {void}\n\t */\n\tnavigateTo(url) {\n\t\tthis.previousPath = this.currentPath;\n\t\tthis.previousRoute = this.currentRoute;\n\t\tthis.previousParams = this.currentParams;\n\t\twindow.history.pushState({ url }, \"\", url);\n\t\tthis._onChanged();\n\t}\n\n\t/**\n\t * Redirects a URL\n\t * @param {string} url\n\t * @returns {void}\n\t */\n\tredirect(url) {\n\t\tthis.navigateTo(url);\n\t}\n\n\tasync _onChanged() {\n\t\tconst path = window.location.pathname;\n\t\tconst { route, params } = this._matchUrlToRoute(path);\n\n\t\tthis.currentPath = addTrailingSlash(path);\n\t\tthis.currentRoute = route;\n\t\tthis.currentParams = params;\n\n\t\t// If no matching route found, route will be '404' route\n\t\t// which has been handled by _matchUrlToRoute already.\n\t\tawait this._handleRoute({ route, params });\n\n\t\tif (!this.#isReady) {\n\t\t\tthis.#isReady = true;\n\t\t\tthis.#isReadyCallbacks.forEach(fn => fn(this));\n\t\t}\n\n\t\tdocument.dispatchEvent(\n\t\t\tnew CustomEvent(\"breeze-route-changed\", {\n\t\t\t\tdetail: {\n\t\t\t\t\tpreviousPath: this.previousPath,\n\t\t\t\t\tpreviousRoute: this.previousRoute,\n\t\t\t\t\tpreviousParams: this.previousParams,\n\t\t\t\t\tcurrentPath: this.currentPath,\n\t\t\t\t\tcurrentRoute: this.currentRoute,\n\t\t\t\t\tcurrentParams: this.currentParams,\n\t\t\t\t\trouter: this,\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t}\n\n\t/**\n\t * Processes route callbacks registered by app\n\t * @param {import('./types.js').MatchedRoute} options\n\t * @returns {Promise<void>}\n\t */\n\tasync _handleRoute({ route, params }) {\n\t\tif (isFunction(route?.handler)) {\n\t\t\troute.handler({ route, params });\n\t\t}\n\n\t\tif (isAsyncFunction(route?.handler)) {\n\t\t\tawait route.handler({ route, params });\n\t\t}\n\t}\n\n\t/**\n\t *\n\t * @param {string} url - Current url users visite or nagivate to.\n\t * @returns {import('./types.js').MatchedRoute}\n\t */\n\t_matchUrlToRoute(url) {\n\t\t/** @type {import('./types.js').RouteParams} */\n\t\tconst params = {};\n\n\t\tif (url !== \"/\") {\n\t\t\turl = addTrailingSlash(url);\n\t\t}\n\n\t\tconst matchedRoute = Object.entries(this._routes).find(\n\t\t\t([route, routeObject]) => {\n\t\t\t\tif (url.split(\"/\").length !== route.split(\"/\").length) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tconst { types } = routeObject;\n\n\t\t\t\tlet routeSegments = route.split(\"/\").slice(1);\n\t\t\t\tlet urlSegments = url.split(\"/\").slice(1);\n\n\t\t\t\t// If each segment in the url matches the corresponding segment in the route path,\n\t\t\t\t// or the route path segment starts with a ':' then the route is matched.\n\t\t\t\tconst match = routeSegments.every((segment, i) => {\n\t\t\t\t\tif (!segment.startsWith(\":\")) {\n\t\t\t\t\t\treturn segment === urlSegments[i];\n\t\t\t\t\t}\n\n\t\t\t\t\t// /product/:id => segment/segment\n\t\t\t\t\t// /product/123 => urlSegment/urlSegment\n\t\t\t\t\tconst type = types[segment.replace(\":\", \"\")];\n\n\t\t\t\t\t// const segmentType =\n\t\t\t\t\tconst isValid =\n\t\t\t\t\t\ttype === \"number\" ? Number.isInteger(Number(urlSegments[i])) : true;\n\n\t\t\t\t\treturn isValid;\n\t\t\t\t});\n\n\t\t\t\tif (!match) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// If the route matches the URL, pull out any params from the URL.\n\t\t\t\trouteSegments.forEach((segment, i) => {\n\t\t\t\t\tif (segment.startsWith(\":\")) {\n\t\t\t\t\t\tconst propName = segment.slice(1);\n\t\t\t\t\t\tparams[propName] = decodeURIComponent(urlSegments[i]);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn true;\n\t\t\t},\n\t\t);\n\n\t\tif (matchedRoute) {\n\t\t\treturn {\n\t\t\t\troute: this._routes[matchedRoute[0]],\n\t\t\t\tparams,\n\t\t\t\tsearchParams: new URLSearchParams(window.location.search),\n\t\t\t};\n\t\t} else {\n\t\t\treturn {\n\t\t\t\troute: this._routes[404],\n\t\t\t\tparams,\n\t\t\t\tsearchParams: null,\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Handles <a> link clicks\n\t * @param {Event} event\n\t * @returns {void}\n\t */\n\t_handleClick(event) {\n\t\tconst anchor = findAnchor(event);\n\t\tif (!anchor) {\n\t\t\treturn;\n\t\t}\n\n\t\t/**\n\t\t * Allow users to define an `data-` attribute to skip the route handling.\n\t\t * Default to `data-breeze-ignore`.\n\t\t */\n\t\tif (anchor.hasAttribute(this.#ignoreAttribute)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!shouldRouterHandleClick(event, anchor)) {\n\t\t\treturn;\n\t\t}\n\n\t\tevent.preventDefault();\n\t\tlet href = anchor.getAttribute(\"href\")?.trim();\n\t\tif (!href?.startsWith(\"/\")) {\n\t\t\thref = \"/\" + href;\n\t\t}\n\n\t\tthis.navigateTo(href);\n\t}\n\n\t/**\n\t * Add or remove search param to current url.\n\t * @param {HTMLInputElement} checkbox\n\t * @param {string} value\n\t * @returns void\n\t */\n\ttoggleParam(checkbox, value) {\n\t\tconst params = new URLSearchParams(location.search);\n\t\tconst name = checkbox.getAttribute(\"name\");\n\t\tif (!name) {\n\t\t\treturn console.warn(`name attribute is not set on ${checkbox.outerHTML}`);\n\t\t}\n\t\tif (checkbox.checked) {\n\t\t\t!params.has(name) && params.set(name, value);\n\t\t} else if (!checkbox.checked) {\n\t\t\tparams.has(name) && params.delete(name);\n\t\t}\n\n\t\tconst newUrl = !!params.size\n\t\t\t? `${location.pathname}?${params.toString()}`\n\t\t\t: location.pathname;\n\t\tthis.navigateTo(newUrl);\n\t}\n}\n"],"names":["addTrailingSlash","url","trim","endsWith","BreezeRouter","ignoreAttribute","isReady","isReadyCallbacks","previousPath","previousRoute","previousParams","currentPath","currentRoute","currentParams","constructor","config","this","_routes","window","addEventListener","_onChanged","bind","document","body","_handleClick","start","ready","fn","push","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","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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "breeze-router",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A lightweight, zero-dependency client-side router for single page applications (SPAs).",
5
5
  "main": "index.js",
6
6
  "type": "module",