jong-router 0.1.13 → 0.1.15

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
@@ -1,30 +1,36 @@
1
- # JongRouter
1
+ [![NPM](https://nodei.co/npm/jong-router.svg?style=flat&data=v,d&color=red)](https://nodei.co/npm/jong-router/)
2
2
 
3
+ # JongRouter
3
4
 
4
5
 
5
- A lightweight and simple-to-use web components router in Vanilla JavaScript with support for guards, nested routes, page not found, passing query parameters to components, passing route parameters to components, passing route data to components, and a router link for single-page application navigation without reloading the page.
6
6
 
7
+ JongRouter is a lightweight client-side router for Web Components, built with Vanilla JavaScript.
8
+ It enables Single Page Application (SPA) navigation without page reloads while staying framework-agnostic.
9
+ Designed for developers who want a simple router for Web Components without bringing in a heavy framework.
7
10
 
8
11
 
9
12
  ## Features
10
13
 
11
-
12
-
13
- - **Routing**: Define routes and associate them with components.
14
-
15
- - **Guards**: Implement route guards to control navigation based on conditions. ([Example](https://github.com/josnin/jong-router/tree/main/samples/guards))
16
-
17
- - **Nested Routes**: Create hierarchical routes for nested components. ([Example](https://github.com/josnin/jong-router/tree/main/samples/nestedroutes))
18
-
19
- - **Page Not Found**: Handle routes that do not match any defined route. ([Example](https://github.com/josnin/jong-router/tree/main/samples/page-not-found))
20
-
21
- - **Query Parameters**: Pass query parameters to components. ([Example](https://github.com/josnin/jong-router/tree/main/samples/query-params))
22
-
23
- - **Route Parameters**: Extract and pass route parameters to components. ([Example](https://github.com/josnin/jong-router/tree/main/samples/route-params))
24
-
25
- - **Route Data**: Include additional data associated with each route. ([Example](https://github.com/josnin/jong-router/tree/main/samples/route-data))
26
-
27
- - **Router Link**: Use attribute `router-link` to navigate without reloading the page. ([Example](https://github.com/josnin/jong-router/tree/main/samples/router-link))
14
+ * SPA Navigation
15
+ Navigate without reloading the page using the `router-link` attribute.
16
+ * Route Guards
17
+ Protect routes with custom guard logic before navigation.
18
+ * Nested Routing
19
+ Easily compose child routers inside Web Components.
20
+ * Shadow DOM Support
21
+ Works seamlessly with Web Components using Shadow DOM.
22
+ * Route Parameters
23
+ Dynamic routes like `/profile/:username`.
24
+ * Query Parameters
25
+ Access query strings like `/profile/jong?tab=settings`.
26
+ * Route Data
27
+ Pass static metadata to components.
28
+ * Programmatic Navigation
29
+ Navigate using `router.navigateTo()`.
30
+ * 404 Page Handling
31
+ Built-in fallback route using **.
32
+ * Buttons & Links Support
33
+ Works with both <a> and <button> elements.
28
34
 
29
35
 
30
36
 
@@ -71,19 +77,23 @@ npm i jong-router
71
77
 
72
78
  ```javascript
73
79
 
74
- const router = new JongRouter([
80
+ const routes = [
75
81
 
76
82
  { pattern: '/', component: import('./components/HomeComponent') },
77
83
 
78
84
  { pattern: '/about', component: import('./components/AboutComponent') },
79
85
 
80
- // Add more routes as needed
86
+ { pattern: '**', html: `<h2>Page not found</h2>` }
81
87
 
82
- ], document.getElementById('app') );
88
+ ]
83
89
 
90
+ const router = new JongRouter(
91
+ routes,
92
+ document.getElementById('app')
93
+ )
84
94
 
95
+ router.init()
85
96
 
86
- router.init();
87
97
 
88
98
  ```
89
99
 
@@ -99,22 +109,19 @@ Create your web components for each route.
99
109
 
100
110
  ```javascript
101
111
 
102
- // Example: HomeComponent.js
103
-
104
112
  class HomeComponent extends HTMLElement {
105
113
 
106
114
  connectedCallback() {
107
115
 
108
- this.innerHTML = '<h1>Home Component</h1>';
116
+ this.innerHTML = `<h1>Home Page</h1>`
109
117
 
110
118
  }
111
119
 
112
120
  }
113
121
 
122
+ customElements.define('home-component', HomeComponent)
114
123
 
115
124
 
116
- customElements.define('home-component', HomeComponent);
117
-
118
125
  ```
119
126
 
120
127
 
@@ -137,9 +144,37 @@ Use the `router-link` attribute to create navigation links.
137
144
 
138
145
  ```
139
146
 
147
+ Buttons also work with router-link
148
+ ```html
149
+ <button router-link href="/about">
150
+ Go to About
151
+ </button>
152
+ ```
153
+
154
+ 4. Programmatic Navigation
155
+ Components can navigate programmatically.
156
+
157
+ Example outside component
158
+
159
+ ```js
160
+ import { router } from "../router-instance"
161
+
162
+ router.navigateTo("/profile/admin")
163
+ ```
140
164
 
165
+ Example inside a component
166
+ ```js
167
+ this.shadowRoot
168
+ .getElementById("btn")
169
+ .addEventListener("click", () => {
170
+
171
+ this.navigateTo("/profile/admin")
172
+
173
+ })
174
+ ```
141
175
 
142
- 4. **Guards**
176
+
177
+ 5. **Guards**
143
178
 
144
179
 
145
180
 
@@ -147,7 +182,7 @@ Implement guards for route conditions
147
182
 
148
183
 
149
184
 
150
- ```javascript
185
+ ```js
151
186
 
152
187
  const router = new JongRouter([
153
188
 
@@ -170,9 +205,11 @@ const router = new JongRouter([
170
205
 
171
206
 
172
207
 
173
- function isAuthenticated() {
208
+ function isAuthenticated(ctx) {
174
209
 
175
210
  // Your authentication logic here
211
+ console.log(ctx.path)
212
+ console.log(ctx.params)
176
213
 
177
214
  return true;
178
215
 
@@ -182,44 +219,146 @@ function isAuthenticated() {
182
219
 
183
220
 
184
221
 
185
- 5. **Handle Route Parameters and Query Parameters:**
222
+ 6. **Handle Route Parameters and Query Parameters:**
186
223
 
187
224
 
225
+ Define dynamic routes Parameter:
226
+ ```js
227
+ {
228
+ pattern: "/profile/:username",
229
+ component: import("./ProfileComponent")
230
+ }
231
+ ```
188
232
 
189
- Access route parameters and query parameters in your components.
233
+ Access inside the component
234
+ ```js
235
+ const params = JSON.parse(
236
+ this.getAttribute("route-params")
237
+ )
190
238
 
239
+ console.log(params.username)
240
+ ```
191
241
 
192
242
 
193
- ```javascript
243
+ Define query parameter
244
+ ```js
245
+ /profile/jong?tab=settings
246
+ ```
194
247
 
195
- // Example: UserComponent.js
248
+ Inside component
249
+ ```js
250
+ const query = JSON.parse(
251
+ this.getAttribute("query-params")
252
+ )
196
253
 
197
- class UserComponent extends HTMLElement {
254
+ console.log(query.tab)
255
+ ```
198
256
 
199
- connectedCallback() {
257
+ 7. **Route Data**
258
+ Attach metadata to routes
200
259
 
201
- const routeParams = JSON.parse(this.getAttribute('route-params'));
260
+ ```js
261
+ {
262
+ pattern: "/profile/:username",
263
+ component: import("./ProfileComponent"),
264
+ data: { role: "admin" }
265
+ }
266
+ ```
267
+ Access in component
268
+ ```js
269
+ const data = JSON.parse(
270
+ this.getAttribute("route-data")
271
+ )
272
+ ```
202
273
 
203
- const queryParams = JSON.parse(this.getAttribute('query-params'));
204
274
 
275
+ 8. **Nested Routes & Sub-Outlest**
205
276
 
277
+ JongRouter supports hierarchical routing. This allows you to render a "Shell" or "Layout" component and then inject sub-pages into it dynamically.
206
278
 
207
- this.innerHTML = `<h1>User Details</h1>
279
+ Step 1: Define Child Routes
280
+ Create a separate file for your sub-navigation.
208
281
 
209
- <p>User ID: ${routeParams.id}</p>
282
+ Example
283
+ ```ts
284
+ // samples/nested/nested-routes.ts
285
+ import { IRoute } from "../../src/jong-router"
210
286
 
211
- <p>Query Param: ${queryParams.example}</p>`;
287
+ const routes: IRoute[] = [
288
+ { pattern: "/nested/c1", html: "<p>Child Page 1</p>" },
289
+ { pattern: "/nested/c2", html: "<p>Child Page 2</p>" },
290
+ { pattern: "/nested/c3", html: "<p>Child Page 3</p>" }
291
+ ]
212
292
 
213
- }
293
+ export default routes
214
294
 
215
- }
295
+ ```
216
296
 
297
+ Step 2: Prepare the Parent Component
298
+ Your parent component must contain an element with the router-outlet attribute. This is where the child routes will be rendered.
299
+
300
+ A parent component can create its own router instance w children routes
301
+ ```ts
302
+ // samples/nested/sample-nested.ts
303
+ export default class SampleNested extends HTMLElement {
304
+ connectedCallback() {
305
+ this.innerHTML = `
306
+ <div class="layout">
307
+ <nav>
308
+ <a href="/nested/c1" router-link>Go to C1</a>
309
+ <a href="/nested/c2" router-link>Go to C2</a>
310
+ </nav>
311
+ <!-- Child routes will appear here -->
312
+ <div router-outlet></div>
313
+ </div>
314
+ `;
315
+ }
316
+ }
217
317
 
318
+ ```
218
319
 
219
- customElements.define('user-component', UserComponent);
320
+ Step 3: Register Nested Routes
321
+ Pass the children to the parent route. You can use a static array or a function for Lazy Loading.
322
+ ```ts
323
+ const routes: IRoute[] = [
324
+ {
325
+ pattern: '/nested',
326
+ component: import('./samples/nested/sample-nested'),
327
+ // Lazy-load the child route definitions
328
+ children: () => import("./samples/nested/nested-routes")
329
+ }
330
+ ]
220
331
 
221
332
  ```
222
333
 
334
+
335
+ ## Playground & Examples
336
+ Playground & Examples
337
+ The repository contains a Playground demonstrating:
338
+ * Basic routing
339
+ * Guarded routes
340
+ * Nested routes
341
+ * Query parameters
342
+ * Route parameters
343
+ * Programmatic navigation
344
+ * Buttons & router-link navigation
345
+
346
+
347
+ # Why JongRouter?
348
+ JongRouter focuses on simplicity and Web Component compatibility.
349
+ Unlike many routers, it:
350
+ * Works without frameworks
351
+ * Supports Shadow DOM
352
+ * Keeps the API extremely small
353
+ * Is easy to embed in micro-frontend architectures
354
+
355
+ Perfect for:
356
+ * Web Component apps
357
+ * Micro-frontends
358
+ * Lightweight SPAs
359
+ * Vanilla JS projects
360
+
361
+
223
362
  ## How to run development server?
224
363
  ```
225
364
  git clone git@github.com:josnin/jong-router.git
@@ -0,0 +1,4 @@
1
+ export declare const attachShadow: ShadowRootInit;
2
+ export default class About extends HTMLElement {
3
+ constructor();
4
+ }
@@ -0,0 +1,2 @@
1
+ import { GuardContext } from './jong-router';
2
+ export declare function authencationGuard(ctx?: GuardContext): boolean;
@@ -0,0 +1,4 @@
1
+ export declare const attachShadow: ShadowRootInit;
2
+ export default class Home extends HTMLElement {
3
+ constructor();
4
+ }
@@ -1,26 +1,57 @@
1
- interface IRoute {
1
+ /**
2
+ * Context passed to Route Guards to decide if a user can access a route.
3
+ */
4
+ export interface GuardContext {
5
+ path?: string;
6
+ params?: Record<string, string>;
7
+ query?: URLSearchParams;
8
+ data?: any;
9
+ }
10
+ /**
11
+ * Definition of a single Route.
12
+ */
13
+ export interface IRoute {
2
14
  pattern: string;
3
15
  component?: Promise<any>;
4
16
  html?: string;
5
- guards?: (() => boolean)[];
17
+ children?: IRoute[] | (() => Promise<{
18
+ default: IRoute[];
19
+ }>);
20
+ guards?: ((ctx?: any) => boolean)[];
6
21
  redirect?: string;
7
- data?: any;
22
+ data?: Record<string, any>;
8
23
  }
9
24
  declare class JongRouter {
10
25
  private routes;
11
26
  private outlet;
12
- private shadowRoot1;
13
- constructor(routes: IRoute[], outlet: HTMLElement, shadowRoot1?: ShadowRoot | undefined);
27
+ constructor(routes: IRoute[], outlet: HTMLElement);
28
+ /**
29
+ * Starts the router by listening to browser events and initial navigation.
30
+ */
14
31
  init(): void;
15
- private setupNavigation;
16
- private navigate;
17
- private loadContent;
18
- private loadComponent;
32
+ /**
33
+ * Intercepts clicks to handle internal navigation without refreshing the page.
34
+ */
19
35
  private handleClick;
36
+ /**
37
+ * Matches the current browser URL against the route table and triggers rendering.
38
+ */
39
+ private navigate;
40
+ /**
41
+ * Handles Guards, Component instantiation, and Nested routes.
42
+ */
43
+ private renderRoute;
44
+ /**
45
+ * Basic string matching. Supports ':param' wildcards and '**' catch-all.
46
+ */
20
47
  private matchRoute;
21
- private extractQueryParams;
48
+ /**
49
+ * Maps URL values to parameter names based on the pattern.
50
+ */
22
51
  private extractRouteParams;
52
+ /**
53
+ * Programmatic navigation (e.g., router.navigateTo('/dashboard'))
54
+ */
23
55
  navigateTo(route: string): void;
24
56
  }
25
- export { IRoute };
26
57
  export default JongRouter;
@@ -1,2 +1,2 @@
1
- var a=class{routes;outlet;shadowRoot1;constructor(e,n,t){this.routes=e,this.outlet=n,this.shadowRoot1=t}init(){this.setupNavigation(),this.navigate()}setupNavigation(){window.addEventListener("popstate",()=>this.navigate()),document.addEventListener("click",e=>this.handleClick(e))}navigate(){let e=window.location.pathname,n=this.routes.find(o=>this.matchRoute(o.pattern,e));if(n){if(n.guards&&n.guards.every(s=>{let i=s.bind(this)();return i||(n.redirect?this.navigateTo(n.redirect):console.warn("Guard prevented navigation, and no redirect route specified!")),i===!0})===!1)return;n.component?this.loadComponent(n.component,this.extractRouteParams(n.pattern,e),n.data):n.html?this.loadContent(n.html):console.warn("no component or html route specified!");return}let t=this.routes.find(o=>o.pattern==="**");t&&(t.component?this.loadComponent(t.component,this.extractRouteParams(t.pattern,e),t.data):t.html&&this.loadContent(t.html))}loadContent(e){this.outlet.innerHTML=e}async loadComponent(e,n,t){e.then(o=>{let s=o.default,i=new s,r=this.extractQueryParams();n&&i.setAttribute("route-params",JSON.stringify(n)),t&&i.setAttribute("route-data",JSON.stringify(t)),r&&i.setAttribute("query-params",JSON.stringify(r)),i.router=this,this.outlet.innerHTML="",this.outlet.appendChild(i)}).catch(o=>{console.error(`Error loading component: ${o}`),this.outlet.innerHTML="Component not found"})}handleClick(e){let t=e.composedPath().includes(this.shadowRoot1)?e.composedPath()[0]:e.target;if((t instanceof HTMLAnchorElement||t instanceof HTMLButtonElement)&&t.hasAttribute("router-link")){e.preventDefault();let o=t.getAttribute("href");window.history.pushState({},"",o),this.navigate()}}matchRoute(e,n){let t=e.split("/").filter(s=>s!==""),o=n.split("/").filter(s=>s!=="");return t.length===o.length&&t.every((s,i)=>s.startsWith(":")||s===o[i])}extractQueryParams(){let e=window.location.search,n=new URLSearchParams(e),t={};return n.forEach((o,s)=>{t[s]=o}),t}extractRouteParams(e,n){return e.split("/").reduce((t,o,s)=>(o.startsWith(":")&&(t[o.slice(1)]=n.split("/")[s]),t),{})}navigateTo(e){window.history.pushState({},"",e),this.navigate()}},c=a;export{c as default};
1
+ var l=class{routes;outlet;constructor(t,i){this.routes=t,this.outlet=i}init(){window.addEventListener("popstate",()=>this.navigate()),document.addEventListener("click",t=>this.handleClick(t)),this.navigate()}handleClick(t){let a=t.composedPath().find(n=>(n instanceof HTMLAnchorElement||n instanceof HTMLButtonElement)&&n.hasAttribute("router-link"));if(!a)return;let e=a.getAttribute("href");!e||e.startsWith("http")||t instanceof MouseEvent&&(t.metaKey||t.ctrlKey||t.shiftKey||t.altKey)||(t.preventDefault(),this.navigateTo(e))}navigate(){let t=window.location.pathname,i=this.routes.sort((e,n)=>n.pattern.length-e.pattern.length).find(e=>this.matchRoute(e.pattern,t));if(!i)return;let a=this.extractRouteParams(i.pattern,t);this.renderRoute(i,a)}async renderRoute(t,i,a=this.outlet){if(t.guards){let n={path:window.location.pathname,params:i,query:new URLSearchParams(window.location.search),data:t.data};if(!t.guards.every(o=>o(n))){t.redirect&&this.navigateTo(t.redirect);return}}let e=null;if(t.component){let r=(await t.component).default;e=new r,e.setAttribute("route-params",JSON.stringify(i)),e.router=this,a.innerHTML="",a.appendChild(e)}else t.html&&(a.innerHTML=t.html);if(t.children){let n;typeof t.children=="function"?n=(await t.children()).default:n=t.children;let r=e?.shadowRoot?.querySelector("[router-outlet]")||e?.querySelector("[router-outlet]")||a,o=window.location.pathname,c=n.sort((s,h)=>h.pattern.length-s.pattern.length).find(s=>this.matchRoute(s.pattern,o));if(c){let s=this.extractRouteParams(c.pattern,o);await this.renderRoute(c,s,r)}}}matchRoute(t,i){if(t==="**")return!0;let a=t.split("/").filter(Boolean),e=i.split("/").filter(Boolean);return a.length>e.length?!1:a.every((n,r)=>n.startsWith(":")||n===e[r])}extractRouteParams(t,i){let a={},e=t.split("/").filter(Boolean),n=i.split("/").filter(Boolean);return e.forEach((r,o)=>{r.startsWith(":")&&(a[r.slice(1)]=n[o])}),a}navigateTo(t){window.history.pushState({},"",t),this.navigate()}},u=l;export{u as default};
2
2
  //# sourceMappingURL=jong-router.min.js.map
@@ -0,0 +1,4 @@
1
+ export declare const attachShadow: ShadowRootInit;
2
+ export default class NotFound extends HTMLElement {
3
+ constructor();
4
+ }
@@ -0,0 +1,6 @@
1
+ export declare const attachShadow: ShadowRootInit;
2
+ export default class Profile extends HTMLElement {
3
+ router: any;
4
+ constructor();
5
+ connectedCallback(): void;
6
+ }
@@ -0,0 +1,5 @@
1
+ export declare const attachShadow: ShadowRootInit;
2
+ export default class Team extends HTMLElement {
3
+ constructor();
4
+ connectedCallback(): void;
5
+ }
package/package.json CHANGED
@@ -1,11 +1,25 @@
1
1
  {
2
2
  "name": "jong-router",
3
- "version": "0.1.13",
4
- "description": "A lightweight and simple-to-use web components router in Vanilla JavaScript with support for guards, nested routes, page not found, passing query parameters to components, passing route parameters to components, passing route data to components, and a router link for single-page application navigation without reloading the page.",
3
+ "version": "0.1.15",
4
+ "description": "JongRouter is a lightweight client-side router for Web Components, built with Vanilla JavaScript. It enables Single Page Application (SPA) navigation without page reloads while staying framework-agnostic.Designed for developers who want a simple router for Web Components without bringing in a heavy framework",
5
5
  "keywords": [
6
+ "redgin",
7
+ "ScalpelJS",
6
8
  "jong-router",
9
+ "web-components",
7
10
  "web components router",
8
- "custom elements"
11
+ "custom elements",
12
+ "vanilla javascript",
13
+ "spa",
14
+ "single-page application",
15
+ "client-side router",
16
+ "frontend router",
17
+ "nested routes",
18
+ "route guards",
19
+ "query parameters",
20
+ "route parameters",
21
+ "javascript router",
22
+ "microfrontend"
9
23
  ],
10
24
  "main": "dist/jong-router.min.js",
11
25
  "types": "dist/jong-router.d.ts",
@@ -0,0 +1,27 @@
1
+ // index.ts
2
+ import JongRouter, { IRoute } from './src/jong-router'
3
+ import { authencationGuard } from './samples/guards/guard'
4
+
5
+ const routes: IRoute[] = [
6
+ { pattern: '/about', html: `<h2>About Page</h2>` },
7
+ { pattern: '/profile/:username', component: import('./samples/profile/profile-component') },
8
+ { pattern: '/tryguard1/:teamId', component: import('./samples/guards/team-component'), guards: [authencationGuard], redirect: '/login' },
9
+ { pattern: '/login', html: `<h2>Login Page</h2>` },
10
+ {
11
+ pattern: "/programmatic",
12
+ component: import("./samples/programmatic/programmatic-navigation"),
13
+ },
14
+
15
+ {
16
+ pattern: '/nested',
17
+ component: import('./samples/nested/sample-nested'),
18
+ children: () => import("./samples/nested/nested-routes")
19
+ },
20
+ { pattern: '**', html: `<h2>404 Page</h2>` }
21
+ ]
22
+
23
+ export const router = new JongRouter(routes, document.getElementById('app')!)
24
+ router.init()
25
+
26
+
27
+