@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dooboostore/simple-web-component",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -92,9 +92,9 @@
92
92
  "webpack": "^5.93.0",
93
93
  "webpack-cli": "^5.1.4",
94
94
  "webpack-dev-server": "^5.2.0",
95
- "@dooboostore/core": "1.0.30",
96
95
  "@dooboostore/core-node": "1.0.12",
97
96
  "@dooboostore/core-web": "1.0.10",
97
+ "@dooboostore/core": "1.0.30",
98
98
  "@dooboostore/simple-boot": "1.0.54"
99
99
  },
100
100
  "scripts": {
@@ -1,5 +1,5 @@
1
1
  import { ReflectUtils } from '@dooboostore/core';
2
- import { SpecialSelector, SwcQueryOptions } from '../types';
2
+ import { SpecialSelector, SwcQueryOptions, HelperHostSet } from '../types';
3
3
 
4
4
  export interface AddEventListenerBaseOptions extends EventListenerOptions {
5
5
  capture?: boolean;
@@ -10,6 +10,7 @@ export interface AddEventListenerBaseOptions extends EventListenerOptions {
10
10
  preventDefault?: boolean;
11
11
  removeOnDisconnected?: boolean;
12
12
  delegate?: boolean;
13
+ filter?: (target: Event | CustomEvent, helper: HelperHostSet) => boolean;
13
14
  }
14
15
 
15
16
  export interface AddEventListenerMetadata {
@@ -53,3 +54,12 @@ export const getAddEventListenerMetadata = (target: any): AddEventListenerMetada
53
54
  export function addEventListenerHost(type: string, options?: AddEventListenerBaseOptions & SwcQueryOptions): MethodDecorator {
54
55
  return addEventListener(':host', type, options);
55
56
  }
57
+
58
+ /**
59
+ * @addEventListenerAppHost decorator - simplified version of @addEventListener for :appHost selector
60
+ * Usage: @addEventListenerAppHost('click')
61
+ * Usage: @addEventListenerAppHost('click', { stopPropagation: true })
62
+ */
63
+ export function addEventListenerAppHost(type: string, options?: AddEventListenerBaseOptions & SwcQueryOptions): MethodDecorator {
64
+ return addEventListener(':appHost', type, options);
65
+ }
@@ -2,10 +2,9 @@ import {ReflectUtils} from '@dooboostore/core';
2
2
  import {ensureInit, getElementConfig} from './elementDefine';
3
3
  import {SwcUtils} from '../utils/Utils';
4
4
  import {SpecialSelector, SwcQueryOptions} from '../types';
5
-
6
5
  export interface AttributeFieldOptions extends SwcQueryOptions {
7
6
  name?: string;
8
- connectedInitialize?: boolean;
7
+ // connectedInitialize?: boolean;
9
8
  type?: typeof Number | typeof Boolean | typeof String;
10
9
  }
11
10
 
@@ -81,9 +80,9 @@ export function attribute(selector: string, options: AttributeFieldOptions = {})
81
80
  return (target: Object, propertyKey: string | symbol) => {
82
81
  const constructor = target.constructor;
83
82
  const privateKey = Symbol(String(propertyKey));
84
-
83
+ //
85
84
  const designType = (Reflect as any).getMetadata('design:type', target, propertyKey);
86
-
85
+ //
87
86
  let metaList = ReflectUtils.getOwnMetadata(ATTRIBUTE_FIELD_METADATA_KEY, constructor) as AttributeFieldMetadata[];
88
87
  if (!metaList) {
89
88
  metaList = [];
@@ -105,56 +104,65 @@ export function attribute(selector: string, options: AttributeFieldOptions = {})
105
104
  });
106
105
  };
107
106
 
108
- Object.defineProperty(target, propertyKey, {
109
- get(this: any) {
110
- const attrName = options.name || String(propertyKey);
111
- const conf = getElementConfig(this);
112
- const currentWin = this._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as any);
113
- const targetType = options.type || designType;
114
-
115
- if (this.__swc_syncing_init || (options.connectedInitialize && !this.__swc_connected)) {
116
- return convertValue(this[privateKey], targetType);
117
- }
118
-
119
- const targets = resolveTargetEls(this, selector, options, currentWin);
120
- const primaryTarget = targets[0];
121
-
122
- if (primaryTarget && typeof (primaryTarget as any).hasAttribute === 'function') {
123
- if ((primaryTarget as any).hasAttribute(attrName)) {
124
- const domVal = (primaryTarget as any).getAttribute(attrName);
125
- return convertValue(domVal, targetType);
126
- }
127
- return null;
128
- }
129
-
130
- return convertValue(this[privateKey], targetType);
131
- },
132
- set(this: any, nv: any) {
133
- const attrName = options.name || String(propertyKey);
134
- const conf = getElementConfig(this);
135
- const currentWin = this._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as any);
107
+ try {
108
+ // Own property를 삭제하여 getter가 작동하도록 함
109
+ // delete target[propertyKey];
110
+
111
+ // const defineTarget = target instanceof Function ? target : (target as any).constructor;
112
+
113
+ Object.defineProperty(target, propertyKey, {
114
+ get(this: any) {
115
+ // try {
116
+ const attrName = options.name || String(propertyKey);
117
+ const conf = getElementConfig(this);
118
+ const currentWin = this._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as any);
119
+ const targetType = options.type || designType;
120
+ //
121
+ // connectedInitialize: true이면 privateKey 값만 반환 (DOM 무시)
122
+ // if (options.connectedInitialize === true && (this as any).__swc_syncing_init) {
123
+ // console.log('cccccccc2');
124
+ // return this[privateKey];
125
+ // // return convertValue(this[privateKey], targetType);
126
+ // }
136
127
 
137
- if (this.__swc_syncing_init) {
138
- if (options.connectedInitialize === true) {
139
128
  const targets = resolveTargetEls(this, selector, options, currentWin);
140
- applySetAttribute(this, targets, attrName, nv);
141
- } else {
142
- this[privateKey] = nv;
143
- }
144
- return;
145
- }
146
-
147
- if (!this.__swc_connected) {
148
- this[privateKey] = nv;
149
- return;
150
- }
151
-
152
- const targets = resolveTargetEls(this, selector, options, currentWin);
153
- applySetAttribute(this, targets, attrName, nv);
154
- },
155
- enumerable: true,
156
- configurable: true
157
- });
129
+ const primaryTarget = targets[0];
130
+
131
+ if (primaryTarget && typeof (primaryTarget as any).hasAttribute === 'function') {
132
+ if ((primaryTarget as any).hasAttribute(attrName)) {
133
+ const domVal = (primaryTarget as any).getAttribute(attrName);
134
+ return convertValue(domVal, targetType);
135
+ }
136
+ return null;
137
+ }
138
+
139
+ return convertValue(this[privateKey], targetType);
140
+ // } catch (err) {
141
+ // console.error('Error in getter:', err);
142
+ // throw err;
143
+ // }
144
+ },
145
+ set(this: any, nv: any) {
146
+ // this[propertyKey] = nv;
147
+ const attrName = options.name || String(propertyKey);
148
+ const conf = getElementConfig(this);
149
+ const currentWin = this._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as any);
150
+
151
+
152
+ // if (!this.__swc_connected) {
153
+ // this[privateKey] = nv;
154
+ // return;
155
+ // }
156
+
157
+ const targets = resolveTargetEls(this, selector, options, currentWin);
158
+ applySetAttribute(this, targets, attrName, nv);
159
+ },
160
+ enumerable: true,
161
+ configurable: true
162
+ });
163
+ } catch (err) {
164
+ console.error('[SWC] defineProperty error:', err);
165
+ }
158
166
  };
159
167
  }
160
168
 
@@ -187,5 +195,6 @@ export function attributeHost(targetOrAttrOrOptions?: any, propertyKeyOrOptions?
187
195
  }
188
196
 
189
197
  export const findAllAttributeMetadata = (target: any): AttributeFieldMetadata[] => {
190
- return ReflectUtils.findAllMetadata<AttributeFieldMetadata[]>(ATTRIBUTE_FIELD_METADATA_KEY, target).flat();
198
+ const actualTarget = target instanceof Function ? target : target.constructor;
199
+ return ReflectUtils.findAllMetadata<AttributeFieldMetadata[]>(ATTRIBUTE_FIELD_METADATA_KEY, actualTarget).flat();
191
200
  };
@@ -1,11 +1,13 @@
1
1
  import { ReflectUtils } from '@dooboostore/core';
2
2
  import { FunctionUtils } from '@dooboostore/core';
3
3
  import { getAddEventListenerMetadata } from './addEventListener';
4
- import { ON_CONSTRUCTOR_METADATA_KEY, ON_BEFORE_CONNECTED_METADATA_KEY, ON_AFTER_CONNECTED_METADATA_KEY, ON_BEFORE_DISCONNECTED_METADATA_KEY, ON_AFTER_DISCONNECTED_METADATA_KEY, ON_BEFORE_ADOPTED_METADATA_KEY, ON_AFTER_ADOPTED_METADATA_KEY, findAllLifecycleMetadata, getOnConnectedInnerHtmlMetadata } from './lifecycles';
4
+ import { ON_INITIALIZE_METADATA_KEY, ON_BEFORE_CONNECTED_METADATA_KEY, ON_AFTER_CONNECTED_METADATA_KEY, ON_BEFORE_DISCONNECTED_METADATA_KEY, ON_AFTER_DISCONNECTED_METADATA_KEY, ON_BEFORE_ADOPTED_METADATA_KEY, ON_AFTER_ADOPTED_METADATA_KEY, findAllLifecycleMetadata, getOnConnectedInnerHtmlMetadata } from './lifecycles';
5
5
  import { getEmitCustomEventMetadataList } from './emitCustomEvent';
6
6
  import { findAllAttributeChangedMetadata } from './changedAttributeHost';
7
7
  import { findAllAttributeApplyMetadata } from './applyAttribute';
8
8
  import { findAllAttributeMetadata } from './attribute';
9
+ import { getQueryMetadata } from './query';
10
+ import { getQueryAllMetadata } from './queryAll';
9
11
  import { SwcUtils } from '../utils/Utils';
10
12
  import { HTML_TAG_ENTRIES, DOM_EVENT_NAMES } from '../config/config';
11
13
  import { SituationTypeContainer, SituationTypeContainers } from '@dooboostore/simple-boot/decorators/inject/Inject';
@@ -39,12 +41,60 @@ export const ensureInit = (inst: any) => {
39
41
  inst._boundListeners = [];
40
42
  inst.__swc_initialized = true;
41
43
 
44
+ // const attributeList = findAllQ(inst);
45
+ // attributeList.forEach(meta => {
46
+ // delete (inst as any)[meta.propertyKey];
47
+ // });
48
+
49
+ const target = inst instanceof Function ? inst : inst.constructor;
50
+
51
+ // ╔════════════════════════════════════════════════════════════════════════════════╗
52
+ // ║ decorator 필드의 own property 삭제 ║
53
+ // ║ ║
54
+ // ║ 이유: ║
55
+ // ║ 1. TypeScript는 초기값이 있는 필드를 constructor에서 own property로 생성 ║
56
+ // ║ @attributeHost('id') myId: string = "default" → this.myId = "default" ║
57
+ // ║ ║
58
+ // ║ 2. JavaScript의 property lookup은 own property를 먼저 찾음 ║
59
+ // ║ - this.myId 접근 ║
60
+ // ║ - 1순위: instance의 own property 있나? → 있으면 그것 반환 ║
61
+ // ║ - 2순위: prototype의 getter 있나? → 있으면 호출 ║
62
+ // ║ ║
63
+ // ║ 3. own property가 있으면 prototype의 getter는 절대 호출되지 않음 ║
64
+ // ║ 따라서 getter에서 정의된 DOM 동기화 로직이 작동하지 않음 ║
65
+ // ║ ║
66
+ // ║ 4. own property를 삭제하면 JavaScript가 prototype의 getter를 찾게 됨 ║
67
+ // ║ → getter 호출됨 → DOM 속성 값 동기화됨 ║
68
+ // ╚════════════════════════════════════════════════════════════════════════════════╝
69
+
70
+ // @attribute 필드의 own property 삭제 → getter/setter 작동
71
+ const attributeList = findAllAttributeMetadata(target);
72
+ attributeList.forEach(meta => {
73
+ delete (inst as any)[meta.propertyKey];
74
+ });
75
+
76
+ // @query 필드의 own property 삭제 → getter 작동
77
+ const queryList = getQueryMetadata(target);
78
+ if (queryList) {
79
+ queryList.forEach(meta => {
80
+ delete (inst as any)[meta.propertyKey];
81
+ });
82
+ }
83
+
84
+ // @queryAll 필드의 own property 삭제 → getter 작동
85
+ const queryAllList = getQueryAllMetadata(target);
86
+ if (queryAllList) {
87
+ queryAllList.forEach(meta => {
88
+ delete (inst as any)[meta.propertyKey];
89
+ });
90
+ }
91
+
42
92
  // Call constructor script if present
43
93
  const hostSet = SwcUtils.getHostSet(inst);
44
94
  inst._executeSwcScript?.('swc-on-constructor', hostSet);
45
95
 
46
- // Call @onConstructor lifecycle methods
47
- const cMethods = findAllLifecycleMetadata(inst, ON_CONSTRUCTOR_METADATA_KEY);
96
+ // Call @onInitialize lifecycle methods
97
+ const cMethods = findAllLifecycleMetadata(inst, ON_INITIALIZE_METADATA_KEY);
48
98
  if (cMethods) {
49
99
  for (const m of cMethods) (inst)._invokeLifecycleMethod(m, hostSet);
50
100
  }
@@ -79,9 +129,10 @@ function buildEnv(configWindow?: Window) {
79
129
  const doc: Document = win?.document;
80
130
  const builtInTagMap = new Map<any, string>();
81
131
  for (const [cls, tag] of HTML_TAG_ENTRIES) {
82
- if (win?.[cls]) builtInTagMap.set(win[cls], tag);
132
+ if ((win as any)?.[cls]) builtInTagMap.set((win as any)[cls], tag);
83
133
  }
84
134
  const domHelpers = SwcUtils.getHelperSet(win);
135
+
85
136
  return { win, doc, builtInTagMap, domHelpers };
86
137
  }
87
138
 
@@ -238,15 +289,16 @@ export const elementDefine =
238
289
 
239
290
  // Collect all observed attributes from all decorators
240
291
  const attributeApplyNames = Array.from(applyAttributeMap.values()).map(it => it.options.name).filter(Boolean) as string[];
241
- const attributeListNames = attributeList.map(it => it.options.name || String(it.propertyKey));
242
-
292
+ // const attributeListNames = attributeList.map(it => it.options.name || String(it.propertyKey));
293
+ const hostAttributes = attributeList.filter(it => it.selector === ':host').map(it=>it.options.name || String(it.propertyKey));
294
+
243
295
  const mergedObservedAttributes = [...new Set([
244
- ...(metadata.observedAttributes ?? []),
245
- ...attrChangeMap.keys(),
296
+ ...(metadata.observedAttributes ?? []),
297
+ ...attrChangeMap.keys(),
246
298
  ...attributeApplyNames,
247
- ...attributeListNames,
248
- ...emitHostCustomEventList.map(it => (it.options as any).attributeName).filter(Boolean),
249
- ...swcLifecycleAttributes,
299
+ ...hostAttributes,
300
+ ...emitHostCustomEventList.map(it => (it.options as any).attributeName).filter(Boolean),
301
+ ...swcLifecycleAttributes,
250
302
  ...swcOnEvents
251
303
  ])];
252
304
 
@@ -259,35 +311,21 @@ export const elementDefine =
259
311
  proto.connectedCallback = function () {
260
312
  ensureInit(this);
261
313
 
314
+ const hostSet = SwcUtils.getHostSet(this as any);
315
+ const appHost = hostSet.$appHost;
316
+ const conf = getElementConfig(this);
317
+ const currentWin = (this as any)._resolveWindow(conf);
318
+
262
319
  if (connectedInnerHtmlList.some(it => it.options.useShadow === true) && !this.shadowRoot) {
263
320
  this.attachShadow({ mode: 'open' });
264
321
  }
265
322
 
266
- const conf = getElementConfig(this);
267
- const currentWin = (this as any)._resolveWindow(conf);
268
- const hostSet = SwcUtils.getHostSet(this as any);
269
-
270
323
  (this as any)._executeSwcScript('swc-on-before-connected', hostSet);
271
324
  const bMethods = findAllLifecycleMetadata(this, ON_BEFORE_CONNECTED_METADATA_KEY);
272
325
  if (bMethods) {
273
326
  for (const m of bMethods) (this as any)._invokeLifecycleMethod(m, hostSet);
274
327
  }
275
328
 
276
- // Initialize @attribute fields
277
- attributeList.forEach(meta => {
278
- if (meta.options.connectedInitialize === true) {
279
- (this as any).__swc_syncing_init = true;
280
- try {
281
- const memoryVal = (this as any)[meta.privateKey];
282
- if (memoryVal !== undefined) {
283
- (this as any)[meta.propertyKey] = memoryVal;
284
- }
285
- } finally {
286
- (this as any).__swc_syncing_init = false;
287
- }
288
- }
289
- });
290
-
291
329
  if (connectedInnerHtmlList.length > 0) {
292
330
  let sContent = '',
293
331
  lContent = '';
@@ -358,6 +396,13 @@ export const elementDefine =
358
396
  const { selector, options } = meta;
359
397
  const matchedEl = (event.target as HTMLElement).closest(selector);
360
398
  if (matchedEl && (br as any).contains(matchedEl)) {
399
+ // Apply filter if specified
400
+ if (options.filter) {
401
+ const helper = SwcUtils.getHelperAndHostSet(currentWin, matchedEl);
402
+ if (!options.filter(event, helper)) {
403
+ continue; // Skip this listener if filter returns false
404
+ }
405
+ }
361
406
  if (options.stopPropagation) event.stopPropagation();
362
407
  if (options.stopImmediatePropagation) event.stopImmediatePropagation();
363
408
  if (options.preventDefault) event.preventDefault();
@@ -416,6 +461,13 @@ export const elementDefine =
416
461
 
417
462
  bindTargets.forEach(t => {
418
463
  const handler = async (event: Event) => {
464
+ // Apply filter if specified
465
+ if (options.filter) {
466
+ const helper = SwcUtils.getHelperAndHostSet(currentWin, t as HTMLElement);
467
+ if (!options.filter(event, helper)) {
468
+ return; // Skip if filter returns false
469
+ }
470
+ }
419
471
  if (options.stopPropagation) event.stopPropagation();
420
472
  if (options.stopImmediatePropagation) event.stopImmediatePropagation();
421
473
  if (options.preventDefault) event.preventDefault();
@@ -439,6 +491,11 @@ export const elementDefine =
439
491
  (this as any)._executeSwcScript('swc-on-connected', hostSet);
440
492
  (this as any)._executeSwcScript('swc-on-after-connected', hostSet);
441
493
  (this as any).__swc_connected = true;
494
+
495
+ // Register to appHost LAST when all lifecycle completed
496
+ if (appHost && typeof (appHost as any)._connected === 'function') {
497
+ (appHost as any)._connected(this);
498
+ }
442
499
  };
443
500
 
444
501
  const originalDisconnected = proto.disconnectedCallback;
@@ -464,6 +521,12 @@ export const elementDefine =
464
521
  (this as any)._executeSwcScript('swc-on-disconnected', hostSet);
465
522
  (this as any)._executeSwcScript('swc-on-after-disconnected', hostSet);
466
523
  (this as any).__swc_connected = false;
524
+
525
+ // Remove from appHost LAST when disconnected
526
+ const appHost = hostSet.$appHost;
527
+ if (appHost && typeof (appHost as any)._disconnected === 'function') {
528
+ (appHost as any)._disconnected(this);
529
+ }
467
530
  };
468
531
 
469
532
  const originalAdopted = proto.adoptedCallback;
@@ -10,3 +10,4 @@ export * from './queryAll';
10
10
  export * from './applyClass';
11
11
  export * from './applyStyle';
12
12
  export * from './applyAttribute';
13
+ export * from './subscribeSwcAppRouteChange';
@@ -1,6 +1,6 @@
1
1
  import { ReflectUtils } from '@dooboostore/core';
2
2
 
3
- export const ON_CONSTRUCTOR_METADATA_KEY = Symbol.for('simple-web-component:on-constructor');
3
+ export const ON_INITIALIZE_METADATA_KEY = Symbol.for('simple-web-component:on-constructor');
4
4
  export const ON_BEFORE_CONNECTED_METADATA_KEY = Symbol.for('simple-web-component:on-before-connected');
5
5
  export const ON_AFTER_CONNECTED_METADATA_KEY = Symbol.for('simple-web-component:on-after-connected');
6
6
  export const ON_BEFORE_DISCONNECTED_METADATA_KEY = Symbol.for('simple-web-component:on-before-disconnected');
@@ -33,7 +33,7 @@ function createLifecycleDecorator(metadataKey: symbol): MethodDecorator {
33
33
  };
34
34
  }
35
35
 
36
- export const onConstructor = createLifecycleDecorator(ON_CONSTRUCTOR_METADATA_KEY);
36
+ export const onInitialize = createLifecycleDecorator(ON_INITIALIZE_METADATA_KEY);
37
37
  export const onBeforeConnected = createLifecycleDecorator(ON_BEFORE_CONNECTED_METADATA_KEY);
38
38
  export const onAfterConnected = createLifecycleDecorator(ON_AFTER_CONNECTED_METADATA_KEY);
39
39
  export const onConnected = onAfterConnected;
@@ -102,11 +102,6 @@ export function query(selectorOrTarget: string, options: any = {}): PropertyDeco
102
102
  };
103
103
  }
104
104
 
105
- export const getQueryMetadata = (target: any): QueryMetadata[] | undefined => {
106
- const constructor = target instanceof Function ? target : target.constructor;
107
- return ReflectUtils.getMetadata(QUERY_METADATA_KEY, constructor);
108
- };
109
-
110
105
  /**
111
106
  * @queryHost decorator - simplified version of @query for :host selector
112
107
  * Usage: @queryHost testzzs: HTMLElement | undefined;
@@ -257,3 +252,8 @@ export function lastAppHost(targetOrOptions?: any, propertyKey?: string | symbol
257
252
  return query(':lastAppHost', {});
258
253
  }
259
254
  }
255
+
256
+ export const getQueryMetadata = (target: any): QueryMetadata[] | undefined => {
257
+ const constructor = target instanceof Function ? target : target.constructor;
258
+ return ReflectUtils.getMetadata(QUERY_METADATA_KEY, constructor);
259
+ };
@@ -102,12 +102,6 @@ export function queryAll(selectorOrTarget: string, options: any = {}): PropertyD
102
102
  });
103
103
  };
104
104
  }
105
-
106
- export const getQueryAllMetadata = (target: any): QueryAllMetadata[] | undefined => {
107
- const constructor = target instanceof Function ? target : target.constructor;
108
- return ReflectUtils.getMetadata(QUERY_ALL_METADATA_KEY, constructor);
109
- };
110
-
111
105
  /**
112
106
  * @hostQueryAll decorator - simplified version of @queryAll for :host selector
113
107
  * Usage: @hostQueryAll()
@@ -261,4 +255,11 @@ export function allLastAppHost(targetOrOptions?: any, propertyKey?: string | sym
261
255
  } else {
262
256
  return queryAll(':lastAppHost', {});
263
257
  }
264
- }
258
+ }
259
+
260
+
261
+ export const getQueryAllMetadata = (target: any): QueryAllMetadata[] | undefined => {
262
+ const constructor = target instanceof Function ? target : target.constructor;
263
+ return ReflectUtils.getMetadata(QUERY_ALL_METADATA_KEY, constructor);
264
+ };
265
+
@@ -0,0 +1,71 @@
1
+ import { ReflectUtils } from '@dooboostore/core';
2
+ import { Router } from '@dooboostore/core-web';
3
+
4
+ export const SUBSCRIBE_SWC_APP_ROUTE_CHANGE_METADATA_KEY = Symbol.for('simple-web-component:subscribe-swc-app-route-change');
5
+
6
+ export interface SwcAppRouteChangeSubscriberMetadata {
7
+ propertyKey: string | symbol;
8
+ pathPattern?: string | string[];
9
+ filter?: (router: Router) => boolean;
10
+ }
11
+
12
+ export interface SwcAppRouteChangeOptions {
13
+ path?: string | string[];
14
+ filter?: (router: Router) => boolean;
15
+ }
16
+
17
+ function createSwcAppRouteChangeSubscriber(options?: SwcAppRouteChangeOptions): MethodDecorator {
18
+ return (target: Object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => {
19
+ const pathPattern = options?.path;
20
+ const filter = options?.filter;
21
+
22
+ const constructor = target.constructor;
23
+ let list = ReflectUtils.getOwnMetadata(SUBSCRIBE_SWC_APP_ROUTE_CHANGE_METADATA_KEY, constructor) as SwcAppRouteChangeSubscriberMetadata[];
24
+ if (!list) {
25
+ list = [];
26
+ ReflectUtils.defineMetadata(SUBSCRIBE_SWC_APP_ROUTE_CHANGE_METADATA_KEY, list, constructor);
27
+ }
28
+ list.push({ propertyKey, pathPattern, filter });
29
+
30
+ return descriptor;
31
+ };
32
+ }
33
+
34
+ // 오버로드 시그니처 - 직접 사용 (괄호 없음)
35
+ export function subscribeSwcAppRouteChange(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
36
+ // 오버로드 시그니처 - 함수 호출 (괄호 있음)
37
+ export function subscribeSwcAppRouteChange(): MethodDecorator;
38
+ export function subscribeSwcAppRouteChange(pathPattern: string): MethodDecorator;
39
+ export function subscribeSwcAppRouteChange(pathPatterns: string[]): MethodDecorator;
40
+ export function subscribeSwcAppRouteChange(options: SwcAppRouteChangeOptions): MethodDecorator;
41
+
42
+ // 실제 구현 (이중 모드)
43
+ export function subscribeSwcAppRouteChange(targetOrOptions?: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): any {
44
+ // 직접 데코레이터로 사용된 경우 (괄호 없음): @subscribeSwcAppRouteChange
45
+ // propertyKey가 string | symbol이고 descriptor가 있으면 직접 사용
46
+ if (propertyKey && (typeof propertyKey === 'string' || typeof propertyKey === 'symbol')) {
47
+ return createSwcAppRouteChangeSubscriber({})(targetOrOptions, propertyKey, descriptor);
48
+ }
49
+
50
+ // 함수로 호출된 경우 (괄호 있음): @subscribeSwcAppRouteChange() / @subscribeSwcAppRouteChange('/path')
51
+ let options: SwcAppRouteChangeOptions = {};
52
+
53
+ if (Array.isArray(targetOrOptions)) {
54
+ // 배열: @subscribeSwcAppRouteChange(['/path1', '/path2'])
55
+ options.path = targetOrOptions;
56
+ } else if (typeof targetOrOptions === 'string') {
57
+ // 문자열: @subscribeSwcAppRouteChange('/path')
58
+ options.path = targetOrOptions;
59
+ } else if (targetOrOptions && typeof targetOrOptions === 'object') {
60
+ // 객체: @subscribeSwcAppRouteChange({ path: '/path', filter: ... })
61
+ options = targetOrOptions;
62
+ }
63
+
64
+ return createSwcAppRouteChangeSubscriber(options);
65
+ }
66
+
67
+ // Helper function to retrieve route change subscribers metadata
68
+ export const getSubscribeSwcAppRouteChangeMetadata = (target: any): SwcAppRouteChangeSubscriberMetadata[] | undefined => {
69
+ const constructor = target instanceof Function ? target : target.constructor;
70
+ return ReflectUtils.getOwnMetadata(SUBSCRIBE_SWC_APP_ROUTE_CHANGE_METADATA_KEY, constructor);
71
+ };