@dooboostore/simple-web-component 1.0.48 → 1.0.50

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 (192) hide show
  1. package/README.md +189 -19
  2. package/dist/cjs/config/config.js +1 -1
  3. package/dist/cjs/config/config.js.map +2 -2
  4. package/dist/cjs/decorators/addEventListener.js +2373 -30
  5. package/dist/cjs/decorators/addEventListener.js.map +3 -3
  6. package/dist/cjs/decorators/applyAttribute.js +31 -0
  7. package/dist/cjs/decorators/applyAttribute.js.map +2 -2
  8. package/dist/cjs/decorators/applyClass.js +55 -18
  9. package/dist/cjs/decorators/applyClass.js.map +3 -3
  10. package/dist/cjs/decorators/applyNode.js +73 -1
  11. package/dist/cjs/decorators/applyNode.js.map +3 -3
  12. package/dist/cjs/decorators/applyProperty.js +107 -3
  13. package/dist/cjs/decorators/applyProperty.js.map +2 -2
  14. package/dist/cjs/decorators/applySlot.js +6 -5
  15. package/dist/cjs/decorators/applySlot.js.map +2 -2
  16. package/dist/cjs/decorators/applyStyle.js +42 -5
  17. package/dist/cjs/decorators/applyStyle.js.map +2 -2
  18. package/dist/cjs/decorators/changedAttribute.js +61 -13
  19. package/dist/cjs/decorators/changedAttribute.js.map +2 -2
  20. package/dist/cjs/decorators/elementDefine.js +254 -439
  21. package/dist/cjs/decorators/elementDefine.js.map +3 -3
  22. package/dist/cjs/decorators/emitCustomEvent.js +55 -0
  23. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  24. package/dist/cjs/decorators/eventMedia.js +84 -0
  25. package/dist/cjs/decorators/eventMedia.js.map +7 -0
  26. package/dist/cjs/decorators/index.js +6 -1
  27. package/dist/cjs/decorators/index.js.map +2 -2
  28. package/dist/cjs/decorators/intersectionObserver.js +200 -0
  29. package/dist/cjs/decorators/intersectionObserver.js.map +7 -0
  30. package/dist/cjs/decorators/mutationObserver.js +186 -55
  31. package/dist/cjs/decorators/mutationObserver.js.map +3 -3
  32. package/dist/cjs/decorators/parameter.js +73 -0
  33. package/dist/cjs/decorators/parameter.js.map +7 -0
  34. package/dist/cjs/decorators/query.js +117 -102
  35. package/dist/cjs/decorators/query.js.map +2 -2
  36. package/dist/cjs/decorators/requestAnimationFrame.js +124 -0
  37. package/dist/cjs/decorators/requestAnimationFrame.js.map +7 -0
  38. package/dist/cjs/decorators/resizeObserver.js +133 -55
  39. package/dist/cjs/decorators/resizeObserver.js.map +2 -2
  40. package/dist/cjs/decorators/setInterval.js +114 -0
  41. package/dist/cjs/decorators/setInterval.js.map +7 -0
  42. package/dist/cjs/decorators/setTimeout.js +117 -0
  43. package/dist/cjs/decorators/setTimeout.js.map +7 -0
  44. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js +9 -0
  45. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +2 -2
  46. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js +9 -0
  47. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
  48. package/dist/cjs/elements/SwcAppMixin.js +19 -3
  49. package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
  50. package/dist/cjs/types/index.js.map +1 -1
  51. package/dist/cjs/types/window.d.js.map +1 -1
  52. package/dist/esm/config/config.js +1 -1
  53. package/dist/esm/config/config.js.map +2 -2
  54. package/dist/esm/decorators/addEventListener.js +2374 -30
  55. package/dist/esm/decorators/addEventListener.js.map +3 -3
  56. package/dist/esm/decorators/applyAttribute.js +31 -0
  57. package/dist/esm/decorators/applyAttribute.js.map +2 -2
  58. package/dist/esm/decorators/applyClass.js +55 -18
  59. package/dist/esm/decorators/applyClass.js.map +3 -3
  60. package/dist/esm/decorators/applyNode.js +73 -1
  61. package/dist/esm/decorators/applyNode.js.map +3 -3
  62. package/dist/esm/decorators/applyProperty.js +107 -3
  63. package/dist/esm/decorators/applyProperty.js.map +2 -2
  64. package/dist/esm/decorators/applySlot.js +6 -5
  65. package/dist/esm/decorators/applySlot.js.map +2 -2
  66. package/dist/esm/decorators/applyStyle.js +42 -5
  67. package/dist/esm/decorators/applyStyle.js.map +2 -2
  68. package/dist/esm/decorators/changedAttribute.js +62 -13
  69. package/dist/esm/decorators/changedAttribute.js.map +2 -2
  70. package/dist/esm/decorators/elementDefine.js +261 -446
  71. package/dist/esm/decorators/elementDefine.js.map +3 -3
  72. package/dist/esm/decorators/emitCustomEvent.js +55 -0
  73. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  74. package/dist/esm/decorators/eventMedia.js +66 -0
  75. package/dist/esm/decorators/eventMedia.js.map +7 -0
  76. package/dist/esm/decorators/index.js +6 -1
  77. package/dist/esm/decorators/index.js.map +2 -2
  78. package/dist/esm/decorators/intersectionObserver.js +182 -0
  79. package/dist/esm/decorators/intersectionObserver.js.map +7 -0
  80. package/dist/esm/decorators/mutationObserver.js +187 -55
  81. package/dist/esm/decorators/mutationObserver.js.map +3 -3
  82. package/dist/esm/decorators/parameter.js +54 -0
  83. package/dist/esm/decorators/parameter.js.map +7 -0
  84. package/dist/esm/decorators/query.js +117 -102
  85. package/dist/esm/decorators/query.js.map +2 -2
  86. package/dist/esm/decorators/requestAnimationFrame.js +105 -0
  87. package/dist/esm/decorators/requestAnimationFrame.js.map +7 -0
  88. package/dist/esm/decorators/resizeObserver.js +134 -55
  89. package/dist/esm/decorators/resizeObserver.js.map +2 -2
  90. package/dist/esm/decorators/setInterval.js +95 -0
  91. package/dist/esm/decorators/setInterval.js.map +7 -0
  92. package/dist/esm/decorators/setTimeout.js +98 -0
  93. package/dist/esm/decorators/setTimeout.js.map +7 -0
  94. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js +9 -0
  95. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +2 -2
  96. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js +9 -0
  97. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
  98. package/dist/esm/elements/SwcAppMixin.js +20 -4
  99. package/dist/esm/elements/SwcAppMixin.js.map +2 -2
  100. package/dist/esm/types/index.js.map +1 -1
  101. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +4961 -1628
  102. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  103. package/dist/types/config/config.d.ts.map +1 -1
  104. package/dist/types/decorators/addEventListener.d.ts +1046 -33
  105. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  106. package/dist/types/decorators/applyAttribute.d.ts +24 -34
  107. package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
  108. package/dist/types/decorators/applyClass.d.ts +36 -34
  109. package/dist/types/decorators/applyClass.d.ts.map +1 -1
  110. package/dist/types/decorators/applyNode.d.ts +32 -20
  111. package/dist/types/decorators/applyNode.d.ts.map +1 -1
  112. package/dist/types/decorators/applyProperty.d.ts +61 -34
  113. package/dist/types/decorators/applyProperty.d.ts.map +1 -1
  114. package/dist/types/decorators/applySlot.d.ts.map +1 -1
  115. package/dist/types/decorators/applyStyle.d.ts +27 -12
  116. package/dist/types/decorators/applyStyle.d.ts.map +1 -1
  117. package/dist/types/decorators/changedAttribute.d.ts +12 -3
  118. package/dist/types/decorators/changedAttribute.d.ts.map +1 -1
  119. package/dist/types/decorators/elementDefine.d.ts +0 -14
  120. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  121. package/dist/types/decorators/emitCustomEvent.d.ts +28 -4
  122. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  123. package/dist/types/decorators/eventMedia.d.ts +25 -0
  124. package/dist/types/decorators/eventMedia.d.ts.map +1 -0
  125. package/dist/types/decorators/index.d.ts +6 -1
  126. package/dist/types/decorators/index.d.ts.map +1 -1
  127. package/dist/types/decorators/intersectionObserver.d.ts +58 -0
  128. package/dist/types/decorators/intersectionObserver.d.ts.map +1 -0
  129. package/dist/types/decorators/mutationObserver.d.ts +44 -25
  130. package/dist/types/decorators/mutationObserver.d.ts.map +1 -1
  131. package/dist/types/decorators/parameter.d.ts +35 -0
  132. package/dist/types/decorators/parameter.d.ts.map +1 -0
  133. package/dist/types/decorators/query.d.ts +57 -14
  134. package/dist/types/decorators/query.d.ts.map +1 -1
  135. package/dist/types/decorators/requestAnimationFrame.d.ts +47 -0
  136. package/dist/types/decorators/requestAnimationFrame.d.ts.map +1 -0
  137. package/dist/types/decorators/resizeObserver.d.ts +43 -23
  138. package/dist/types/decorators/resizeObserver.d.ts.map +1 -1
  139. package/dist/types/decorators/setInterval.d.ts +41 -0
  140. package/dist/types/decorators/setInterval.d.ts.map +1 -0
  141. package/dist/types/decorators/setTimeout.d.ts +41 -0
  142. package/dist/types/decorators/setTimeout.d.ts.map +1 -0
  143. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts +7 -1
  144. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +1 -1
  145. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +6 -0
  146. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +1 -1
  147. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  148. package/dist/types/types/index.d.ts +104 -0
  149. package/dist/types/types/index.d.ts.map +1 -1
  150. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +4961 -1628
  151. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  152. package/package.json +7 -5
  153. package/src/config/config.ts +1 -1
  154. package/src/decorators/addEventListener.ts +1446 -34
  155. package/src/decorators/applyAttribute.ts +60 -36
  156. package/src/decorators/applyClass.ts +86 -26
  157. package/src/decorators/applyNode.ts +124 -36
  158. package/src/decorators/applyProperty.ts +172 -40
  159. package/src/decorators/applySlot.ts +6 -5
  160. package/src/decorators/applyStyle.ts +74 -17
  161. package/src/decorators/changedAttribute.ts +76 -15
  162. package/src/decorators/elementDefine.ts +310 -582
  163. package/src/decorators/emitCustomEvent.ts +73 -5
  164. package/src/decorators/eventMedia.ts +78 -0
  165. package/src/decorators/index.ts +6 -1
  166. package/src/decorators/intersectionObserver.ts +275 -0
  167. package/src/decorators/mutationObserver.ts +282 -89
  168. package/src/decorators/parameter.ts +81 -0
  169. package/src/decorators/query.ts +168 -126
  170. package/src/decorators/requestAnimationFrame.ts +158 -0
  171. package/src/decorators/resizeObserver.ts +211 -89
  172. package/src/decorators/setInterval.ts +136 -0
  173. package/src/decorators/setTimeout.ts +139 -0
  174. package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +17 -0
  175. package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +19 -0
  176. package/src/elements/SwcAppMixin.ts +20 -5
  177. package/src/types/index.ts +141 -0
  178. package/src/types/window.d.ts +2 -18
  179. package/dist/cjs/decorators/queryAll.js +0 -181
  180. package/dist/cjs/decorators/queryAll.js.map +0 -7
  181. package/dist/cjs/examples/RxJSOperatorsExample.js +0 -285
  182. package/dist/cjs/examples/RxJSOperatorsExample.js.map +0 -7
  183. package/dist/esm/decorators/queryAll.js +0 -162
  184. package/dist/esm/decorators/queryAll.js.map +0 -7
  185. package/dist/esm/examples/RxJSOperatorsExample.js +0 -266
  186. package/dist/esm/examples/RxJSOperatorsExample.js.map +0 -7
  187. package/dist/types/decorators/queryAll.d.ts +0 -22
  188. package/dist/types/decorators/queryAll.d.ts.map +0 -1
  189. package/dist/types/examples/RxJSOperatorsExample.d.ts +0 -94
  190. package/dist/types/examples/RxJSOperatorsExample.d.ts.map +0 -1
  191. package/src/decorators/queryAll.ts +0 -186
  192. package/src/examples/RxJSOperatorsExample.ts +0 -279
@@ -1,39 +1,34 @@
1
- import {ActionExpression, FunctionUtils, ReflectUtils, Subject} from '@dooboostore/core';
2
- import {debounceTime, distinctUntilChanged, throttleTime} from '@dooboostore/core/message/operators';
3
- import {getAddEventListenerMetadata} from './addEventListener';
4
- import {getMutationObserverMetadata} from './mutationObserver';
5
- import type {MutationObserverBaseOptions, MutationObserverMetadata} from './mutationObserver';
6
- import {getResizeObserverMetadata} from './resizeObserver';
7
- import type {ResizeObserverMetadata} from './resizeObserver';
1
+ import {ActionExpression, FunctionUtils, ReflectUtils} from '@dooboostore/core';
2
+ import {EventListenerLifeCycler} from './addEventListener';
3
+ import {MutationObserverLifeCycler} from './mutationObserver';
4
+ import {ResizeObserverLifeCycler} from './resizeObserver';
5
+ import {IntersectionObserverLifeCycler} from './intersectionObserver';
8
6
  import {findAllLifecycleMetadata, findAllOnConnectedAfterMetadata, findAllOnConnectedBeforeMetadata, findAllOnConnectedMetadata, ON_AFTER_ADOPTED_METADATA_KEY, ON_AFTER_DISCONNECTED_METADATA_KEY, ON_BEFORE_ADOPTED_METADATA_KEY, ON_BEFORE_DISCONNECTED_METADATA_KEY, ON_CONNECTED_COMPLETED_METADATA_KEY, ON_INITIALIZE_METADATA_KEY} from './lifecycles';
9
- import {getEmitCustomEventMetadataList} from './emitCustomEvent';
10
- import {convertAttributeValue, findAllAttributeChangedMetadata} from './changedAttribute';
11
- import {findAllAttributeApplyMetadata, findAllAttributeMetadata, getAttributeValue} from './applyAttribute';
12
- import {getQueryMetadata} from './query';
13
- import {getQueryAllMetadata} from './queryAll';
7
+ import {EmitCustomEventLifeCycler} from './emitCustomEvent';
8
+ import {ChangedAttributeLifeCycler} from './changedAttribute';
9
+ import {findAllAttributeApplyMetadata, findAllAttributeMetadata} from './applyAttribute';
10
+ import {getQueryMetadata, getQueryAllMetadata} from './query';
14
11
  import {SwcUtils} from '../utils/Utils';
15
12
  import {DOM_EVENT_NAMES, HTML_TAG_ENTRIES} from '../config/config';
16
13
  import {SituationTypeContainer, SituationTypeContainers} from '@dooboostore/simple-boot/decorators/inject/Inject';
17
- import {HelperHostSet, HostSet, InjectSituationType} from '../types';
14
+ import {FirstCheckMaker} from '@dooboostore/simple-boot';
15
+ import {buildSwcParameterArgs, getParameterMetadata} from './parameter';
16
+ import {ElementDefineLifeCycler, HelperHostSet, HostSet, InjectSituationType, IntersectionObserverSet, MutationObserverSet, OnConnectedResult, ResizeObserverSet, ResizeObserverSetEntry, SwcRootType} from '../types';
18
17
  import {ConvertUtils, ElementApply} from '@dooboostore/core-web';
19
18
  import {isSSR} from "../elements/SwcAppMixin";
20
19
  import {findAllStateMetadata} from "./state";
21
20
  import {findAllPropertyMetadata} from "./applyProperty";
21
+ import {MessageSubscribeLifeCycler} from "./subscribeSwcAppMessageWhileConnected";
22
+ import {RouteSubscribeLifeCycler} from "./subscribeSwcAppRouteChangeWhileConnected";
23
+ import {SetIntervalLifeCycler} from "./setInterval";
24
+ import {SetTimeoutLifeCycler} from "./setTimeout";
25
+ import {RequestAnimationFrameLifeCycler} from "./requestAnimationFrame";
26
+ import {EventMediaLifeCycler} from "./eventMedia";
22
27
 
23
28
  // --- Core Interfaces & Types ---
24
29
 
25
30
  export const ELEMENT_CONFIG_KEY = Symbol.for('simple-web-component:element-config');
26
31
 
27
- /** 바인딩된 이벤트 리스너 한 건 */
28
- export interface BoundListener {
29
- target: EventTarget;
30
- type: string;
31
- handler: EventListener;
32
- options: AddEventListenerOptions;
33
- onRemoves?: Array<{ fn: (target: EventTarget, optionValue: unknown) => void; opts: unknown }>;
34
- subscription?: { unsubscribe: () => void };
35
- }
36
-
37
32
  export interface ElementConfig {
38
33
  extends?: string;
39
34
  observedAttributes?: string[];
@@ -245,6 +240,13 @@ const setupPrototype = (proto: any, win: Window) => {
245
240
  if (typeof (this as any)[methodName] !== 'function') return;
246
241
  const useHostSet = hostSet ?? SwcUtils.getHelperAndHostSet(win, this);
247
242
  const app = useHostSet?.$appHost?.simpleApplication;
243
+
244
+ // @hostSet/@helperHostSet/@helperSet(parameter.ts)를 위한 값. lifecycle 메서드는
245
+ // event/matched 개념이 없으므로 이 세 가지만 채운다.
246
+ const helperSetValue = SwcUtils.getHelperSet(win);
247
+ const helperHostSetValue = {...helperSetValue, ...useHostSet, $this: this};
248
+ const kindValues = {hostSet: useHostSet, helperHostSet: helperHostSetValue, helperSet: helperSetValue};
249
+
248
250
  // console.log('---->hh',app, this, methodName);
249
251
  if (app) {
250
252
  const otherStorage = new Map<any, any>();
@@ -257,16 +259,30 @@ const setupPrototype = (proto: any, win: Window) => {
257
259
  }), new SituationTypeContainer({situationType: InjectSituationType.LAST_APP_HOST, data: useHostSet.$lastAppHost})]);
258
260
  otherStorage.set(SituationTypeContainers, situations);
259
261
 
262
+ // @hostSet/@helperHostSet/@helperSet가 붙은 파라미터는 @Inject/situationType 기반
263
+ // 해석보다 먼저 처리되고(firstCheckMaker), 그 외 파라미터는 기존처럼 @Inject/타입
264
+ // 기반으로 정상 해석된다 — 같은 메서드에서 두 방식을 섞어 써도 된다.
265
+ const firstCheckMaker: FirstCheckMaker = ({target, targetKey}, token, idx) => {
266
+ const saves = getParameterMetadata(target, targetKey!);
267
+ const found = saves.find(s => s.index === idx);
268
+ return found ? (kindValues as any)[found.kind] : undefined;
269
+ };
270
+
260
271
  return app.simstanceManager.executeBindParameterSim(
261
272
  {
262
273
  target: this,
263
274
  targetKey: methodName,
264
- inputParameters: extraArgs
275
+ inputParameters: extraArgs,
276
+ firstCheckMaker: [firstCheckMaker]
265
277
  },
266
278
  otherStorage
267
279
  );
268
280
  } else {
269
- return (this as any)[methodName](...extraArgs);
281
+ // SimpleApplication(DI 컨테이너)이 없는 standalone 사용 시에도 @hostSet/@helperHostSet/
282
+ // @helperSet만큼은 buildSwcParameterArgs로 주입해준다 — 해당 데코레이터가 하나도 없으면
283
+ // extraArgs를 그대로 반환하므로(기존 동작 그대로) 하위호환 깨지지 않는다.
284
+ const args = buildSwcParameterArgs(this, methodName, kindValues, extraArgs);
285
+ return (this as any)[methodName](...args);
270
286
  }
271
287
  };
272
288
 
@@ -350,12 +366,6 @@ export const elementDefine =
350
366
  metadata.extends = extendsTagName;
351
367
  }
352
368
 
353
- const emitCustomEventList = getEmitCustomEventMetadataList(constructor) || [];
354
- const emitHostCustomEventList = emitCustomEventList.filter(meta => meta.selector === '$this' || meta.selector === '');
355
- const addEventListenerList = getAddEventListenerMetadata(constructor) || [];
356
- const mutationObserverList = getMutationObserverMetadata(constructor) || [];
357
- const resizeObserverList = getResizeObserverMetadata(constructor) || [];
358
- const attrChangeMap = findAllAttributeChangedMetadata(constructor);
359
369
  const attributeList = findAllAttributeMetadata(constructor);
360
370
  const applyAttributeMap = findAllAttributeApplyMetadata(constructor);
361
371
 
@@ -368,37 +378,258 @@ export const elementDefine =
368
378
  const hostAttributes = attributeList.filter(it => it.selector === '$this').map(it => it.propertyKey || String(it.propertyKey));
369
379
  // Get original static observedAttributes before they're overwritten by Object.defineProperty
370
380
  const originalStaticObservedAttributes = (constructor.observedAttributes ?? []) as string[];
371
- const mergedObservedAttributes = [...new Set([...(metadata.observedAttributes ?? []), ...originalStaticObservedAttributes, ...attrChangeMap.keys(), ...attributeApplyNames, ...hostAttributes, ...emitHostCustomEventList.map(it => (it.options as any).attributeName).filter(Boolean), ...swcLifecycleAttributes, ...swcOnEvents])];
372
- // const connectedInnerHtmlList = getOnConnectedInnerHtmlMetadata(constructor) || [];
373
381
 
374
382
  const proto = constructor.prototype;
375
383
  setupPrototype(proto, win);
376
384
 
377
385
  // 값을 기대하지않는다.
378
386
  let helperHostSet: HelperHostSet | null = null;
379
- // 이벤트 리스너 / MutationObserver / ResizeObserver 인스턴스 (클로저 변수 — connected/disconnected 공유)
380
- let boundListeners: BoundListener[] = [];
381
- let shadowMutationObserver: MutationObserver | null = null;
382
- let lightMutationObserver: MutationObserver | null = null;
383
- let resizeObserver: ResizeObserver | null = null;
384
- // 생성된 모든 track(Mutation)Observer (disconnected 시 정리)
385
- let resizeObservers: Array<ResizeObserver | MutationObserver> = [];
386
- // 메타별 removeObserver 콜백 목록 (disconnected 시 호출)
387
- let removeObserverCallbacks: Array<{ fn: (target: Element, optionValue: unknown) => void; target: any; opts: unknown }> = [];
387
+ // ── cyclers: 데코레이터별 라이프사이클 위임 클래스 (elementDefine 시 1회 생성, 계속 재사용) ──
388
+ const eventCycler = new EventListenerLifeCycler();
389
+ const cyclers: ElementDefineLifeCycler[] = [
390
+ eventCycler,
391
+ new ChangedAttributeLifeCycler(),
392
+ new EmitCustomEventLifeCycler(),
393
+ new ResizeObserverLifeCycler(),
394
+ new IntersectionObserverLifeCycler(),
395
+ new MutationObserverLifeCycler(),
396
+ new MessageSubscribeLifeCycler(),
397
+ new RouteSubscribeLifeCycler(),
398
+ new SetIntervalLifeCycler(),
399
+ new SetTimeoutLifeCycler(),
400
+ new RequestAnimationFrameLifeCycler(),
401
+ new EventMediaLifeCycler(),
402
+ ];
403
+
404
+ // observedAttributes 기여 cycler (define-time, constructor 기반)
405
+ const cyclerObservedAttributes = cyclers.flatMap(c => c.getObservedAttributeNames?.(constructor) ?? []);
406
+ const mergedObservedAttributes = [...new Set([...(metadata.observedAttributes ?? []), ...originalStaticObservedAttributes, ...cyclerObservedAttributes, ...attributeApplyNames, ...hostAttributes, ...swcLifecycleAttributes, ...swcOnEvents])];
407
+
408
+ // 렌더 후 모든 시클러 onConnected 호출 → Set 수집 후 observer 생성
409
+ const buildObservers = async (helperHostSet: HelperHostSet): Promise<void> => {
410
+ const inst = helperHostSet.$this;
411
+ const observers: Array<MutationObserver | ResizeObserver | IntersectionObserver> = [];
412
+ let resizeObserverSet: ResizeObserverSet = [];
413
+ let mutationObserverSet: MutationObserverSet = [];
414
+ let intersectionObserverSet: IntersectionObserverSet = [];
415
+
416
+ // delegate 추적 root 해석: shadow/light/auto/all → 실제 root 목록
417
+ const resolveDelegateRoots = (delegateRoot: SwcRootType | undefined): (HTMLElement | ShadowRoot)[] => {
418
+ const r = delegateRoot || 'auto';
419
+ const roots: (HTMLElement | ShadowRoot)[] = [];
420
+ if (r === 'auto') roots.push(inst.shadowRoot || inst);
421
+ else if (r === 'light') roots.push(inst);
422
+ else if (r === 'shadow' && inst.shadowRoot) roots.push(inst.shadowRoot);
423
+ else if (r === 'all') { roots.push(inst); if (inst.shadowRoot) roots.push(inst.shadowRoot); }
424
+ return roots;
425
+ };
426
+
427
+ // 1. 모든 시클러 onConnected → observer Set 수집 (여러 시클러가 반환한 Set 을 전부 누적)
428
+ for (const c of cyclers) {
429
+ const result = (await c.onConnected?.(helperHostSet, resizeObserverSet)) as OnConnectedResult | void;
430
+ if (!result) continue;
431
+ if (result.resizeObserverSet?.length) resizeObserverSet = [...resizeObserverSet, ...result.resizeObserverSet];
432
+ if (result.mutationObserverSet?.length) mutationObserverSet = [...mutationObserverSet, ...result.mutationObserverSet];
433
+ if (result.intersectionObserverSet?.length) intersectionObserverSet = [...intersectionObserverSet, ...result.intersectionObserverSet];
434
+ }
435
+
436
+ // 2. 타입별로 observer 생성
437
+ // ResizeObserver 먼저: mutation callback 이 observe/unobserve 를 주입받아야 함
438
+ // observeTargets 중 delegate 항목은 셀렉터 문자열이라 초기 observe 하지 않는다 (동적 추적 전용).
439
+ // 동적 observe/unobserve 는 중복 호출을 막기 위해 observe 대상 WeakSet 으로 추적한다.
440
+ const resizeDelegates = (resizeObserverSet ?? []).filter(t => t.delegate && typeof t.target === 'string') as Array<ResizeObserverSetEntry & { target: string }>;
441
+ let resizeObserve: ((el: Element, opts?: ResizeObserverOptions) => void) | undefined;
442
+ let resizeUnobserve: ((el: Element) => void) | undefined;
443
+ const resizeObserved = new WeakSet<Element>();
444
+ if (resizeObserverSet?.length) {
445
+ const roCallbacks = [...new Set(resizeObserverSet.map(e => e.callback))];
446
+ const ro = new win.ResizeObserver((entries, obs) => {
447
+ for (const cb of roCallbacks) cb(entries, obs);
448
+ });
449
+ for (const e of resizeObserverSet) {
450
+ if (e.delegate) continue;
451
+ if (typeof e.target === 'string') {
452
+ // 문자열(non-delegate)이면 root 에서 검색된 요소들을 observe 대상으로 추가한다.
453
+ const scope = resolveDelegateRoots(e.delegateRoot);
454
+ for (const sr of scope) {
455
+ sr.querySelectorAll(e.target).forEach(el => {
456
+ if (!resizeObserved.has(el)) { ro.observe(el, e.options); resizeObserved.add(el); }
457
+ });
458
+ }
459
+ continue;
460
+ }
461
+ ro.observe(e.target as Element, e.options);
462
+ if (e.target instanceof Element) resizeObserved.add(e.target);
463
+ }
464
+ resizeObserve = (el, opts) => {
465
+ if (resizeObserved.has(el)) return;
466
+ ro.observe(el, opts);
467
+ resizeObserved.add(el);
468
+ };
469
+ resizeUnobserve = (el) => {
470
+ if (!resizeObserved.has(el)) return;
471
+ ro.unobserve(el);
472
+ resizeObserved.delete(el);
473
+ };
474
+ observers.push(ro);
475
+ }
476
+
477
+ // IntersectionObserver: 옵션 그룹별로 1개씩 생성.
478
+ // delegate 항목은 동적 추적용 셀렉터이므로 observe/unobserve 함수를 MutationObserver 콜백에 넘긴다.
479
+ // 동적 observe/unobserve 중복 방지를 위해 그룹별 WeakSet 으로 추적한다.
480
+ const intersectionDelegates: Array<{ target: string; delegateRoot?: SwcRootType; observe: (el: Element) => void; unobserve: (el: Element) => void }> = [];
481
+ for (const group of intersectionObserverSet) {
482
+ const ioCallbacks = [...new Set(group.observeTargets.map(e => e.callback))];
483
+ const io = new win.IntersectionObserver((entries, obs) => {
484
+ for (const cb of ioCallbacks) cb(entries, obs);
485
+ }, group.options);
486
+ const ioObserved = new WeakSet<Element>();
487
+ for (const e of group.observeTargets) {
488
+ if (e.delegate) {
489
+ const target = e.target as string;
490
+ const observe = (el: Element) => {
491
+ if (ioObserved.has(el)) return;
492
+ io.observe(el);
493
+ ioObserved.add(el);
494
+ };
495
+ const unobserve = (el: Element) => {
496
+ if (!ioObserved.has(el)) return;
497
+ io.unobserve(el);
498
+ ioObserved.delete(el);
499
+ };
500
+ intersectionDelegates.push({ target, delegateRoot: e.delegateRoot, observe, unobserve });
501
+ continue;
502
+ }
503
+ if (typeof e.target === 'string') {
504
+ // 문자열(non-delegate)이면 root 에서 검색된 요소들을 observe 대상으로 추가한다.
505
+ const scope = resolveDelegateRoots(e.delegateRoot);
506
+ for (const sr of scope) {
507
+ sr.querySelectorAll(e.target).forEach(el => {
508
+ if (!ioObserved.has(el)) { io.observe(el); ioObserved.add(el); }
509
+ });
510
+ }
511
+ continue;
512
+ }
513
+ io.observe(e.target as Element);
514
+ if (e.target instanceof Element) ioObserved.add(e.target);
515
+ }
516
+ observers.push(io);
517
+ }
518
+
519
+ // resize/intersection delegate 동적 추적 — MutationObserver 콜백에서 추가/제거된 요소를 observe/unobserve 한다.
520
+ const handleResizeDelegate = (n: Node, observe: boolean) => {
521
+ if (!resizeObserve || !resizeUnobserve || n.nodeType !== 1) return;
522
+ const el = n as HTMLElement;
523
+ for (const d of resizeDelegates) {
524
+ const isThis = d.target === '$this' || d.target === '';
525
+ const matchesSel = (node: any) => {
526
+ if (!node || node.nodeType !== 1) return false;
527
+ if (isThis) return true;
528
+ return (node as HTMLElement).matches?.(d.target) || !!(node as HTMLElement).closest?.(d.target);
529
+ };
530
+ if (matchesSel(el)) {
531
+ observe ? resizeObserve(el, d.options)
532
+ : resizeUnobserve(el);
533
+ } else if (!isThis) {
534
+ el.querySelectorAll(d.target).forEach(sub => {
535
+ observe ? resizeObserve(sub as HTMLElement, d.options)
536
+ : resizeUnobserve(sub as HTMLElement);
537
+ });
538
+ }
539
+ }
540
+ };
541
+ const handleIntersectionDelegate = (n: Node, observe: boolean) => {
542
+ if (n.nodeType !== 1) return;
543
+ const el = n as HTMLElement;
544
+ for (const d of intersectionDelegates) {
545
+ const isThis = d.target === '$this' || d.target === '';
546
+ const matchesSel = (node: any) => {
547
+ if (!node || node.nodeType !== 1) return false;
548
+ if (isThis) return true;
549
+ return (node as HTMLElement).matches?.(d.target) || !!(node as HTMLElement).closest?.(d.target);
550
+ };
551
+ if (matchesSel(el)) {
552
+ observe ? d.observe(el) : d.unobserve(el);
553
+ } else if (!isThis) {
554
+ el.querySelectorAll(d.target).forEach(sub => {
555
+ observe ? d.observe(sub as HTMLElement) : d.unobserve(sub as HTMLElement);
556
+ });
557
+ }
558
+ }
559
+ };
560
+
561
+ if (mutationObserverSet?.length || resizeDelegates.length > 0 || intersectionDelegates.length > 0) {
562
+ // 하나의 MutationObserver 로 resize/intersection delegate 동적 추적 + mutation 콜백을 함께 처리
563
+ const callbacks = [...new Set((mutationObserverSet ?? []).map(e => e.callback))];
564
+ const mo = new win.MutationObserver((mutations, obs) => {
565
+ for (const mut of mutations) {
566
+ Array.from(mut.addedNodes || []).forEach(n => { handleResizeDelegate(n as Node, true); handleIntersectionDelegate(n as Node, true); });
567
+ Array.from(mut.removedNodes || []).forEach(n => { handleResizeDelegate(n as Node, false); handleIntersectionDelegate(n as Node, false); });
568
+ }
569
+ for (const cb of callbacks) cb(mutations, obs);
570
+ });
571
+
572
+ // observe 대상 전체(엔트리 + delegate 추적 root)를 하나로 모아 중복 observe 를 제거한다.
573
+ // 같은 target 이 여러 소스에서 지정되면 MutationObserverInit 을 합집합(최대 감지범위)으로 병합한다.
574
+ const moObserveTargets = new Map<Element | ShadowRoot, MutationObserverInit>();
575
+ const mergeMutationInit = (existing: MutationObserverInit | undefined, next: MutationObserverInit): MutationObserverInit => {
576
+ if (!existing) return next;
577
+ const mergedFilter = existing.attributeFilter || next.attributeFilter
578
+ ? [...new Set([...(existing.attributeFilter ?? []), ...(next.attributeFilter ?? [])])]
579
+ : undefined;
580
+ return {
581
+ childList: existing.childList || next.childList,
582
+ attributes: existing.attributes || next.attributes,
583
+ characterData: existing.characterData || next.characterData,
584
+ subtree: existing.subtree || next.subtree,
585
+ attributeOldValue: existing.attributeOldValue || next.attributeOldValue,
586
+ characterDataOldValue: existing.characterDataOldValue || next.characterDataOldValue,
587
+ attributeFilter: mergedFilter,
588
+ };
589
+ };
590
+ const addObserveTarget = (target: Element | ShadowRoot, options: MutationObserverInit) => {
591
+ moObserveTargets.set(target, mergeMutationInit(moObserveTargets.get(target), options));
592
+ };
593
+
594
+ // 1) mutationObserverSet 엔트리
595
+ for (const e of mutationObserverSet ?? []) {
596
+ if (e.delegate) continue;
597
+ const init = e.options ?? { childList: true };
598
+ if (typeof e.target === 'string') {
599
+ // 문자열(non-delegate)이면 root 에서 검색된 요소들을 observe 대상으로 추가한다.
600
+ const scope = resolveDelegateRoots(e.delegateRoot);
601
+ for (const sr of scope) {
602
+ sr.querySelectorAll(e.target).forEach(el => addObserveTarget(el, init));
603
+ }
604
+ continue;
605
+ }
606
+ addObserveTarget(e.target, init);
607
+ }
608
+
609
+ // 2) delegate 추적 root: shadow/light/all/auto 로 해석해 전부 observe
610
+ for (const d of resizeDelegates) {
611
+ for (const r of resolveDelegateRoots(d.delegateRoot)) addObserveTarget(r, { childList: true, subtree: true });
612
+ }
613
+ for (const d of intersectionDelegates) {
614
+ for (const r of resolveDelegateRoots(d.delegateRoot)) addObserveTarget(r, { childList: true, subtree: true });
615
+ }
616
+
617
+ for (const [target, options] of moObserveTargets) {
618
+ mo.observe(target, options);
619
+ }
620
+ observers.push(mo);
621
+ }
622
+
623
+ inst.__swc_observers = observers;
624
+ };
625
+
388
626
  const originalConnected = proto.connectedCallback;
389
627
  proto.connectedCallback = async function () {
390
628
  ensureInit(this);
391
- // 재연결 시 누적 방지 — 클로저 변수 리셋
392
- boundListeners = [];
393
- resizeObservers = [];
394
- removeObserverCallbacks = [];
395
- // connected 됐을시 최신화
629
+ // 재연결 시 누적 방지 — cycler 내부 리소스 초기화
396
630
  helperHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
397
631
  const appHost = helperHostSet.$appHost;
398
632
  const useSsr = isSSR(this);
399
- // console.log('isSsr', useSsr, this);
400
- // console.log('----helperHostSet--->', this, helperHostSet, useSsr)
401
- // let connectedApp: SwcAppInterface |undefined;
402
633
  try {
403
634
  if (appHost && typeof (appHost as any)._connected === 'function') {
404
635
  await (appHost as any)._connected(this);
@@ -407,27 +638,19 @@ export const elementDefine =
407
638
  (appHost as any)._connected_safari_and_standby.push(this);
408
639
  }
409
640
 
410
- // const hostSet = SwcUtils.getHostSet(this as any);
411
641
  const conf = getElementConfig(this);
412
642
  const currentWin = (this as any)._resolveWindow(conf);
413
643
 
414
- const bMethods = findAllOnConnectedBeforeMetadata(this); //.filter(it => useSsr ? !it.options.ssrFirst : true);
415
- // console.log('beforeConnected', bMethods)
416
- for (const m of bMethods) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
644
+ // before-connected 라이프사이클 메서드 (@onConnectedBefore)
645
+ for (const m of findAllOnConnectedBeforeMetadata(this)) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
417
646
  (this as any)._executeSwcScript('swc-on-before-connected', helperHostSet);
418
647
 
419
- // console.log('vvvvvvvvvvvvvvvu-seSsr-vvvvvvvvvv>', useSsr, this.tagName, this.getAttribute('seq'))
420
- // // ssr 처리라서 이미 내려준거그대로 상요하면된다 따라서 호출안한다
421
- // const targetConnectedList = findAllOnConnectedMetadata(constructor).filter(it => useSsr ? !it.options.ssrFirst : true);
648
+ // ── 렌더 (@onConnected / @onConnectedBody*) — elementDefine 책임 ──
422
649
  const targetConnectedList = useSsr ? [] : findAllOnConnectedMetadata(constructor);
423
650
  const shadowMode = conf?.useShadow || targetConnectedList.find(it => it.options.useShadow)?.options.useShadow;
424
- // console.log('--------->', this.tagName, shadowMode && !this.shadowRoot)
425
651
  if (shadowMode && !this.shadowRoot) {
426
652
  const mode = shadowMode === true ? 'open' : shadowMode;
427
- console.log('tagName=', this.tagName, 'mode', mode, this.shadowRoot);
428
- // if (this.tagName!=='INDEX-ROUTER') {
429
653
  this.attachShadow({ mode: mode as ShadowRootMode });
430
- // }
431
654
  }
432
655
 
433
656
  const stateContext: any = { ...helperHostSet };
@@ -438,20 +661,14 @@ export const elementDefine =
438
661
  const shadowChildren: Node[] = [];
439
662
  const lightChildren: Node[] = [];
440
663
 
441
- console.log('targetConnectList', this.tagName, targetConnectedList.length);
442
664
  if (targetConnectedList.length > 0) {
443
- // let sContent = '',
444
- // lContent = '';
445
665
  for (const meta of targetConnectedList) {
446
- // const res = (this as any)[meta.propertyKey]();
447
666
  let res = await (this as any)._invokeLifecycleMethod(meta.propertyKey, helperHostSet);
448
- // res = SwcUtils.projectProcessHtml(this, res);
449
667
  if (typeof res === 'string') {
450
668
  const htmlTemplateElement = doc.createElement('template');
451
- htmlTemplateElement.innerHTML = res; //SwcUtils.projectProcessHtml(this._swcId, res, doc);
669
+ htmlTemplateElement.innerHTML = res;
452
670
  res = htmlTemplateElement.content;
453
671
  }
454
-
455
672
  if (res) {
456
673
  const nodes = Array.isArray(res) ? res : [res];
457
674
  if (meta.options.useShadow || conf?.useShadow) {
@@ -487,440 +704,20 @@ export const elementDefine =
487
704
  globalDelegatedRoots.add(root);
488
705
  }
489
706
 
490
- // Separate delegate and non-delegate listeners
491
- const delegateListeners: typeof addEventListenerList = [];
492
- const nonDelegateListeners: typeof addEventListenerList = [];
493
-
494
- addEventListenerList.forEach(meta => {
495
- // Function-based selectors cannot be used with delegate listeners
496
- const isStringSelector = typeof meta.selector === 'string';
497
- if (meta.options.delegate && isStringSelector && meta.selector !== '$window' && meta.selector !== '$document' && meta.selector !== '$host' && meta.selector !== '$appHost' && meta.selector !== '$firstHost' && meta.selector !== '$lastHost' && meta.selector !== '$firstAppHost' && meta.selector !== '$lastAppHost' && meta.selector !== '$hosts' && meta.selector !== '$appHosts' && meta.selector !== '$this' && meta.selector !== '') {
498
- delegateListeners.push(meta);
499
- } else {
500
- nonDelegateListeners.push(meta);
501
- }
502
- });
503
-
504
- const delegatesByTypeAndRoot = new Map<string, any[]>();
505
- delegateListeners.forEach(meta => {
506
- const r = meta.options.root || 'auto';
507
- const rootKey = `${meta.type}:${r}`;
508
- if (!delegatesByTypeAndRoot.has(rootKey)) {
509
- delegatesByTypeAndRoot.set(rootKey, []);
510
- }
511
- delegatesByTypeAndRoot.get(rootKey)!.push(meta);
512
- });
513
-
514
- delegatesByTypeAndRoot.forEach((metaList, typeRootKey) => {
515
- const [type, rStr] = typeRootKey.split(':');
516
- const r = rStr === 'auto' ? 'auto' : rStr;
517
- const opts = { capture: metaList[0].options.capture, once: metaList[0].options.once, passive: metaList[0].options.passive };
518
-
519
- const bindRoots: (HTMLElement | ShadowRoot)[] = [];
520
- if (r === 'auto') bindRoots.push(this.shadowRoot || (this as any));
521
- else if (r === 'light') bindRoots.push(this as any);
522
- else if (r === 'shadow' && this.shadowRoot) bindRoots.push(this.shadowRoot);
523
- else if (r === 'all') {
524
- bindRoots.push(this as any);
525
- if (this.shadowRoot) bindRoots.push(this.shadowRoot);
526
- }
527
-
528
- bindRoots.forEach(br => {
529
- const sortedMetaList = [...metaList].sort((a, b) => {
530
- const aStop = a.options.stopPropagation ? 1 : 0;
531
- const bStop = b.options.stopPropagation ? 1 : 0;
532
- return bStop - aStop;
533
- });
534
-
535
- const unifiedHandler = async (event: Event) => {
536
- for (const meta of sortedMetaList) {
537
- const { selector, options } = meta;
538
- const matchedEl = (event.target as HTMLElement)?.closest(selector);
539
- if (matchedEl && (br as any).contains(matchedEl)) {
540
- // Apply filter if specified
541
- if (options.filter) {
542
- const helper = SwcUtils.getHelperAndHostSet(currentWin, matchedEl);
543
- if (!options.filter(event, helper)) {
544
- continue; // Skip this listener if filter returns false
545
- }
546
- }
547
- if (options.stopPropagation) event.stopPropagation();
548
- if (options.stopImmediatePropagation) event.stopImmediatePropagation();
549
- if (options.preventDefault) event.preventDefault();
550
- const currentHostSet = SwcUtils.getHostSet(this as any);
551
- const args = { event, ...currentHostSet, $el: matchedEl, $root: br };
552
- await (this as any)[meta.propertyKey](event, { ...currentHostSet, $matchedElement: matchedEl }, args);
553
- if ((event as any).cancelBubble) break;
554
- }
555
- }
556
- };
557
- br.addEventListener(type, unifiedHandler, opts);
558
- // 각 메타의 removeListener를 메타 옵션과 함께 보관 (delegate 그룹은 여러 메타 가능)
559
- const onRemoves: Array<{ fn: (target: Element, optionValue: any) => void; opts: any }> = [];
560
- for (const m of sortedMetaList) {
561
- if (m.options.removeListener) {
562
- onRemoves.push({ fn: m.options.removeListener, opts: m.options });
563
- }
564
- }
565
- boundListeners.push({ target: br, type, handler: unifiedHandler, options: opts, onRemoves });
566
- });
567
- });
568
-
569
- nonDelegateListeners.forEach(meta => {
570
- const { selector, type, options } = meta;
571
- const opts = { capture: options.capture, once: options.once, passive: options.passive };
572
- const bindTargets: EventTarget[] = [];
573
- const r = options.root || 'auto';
574
-
575
- // Resolve selector if it's a function
576
- let resolvedSelector: string | null = null;
577
- if (typeof selector === 'function') {
578
- const hostSet = SwcUtils.getHelperAndHostSet(currentWin, this);
579
- const result = selector(this, hostSet);
580
- if (typeof result === 'string') {
581
- resolvedSelector = result;
582
- } else if (result instanceof currentWin.Element) {
583
- bindTargets.push(result as EventTarget);
584
- } else if (result instanceof currentWin.NodeList) {
585
- bindTargets.push(...Array.from(result as EventTarget[]));
586
- } else if (Array.isArray(result)) {
587
- bindTargets.push(...(result as EventTarget[]));
588
- }
589
- } else {
590
- resolvedSelector = selector;
591
- }
592
-
593
- const applyRootOption = (target: any) => {
594
- if (!target) return;
595
- if (r === 'auto') {
596
- bindTargets.push(target.shadowRoot || target);
597
- } else {
598
- if (r === 'light' || r === 'all') bindTargets.push(target);
599
- if ((r === 'shadow' || r === 'all') && target.shadowRoot) bindTargets.push(target.shadowRoot);
600
- }
601
- };
602
-
603
- if (resolvedSelector) {
604
- if (resolvedSelector === '$window') bindTargets.push(currentWin);
605
- else if (resolvedSelector === '$document') bindTargets.push(currentWin.document);
606
- else if (resolvedSelector === '$host') applyRootOption(helperHostSet.$host);
607
- else if (resolvedSelector === '$parentHost') applyRootOption(helperHostSet.$parentHost);
608
- else if (resolvedSelector === '$appHost') applyRootOption(helperHostSet.$appHost);
609
- else if (resolvedSelector === '$firstHost') applyRootOption(helperHostSet.$firstHost);
610
- else if (resolvedSelector === '$lastHost') applyRootOption(helperHostSet.$lastHost);
611
- else if (resolvedSelector === '$firstAppHost') applyRootOption(helperHostSet.$firstAppHost);
612
- else if (resolvedSelector === '$lastAppHost') applyRootOption(helperHostSet.$lastAppHost);
613
- else if (resolvedSelector === '$hosts') helperHostSet.$hosts.forEach(applyRootOption);
614
- else if (resolvedSelector === '$appHosts') helperHostSet.$appHosts.forEach(applyRootOption);
615
- else if (resolvedSelector === '$this' || !resolvedSelector) applyRootOption(this);
616
- else {
617
- const searchRoots: (HTMLElement | ShadowRoot)[] = [];
618
- if (r === 'auto') searchRoots.push(this.shadowRoot || (this as any));
619
- else if (r === 'light') searchRoots.push(this as any);
620
- else if (r === 'shadow' && this.shadowRoot) searchRoots.push(this.shadowRoot);
621
- else if (r === 'all') {
622
- searchRoots.push(this as any);
623
- if (this.shadowRoot) searchRoots.push(this.shadowRoot);
624
- }
625
-
626
- searchRoots.forEach(sr => {
627
- sr.querySelectorAll(resolvedSelector).forEach(el => bindTargets.push(el));
628
- });
629
- }
630
- }
631
-
632
- bindTargets.forEach(t => {
633
- const handler = async (event: Event) => {
634
- // Apply filter if specified
635
- if (options.filter) {
636
- const helper = SwcUtils.getHelperAndHostSet(currentWin, t as HTMLElement);
637
- if (!options.filter(event, { currentThis: this, helper })) {
638
- return; // Skip if filter returns false
639
- }
640
- }
641
- if (options.stopPropagation) event.stopPropagation();
642
- if (options.stopImmediatePropagation) event.stopImmediatePropagation();
643
- if (options.preventDefault) event.preventDefault();
644
- const currentHostSet = SwcUtils.getHostSet(this as any);
645
- const args = { event, ...currentHostSet, $el: t, $root: t };
646
- await (this as any)[meta.propertyKey](event, { currentHostSet, $matchedElement: event.currentTarget }, args);
647
- };
648
-
649
- // Create event stream with Observable-based operators
650
- const eventSubject = new Subject<Event>();
651
- let eventStream = eventSubject as any;
652
-
653
- // Apply operators from @dooboostore/core/operators
654
- if (options.debounceTime !== undefined && options.debounceTime > 0) {
655
- eventStream = eventStream.pipe(debounceTime(options.debounceTime));
656
- }
657
-
658
- if (options.throttleTime !== undefined && options.throttleTime > 0) {
659
- eventStream = eventStream.pipe(throttleTime(options.throttleTime));
660
- }
661
-
662
- if (options.distinctUntilChanged !== undefined && options.distinctUntilChanged !== false) {
663
- if (typeof options.distinctUntilChanged === 'function') {
664
- eventStream = eventStream.pipe(distinctUntilChanged(options.distinctUntilChanged));
665
- } else {
666
- eventStream = eventStream.pipe(distinctUntilChanged());
667
- }
668
- }
669
-
670
- // Subscribe to the event stream
671
- const subscription = eventStream.subscribe({
672
- next: (event: Event) => {
673
- handler(event).catch((err: any) => console.error('Event handler error:', err));
674
- },
675
- error: (err: any) => console.error('Event stream error:', err)
676
- });
677
-
678
- // Create wrapper handler that emits to the subject
679
- const wrappedHandler = (event: Event) => {
680
- eventSubject.next(event);
681
- };
682
-
683
- t.addEventListener(type, wrappedHandler, opts);
684
- const onRemoves: Array<{ fn: (target: Element, optionValue: any) => void; opts: any }> = options.removeListener ? [{ fn: options.removeListener, opts }] : [];
685
- boundListeners.push({ target: t, type, handler: wrappedHandler, options: opts, subscription, onRemoves });
686
- });
687
- });
688
-
689
- // ─── MutationObserver / ResizeObserver 처리 ───
690
- // shadow/light 루트별로 각각 observer를 두고, 해당 루트에 데코레이터 메타가 있으면 observer를 생성한다.
691
- // 두 루트 모두 있으면 observer 2개, 한쪽만 있으면 1개, 없으면 0개.
692
- shadowMutationObserver = null;
693
- lightMutationObserver = null;
694
- resizeObserver = null;
695
-
696
- // ResizeObserver: @resizeObserver 데코레이터가 하나라도 있으면 observer 하나 생성 (shadow 있으면 shadow 루트, 없으면 light)
697
- if (resizeObserverList.length > 0) {
698
- const root: HTMLElement | ShadowRoot = this.shadowRoot || (this as any);
699
- resizeObserver = new ResizeObserver((entries: ResizeObserverEntry[], obs: ResizeObserver) => {
700
- for (const m of resizeObserverList) {
701
- let matchedEls: HTMLElement[] = [];
702
- if (m.options.delegate && typeof m.selector === 'string') {
703
- const isThis = m.selector === '$this' || m.selector === '';
704
- const matchesSel = (node: any): boolean => {
705
- if (!node || node.nodeType !== 1) return false;
706
- if (isThis) return true;
707
- const el = node as HTMLElement;
708
- return el.matches?.(m.selector as string) || !!el.closest?.(m.selector as string);
709
- };
710
- matchedEls = entries.map(e => e.target as HTMLElement).filter(matchesSel);
711
- } else {
712
- matchedEls = entries.map(e => e.target as HTMLElement).filter(t => t && t.nodeType === 1);
713
- }
714
- if (matchedEls.length === 0) continue;
715
- if (m.options.filter) {
716
- const helper = SwcUtils.getHelperAndHostSet(currentWin, this);
717
- if (!m.options.filter(matchedEls, { currentThis: this, helper })) continue;
718
- }
719
- const hostSet = SwcUtils.getHostSet(this as any);
720
- (this as any)[m.propertyKey](matchedEls, entries, obs, { ...hostSet, $root: root });
721
- }
722
- });
723
- resizeObservers.push(resizeObserver);
724
- // resizeObserver 메타별 removeObserver 수집
725
- for (const m of resizeObserverList) {
726
- if (m.options.removeObserver) {
727
- removeObserverCallbacks.push({ fn: m.options.removeObserver, target: root, opts: m.options });
728
- }
729
- }
730
- }
731
- // MutationObserver: shadow/light 루트별 그룹핑
732
- const shadowMetas = mutationObserverList.filter(m => {
733
- const r = m.options.root || 'auto';
734
- return this.shadowRoot && (r === 'shadow' || r === 'all' || r === 'auto');
735
- });
736
- const lightMetas = mutationObserverList.filter(m => {
737
- const r = m.options.root || 'auto';
738
- return r === 'light' || r === 'all' || (!this.shadowRoot && r === 'auto');
739
- });
740
-
741
- const setupMutationObserver = (root: HTMLElement | ShadowRoot, metas: MutationObserverMetadata[], resizeDelegates: ResizeObserverMetadata[] = []): MutationObserver | null => {
742
- // mutation 메타가 없으면 observer 생성 안 함 (resize delegate는 보조)
743
- if (metas.length === 0) return null;
744
-
745
- // 사용자가 입력한 옵션 그대로 (아무것도 안 주면 childList 기본)
746
- const initFrom = (o: MutationObserverBaseOptions): MutationObserverInit => {
747
- const init: MutationObserverInit = {
748
- childList: o.childList,
749
- attributes: o.attributes,
750
- characterData: o.characterData,
751
- subtree: o.subtree,
752
- attributeFilter: o.attributeFilter,
753
- attributeOldValue: o.attributeOldValue,
754
- characterDataOldValue: o.characterDataOldValue
755
- };
756
- // MutationObserver는 최소 1개는 true여야 함
757
- if (!init.childList && !init.attributes && !init.characterData) init.childList = true;
758
- return init;
759
- };
760
-
761
- const observer = new MutationObserver((mutations: MutationRecord[], obs: MutationObserver) => {
762
- for (const meta of metas) {
763
- const { selector, options } = meta;
764
- let matchedEls: HTMLElement[] = [];
765
-
766
- if (options.delegate && typeof selector === 'string') {
767
- const isThis = selector === '$this' || selector === '';
768
- const matchesSel = (node: any): boolean => {
769
- if (!node || node.nodeType !== 1) return false;
770
- if (isThis) return true;
771
- const el = node as HTMLElement;
772
- return el.matches?.(selector as string) || !!el.closest?.(selector as string);
773
- };
774
- matchedEls = mutations.flatMap(m => {
775
- const els: HTMLElement[] = [];
776
- if (matchesSel(m.target)) els.push(m.target as HTMLElement);
777
- Array.from(m.addedNodes || [])
778
- .filter(matchesSel)
779
- .forEach(n => els.push(n as HTMLElement));
780
- Array.from(m.removedNodes || [])
781
- .filter(matchesSel)
782
- .forEach(n => els.push(n as HTMLElement));
783
- return els;
784
- });
785
- } else if (typeof selector === 'string') {
786
- // non-delegate: observe 대상이 매칭 요소들이므로 그들의 mutation 대상 + 추가/제거 노드
787
- const isThis = selector === '$this' || selector === '';
788
- const matchesSel = (node: any): boolean => {
789
- if (!node || node.nodeType !== 1) return false;
790
- if (isThis) return true;
791
- return (node as HTMLElement).matches?.(selector as string);
792
- };
793
- matchedEls = mutations.flatMap(m => {
794
- const els: HTMLElement[] = [];
795
- if (matchesSel(m.target)) els.push(m.target as HTMLElement);
796
- Array.from(m.addedNodes || [])
797
- .filter(matchesSel)
798
- .forEach(n => els.push(n as HTMLElement));
799
- Array.from(m.removedNodes || [])
800
- .filter(matchesSel)
801
- .forEach(n => els.push(n as HTMLElement));
802
- return els;
803
- });
804
- } else {
805
- matchedEls = mutations.map(m => m.target as HTMLElement).filter(t => t && t.nodeType === 1);
806
- }
807
-
808
- if (matchedEls.length === 0) continue;
809
- if (options.filter) {
810
- const helper = SwcUtils.getHelperAndHostSet(currentWin, this);
811
- if (!options.filter(matchedEls, { currentThis: this, helper })) continue;
812
- }
813
- const hostSet = SwcUtils.getHostSet(this as any);
814
- // 1번째: 매치된 element 배열, 2번째: 원본 mutations, 3번째: observer
815
- (this as any)[meta.propertyKey](matchedEls, mutations, obs, { ...hostSet, $root: root });
816
- }
817
-
818
- // ─── resizeObserver delegate 동적 추적 ───
819
- // 추가/삭제된 요소가 delegate 셀렉터와 매칭되면 resizeObserver에 observe/unobserve
820
- if (resizeDelegates.length > 0 && resizeObserver) {
821
- const handle = (n: Node, observe: boolean) => {
822
- if (n.nodeType !== 1) return;
823
- const el = n as HTMLElement;
824
- for (const m of resizeDelegates) {
825
- if (typeof m.selector !== 'string') continue;
826
- const isThis = m.selector === '$this' || m.selector === '';
827
- const matchesSel = (node: any): boolean => {
828
- if (!node || node.nodeType !== 1) return false;
829
- if (isThis) return true;
830
- const e = node as HTMLElement;
831
- return e.matches?.(m.selector as string) || !!e.closest?.(m.selector as string);
832
- };
833
- if (matchesSel(el)) {
834
- if (observe) resizeObserver.observe(el, m.options.box ? { box: m.options.box } : undefined);
835
- else resizeObserver.unobserve(el);
836
- } else if (!isThis) {
837
- el.querySelectorAll(m.selector).forEach(sub => {
838
- if (observe) resizeObserver.observe(sub as HTMLElement, m.options.box ? { box: m.options.box } : undefined);
839
- else resizeObserver.unobserve(sub as HTMLElement);
840
- });
841
- }
842
- }
843
- };
844
- for (const m of mutations) {
845
- Array.from(m.addedNodes || []).forEach(n => handle(n, true));
846
- Array.from(m.removedNodes || []).forEach(n => handle(n, false));
847
- }
848
- }
849
- });
850
-
851
- // delegate 메타(mutation 또는 resize)가 하나라도 있으면 → 루트에 subtree observe 1개로 전부 감지 (콜백에서 메타별 매칭 처리)
852
- const hasDelegate = metas.some(m => m.options.delegate) || resizeDelegates.some(m => m.options.delegate);
853
- if (hasDelegate) {
854
- observer.observe(root, { childList: true, subtree: true });
855
- }
856
-
857
- // non-delegate 메타: 셀렉터 매칭 요소들을 사용자 옵션 그대로 observe
858
- const nonDelegateMetas = metas.filter(m => !m.options.delegate);
859
- for (const m of nonDelegateMetas) {
860
- const { selector, options } = m;
861
- const mInit = initFrom(options);
862
- if (typeof selector === 'string') {
863
- if (selector === '$this' || selector === '') {
864
- observer.observe(root, mInit);
865
- } else {
866
- root.querySelectorAll(selector).forEach(el => observer.observe(el as HTMLElement, mInit));
867
- }
868
- } else if (typeof selector === 'function') {
869
- // 함수 셀렉터는 루트 관찰로 대체 (매칭 평가 불가)
870
- observer.observe(root, mInit);
871
- }
872
- }
873
-
874
- return observer;
875
- };
876
-
877
- // resizeObserver delegate 메타도 루트별로 필터해서 넘김
878
- const shadowResizeDelegateMetas = resizeObserverList.filter(m => {
879
- if (!m.options.delegate) return false;
880
- const r = m.options.root || 'auto';
881
- return this.shadowRoot && (r === 'shadow' || r === 'all' || r === 'auto');
882
- });
883
- const lightResizeDelegateMetas = resizeObserverList.filter(m => {
884
- if (!m.options.delegate) return false;
885
- const r = m.options.root || 'auto';
886
- return r === 'light' || r === 'all' || (!this.shadowRoot && r === 'auto');
887
- });
888
-
889
- if (this.shadowRoot) shadowMutationObserver = setupMutationObserver(this.shadowRoot, shadowMetas, shadowResizeDelegateMetas);
890
- lightMutationObserver = setupMutationObserver(this as any, lightMetas, lightResizeDelegateMetas);
891
-
892
- // mutationObserver 메타별 removeObserver 수집
893
- for (const m of mutationObserverList) {
894
- if (m.options.removeObserver) {
895
- const rootForM = this.shadowRoot && (m.options.root === 'shadow' || m.options.root === 'all' || (m.options.root === 'auto' && this.shadowRoot)) ? this.shadowRoot : (this as any);
896
- removeObserverCallbacks.push({ fn: m.options.removeObserver, target: rootForM, opts: m.options });
897
- }
898
- }
707
+ // ── cycler 실행 (렌더 후): 모든 시클러 onConnected → observer Set 수집 → observer 생성 ──
708
+ // helperHostSet 은 클로저 변수라 async 흐름에서 다른 인스턴스로 덮어써질 수 있다.
709
+ // 반드시 현재 인스턴스(this)로 재계산한 값을 사용한다.
710
+ await buildObservers(SwcUtils.getHelperAndHostSet(win, this as any));
899
711
 
900
712
  if (originalConnected) await originalConnected.apply(this);
901
713
 
902
- const aMethods = findAllOnConnectedAfterMetadata(this); //.filter(it => { return useSsr ? !it.options.ssrFirst : true; });
903
- // console.log('afterConnected', this.tagName, aMethods, getOnConnectedAfterMetadata(this))
904
- for (const m of aMethods) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
714
+ // after-connected 라이프사이클 메서드 (@onConnectedAfter)
715
+ for (const m of findAllOnConnectedAfterMetadata(this)) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
905
716
  (this as any)._executeSwcScript('swc-on-connected', helperHostSet);
906
717
  (this as any)._executeSwcScript('swc-on-after-connected', helperHostSet);
907
718
  (this as any).__swc_connected = true;
908
-
909
- // Trigger @changedAttributeThis with while: 'connected'
910
- for (let [name, metaList] of Array.from(attrChangeMap)) {
911
- for (const meta of metaList) {
912
- if (meta.options.while === 'connected') {
913
- const val = getAttributeValue(this, name, { type: meta.options.type });
914
- if (val !== null) {
915
- await (this as any)[meta.propertyKey](val, null, name, helperHostSet);
916
- }
917
- }
918
- }
919
- }
920
719
  } finally {
921
- for (let m of findAllLifecycleMetadata(this, ON_CONNECTED_COMPLETED_METADATA_KEY)) {
922
- await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
923
- }
720
+ for (const m of findAllLifecycleMetadata(this, ON_CONNECTED_COMPLETED_METADATA_KEY)) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
924
721
  if (appHost && typeof (appHost as any)._connectedDone === 'function') {
925
722
  await (appHost as any)._connectedDone(this);
926
723
  }
@@ -932,82 +729,26 @@ export const elementDefine =
932
729
  ////////////////////////////////////////////////
933
730
  const originalDisconnected = proto.disconnectedCallback;
934
731
  proto.disconnectedCallback = function () {
935
- // const helperHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
936
- // Remove from appHost LAST when disconnected
937
- // console.log('disconnnnnnnnnnnnnnnnnnn', helperHostSet, this)
938
- const appHost = helperHostSet.$appHost;
732
+ const appHost = helperHostSet?.$appHost;
939
733
  if (appHost && typeof (appHost as any)._disconnected === 'function') {
940
734
  (appHost as any)._disconnected(this);
941
735
  }
942
736
 
943
737
  (this as any)._executeSwcScript('swc-on-before-disconnected', helperHostSet);
944
- const bMethods = findAllLifecycleMetadata(this, ON_BEFORE_DISCONNECTED_METADATA_KEY);
945
- for (const m of bMethods) (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
946
- // event listener 정리
947
- if (boundListeners.length > 0) {
948
- boundListeners.forEach((l: BoundListener) => {
949
- l.target.removeEventListener(l.type, l.handler, l.options);
950
- // Cleanup Observable subscription if present
951
- if (l.subscription && typeof l.subscription.unsubscribe === 'function') {
952
- l.subscription.unsubscribe();
953
- }
954
- // 사용자 정의 정리 콜백 호출
955
- if (Array.isArray(l.onRemoves)) {
956
- for (const r of l.onRemoves) {
957
- try {
958
- r.fn(l.target, r.opts);
959
- } catch (e) {
960
- console.error('[SWC] removeListener error:', e);
961
- }
962
- }
963
- }
964
- });
965
- boundListeners = [];
966
- }
738
+ for (const m of findAllLifecycleMetadata(this, ON_BEFORE_DISCONNECTED_METADATA_KEY)) (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
739
+ for (const c of cyclers) c.onDisconnected?.(helperHostSet!);
967
740
 
968
- // MutationObserver / ResizeObserver 정리
969
- const _obs: Array<MutationObserver | ResizeObserver | null> = [shadowMutationObserver, lightMutationObserver, resizeObserver, ...resizeObservers];
970
- for (const o of _obs) {
971
- try {
972
- (o as any)?.disconnect?.();
973
- } catch (e) {
974
- console.error('[SWC] observer disconnect error:', e);
975
- }
976
- }
977
- shadowMutationObserver = null;
978
- lightMutationObserver = null;
979
- resizeObserver = null;
980
- resizeObservers = [];
981
-
982
- // 메타별 removeObserver 콜백 호출
983
- for (const cb of removeObserverCallbacks) {
984
- try {
985
- cb.fn(cb.target, cb.opts);
986
- } catch (e) {
987
- console.error('[SWC] removeObserver error:', e);
988
- }
741
+ for (const o of (this as any).__swc_observers ?? []) {
742
+ try { (o as any)?.disconnect?.(); } catch (e) { console.error('[SWC] observer disconnect error:', e); }
989
743
  }
990
- removeObserverCallbacks = [];
744
+ (this as any).__swc_observers = [];
991
745
 
992
746
  // elementApply event listener 정리
993
747
  new ElementApply(this, { id: this._swcId }).removeAllEventListener();
994
748
 
995
- // globalDelegatedRoots remove
996
- // const roots = [this, this.getRootNode()];
997
- // if (this.shadowRoot) {
998
- // roots.push(this.shadowRoot);
999
- // }
1000
- // roots.forEach(root => {
1001
- // DOM_EVENT_NAMES.forEach(type => {
1002
- // root.removeEventListener(type, handleGlobalSwcEvent);
1003
- // });
1004
- // }
1005
- // )
1006
-
1007
749
  if (originalDisconnected) originalDisconnected.apply(this);
1008
750
 
1009
- const aMethods = findAllLifecycleMetadata(this, ON_AFTER_DISCONNECTED_METADATA_KEY);
1010
- for (const m of aMethods) (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
751
+ for (const m of findAllLifecycleMetadata(this, ON_AFTER_DISCONNECTED_METADATA_KEY)) (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
1011
752
  (this as any)._executeSwcScript('swc-on-disconnected', helperHostSet);
1012
753
  (this as any)._executeSwcScript('swc-on-after-disconnected', helperHostSet);
1013
754
  (this as any).__swc_connected = false;
@@ -1017,13 +758,12 @@ export const elementDefine =
1017
758
  proto.adoptedCallback = function () {
1018
759
  const hostSet = SwcUtils.getHostSet(this as any);
1019
760
  (this as any)._executeSwcScript('swc-on-before-adopted', hostSet);
1020
- const bMethods = findAllLifecycleMetadata(this, ON_BEFORE_ADOPTED_METADATA_KEY);
1021
- for (const m of bMethods) (this as any)._invokeLifecycleMethod(m.propertyKey, hostSet);
761
+ for (const m of findAllLifecycleMetadata(this, ON_BEFORE_ADOPTED_METADATA_KEY)) (this as any)._invokeLifecycleMethod(m.propertyKey, hostSet);
762
+ for (const c of cyclers) c.onAdopted?.(helperHostSet!);
1022
763
 
1023
764
  if (originalAdopted) originalAdopted.apply(this);
1024
765
 
1025
- const aMethods = findAllLifecycleMetadata(this, ON_AFTER_ADOPTED_METADATA_KEY);
1026
- for (const m of aMethods) (this as any)._invokeLifecycleMethod(m.propertyKey, hostSet);
766
+ for (const m of findAllLifecycleMetadata(this, ON_AFTER_ADOPTED_METADATA_KEY)) (this as any)._invokeLifecycleMethod(m.propertyKey, hostSet);
1027
767
  (this as any)._executeSwcScript('swc-on-adopted', hostSet);
1028
768
  (this as any)._executeSwcScript('swc-on-after-adopted', hostSet);
1029
769
  };
@@ -1032,7 +772,9 @@ export const elementDefine =
1032
772
  proto.attributeChangedCallback = function (name: string, old: string | null, newVal: string | null) {
1033
773
  if (originalAttributeChanged) originalAttributeChanged.apply(this, [name, old, newVal]);
1034
774
 
1035
- const hSet = SwcUtils.getHostSet(this as any);
775
+ // attributeChangedCallback 은 connected 이전에도 호출될 수 있으므로
776
+ // 클로저의 helperHostSet(null 가능) 대신 이 시점에 새로 계산한다.
777
+ const helperAndHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
1036
778
 
1037
779
  // Process expression directive before passing to handlers
1038
780
  let processedVal: any = newVal;
@@ -1041,18 +783,17 @@ export const elementDefine =
1041
783
  const expr = ae.getFirstExpression('callReturn');
1042
784
  if (expr) {
1043
785
  const win = (this as any)._resolveWindow?.() || ((typeof window !== 'undefined' ? window : undefined) as any);
1044
- const helperAndHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
786
+ const exprHelperAndHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
1045
787
  const script = ConvertUtils.decodeHtmlEntity(expr.script, win.document);
1046
788
  try {
1047
789
  const result = FunctionUtils.executeReturn({
1048
790
  script: script,
1049
791
  context: this,
1050
- args: helperAndHostSet
792
+ args: exprHelperAndHostSet
1051
793
  });
1052
- // Pass the result object directly without serialization
1053
794
  processedVal = result;
1054
795
  } catch (e) {
1055
- console.error(`[SWC] Failed to execute directive {{= ${expr.script} }} on attribute ${name}: ${helperAndHostSet}`, e);
796
+ console.error(`[SWC] Failed to execute directive {{= ${expr.script} }} on attribute ${name}: ${exprHelperAndHostSet}`, e);
1056
797
  processedVal = newVal;
1057
798
  }
1058
799
  }
@@ -1065,21 +806,8 @@ export const elementDefine =
1065
806
  }
1066
807
  }
1067
808
 
1068
- const hostCustomEventMeta = emitHostCustomEventList.find(it => (it.options as any).attributeName === name);
1069
- if (hostCustomEventMeta && newVal !== null) {
1070
- (this as any)._bindAttributeEvent(this as any, name, newVal, hostCustomEventMeta.type);
1071
- }
1072
-
1073
- const metaList = attrChangeMap.get(name);
1074
- if (metaList && Array.isArray(metaList)) {
1075
- for (const meta of metaList) {
1076
- if (meta.options.while === 'connected' && !(this as any).__swc_connected) {
1077
- continue;
1078
- }
1079
- const convertedVal = convertAttributeValue(processedVal, meta.options.type);
1080
- (this as any)[meta.propertyKey](convertedVal, old, name, hSet);
1081
- }
1082
- }
809
+ // cycler 위임: attribute 옵저빙(@emitCustomEvent(attributeName) / @changedAttribute 등)
810
+ for (const c of cyclers) c.onAttributeChanged?.(helperAndHostSet, name, old, processedVal);
1083
811
  };
1084
812
 
1085
813
  Object.defineProperty(constructor, 'observedAttributes', {