@dooboostore/simple-web-component 1.0.48 → 1.0.49

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 (150) hide show
  1. package/README.md +117 -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 +387 -29
  5. package/dist/cjs/decorators/addEventListener.js.map +3 -3
  6. package/dist/cjs/decorators/applyAttribute.js +25 -0
  7. package/dist/cjs/decorators/applyAttribute.js.map +2 -2
  8. package/dist/cjs/decorators/applyClass.js +49 -18
  9. package/dist/cjs/decorators/applyClass.js.map +3 -3
  10. package/dist/cjs/decorators/applyNode.js +67 -1
  11. package/dist/cjs/decorators/applyNode.js.map +3 -3
  12. package/dist/cjs/decorators/applyProperty.js +101 -3
  13. package/dist/cjs/decorators/applyProperty.js.map +2 -2
  14. package/dist/cjs/decorators/applyStyle.js +36 -5
  15. package/dist/cjs/decorators/applyStyle.js.map +2 -2
  16. package/dist/cjs/decorators/changedAttribute.js +39 -0
  17. package/dist/cjs/decorators/changedAttribute.js.map +2 -2
  18. package/dist/cjs/decorators/elementDefine.js +237 -437
  19. package/dist/cjs/decorators/elementDefine.js.map +3 -3
  20. package/dist/cjs/decorators/emitCustomEvent.js +55 -0
  21. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  22. package/dist/cjs/decorators/index.js +3 -1
  23. package/dist/cjs/decorators/index.js.map +2 -2
  24. package/dist/cjs/decorators/intersectionObserver.js +209 -0
  25. package/dist/cjs/decorators/intersectionObserver.js.map +7 -0
  26. package/dist/cjs/decorators/mutationObserver.js +140 -0
  27. package/dist/cjs/decorators/mutationObserver.js.map +3 -3
  28. package/dist/cjs/decorators/query.js +117 -102
  29. package/dist/cjs/decorators/query.js.map +2 -2
  30. package/dist/cjs/decorators/resizeObserver.js +87 -0
  31. package/dist/cjs/decorators/resizeObserver.js.map +2 -2
  32. package/dist/cjs/decorators/setInterval.js +82 -0
  33. package/dist/cjs/decorators/setInterval.js.map +7 -0
  34. package/dist/cjs/decorators/setTimeout.js +82 -0
  35. package/dist/cjs/decorators/setTimeout.js.map +7 -0
  36. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js +9 -0
  37. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +2 -2
  38. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js +9 -0
  39. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
  40. package/dist/cjs/types/index.js.map +1 -1
  41. package/dist/esm/config/config.js +1 -1
  42. package/dist/esm/config/config.js.map +2 -2
  43. package/dist/esm/decorators/addEventListener.js +388 -29
  44. package/dist/esm/decorators/addEventListener.js.map +3 -3
  45. package/dist/esm/decorators/applyAttribute.js +25 -0
  46. package/dist/esm/decorators/applyAttribute.js.map +2 -2
  47. package/dist/esm/decorators/applyClass.js +49 -18
  48. package/dist/esm/decorators/applyClass.js.map +3 -3
  49. package/dist/esm/decorators/applyNode.js +67 -1
  50. package/dist/esm/decorators/applyNode.js.map +3 -3
  51. package/dist/esm/decorators/applyProperty.js +101 -3
  52. package/dist/esm/decorators/applyProperty.js.map +2 -2
  53. package/dist/esm/decorators/applyStyle.js +36 -5
  54. package/dist/esm/decorators/applyStyle.js.map +2 -2
  55. package/dist/esm/decorators/changedAttribute.js +40 -0
  56. package/dist/esm/decorators/changedAttribute.js.map +2 -2
  57. package/dist/esm/decorators/elementDefine.js +244 -444
  58. package/dist/esm/decorators/elementDefine.js.map +3 -3
  59. package/dist/esm/decorators/emitCustomEvent.js +55 -0
  60. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  61. package/dist/esm/decorators/index.js +3 -1
  62. package/dist/esm/decorators/index.js.map +2 -2
  63. package/dist/esm/decorators/intersectionObserver.js +191 -0
  64. package/dist/esm/decorators/intersectionObserver.js.map +7 -0
  65. package/dist/esm/decorators/mutationObserver.js +141 -0
  66. package/dist/esm/decorators/mutationObserver.js.map +3 -3
  67. package/dist/esm/decorators/query.js +117 -102
  68. package/dist/esm/decorators/query.js.map +2 -2
  69. package/dist/esm/decorators/resizeObserver.js +88 -0
  70. package/dist/esm/decorators/resizeObserver.js.map +2 -2
  71. package/dist/esm/decorators/setInterval.js +64 -0
  72. package/dist/esm/decorators/setInterval.js.map +7 -0
  73. package/dist/esm/decorators/setTimeout.js +64 -0
  74. package/dist/esm/decorators/setTimeout.js.map +7 -0
  75. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js +9 -0
  76. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +2 -2
  77. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js +9 -0
  78. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
  79. package/dist/esm/types/index.js.map +1 -1
  80. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +2565 -1474
  81. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  82. package/dist/types/config/config.d.ts.map +1 -1
  83. package/dist/types/decorators/addEventListener.d.ts +84 -33
  84. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  85. package/dist/types/decorators/applyAttribute.d.ts +22 -34
  86. package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
  87. package/dist/types/decorators/applyClass.d.ts +34 -34
  88. package/dist/types/decorators/applyClass.d.ts.map +1 -1
  89. package/dist/types/decorators/applyNode.d.ts +30 -20
  90. package/dist/types/decorators/applyNode.d.ts.map +1 -1
  91. package/dist/types/decorators/applyProperty.d.ts +57 -34
  92. package/dist/types/decorators/applyProperty.d.ts.map +1 -1
  93. package/dist/types/decorators/applyStyle.d.ts +25 -12
  94. package/dist/types/decorators/applyStyle.d.ts.map +1 -1
  95. package/dist/types/decorators/changedAttribute.d.ts +11 -0
  96. package/dist/types/decorators/changedAttribute.d.ts.map +1 -1
  97. package/dist/types/decorators/elementDefine.d.ts +0 -14
  98. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  99. package/dist/types/decorators/emitCustomEvent.d.ts +28 -4
  100. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  101. package/dist/types/decorators/index.d.ts +3 -1
  102. package/dist/types/decorators/index.d.ts.map +1 -1
  103. package/dist/types/decorators/intersectionObserver.d.ts +49 -0
  104. package/dist/types/decorators/intersectionObserver.d.ts.map +1 -0
  105. package/dist/types/decorators/mutationObserver.d.ts +35 -25
  106. package/dist/types/decorators/mutationObserver.d.ts.map +1 -1
  107. package/dist/types/decorators/query.d.ts +57 -14
  108. package/dist/types/decorators/query.d.ts.map +1 -1
  109. package/dist/types/decorators/resizeObserver.d.ts +34 -23
  110. package/dist/types/decorators/resizeObserver.d.ts.map +1 -1
  111. package/dist/types/decorators/setInterval.d.ts +29 -0
  112. package/dist/types/decorators/setInterval.d.ts.map +1 -0
  113. package/dist/types/decorators/setTimeout.d.ts +29 -0
  114. package/dist/types/decorators/setTimeout.d.ts.map +1 -0
  115. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts +7 -1
  116. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +1 -1
  117. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +6 -0
  118. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +1 -1
  119. package/dist/types/types/index.d.ts +104 -0
  120. package/dist/types/types/index.d.ts.map +1 -1
  121. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +2565 -1474
  122. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  123. package/package.json +7 -5
  124. package/src/config/config.ts +1 -1
  125. package/src/decorators/addEventListener.ts +394 -34
  126. package/src/decorators/applyAttribute.ts +50 -36
  127. package/src/decorators/applyClass.ts +76 -26
  128. package/src/decorators/applyNode.ts +114 -36
  129. package/src/decorators/applyProperty.ts +152 -40
  130. package/src/decorators/applyStyle.ts +64 -17
  131. package/src/decorators/changedAttribute.ts +46 -0
  132. package/src/decorators/elementDefine.ts +281 -580
  133. package/src/decorators/emitCustomEvent.ts +73 -5
  134. package/src/decorators/index.ts +3 -1
  135. package/src/decorators/intersectionObserver.ts +259 -0
  136. package/src/decorators/mutationObserver.ts +215 -39
  137. package/src/decorators/query.ts +168 -126
  138. package/src/decorators/resizeObserver.ts +152 -46
  139. package/src/decorators/setInterval.ts +80 -0
  140. package/src/decorators/setTimeout.ts +80 -0
  141. package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +17 -0
  142. package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +19 -0
  143. package/src/types/index.ts +141 -0
  144. package/dist/cjs/decorators/queryAll.js +0 -181
  145. package/dist/cjs/decorators/queryAll.js.map +0 -7
  146. package/dist/esm/decorators/queryAll.js +0 -162
  147. package/dist/esm/decorators/queryAll.js.map +0 -7
  148. package/dist/types/decorators/queryAll.d.ts +0 -22
  149. package/dist/types/decorators/queryAll.d.ts.map +0 -1
  150. package/src/decorators/queryAll.ts +0 -186
@@ -1,39 +1,30 @@
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 {ElementDefineLifeCycler, HelperHostSet, HostSet, InjectSituationType, IntersectionObserverSet, MutationObserverSet, OnConnectedResult, ResizeObserverSet, ResizeObserverSetEntry, SwcRootType} from '../types';
18
15
  import {ConvertUtils, ElementApply} from '@dooboostore/core-web';
19
16
  import {isSSR} from "../elements/SwcAppMixin";
20
17
  import {findAllStateMetadata} from "./state";
21
18
  import {findAllPropertyMetadata} from "./applyProperty";
19
+ import {MessageSubscribeLifeCycler} from "./subscribeSwcAppMessageWhileConnected";
20
+ import {RouteSubscribeLifeCycler} from "./subscribeSwcAppRouteChangeWhileConnected";
21
+ import {SetIntervalLifeCycler} from "./setInterval";
22
+ import {SetTimeoutLifeCycler} from "./setTimeout";
22
23
 
23
24
  // --- Core Interfaces & Types ---
24
25
 
25
26
  export const ELEMENT_CONFIG_KEY = Symbol.for('simple-web-component:element-config');
26
27
 
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
28
  export interface ElementConfig {
38
29
  extends?: string;
39
30
  observedAttributes?: string[];
@@ -350,12 +341,6 @@ export const elementDefine =
350
341
  metadata.extends = extendsTagName;
351
342
  }
352
343
 
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
344
  const attributeList = findAllAttributeMetadata(constructor);
360
345
  const applyAttributeMap = findAllAttributeApplyMetadata(constructor);
361
346
 
@@ -368,37 +353,256 @@ export const elementDefine =
368
353
  const hostAttributes = attributeList.filter(it => it.selector === '$this').map(it => it.propertyKey || String(it.propertyKey));
369
354
  // Get original static observedAttributes before they're overwritten by Object.defineProperty
370
355
  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
356
 
374
357
  const proto = constructor.prototype;
375
358
  setupPrototype(proto, win);
376
359
 
377
360
  // 값을 기대하지않는다.
378
361
  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 }> = [];
362
+ // ── cyclers: 데코레이터별 라이프사이클 위임 클래스 (elementDefine 시 1회 생성, 계속 재사용) ──
363
+ const eventCycler = new EventListenerLifeCycler();
364
+ const cyclers: ElementDefineLifeCycler[] = [
365
+ eventCycler,
366
+ new ChangedAttributeLifeCycler(),
367
+ new EmitCustomEventLifeCycler(),
368
+ new ResizeObserverLifeCycler(),
369
+ new IntersectionObserverLifeCycler(),
370
+ new MutationObserverLifeCycler(),
371
+ new MessageSubscribeLifeCycler(),
372
+ new RouteSubscribeLifeCycler(),
373
+ new SetIntervalLifeCycler(),
374
+ new SetTimeoutLifeCycler(),
375
+ ];
376
+
377
+ // observedAttributes 기여 cycler (define-time, constructor 기반)
378
+ const cyclerObservedAttributes = cyclers.flatMap(c => c.getObservedAttributeNames?.(constructor) ?? []);
379
+ const mergedObservedAttributes = [...new Set([...(metadata.observedAttributes ?? []), ...originalStaticObservedAttributes, ...cyclerObservedAttributes, ...attributeApplyNames, ...hostAttributes, ...swcLifecycleAttributes, ...swcOnEvents])];
380
+
381
+ // 렌더 후 모든 시클러 onConnected 호출 → Set 수집 후 observer 생성
382
+ const buildObservers = async (helperHostSet: HelperHostSet): Promise<void> => {
383
+ const inst = helperHostSet.$this;
384
+ const observers: Array<MutationObserver | ResizeObserver | IntersectionObserver> = [];
385
+ let resizeObserverSet: ResizeObserverSet = [];
386
+ let mutationObserverSet: MutationObserverSet = [];
387
+ let intersectionObserverSet: IntersectionObserverSet = [];
388
+
389
+ // delegate 추적 root 해석: shadow/light/auto/all → 실제 root 목록
390
+ const resolveDelegateRoots = (delegateRoot: SwcRootType | undefined): (HTMLElement | ShadowRoot)[] => {
391
+ const r = delegateRoot || 'auto';
392
+ const roots: (HTMLElement | ShadowRoot)[] = [];
393
+ if (r === 'auto') roots.push(inst.shadowRoot || inst);
394
+ else if (r === 'light') roots.push(inst);
395
+ else if (r === 'shadow' && inst.shadowRoot) roots.push(inst.shadowRoot);
396
+ else if (r === 'all') { roots.push(inst); if (inst.shadowRoot) roots.push(inst.shadowRoot); }
397
+ return roots;
398
+ };
399
+
400
+ // 1. 모든 시클러 onConnected → observer Set 수집 (여러 시클러가 반환한 Set 을 전부 누적)
401
+ for (const c of cyclers) {
402
+ const result = (await c.onConnected?.(helperHostSet, resizeObserverSet)) as OnConnectedResult | void;
403
+ if (!result) continue;
404
+ if (result.resizeObserverSet?.length) resizeObserverSet = [...resizeObserverSet, ...result.resizeObserverSet];
405
+ if (result.mutationObserverSet?.length) mutationObserverSet = [...mutationObserverSet, ...result.mutationObserverSet];
406
+ if (result.intersectionObserverSet?.length) intersectionObserverSet = [...intersectionObserverSet, ...result.intersectionObserverSet];
407
+ }
408
+
409
+ // 2. 타입별로 observer 생성
410
+ // ResizeObserver 먼저: mutation callback 이 observe/unobserve 를 주입받아야 함
411
+ // observeTargets 중 delegate 항목은 셀렉터 문자열이라 초기 observe 하지 않는다 (동적 추적 전용).
412
+ // 동적 observe/unobserve 는 중복 호출을 막기 위해 observe 대상 WeakSet 으로 추적한다.
413
+ const resizeDelegates = (resizeObserverSet ?? []).filter(t => t.delegate && typeof t.target === 'string') as Array<ResizeObserverSetEntry & { target: string }>;
414
+ let resizeObserve: ((el: Element, opts?: ResizeObserverOptions) => void) | undefined;
415
+ let resizeUnobserve: ((el: Element) => void) | undefined;
416
+ const resizeObserved = new WeakSet<Element>();
417
+ if (resizeObserverSet?.length) {
418
+ const roCallbacks = [...new Set(resizeObserverSet.map(e => e.callback))];
419
+ const ro = new (win as any).ResizeObserver((entries, obs) => {
420
+ for (const cb of roCallbacks) cb(entries, obs);
421
+ });
422
+ for (const e of resizeObserverSet) {
423
+ if (e.delegate) continue;
424
+ if (typeof e.target === 'string') {
425
+ // 문자열(non-delegate)이면 root 에서 검색된 요소들을 observe 대상으로 추가한다.
426
+ const scope = resolveDelegateRoots(e.delegateRoot);
427
+ for (const sr of scope) {
428
+ sr.querySelectorAll(e.target).forEach(el => {
429
+ if (!resizeObserved.has(el)) { ro.observe(el, e.options); resizeObserved.add(el); }
430
+ });
431
+ }
432
+ continue;
433
+ }
434
+ ro.observe(e.target as Element, e.options);
435
+ if (e.target instanceof Element) resizeObserved.add(e.target);
436
+ }
437
+ resizeObserve = (el, opts) => {
438
+ if (resizeObserved.has(el)) return;
439
+ ro.observe(el, opts);
440
+ resizeObserved.add(el);
441
+ };
442
+ resizeUnobserve = (el) => {
443
+ if (!resizeObserved.has(el)) return;
444
+ ro.unobserve(el);
445
+ resizeObserved.delete(el);
446
+ };
447
+ observers.push(ro);
448
+ }
449
+
450
+ // IntersectionObserver: 옵션 그룹별로 1개씩 생성.
451
+ // delegate 항목은 동적 추적용 셀렉터이므로 observe/unobserve 함수를 MutationObserver 콜백에 넘긴다.
452
+ // 동적 observe/unobserve 중복 방지를 위해 그룹별 WeakSet 으로 추적한다.
453
+ const intersectionDelegates: Array<{ target: string; delegateRoot?: SwcRootType; observe: (el: Element) => void; unobserve: (el: Element) => void }> = [];
454
+ for (const group of intersectionObserverSet) {
455
+ const ioCallbacks = [...new Set(group.observeTargets.map(e => e.callback))];
456
+ const io = new (win as any).IntersectionObserver((entries, obs) => {
457
+ for (const cb of ioCallbacks) cb(entries, obs);
458
+ }, group.options);
459
+ const ioObserved = new WeakSet<Element>();
460
+ for (const e of group.observeTargets) {
461
+ if (e.delegate) {
462
+ const target = e.target as string;
463
+ const observe = (el: Element) => {
464
+ if (ioObserved.has(el)) return;
465
+ io.observe(el);
466
+ ioObserved.add(el);
467
+ };
468
+ const unobserve = (el: Element) => {
469
+ if (!ioObserved.has(el)) return;
470
+ io.unobserve(el);
471
+ ioObserved.delete(el);
472
+ };
473
+ intersectionDelegates.push({ target, delegateRoot: e.delegateRoot, observe, unobserve });
474
+ continue;
475
+ }
476
+ if (typeof e.target === 'string') {
477
+ // 문자열(non-delegate)이면 root 에서 검색된 요소들을 observe 대상으로 추가한다.
478
+ const scope = resolveDelegateRoots(e.delegateRoot);
479
+ for (const sr of scope) {
480
+ sr.querySelectorAll(e.target).forEach(el => {
481
+ if (!ioObserved.has(el)) { io.observe(el); ioObserved.add(el); }
482
+ });
483
+ }
484
+ continue;
485
+ }
486
+ io.observe(e.target as Element);
487
+ if (e.target instanceof Element) ioObserved.add(e.target);
488
+ }
489
+ observers.push(io);
490
+ }
491
+
492
+ // resize/intersection delegate 동적 추적 — MutationObserver 콜백에서 추가/제거된 요소를 observe/unobserve 한다.
493
+ const handleResizeDelegate = (n: Node, observe: boolean) => {
494
+ if (!resizeObserve || !resizeUnobserve || n.nodeType !== 1) return;
495
+ const el = n as HTMLElement;
496
+ for (const d of resizeDelegates) {
497
+ const isThis = d.target === '$this' || d.target === '';
498
+ const matchesSel = (node: any) => {
499
+ if (!node || node.nodeType !== 1) return false;
500
+ if (isThis) return true;
501
+ return (node as HTMLElement).matches?.(d.target) || !!(node as HTMLElement).closest?.(d.target);
502
+ };
503
+ if (matchesSel(el)) {
504
+ observe ? resizeObserve(el, d.options)
505
+ : resizeUnobserve(el);
506
+ } else if (!isThis) {
507
+ el.querySelectorAll(d.target).forEach(sub => {
508
+ observe ? resizeObserve(sub as HTMLElement, d.options)
509
+ : resizeUnobserve(sub as HTMLElement);
510
+ });
511
+ }
512
+ }
513
+ };
514
+ const handleIntersectionDelegate = (n: Node, observe: boolean) => {
515
+ if (n.nodeType !== 1) return;
516
+ const el = n as HTMLElement;
517
+ for (const d of intersectionDelegates) {
518
+ const isThis = d.target === '$this' || d.target === '';
519
+ const matchesSel = (node: any) => {
520
+ if (!node || node.nodeType !== 1) return false;
521
+ if (isThis) return true;
522
+ return (node as HTMLElement).matches?.(d.target) || !!(node as HTMLElement).closest?.(d.target);
523
+ };
524
+ if (matchesSel(el)) {
525
+ observe ? d.observe(el) : d.unobserve(el);
526
+ } else if (!isThis) {
527
+ el.querySelectorAll(d.target).forEach(sub => {
528
+ observe ? d.observe(sub as HTMLElement) : d.unobserve(sub as HTMLElement);
529
+ });
530
+ }
531
+ }
532
+ };
533
+
534
+ if (mutationObserverSet?.length || resizeDelegates.length > 0 || intersectionDelegates.length > 0) {
535
+ // 하나의 MutationObserver 로 resize/intersection delegate 동적 추적 + mutation 콜백을 함께 처리
536
+ const callbacks = [...new Set((mutationObserverSet ?? []).map(e => e.callback))];
537
+ const mo = new (win as any).MutationObserver((mutations, obs) => {
538
+ for (const mut of mutations) {
539
+ Array.from(mut.addedNodes || []).forEach(n => { handleResizeDelegate(n as Node, true); handleIntersectionDelegate(n as Node, true); });
540
+ Array.from(mut.removedNodes || []).forEach(n => { handleResizeDelegate(n as Node, false); handleIntersectionDelegate(n as Node, false); });
541
+ }
542
+ for (const cb of callbacks) cb(mutations, obs);
543
+ });
544
+
545
+ // observe 대상 전체(엔트리 + delegate 추적 root)를 하나로 모아 중복 observe 를 제거한다.
546
+ // 같은 target 이 여러 소스에서 지정되면 MutationObserverInit 을 합집합(최대 감지범위)으로 병합한다.
547
+ const moObserveTargets = new Map<Element | ShadowRoot, MutationObserverInit>();
548
+ const mergeMutationInit = (existing: MutationObserverInit | undefined, next: MutationObserverInit): MutationObserverInit => {
549
+ if (!existing) return next;
550
+ const mergedFilter = existing.attributeFilter || next.attributeFilter
551
+ ? [...new Set([...(existing.attributeFilter ?? []), ...(next.attributeFilter ?? [])])]
552
+ : undefined;
553
+ return {
554
+ childList: existing.childList || next.childList,
555
+ attributes: existing.attributes || next.attributes,
556
+ characterData: existing.characterData || next.characterData,
557
+ subtree: existing.subtree || next.subtree,
558
+ attributeOldValue: existing.attributeOldValue || next.attributeOldValue,
559
+ characterDataOldValue: existing.characterDataOldValue || next.characterDataOldValue,
560
+ attributeFilter: mergedFilter,
561
+ };
562
+ };
563
+ const addObserveTarget = (target: Element | ShadowRoot, options: MutationObserverInit) => {
564
+ moObserveTargets.set(target, mergeMutationInit(moObserveTargets.get(target), options));
565
+ };
566
+
567
+ // 1) mutationObserverSet 엔트리
568
+ for (const e of mutationObserverSet ?? []) {
569
+ if (e.delegate) continue;
570
+ const init = e.options ?? { childList: true };
571
+ if (typeof e.target === 'string') {
572
+ // 문자열(non-delegate)이면 root 에서 검색된 요소들을 observe 대상으로 추가한다.
573
+ const scope = resolveDelegateRoots(e.delegateRoot);
574
+ for (const sr of scope) {
575
+ sr.querySelectorAll(e.target).forEach(el => addObserveTarget(el, init));
576
+ }
577
+ continue;
578
+ }
579
+ addObserveTarget(e.target, init);
580
+ }
581
+
582
+ // 2) delegate 추적 root: shadow/light/all/auto 로 해석해 전부 observe
583
+ for (const d of resizeDelegates) {
584
+ for (const r of resolveDelegateRoots(d.delegateRoot)) addObserveTarget(r, { childList: true, subtree: true });
585
+ }
586
+ for (const d of intersectionDelegates) {
587
+ for (const r of resolveDelegateRoots(d.delegateRoot)) addObserveTarget(r, { childList: true, subtree: true });
588
+ }
589
+
590
+ for (const [target, options] of moObserveTargets) {
591
+ mo.observe(target, options);
592
+ }
593
+ observers.push(mo);
594
+ }
595
+
596
+ inst.__swc_observers = observers;
597
+ };
598
+
388
599
  const originalConnected = proto.connectedCallback;
389
600
  proto.connectedCallback = async function () {
390
601
  ensureInit(this);
391
- // 재연결 시 누적 방지 — 클로저 변수 리셋
392
- boundListeners = [];
393
- resizeObservers = [];
394
- removeObserverCallbacks = [];
395
- // connected 됐을시 최신화
602
+ // 재연결 시 누적 방지 — cycler 내부 리소스 초기화
396
603
  helperHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
397
604
  const appHost = helperHostSet.$appHost;
398
605
  const useSsr = isSSR(this);
399
- // console.log('isSsr', useSsr, this);
400
- // console.log('----helperHostSet--->', this, helperHostSet, useSsr)
401
- // let connectedApp: SwcAppInterface |undefined;
402
606
  try {
403
607
  if (appHost && typeof (appHost as any)._connected === 'function') {
404
608
  await (appHost as any)._connected(this);
@@ -407,27 +611,19 @@ export const elementDefine =
407
611
  (appHost as any)._connected_safari_and_standby.push(this);
408
612
  }
409
613
 
410
- // const hostSet = SwcUtils.getHostSet(this as any);
411
614
  const conf = getElementConfig(this);
412
615
  const currentWin = (this as any)._resolveWindow(conf);
413
616
 
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);
617
+ // before-connected 라이프사이클 메서드 (@onConnectedBefore)
618
+ for (const m of findAllOnConnectedBeforeMetadata(this)) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
417
619
  (this as any)._executeSwcScript('swc-on-before-connected', helperHostSet);
418
620
 
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);
621
+ // ── 렌더 (@onConnected / @onConnectedBody*) — elementDefine 책임 ──
422
622
  const targetConnectedList = useSsr ? [] : findAllOnConnectedMetadata(constructor);
423
623
  const shadowMode = conf?.useShadow || targetConnectedList.find(it => it.options.useShadow)?.options.useShadow;
424
- // console.log('--------->', this.tagName, shadowMode && !this.shadowRoot)
425
624
  if (shadowMode && !this.shadowRoot) {
426
625
  const mode = shadowMode === true ? 'open' : shadowMode;
427
- console.log('tagName=', this.tagName, 'mode', mode, this.shadowRoot);
428
- // if (this.tagName!=='INDEX-ROUTER') {
429
626
  this.attachShadow({ mode: mode as ShadowRootMode });
430
- // }
431
627
  }
432
628
 
433
629
  const stateContext: any = { ...helperHostSet };
@@ -438,20 +634,14 @@ export const elementDefine =
438
634
  const shadowChildren: Node[] = [];
439
635
  const lightChildren: Node[] = [];
440
636
 
441
- console.log('targetConnectList', this.tagName, targetConnectedList.length);
442
637
  if (targetConnectedList.length > 0) {
443
- // let sContent = '',
444
- // lContent = '';
445
638
  for (const meta of targetConnectedList) {
446
- // const res = (this as any)[meta.propertyKey]();
447
639
  let res = await (this as any)._invokeLifecycleMethod(meta.propertyKey, helperHostSet);
448
- // res = SwcUtils.projectProcessHtml(this, res);
449
640
  if (typeof res === 'string') {
450
641
  const htmlTemplateElement = doc.createElement('template');
451
- htmlTemplateElement.innerHTML = res; //SwcUtils.projectProcessHtml(this._swcId, res, doc);
642
+ htmlTemplateElement.innerHTML = res;
452
643
  res = htmlTemplateElement.content;
453
644
  }
454
-
455
645
  if (res) {
456
646
  const nodes = Array.isArray(res) ? res : [res];
457
647
  if (meta.options.useShadow || conf?.useShadow) {
@@ -487,440 +677,20 @@ export const elementDefine =
487
677
  globalDelegatedRoots.add(root);
488
678
  }
489
679
 
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
- }
680
+ // ── cycler 실행 (렌더 후): 모든 시클러 onConnected → observer Set 수집 → observer 생성 ──
681
+ // helperHostSet 은 클로저 변수라 async 흐름에서 다른 인스턴스로 덮어써질 수 있다.
682
+ // 반드시 현재 인스턴스(this)로 재계산한 값을 사용한다.
683
+ await buildObservers(SwcUtils.getHelperAndHostSet(win, this as any));
899
684
 
900
685
  if (originalConnected) await originalConnected.apply(this);
901
686
 
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);
687
+ // after-connected 라이프사이클 메서드 (@onConnectedAfter)
688
+ for (const m of findAllOnConnectedAfterMetadata(this)) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
905
689
  (this as any)._executeSwcScript('swc-on-connected', helperHostSet);
906
690
  (this as any)._executeSwcScript('swc-on-after-connected', helperHostSet);
907
691
  (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
692
  } finally {
921
- for (let m of findAllLifecycleMetadata(this, ON_CONNECTED_COMPLETED_METADATA_KEY)) {
922
- await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
923
- }
693
+ for (const m of findAllLifecycleMetadata(this, ON_CONNECTED_COMPLETED_METADATA_KEY)) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
924
694
  if (appHost && typeof (appHost as any)._connectedDone === 'function') {
925
695
  await (appHost as any)._connectedDone(this);
926
696
  }
@@ -932,82 +702,26 @@ export const elementDefine =
932
702
  ////////////////////////////////////////////////
933
703
  const originalDisconnected = proto.disconnectedCallback;
934
704
  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;
705
+ const appHost = helperHostSet?.$appHost;
939
706
  if (appHost && typeof (appHost as any)._disconnected === 'function') {
940
707
  (appHost as any)._disconnected(this);
941
708
  }
942
709
 
943
710
  (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
- }
711
+ for (const m of findAllLifecycleMetadata(this, ON_BEFORE_DISCONNECTED_METADATA_KEY)) (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
712
+ for (const c of cyclers) c.onDisconnected?.(helperHostSet!);
967
713
 
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
- }
714
+ for (const o of (this as any).__swc_observers ?? []) {
715
+ try { (o as any)?.disconnect?.(); } catch (e) { console.error('[SWC] observer disconnect error:', e); }
989
716
  }
990
- removeObserverCallbacks = [];
717
+ (this as any).__swc_observers = [];
991
718
 
992
719
  // elementApply event listener 정리
993
720
  new ElementApply(this, { id: this._swcId }).removeAllEventListener();
994
721
 
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
722
  if (originalDisconnected) originalDisconnected.apply(this);
1008
723
 
1009
- const aMethods = findAllLifecycleMetadata(this, ON_AFTER_DISCONNECTED_METADATA_KEY);
1010
- for (const m of aMethods) (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
724
+ for (const m of findAllLifecycleMetadata(this, ON_AFTER_DISCONNECTED_METADATA_KEY)) (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
1011
725
  (this as any)._executeSwcScript('swc-on-disconnected', helperHostSet);
1012
726
  (this as any)._executeSwcScript('swc-on-after-disconnected', helperHostSet);
1013
727
  (this as any).__swc_connected = false;
@@ -1017,13 +731,12 @@ export const elementDefine =
1017
731
  proto.adoptedCallback = function () {
1018
732
  const hostSet = SwcUtils.getHostSet(this as any);
1019
733
  (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);
734
+ for (const m of findAllLifecycleMetadata(this, ON_BEFORE_ADOPTED_METADATA_KEY)) (this as any)._invokeLifecycleMethod(m.propertyKey, hostSet);
735
+ for (const c of cyclers) c.onAdopted?.(helperHostSet!);
1022
736
 
1023
737
  if (originalAdopted) originalAdopted.apply(this);
1024
738
 
1025
- const aMethods = findAllLifecycleMetadata(this, ON_AFTER_ADOPTED_METADATA_KEY);
1026
- for (const m of aMethods) (this as any)._invokeLifecycleMethod(m.propertyKey, hostSet);
739
+ for (const m of findAllLifecycleMetadata(this, ON_AFTER_ADOPTED_METADATA_KEY)) (this as any)._invokeLifecycleMethod(m.propertyKey, hostSet);
1027
740
  (this as any)._executeSwcScript('swc-on-adopted', hostSet);
1028
741
  (this as any)._executeSwcScript('swc-on-after-adopted', hostSet);
1029
742
  };
@@ -1032,7 +745,9 @@ export const elementDefine =
1032
745
  proto.attributeChangedCallback = function (name: string, old: string | null, newVal: string | null) {
1033
746
  if (originalAttributeChanged) originalAttributeChanged.apply(this, [name, old, newVal]);
1034
747
 
1035
- const hSet = SwcUtils.getHostSet(this as any);
748
+ // attributeChangedCallback 은 connected 이전에도 호출될 수 있으므로
749
+ // 클로저의 helperHostSet(null 가능) 대신 이 시점에 새로 계산한다.
750
+ const helperAndHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
1036
751
 
1037
752
  // Process expression directive before passing to handlers
1038
753
  let processedVal: any = newVal;
@@ -1041,18 +756,17 @@ export const elementDefine =
1041
756
  const expr = ae.getFirstExpression('callReturn');
1042
757
  if (expr) {
1043
758
  const win = (this as any)._resolveWindow?.() || ((typeof window !== 'undefined' ? window : undefined) as any);
1044
- const helperAndHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
759
+ const exprHelperAndHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
1045
760
  const script = ConvertUtils.decodeHtmlEntity(expr.script, win.document);
1046
761
  try {
1047
762
  const result = FunctionUtils.executeReturn({
1048
763
  script: script,
1049
764
  context: this,
1050
- args: helperAndHostSet
765
+ args: exprHelperAndHostSet
1051
766
  });
1052
- // Pass the result object directly without serialization
1053
767
  processedVal = result;
1054
768
  } catch (e) {
1055
- console.error(`[SWC] Failed to execute directive {{= ${expr.script} }} on attribute ${name}: ${helperAndHostSet}`, e);
769
+ console.error(`[SWC] Failed to execute directive {{= ${expr.script} }} on attribute ${name}: ${exprHelperAndHostSet}`, e);
1056
770
  processedVal = newVal;
1057
771
  }
1058
772
  }
@@ -1065,21 +779,8 @@ export const elementDefine =
1065
779
  }
1066
780
  }
1067
781
 
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
- }
782
+ // cycler 위임: attribute 옵저빙(@emitCustomEvent(attributeName) / @changedAttribute 등)
783
+ for (const c of cyclers) c.onAttributeChanged?.(helperAndHostSet, name, old, processedVal);
1083
784
  };
1084
785
 
1085
786
  Object.defineProperty(constructor, 'observedAttributes', {