@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.
Files changed (89) hide show
  1. package/README.md +216 -3424
  2. package/dist/cjs/decorators/addEventListener.js +4 -0
  3. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  4. package/dist/cjs/decorators/attribute.js +32 -43
  5. package/dist/cjs/decorators/attribute.js.map +3 -3
  6. package/dist/cjs/decorators/elementDefine.js +45 -19
  7. package/dist/cjs/decorators/elementDefine.js.map +2 -2
  8. package/dist/cjs/decorators/index.js +1 -0
  9. package/dist/cjs/decorators/index.js.map +2 -2
  10. package/dist/cjs/decorators/lifecycles.js +5 -5
  11. package/dist/cjs/decorators/lifecycles.js.map +2 -2
  12. package/dist/cjs/decorators/query.js +4 -4
  13. package/dist/cjs/decorators/query.js.map +2 -2
  14. package/dist/cjs/decorators/queryAll.js +4 -4
  15. package/dist/cjs/decorators/queryAll.js.map +2 -2
  16. package/dist/cjs/decorators/subscribeSwcAppRouteChange.js +59 -0
  17. package/dist/cjs/decorators/subscribeSwcAppRouteChange.js.map +7 -0
  18. package/dist/cjs/elements/SwcAppEngine.js +18 -63
  19. package/dist/cjs/elements/SwcAppEngine.js.map +3 -3
  20. package/dist/cjs/elements/SwcAppMixin.js +103 -24
  21. package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
  22. package/dist/cjs/elements/register.js +0 -1
  23. package/dist/cjs/elements/register.js.map +2 -2
  24. package/dist/cjs/utils/Utils.js +18 -0
  25. package/dist/cjs/utils/Utils.js.map +3 -3
  26. package/dist/esm/decorators/addEventListener.js +4 -0
  27. package/dist/esm/decorators/addEventListener.js.map +2 -2
  28. package/dist/esm/decorators/attribute.js +32 -43
  29. package/dist/esm/decorators/attribute.js.map +3 -3
  30. package/dist/esm/decorators/elementDefine.js +46 -20
  31. package/dist/esm/decorators/elementDefine.js.map +2 -2
  32. package/dist/esm/decorators/index.js +1 -0
  33. package/dist/esm/decorators/index.js.map +2 -2
  34. package/dist/esm/decorators/lifecycles.js +5 -5
  35. package/dist/esm/decorators/lifecycles.js.map +2 -2
  36. package/dist/esm/decorators/query.js +4 -4
  37. package/dist/esm/decorators/query.js.map +2 -2
  38. package/dist/esm/decorators/queryAll.js +4 -4
  39. package/dist/esm/decorators/queryAll.js.map +2 -2
  40. package/dist/esm/decorators/subscribeSwcAppRouteChange.js +40 -0
  41. package/dist/esm/decorators/subscribeSwcAppRouteChange.js.map +7 -0
  42. package/dist/esm/elements/SwcAppEngine.js +18 -63
  43. package/dist/esm/elements/SwcAppEngine.js.map +2 -2
  44. package/dist/esm/elements/SwcAppMixin.js +103 -24
  45. package/dist/esm/elements/SwcAppMixin.js.map +2 -2
  46. package/dist/esm/elements/register.js +0 -1
  47. package/dist/esm/elements/register.js.map +2 -2
  48. package/dist/esm/utils/Utils.js +18 -0
  49. package/dist/esm/utils/Utils.js.map +3 -3
  50. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +177 -2477
  51. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  52. package/dist/types/decorators/addEventListener.d.ts +8 -1
  53. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  54. package/dist/types/decorators/attribute.d.ts +0 -1
  55. package/dist/types/decorators/attribute.d.ts.map +1 -1
  56. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  57. package/dist/types/decorators/index.d.ts +1 -0
  58. package/dist/types/decorators/index.d.ts.map +1 -1
  59. package/dist/types/decorators/lifecycles.d.ts +2 -2
  60. package/dist/types/decorators/lifecycles.d.ts.map +1 -1
  61. package/dist/types/decorators/query.d.ts +1 -1
  62. package/dist/types/decorators/query.d.ts.map +1 -1
  63. package/dist/types/decorators/queryAll.d.ts +1 -1
  64. package/dist/types/decorators/queryAll.d.ts.map +1 -1
  65. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts +18 -0
  66. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts.map +1 -0
  67. package/dist/types/elements/SwcAppEngine.d.ts +3 -5
  68. package/dist/types/elements/SwcAppEngine.d.ts.map +1 -1
  69. package/dist/types/elements/SwcAppMixin.d.ts +9 -0
  70. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  71. package/dist/types/elements/register.d.ts +1 -1
  72. package/dist/types/elements/register.d.ts.map +1 -1
  73. package/dist/types/utils/Utils.d.ts +3 -0
  74. package/dist/types/utils/Utils.d.ts.map +1 -1
  75. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +177 -2477
  76. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  77. package/package.json +2 -2
  78. package/src/decorators/addEventListener.ts +11 -1
  79. package/src/decorators/attribute.ts +62 -53
  80. package/src/decorators/elementDefine.ts +93 -30
  81. package/src/decorators/index.ts +1 -0
  82. package/src/decorators/lifecycles.ts +2 -2
  83. package/src/decorators/query.ts +5 -5
  84. package/src/decorators/queryAll.ts +8 -7
  85. package/src/decorators/subscribeSwcAppRouteChange.ts +71 -0
  86. package/src/elements/SwcAppEngine.ts +25 -81
  87. package/src/elements/SwcAppMixin.ts +143 -28
  88. package/src/elements/register.ts +0 -2
  89. 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
- onStarted?: (app: SwcAppInterface) => void;
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?: SwcAttributeConfigType) {
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
- if (userConfig.rootRouter) {
68
- this._config = {
69
- routeType: 'element',
70
- window: win,
71
- ...userConfig
72
- } as SwcConfigType;
73
- const _config = this._config;
74
-
75
- // Router Selection
76
- if (_config.routeType === 'path') {
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
- // App start
99
- const containerSymbol = _config.container || (this.host as any).getContainerSymbol?.();
100
- this.simpleApplication = new SimpleApplication(new SimOption({
101
- excludeProxys: [Node],
102
- rootRouter: _config.rootRouter,
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
- // Rendering
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 "../types";
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 !== 'swap') {
19
- await this.__swc_engine.connect(config);
104
+ if (mode === 'direct') {
105
+ await this.__swc_engine.connect(swcConfig);
20
106
  this.setAttribute('swc-connect-mode', 'direct');
21
- return;
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
- const clonedApp = this.cloneNode(true) as any;
32
- clonedApp.style.display = 'none';
33
- parent.insertBefore(clonedApp, this.nextSibling);
111
+ const clonedApp = this.cloneNode(true) as any;
112
+ clonedApp.style.display = 'none';
113
+ this.parentNode.insertBefore(clonedApp, this.nextSibling);
34
114
 
35
- try {
36
- await clonedApp.connect({ ...(config || {}), connectMode: 'direct' });
37
- if (this.parentNode) {
38
- this.parentNode.removeChild(this);
39
- }
40
- clonedApp.setAttribute('swc-connect-mode', 'swap');
41
- clonedApp.style.display = '';
42
- } catch (e) {
43
- if (clonedApp.parentNode) {
44
- clonedApp.parentNode.removeChild(clonedApp);
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
- throw e;
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
- this.connect();
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
  }
@@ -732,6 +732,4 @@ export const registerAllElements = (w: any) => {
732
732
  @elementDefine('swc-app-body', { extends: 'body', window: w })
733
733
  class SwcAppBody extends SwcAppMixin(HTMLBodyElement) {}
734
734
 
735
-
736
- return []
737
735
  };
@@ -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
  }