@dooboostore/simple-web-component 1.0.26 → 1.0.28
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 +216 -3424
- package/dist/cjs/decorators/addEventListener.js +4 -0
- package/dist/cjs/decorators/addEventListener.js.map +2 -2
- package/dist/cjs/decorators/attribute.js +32 -43
- package/dist/cjs/decorators/attribute.js.map +3 -3
- package/dist/cjs/decorators/elementDefine.js +45 -19
- package/dist/cjs/decorators/elementDefine.js.map +2 -2
- package/dist/cjs/decorators/index.js +1 -0
- package/dist/cjs/decorators/index.js.map +2 -2
- package/dist/cjs/decorators/lifecycles.js +5 -5
- package/dist/cjs/decorators/lifecycles.js.map +2 -2
- package/dist/cjs/decorators/query.js +4 -4
- package/dist/cjs/decorators/query.js.map +2 -2
- package/dist/cjs/decorators/queryAll.js +4 -4
- package/dist/cjs/decorators/queryAll.js.map +2 -2
- package/dist/cjs/decorators/subscribeSwcAppRouteChange.js +59 -0
- package/dist/cjs/decorators/subscribeSwcAppRouteChange.js.map +7 -0
- package/dist/cjs/elements/SwcAppEngine.js +18 -63
- package/dist/cjs/elements/SwcAppEngine.js.map +3 -3
- package/dist/cjs/elements/SwcAppMixin.js +103 -24
- package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
- package/dist/cjs/elements/register.js +0 -1
- package/dist/cjs/elements/register.js.map +2 -2
- package/dist/cjs/utils/Utils.js +18 -0
- package/dist/cjs/utils/Utils.js.map +3 -3
- package/dist/esm/decorators/addEventListener.js +4 -0
- package/dist/esm/decorators/addEventListener.js.map +2 -2
- package/dist/esm/decorators/attribute.js +32 -43
- package/dist/esm/decorators/attribute.js.map +3 -3
- package/dist/esm/decorators/elementDefine.js +46 -20
- package/dist/esm/decorators/elementDefine.js.map +2 -2
- package/dist/esm/decorators/index.js +1 -0
- package/dist/esm/decorators/index.js.map +2 -2
- package/dist/esm/decorators/lifecycles.js +5 -5
- package/dist/esm/decorators/lifecycles.js.map +2 -2
- package/dist/esm/decorators/query.js +4 -4
- package/dist/esm/decorators/query.js.map +2 -2
- package/dist/esm/decorators/queryAll.js +4 -4
- package/dist/esm/decorators/queryAll.js.map +2 -2
- package/dist/esm/decorators/subscribeSwcAppRouteChange.js +40 -0
- package/dist/esm/decorators/subscribeSwcAppRouteChange.js.map +7 -0
- package/dist/esm/elements/SwcAppEngine.js +18 -63
- package/dist/esm/elements/SwcAppEngine.js.map +2 -2
- package/dist/esm/elements/SwcAppMixin.js +103 -24
- package/dist/esm/elements/SwcAppMixin.js.map +2 -2
- package/dist/esm/elements/register.js +0 -1
- package/dist/esm/elements/register.js.map +2 -2
- package/dist/esm/utils/Utils.js +18 -0
- package/dist/esm/utils/Utils.js.map +3 -3
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +177 -2477
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
- package/dist/types/decorators/addEventListener.d.ts +8 -1
- package/dist/types/decorators/addEventListener.d.ts.map +1 -1
- package/dist/types/decorators/attribute.d.ts +0 -1
- package/dist/types/decorators/attribute.d.ts.map +1 -1
- package/dist/types/decorators/elementDefine.d.ts.map +1 -1
- package/dist/types/decorators/index.d.ts +1 -0
- package/dist/types/decorators/index.d.ts.map +1 -1
- package/dist/types/decorators/lifecycles.d.ts +2 -2
- package/dist/types/decorators/lifecycles.d.ts.map +1 -1
- package/dist/types/decorators/query.d.ts +1 -1
- package/dist/types/decorators/query.d.ts.map +1 -1
- package/dist/types/decorators/queryAll.d.ts +1 -1
- package/dist/types/decorators/queryAll.d.ts.map +1 -1
- package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts +18 -0
- package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts.map +1 -0
- package/dist/types/elements/SwcAppEngine.d.ts +3 -5
- package/dist/types/elements/SwcAppEngine.d.ts.map +1 -1
- package/dist/types/elements/SwcAppMixin.d.ts +9 -0
- package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
- package/dist/types/elements/register.d.ts +1 -1
- package/dist/types/elements/register.d.ts.map +1 -1
- package/dist/types/utils/Utils.d.ts +3 -0
- package/dist/types/utils/Utils.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +177 -2477
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
- package/package.json +2 -2
- package/src/decorators/addEventListener.ts +11 -1
- package/src/decorators/attribute.ts +62 -53
- package/src/decorators/elementDefine.ts +93 -30
- package/src/decorators/index.ts +1 -0
- package/src/decorators/lifecycles.ts +2 -2
- package/src/decorators/query.ts +5 -5
- package/src/decorators/queryAll.ts +8 -7
- package/src/decorators/subscribeSwcAppRouteChange.ts +71 -0
- package/src/elements/SwcAppEngine.ts +25 -81
- package/src/elements/SwcAppMixin.ts +143 -28
- package/src/elements/register.ts +0 -2
- package/src/utils/Utils.ts +33 -0
|
@@ -13,13 +13,12 @@ import { SwcAppInterface } from '../types';
|
|
|
13
13
|
import {Intent} from "@dooboostore/simple-boot";
|
|
14
14
|
|
|
15
15
|
export type SwcConfigType = {
|
|
16
|
-
rootRouter: ConstructorType<any> | symbol;
|
|
17
16
|
routeType: 'path' | 'hash' | 'element';
|
|
18
17
|
connectMode?: 'direct' | 'swap';
|
|
19
18
|
container?: symbol;
|
|
20
19
|
path?: string;
|
|
21
20
|
window: Window;
|
|
22
|
-
|
|
21
|
+
onEngineStarted?: (sp: SimpleApplication, app: SwcAppInterface) => void;
|
|
23
22
|
onConnected?: (app: SwcAppInterface) => void;
|
|
24
23
|
onDisconnected?: (app: SwcAppInterface) => void;
|
|
25
24
|
};
|
|
@@ -30,7 +29,6 @@ export class SwcAppEngine {
|
|
|
30
29
|
public simpleApplication?: SimpleApplication;
|
|
31
30
|
public router?: Router;
|
|
32
31
|
private routerSubscription?: Subscription;
|
|
33
|
-
private _isInitialized = false;
|
|
34
32
|
private _config?: SwcConfigType;
|
|
35
33
|
|
|
36
34
|
constructor(private host: HTMLElement) {}
|
|
@@ -38,96 +36,42 @@ export class SwcAppEngine {
|
|
|
38
36
|
get config() {
|
|
39
37
|
return this._config;
|
|
40
38
|
}
|
|
41
|
-
async connect(config
|
|
42
|
-
if (this._isInitialized) return;
|
|
43
|
-
|
|
44
|
-
let userConfig = config;
|
|
45
|
-
const win = this.host.ownerDocument?.defaultView || window;
|
|
46
|
-
const Node = (win as any).Node as typeof globalThis.Node;
|
|
47
|
-
if (!userConfig) {
|
|
48
|
-
const configStr = this.host.getAttribute('swc-get-application-config');
|
|
49
|
-
if (configStr) {
|
|
50
|
-
try {
|
|
51
|
-
// console.log('vvvvvvvvvvvvvvvv->>>>', SwcUtils.findNearestSwcHost(this.host), SwcUtils.getHostSet(this.host))
|
|
52
|
-
userConfig = FunctionUtils.executeReturn({
|
|
53
|
-
script: configStr,
|
|
54
|
-
context: this.host,
|
|
55
|
-
args: SwcUtils.getHelperAndHostSet(win, SwcUtils.findNearestSwcHost(this.host))
|
|
56
|
-
});
|
|
57
|
-
} catch (e) {
|
|
58
|
-
console.error('[SWC-APP-ENGINE] Script execution failed:', e);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!userConfig) return;
|
|
64
|
-
this._isInitialized = true;
|
|
65
|
-
|
|
39
|
+
async connect(config: SwcConfigType) {
|
|
66
40
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.router = new PathRouter({ window: _config.window, firstUrl: _config.path });
|
|
78
|
-
} else if (_config.routeType === 'hash') {
|
|
79
|
-
this.router = new HashRouter({ window: _config.window, firstUrl: _config.path });
|
|
80
|
-
} else if (_config.routeType === 'element') {
|
|
81
|
-
this.router = new ElementRouter({ window: _config.window, firstUrl: _config.path });
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// console.log('vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv', this.router)
|
|
85
|
-
|
|
86
|
-
// Routing sync
|
|
87
|
-
if (this.router) {
|
|
88
|
-
this.routerSubscription = this.router.observable.subscribe((route: RouterEventType) => {
|
|
89
|
-
if (route.triggerPoint === 'end') {
|
|
90
|
-
const targetPath = (route.path || '/') + route.search;
|
|
91
|
-
const intent = new Intent(targetPath);
|
|
92
|
-
this.simpleApplication?.routing(intent);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}
|
|
41
|
+
this._config = config;
|
|
42
|
+
|
|
43
|
+
// Router Selection
|
|
44
|
+
if (this._config.routeType === 'path') {
|
|
45
|
+
this.router = new PathRouter({ window: this._config.window, firstUrl: this._config.path });
|
|
46
|
+
} else if (this._config.routeType === 'hash') {
|
|
47
|
+
this.router = new HashRouter({ window: this._config.window, firstUrl: this._config.path });
|
|
48
|
+
} else if (this._config.routeType === 'element') {
|
|
49
|
+
this.router = new ElementRouter({ window: this._config.window, firstUrl: this._config.path });
|
|
50
|
+
}
|
|
96
51
|
|
|
52
|
+
// console.log('vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv', this.router)
|
|
97
53
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
54
|
+
// App start
|
|
55
|
+
const containerSymbol = this._config.container || (this.host as any).getContainerSymbol?.();
|
|
56
|
+
this.simpleApplication = new SimpleApplication(
|
|
57
|
+
new SimOption({
|
|
58
|
+
excludeProxys: [Node],
|
|
103
59
|
container: containerSymbol
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
const otherInstanceSim = new Map<any, any>();
|
|
107
|
-
if (this.router) otherInstanceSim.set(Router, this.router);
|
|
108
|
-
this.simpleApplication.run(otherInstanceSim);
|
|
60
|
+
})
|
|
61
|
+
);
|
|
109
62
|
|
|
63
|
+
const otherInstanceSim = new Map<any, any>();
|
|
64
|
+
if (this.router) otherInstanceSim.set(Router, this.router);
|
|
65
|
+
this.simpleApplication.run(otherInstanceSim);
|
|
110
66
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const data = this.simpleApplication.sim(_config.rootRouter);
|
|
114
|
-
if (data instanceof Node) {
|
|
115
|
-
// console.warn('[SWC-APP-ENGINE] Rendering failed: Root element is not a DocumentFragment.', data);
|
|
116
|
-
if (this.host.shadowRoot) this.host.shadowRoot.replaceChildren(data);
|
|
117
|
-
else this.host.replaceChildren(data);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (_config.path) this.router?.go(_config.path);
|
|
121
|
-
if (_config.onStarted) {
|
|
122
|
-
_config.onStarted(this.host as unknown as SwcAppInterface);
|
|
123
|
-
}
|
|
67
|
+
if (this._config.onEngineStarted) {
|
|
68
|
+
this._config.onEngineStarted(this.simpleApplication, this.host as unknown as SwcAppInterface);
|
|
124
69
|
}
|
|
125
70
|
} catch (e) {
|
|
126
71
|
console.error('[SWC-APP-ENGINE] Init failed:', e);
|
|
127
72
|
}
|
|
128
73
|
}
|
|
129
74
|
|
|
130
|
-
|
|
131
75
|
disconnect() {
|
|
132
76
|
this.routerSubscription?.unsubscribe();
|
|
133
77
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import { SwcAppEngine, SwcAttributeConfigType } from './SwcAppEngine';
|
|
2
|
-
import {SwcAppInterface} from
|
|
1
|
+
import { SwcAppEngine, SwcAttributeConfigType, SwcConfigType } from './SwcAppEngine';
|
|
2
|
+
import { SwcAppInterface } from '../types';
|
|
3
|
+
import { SwcUtils } from '../utils/Utils';
|
|
4
|
+
import { FunctionUtils, Subscription } from '@dooboostore/core';
|
|
5
|
+
import { RouterEventType } from '@dooboostore/core-web';
|
|
6
|
+
import { getSubscribeSwcAppRouteChangeMetadata } from '../decorators/subscribeSwcAppRouteChange';
|
|
3
7
|
|
|
4
8
|
export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Base: T) {
|
|
5
|
-
return class extends Base implements SwcAppInterface{
|
|
9
|
+
return class extends Base implements SwcAppInterface {
|
|
6
10
|
__swc_engine = new SwcAppEngine(this as any);
|
|
11
|
+
_swc_connected_instance = new Set<any>();
|
|
12
|
+
_routerSubscription?: Subscription;
|
|
13
|
+
_lastRouterEvent?: RouterEventType;
|
|
7
14
|
|
|
8
15
|
get simpleApplication() {
|
|
9
16
|
return this.__swc_engine.simpleApplication;
|
|
@@ -12,51 +19,159 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
12
19
|
return this.__swc_engine.router;
|
|
13
20
|
}
|
|
14
21
|
|
|
22
|
+
_invokeRouteChangeSubscribers(instance: any, re: RouterEventType) {
|
|
23
|
+
const routeChangeSubscribers = getSubscribeSwcAppRouteChangeMetadata(instance);
|
|
24
|
+
if (routeChangeSubscribers && Array.isArray(routeChangeSubscribers)) {
|
|
25
|
+
routeChangeSubscribers.forEach((metadata: any) => {
|
|
26
|
+
const methodName = metadata.propertyKey || metadata;
|
|
27
|
+
const pathPattern = metadata.pathPattern;
|
|
28
|
+
const filter = metadata.filter;
|
|
29
|
+
|
|
30
|
+
// Path pattern matching (support both string and array)
|
|
31
|
+
let pathMatched = false;
|
|
32
|
+
let pathData: any = null;
|
|
33
|
+
|
|
34
|
+
if (!pathPattern) {
|
|
35
|
+
// pathPattern이 없으면 모든 경로 매칭 (와일드카드)
|
|
36
|
+
pathMatched = true;
|
|
37
|
+
pathData = {};
|
|
38
|
+
} else if (Array.isArray(pathPattern)) {
|
|
39
|
+
// 배열: 하나라도 매칭되면 OK
|
|
40
|
+
for (const pattern of pathPattern) {
|
|
41
|
+
const data = SwcUtils.parsePathPattern(pattern, re.path || '/');
|
|
42
|
+
if (data !== null) {
|
|
43
|
+
pathMatched = true;
|
|
44
|
+
pathData = data;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
// 문자열
|
|
50
|
+
pathData = SwcUtils.parsePathPattern(pathPattern, re.path || '/');
|
|
51
|
+
pathMatched = pathData !== null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Filter validation
|
|
55
|
+
const filterPassed = !filter || filter(this.router!);
|
|
56
|
+
|
|
57
|
+
// Invoke method if conditions pass
|
|
58
|
+
if (pathMatched && filterPassed) {
|
|
59
|
+
instance[methodName]?.(re, pathData || {});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_connected(instance: any) {
|
|
66
|
+
// console.log('------------------>', instance);
|
|
67
|
+
if (instance) {
|
|
68
|
+
this._swc_connected_instance.add(instance);
|
|
69
|
+
|
|
70
|
+
// Notify current route subscribers immediately after connection
|
|
71
|
+
if (this.router && this._lastRouterEvent) {
|
|
72
|
+
this._invokeRouteChangeSubscribers(instance, this._lastRouterEvent);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_disconnected(instance: any) {
|
|
78
|
+
if (instance) {
|
|
79
|
+
this._swc_connected_instance.delete(instance);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_handleRouteChange(route: RouterEventType) {
|
|
84
|
+
if (route.triggerPoint === 'end') {
|
|
85
|
+
// Iterate through all connected component instances
|
|
86
|
+
this._swc_connected_instance.forEach((instance: any) => {
|
|
87
|
+
this._invokeRouteChangeSubscribers(instance, route);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Save last router event
|
|
92
|
+
this._lastRouterEvent = route;
|
|
93
|
+
}
|
|
94
|
+
|
|
15
95
|
async connect(config?: SwcAttributeConfigType) {
|
|
96
|
+
const swcConfig = {
|
|
97
|
+
routeType: 'element',
|
|
98
|
+
window: config.window,
|
|
99
|
+
...config
|
|
100
|
+
} as SwcConfigType;
|
|
101
|
+
|
|
16
102
|
const mode = config?.connectMode || 'direct';
|
|
17
103
|
|
|
18
|
-
if (mode
|
|
19
|
-
await this.__swc_engine.connect(
|
|
104
|
+
if (mode === 'direct') {
|
|
105
|
+
await this.__swc_engine.connect(swcConfig);
|
|
20
106
|
this.setAttribute('swc-connect-mode', 'direct');
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const parent = this.parentNode;
|
|
25
|
-
if (!parent) {
|
|
26
|
-
await this.__swc_engine.connect(config);
|
|
107
|
+
} else if (mode === 'swap' && !this.parentNode) {
|
|
108
|
+
await this.__swc_engine.connect(swcConfig);
|
|
27
109
|
this.setAttribute('swc-connect-mode', 'direct');
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
110
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
111
|
+
const clonedApp = this.cloneNode(true) as any;
|
|
112
|
+
clonedApp.style.display = 'none';
|
|
113
|
+
this.parentNode.insertBefore(clonedApp, this.nextSibling);
|
|
34
114
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
115
|
+
try {
|
|
116
|
+
await clonedApp.connect({ ...(config || {}), connectMode: 'direct' });
|
|
117
|
+
if (this.parentNode) {
|
|
118
|
+
this.parentNode.removeChild(this);
|
|
119
|
+
}
|
|
120
|
+
clonedApp.setAttribute('swc-connect-mode', 'swap');
|
|
121
|
+
clonedApp.style.display = '';
|
|
122
|
+
} catch (e) {
|
|
123
|
+
if (clonedApp.parentNode) {
|
|
124
|
+
clonedApp.parentNode.removeChild(clonedApp);
|
|
125
|
+
}
|
|
126
|
+
throw e;
|
|
45
127
|
}
|
|
46
|
-
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// console.log('vvvvvvv222222', this.router);
|
|
131
|
+
// Subscribe to router changes after connect
|
|
132
|
+
if (!this._routerSubscription && this.router) {
|
|
133
|
+
this._routerSubscription = this.router.observable.subscribe((route: RouterEventType) => {
|
|
134
|
+
// console.log('Route changed:', route);
|
|
135
|
+
this._handleRouteChange(route);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Navigate to initial path if specified
|
|
139
|
+
// const config = this.__swc_engine.config;
|
|
140
|
+
// if (config?.path) {
|
|
141
|
+
// this.router.go(config.path);
|
|
142
|
+
// }
|
|
47
143
|
}
|
|
48
144
|
}
|
|
49
145
|
|
|
50
146
|
connectedCallback() {
|
|
51
147
|
// @ts-ignore
|
|
52
148
|
if (super.connectedCallback) super.connectedCallback();
|
|
53
|
-
|
|
149
|
+
|
|
150
|
+
const configStr = this.getAttribute('swc-get-application-config');
|
|
151
|
+
const win = this.ownerDocument?.defaultView || window;
|
|
152
|
+
if (configStr && win) {
|
|
153
|
+
try {
|
|
154
|
+
// console.log('vvvvvvvvvvvvvvvv->>>>', SwcUtils.findNearestSwcHost(this.host), SwcUtils.getHostSet(this.host))
|
|
155
|
+
const userConfig = FunctionUtils.executeReturn({
|
|
156
|
+
script: configStr,
|
|
157
|
+
context: this,
|
|
158
|
+
args: SwcUtils.getHelperAndHostSet(win, SwcUtils.findNearestSwcHost(this))
|
|
159
|
+
});
|
|
160
|
+
this.connect(userConfig);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
console.error('[SWC-APP-ENGINE] Script execution failed:', e);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
54
166
|
this.__swc_engine?.config?.onConnected?.(this);
|
|
55
167
|
}
|
|
56
168
|
|
|
57
169
|
disconnectedCallback() {
|
|
58
170
|
// @ts-ignore
|
|
59
171
|
if (super.disconnectedCallback) super.disconnectedCallback();
|
|
172
|
+
this._routerSubscription?.unsubscribe();
|
|
173
|
+
this._routerSubscription = undefined;
|
|
174
|
+
this._lastRouterEvent = undefined;
|
|
60
175
|
this.__swc_engine.disconnect();
|
|
61
176
|
this.__swc_engine?.config?.onDisconnected?.(this);
|
|
62
177
|
}
|
package/src/elements/register.ts
CHANGED
package/src/utils/Utils.ts
CHANGED
|
@@ -282,4 +282,37 @@ export namespace SwcUtils {
|
|
|
282
282
|
...loopContext
|
|
283
283
|
} as any;
|
|
284
284
|
};
|
|
285
|
+
|
|
286
|
+
export const parsePathPattern = (pattern: string | undefined, currentPath: string): { [key: string]: string } | null => {
|
|
287
|
+
// pattern이 null/undefined면 와일드카드 (모든 경로 매칭)
|
|
288
|
+
if (pattern === null || pattern === undefined) return {};
|
|
289
|
+
|
|
290
|
+
// 공백만 있는 패턴도 와일드카드
|
|
291
|
+
const trimmedPattern = pattern.trim();
|
|
292
|
+
if (trimmedPattern === '' && pattern !== '') return {}; // 공백 문자만 있는 경우
|
|
293
|
+
|
|
294
|
+
// 패턴을 정규식으로 변환
|
|
295
|
+
// 지원 형식:
|
|
296
|
+
// {id} → ([^/]+) 기본 (/ 제외한 모든 문자)
|
|
297
|
+
// {tail:.*} → (.*) 정규표현식 직접 지정
|
|
298
|
+
// {slug:[a-z0-9-]+} → ([a-z0-9-]+)
|
|
299
|
+
const paramNames: string[] = [];
|
|
300
|
+
const regexPattern = trimmedPattern.replace(/{(\w+)(?::(.+?))?}/g, (match, paramName, customRegex) => {
|
|
301
|
+
paramNames.push(paramName);
|
|
302
|
+
// customRegex가 있으면 사용, 없으면 기본값 ([^/]+)
|
|
303
|
+
return `(${customRegex || '[^/]+'})`;
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
307
|
+
const match = currentPath.match(regex);
|
|
308
|
+
|
|
309
|
+
if (!match) return null;
|
|
310
|
+
|
|
311
|
+
const result: { [key: string]: string } = {};
|
|
312
|
+
paramNames.forEach((name, index) => {
|
|
313
|
+
result[name] = match[index + 1];
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return result;
|
|
317
|
+
};
|
|
285
318
|
}
|