@sondor-technology/sondor-components 1.0.1-develop.5 → 1.0.1-develop.6
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.
|
@@ -1,7 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export interface RouteDefinition {
|
|
2
|
+
component: string;
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
}
|
|
5
|
+
export type RouteConfig = Record<string, RouteDefinition>;
|
|
6
|
+
export interface RouteMatch {
|
|
7
|
+
path: string;
|
|
8
|
+
component: string;
|
|
9
|
+
params: Record<string, string>;
|
|
10
|
+
definition: RouteDefinition;
|
|
11
|
+
}
|
|
12
|
+
export declare class AppRouter {
|
|
13
|
+
private _routes;
|
|
14
|
+
private _initialized;
|
|
15
|
+
constructor(routes?: RouteConfig);
|
|
16
|
+
/**
|
|
17
|
+
* Register or update the route configuration for this application.
|
|
18
|
+
*/
|
|
19
|
+
setRoutes(routes: RouteConfig): void;
|
|
20
|
+
/**
|
|
21
|
+
* Programmatically navigate to an internal path.
|
|
22
|
+
*/
|
|
23
|
+
navigate(path: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Matches a URL path against the registered routes.
|
|
26
|
+
*/
|
|
27
|
+
matchRoute(currentPath: string): RouteMatch | null;
|
|
3
28
|
private _notify;
|
|
4
29
|
}
|
|
5
30
|
export declare const router: AppRouter;
|
|
6
|
-
export {};
|
|
7
31
|
//# sourceMappingURL=router.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../src/components/router/router.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../src/components/router/router.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAE1D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,UAAU,EAAE,eAAe,CAAC;CAC7B;AASD,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,YAAY,CAAS;IAE7B,YAAY,MAAM,CAAC,EAAE,WAAW,EA+B/B;IAED;;OAEG;IACI,SAAS,CAAC,MAAM,EAAE,WAAW,QAUnC;IAED;;OAEG;IACI,QAAQ,CAAC,IAAI,EAAE,MAAM,QAG3B;IAED;;OAEG;IACI,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CA+BxD;IAED,OAAO,CAAC,OAAO;CAahB;AAGD,eAAO,MAAM,MAAM,WAAkB,CAAC"}
|
|
@@ -1,37 +1,97 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
function getRouteWeight(pattern) {
|
|
2
|
+
const segments = pattern.split('/').filter(Boolean);
|
|
3
|
+
return segments.reduce((weight, seg) => {
|
|
4
|
+
return weight + (seg.startsWith(':') ? 1 : 10);
|
|
5
|
+
}, 0);
|
|
6
|
+
}
|
|
7
|
+
export class AppRouter {
|
|
8
|
+
constructor(routes) {
|
|
9
|
+
this._routes = {};
|
|
10
|
+
this._initialized = false;
|
|
11
|
+
if (routes) {
|
|
12
|
+
this.setRoutes(routes);
|
|
13
|
+
}
|
|
3
14
|
window.addEventListener('popstate', () => this._notify());
|
|
4
15
|
document.addEventListener('click', (e) => {
|
|
5
|
-
// 1. Ignore modified clicks (Ctrl+Click, etc)
|
|
6
16
|
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
7
17
|
return;
|
|
8
18
|
}
|
|
9
|
-
// 2. Safely traverse the path
|
|
10
19
|
const path = e.composedPath();
|
|
11
20
|
const link = path.find((el) => el && typeof el.hasAttribute === 'function' && el.hasAttribute('href'));
|
|
12
21
|
if (!link)
|
|
13
22
|
return;
|
|
14
23
|
const href = link.getAttribute('href');
|
|
15
24
|
const target = link.getAttribute('target');
|
|
16
|
-
// 3. Ignore external links
|
|
17
25
|
if (!href || href.startsWith('http') || href.startsWith('mailto:') || target === '_blank') {
|
|
18
26
|
return;
|
|
19
27
|
}
|
|
20
|
-
// 4. Intercept internal links
|
|
21
28
|
if (href.startsWith('/')) {
|
|
22
29
|
e.preventDefault();
|
|
23
|
-
|
|
24
|
-
this._notify();
|
|
30
|
+
this.navigate(href);
|
|
25
31
|
}
|
|
26
32
|
});
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Register or update the route configuration for this application.
|
|
36
|
+
*/
|
|
37
|
+
setRoutes(routes) {
|
|
38
|
+
this._routes = routes;
|
|
39
|
+
// Trigger initial route resolution once routes are registered
|
|
40
|
+
if (!this._initialized) {
|
|
41
|
+
this._initialized = true;
|
|
42
|
+
setTimeout(() => this._notify(), 0);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
this._notify();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Programmatically navigate to an internal path.
|
|
50
|
+
*/
|
|
51
|
+
navigate(path) {
|
|
52
|
+
window.history.pushState(null, '', path);
|
|
53
|
+
this._notify();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Matches a URL path against the registered routes.
|
|
57
|
+
*/
|
|
58
|
+
matchRoute(currentPath) {
|
|
59
|
+
const sortedRoutes = Object.entries(this._routes).sort(([patternA], [patternB]) => getRouteWeight(patternB) - getRouteWeight(patternA));
|
|
60
|
+
for (const [pattern, routeDef] of sortedRoutes) {
|
|
61
|
+
const paramNames = [];
|
|
62
|
+
const regexPath = pattern.replace(/:([a-zA-Z0-9_]+)/g, (_, paramName) => {
|
|
63
|
+
paramNames.push(paramName);
|
|
64
|
+
return '([^/]+)';
|
|
65
|
+
});
|
|
66
|
+
const regex = new RegExp(`^${regexPath}$`);
|
|
67
|
+
const match = currentPath.match(regex);
|
|
68
|
+
if (match) {
|
|
69
|
+
const params = {};
|
|
70
|
+
paramNames.forEach((name, index) => {
|
|
71
|
+
params[name] = decodeURIComponent(match[index + 1]);
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
path: currentPath,
|
|
75
|
+
component: routeDef.component,
|
|
76
|
+
params,
|
|
77
|
+
definition: routeDef
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
30
82
|
}
|
|
31
83
|
_notify() {
|
|
84
|
+
const currentPath = window.location.pathname;
|
|
85
|
+
const match = this.matchRoute(currentPath);
|
|
32
86
|
window.dispatchEvent(new CustomEvent('route-changed', {
|
|
33
|
-
detail:
|
|
87
|
+
detail: {
|
|
88
|
+
path: currentPath,
|
|
89
|
+
component: match ? match.component : 'not-found-page',
|
|
90
|
+
params: match ? match.params : {},
|
|
91
|
+
definition: match ? match.definition : null
|
|
92
|
+
}
|
|
34
93
|
}));
|
|
35
94
|
}
|
|
36
95
|
}
|
|
96
|
+
// Export a singleton instance for standard app-wide use
|
|
37
97
|
export const router = new AppRouter();
|
package/package.json
CHANGED
|
@@ -1,14 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export interface RouteDefinition {
|
|
2
|
+
component: string;
|
|
3
|
+
[key: string]: unknown; // Allows consuming apps to attach custom metadata (e.g., titles, auth roles)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type RouteConfig = Record<string, RouteDefinition>;
|
|
7
|
+
|
|
8
|
+
export interface RouteMatch {
|
|
9
|
+
path: string;
|
|
10
|
+
component: string;
|
|
11
|
+
params: Record<string, string>;
|
|
12
|
+
definition: RouteDefinition;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getRouteWeight(pattern: string): number {
|
|
16
|
+
const segments = pattern.split('/').filter(Boolean);
|
|
17
|
+
return segments.reduce((weight, seg) => {
|
|
18
|
+
return weight + (seg.startsWith(':') ? 1 : 10);
|
|
19
|
+
}, 0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class AppRouter {
|
|
23
|
+
private _routes: RouteConfig = {};
|
|
24
|
+
private _initialized = false;
|
|
25
|
+
|
|
26
|
+
constructor(routes?: RouteConfig) {
|
|
27
|
+
if (routes) {
|
|
28
|
+
this.setRoutes(routes);
|
|
29
|
+
}
|
|
30
|
+
|
|
3
31
|
window.addEventListener('popstate', () => this._notify());
|
|
4
32
|
|
|
5
33
|
document.addEventListener('click', (e) => {
|
|
6
|
-
// 1. Ignore modified clicks (Ctrl+Click, etc)
|
|
7
34
|
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
8
35
|
return;
|
|
9
36
|
}
|
|
10
37
|
|
|
11
|
-
// 2. Safely traverse the path
|
|
12
38
|
const path = e.composedPath();
|
|
13
39
|
const link = path.find((el: any) =>
|
|
14
40
|
el && typeof el.hasAttribute === 'function' && el.hasAttribute('href')
|
|
@@ -19,29 +45,90 @@ class AppRouter {
|
|
|
19
45
|
const href = link.getAttribute('href');
|
|
20
46
|
const target = link.getAttribute('target');
|
|
21
47
|
|
|
22
|
-
// 3. Ignore external links
|
|
23
48
|
if (!href || href.startsWith('http') || href.startsWith('mailto:') || target === '_blank') {
|
|
24
49
|
return;
|
|
25
50
|
}
|
|
26
51
|
|
|
27
|
-
// 4. Intercept internal links
|
|
28
52
|
if (href.startsWith('/')) {
|
|
29
53
|
e.preventDefault();
|
|
30
|
-
|
|
31
|
-
this._notify();
|
|
54
|
+
this.navigate(href);
|
|
32
55
|
}
|
|
33
56
|
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Register or update the route configuration for this application.
|
|
61
|
+
*/
|
|
62
|
+
public setRoutes(routes: RouteConfig) {
|
|
63
|
+
this._routes = routes;
|
|
34
64
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
65
|
+
// Trigger initial route resolution once routes are registered
|
|
66
|
+
if (!this._initialized) {
|
|
67
|
+
this._initialized = true;
|
|
68
|
+
setTimeout(() => this._notify(), 0);
|
|
69
|
+
} else {
|
|
70
|
+
this._notify();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Programmatically navigate to an internal path.
|
|
76
|
+
*/
|
|
77
|
+
public navigate(path: string) {
|
|
78
|
+
window.history.pushState(null, '', path);
|
|
79
|
+
this._notify();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Matches a URL path against the registered routes.
|
|
84
|
+
*/
|
|
85
|
+
public matchRoute(currentPath: string): RouteMatch | null {
|
|
86
|
+
const sortedRoutes = Object.entries(this._routes).sort(
|
|
87
|
+
([patternA], [patternB]) => getRouteWeight(patternB) - getRouteWeight(patternA)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
for (const [pattern, routeDef] of sortedRoutes) {
|
|
91
|
+
const paramNames: string[] = [];
|
|
92
|
+
const regexPath = pattern.replace(/:([a-zA-Z0-9_]+)/g, (_, paramName) => {
|
|
93
|
+
paramNames.push(paramName);
|
|
94
|
+
return '([^/]+)';
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const regex = new RegExp(`^${regexPath}$`);
|
|
98
|
+
const match = currentPath.match(regex);
|
|
99
|
+
|
|
100
|
+
if (match) {
|
|
101
|
+
const params: Record<string, string> = {};
|
|
102
|
+
paramNames.forEach((name, index) => {
|
|
103
|
+
params[name] = decodeURIComponent(match[index + 1]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
path: currentPath,
|
|
108
|
+
component: routeDef.component,
|
|
109
|
+
params,
|
|
110
|
+
definition: routeDef
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return null;
|
|
38
116
|
}
|
|
39
117
|
|
|
40
118
|
private _notify() {
|
|
119
|
+
const currentPath = window.location.pathname;
|
|
120
|
+
const match = this.matchRoute(currentPath);
|
|
121
|
+
|
|
41
122
|
window.dispatchEvent(new CustomEvent('route-changed', {
|
|
42
|
-
detail:
|
|
123
|
+
detail: {
|
|
124
|
+
path: currentPath,
|
|
125
|
+
component: match ? match.component : 'not-found-page',
|
|
126
|
+
params: match ? match.params : {},
|
|
127
|
+
definition: match ? match.definition : null
|
|
128
|
+
}
|
|
43
129
|
}));
|
|
44
130
|
}
|
|
45
131
|
}
|
|
46
132
|
|
|
133
|
+
// Export a singleton instance for standard app-wide use
|
|
47
134
|
export const router = new AppRouter();
|