@dooboostore/simple-web-component 1.0.50 → 1.0.52

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 (241) hide show
  1. package/README.md +735 -354
  2. package/dist/cjs/SwcAppEngine.js.map +2 -2
  3. package/dist/cjs/decorators/addEventListener.js +64 -146
  4. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  5. package/dist/cjs/decorators/applyAttribute.js +36 -48
  6. package/dist/cjs/decorators/applyAttribute.js.map +2 -2
  7. package/dist/cjs/decorators/applyClass.js +9 -12
  8. package/dist/cjs/decorators/applyClass.js.map +2 -2
  9. package/dist/cjs/decorators/applyNode.js +14 -22
  10. package/dist/cjs/decorators/applyNode.js.map +2 -2
  11. package/dist/cjs/decorators/applyProperty.js +12 -42
  12. package/dist/cjs/decorators/applyProperty.js.map +2 -2
  13. package/dist/cjs/decorators/applySlot.js +3 -3
  14. package/dist/cjs/decorators/applySlot.js.map +2 -2
  15. package/dist/cjs/decorators/applyStyle.js +9 -12
  16. package/dist/cjs/decorators/applyStyle.js.map +2 -2
  17. package/dist/cjs/decorators/around.js +124 -0
  18. package/dist/cjs/decorators/around.js.map +7 -0
  19. package/dist/cjs/decorators/changedAttribute.js +40 -12
  20. package/dist/cjs/decorators/changedAttribute.js.map +2 -2
  21. package/dist/cjs/decorators/elementDefine.js +224 -195
  22. package/dist/cjs/decorators/elementDefine.js.map +3 -3
  23. package/dist/cjs/decorators/emitCustomEvent.js +14 -12
  24. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  25. package/dist/cjs/decorators/eventMedia.js +36 -8
  26. package/dist/cjs/decorators/eventMedia.js.map +2 -2
  27. package/dist/cjs/decorators/fetch.js +278 -0
  28. package/dist/cjs/decorators/fetch.js.map +7 -0
  29. package/dist/cjs/decorators/index.js +5 -2
  30. package/dist/cjs/decorators/index.js.map +2 -2
  31. package/dist/cjs/decorators/intersectionObserver.js +33 -13
  32. package/dist/cjs/decorators/intersectionObserver.js.map +2 -2
  33. package/dist/cjs/decorators/lifecycles.js +11 -11
  34. package/dist/cjs/decorators/lifecycles.js.map +2 -2
  35. package/dist/cjs/decorators/mutationObserver.js +55 -39
  36. package/dist/cjs/decorators/mutationObserver.js.map +3 -3
  37. package/dist/cjs/decorators/parameter.js +92 -9
  38. package/dist/cjs/decorators/parameter.js.map +2 -2
  39. package/dist/cjs/decorators/publishSwcAppMessage.js +2 -2
  40. package/dist/cjs/decorators/publishSwcAppMessage.js.map +2 -2
  41. package/dist/cjs/decorators/query.js +11 -5
  42. package/dist/cjs/decorators/query.js.map +2 -2
  43. package/dist/cjs/decorators/requestAnimationFrame.js +7 -16
  44. package/dist/cjs/decorators/requestAnimationFrame.js.map +2 -2
  45. package/dist/cjs/decorators/resizeObserver.js +41 -17
  46. package/dist/cjs/decorators/resizeObserver.js.map +3 -3
  47. package/dist/cjs/decorators/setInterval.js +36 -25
  48. package/dist/cjs/decorators/setInterval.js.map +2 -2
  49. package/dist/cjs/decorators/setTimeout.js +36 -25
  50. package/dist/cjs/decorators/setTimeout.js.map +2 -2
  51. package/dist/cjs/decorators/state.js +13 -10
  52. package/dist/cjs/decorators/state.js.map +2 -2
  53. package/dist/cjs/decorators/{subscribeSwcAppMessageWhileConnected.js → subscribeSwcAppMessage.js} +20 -21
  54. package/dist/cjs/decorators/subscribeSwcAppMessage.js.map +7 -0
  55. package/dist/cjs/decorators/{subscribeSwcAppRouteChangeWhileConnected.js → subscribeSwcAppRouteChange.js} +23 -19
  56. package/dist/cjs/decorators/subscribeSwcAppRouteChange.js.map +7 -0
  57. package/dist/cjs/decorators/swcAppRoute.js +149 -0
  58. package/dist/cjs/decorators/swcAppRoute.js.map +7 -0
  59. package/dist/cjs/elements/SwcApp.js +70 -0
  60. package/dist/cjs/elements/SwcApp.js.map +2 -2
  61. package/dist/cjs/elements/SwcAppMixin.js +262 -83
  62. package/dist/cjs/elements/SwcAppMixin.js.map +3 -3
  63. package/dist/cjs/types/index.js +5 -1
  64. package/dist/cjs/types/index.js.map +2 -2
  65. package/dist/cjs/utils/Utils.js +36 -15
  66. package/dist/cjs/utils/Utils.js.map +2 -2
  67. package/dist/esm/SwcAppEngine.js.map +2 -2
  68. package/dist/esm/decorators/addEventListener.js +64 -146
  69. package/dist/esm/decorators/addEventListener.js.map +2 -2
  70. package/dist/esm/decorators/applyAttribute.js +36 -48
  71. package/dist/esm/decorators/applyAttribute.js.map +2 -2
  72. package/dist/esm/decorators/applyClass.js +9 -12
  73. package/dist/esm/decorators/applyClass.js.map +2 -2
  74. package/dist/esm/decorators/applyNode.js +15 -23
  75. package/dist/esm/decorators/applyNode.js.map +2 -2
  76. package/dist/esm/decorators/applyProperty.js +12 -42
  77. package/dist/esm/decorators/applyProperty.js.map +2 -2
  78. package/dist/esm/decorators/applySlot.js +3 -3
  79. package/dist/esm/decorators/applySlot.js.map +2 -2
  80. package/dist/esm/decorators/applyStyle.js +9 -12
  81. package/dist/esm/decorators/applyStyle.js.map +2 -2
  82. package/dist/esm/decorators/around.js +105 -0
  83. package/dist/esm/decorators/around.js.map +7 -0
  84. package/dist/esm/decorators/changedAttribute.js +40 -12
  85. package/dist/esm/decorators/changedAttribute.js.map +2 -2
  86. package/dist/esm/decorators/elementDefine.js +222 -193
  87. package/dist/esm/decorators/elementDefine.js.map +3 -3
  88. package/dist/esm/decorators/emitCustomEvent.js +15 -13
  89. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  90. package/dist/esm/decorators/eventMedia.js +36 -8
  91. package/dist/esm/decorators/eventMedia.js.map +2 -2
  92. package/dist/esm/decorators/fetch.js +259 -0
  93. package/dist/esm/decorators/fetch.js.map +7 -0
  94. package/dist/esm/decorators/index.js +5 -2
  95. package/dist/esm/decorators/index.js.map +2 -2
  96. package/dist/esm/decorators/intersectionObserver.js +33 -13
  97. package/dist/esm/decorators/intersectionObserver.js.map +2 -2
  98. package/dist/esm/decorators/lifecycles.js +11 -11
  99. package/dist/esm/decorators/lifecycles.js.map +2 -2
  100. package/dist/esm/decorators/mutationObserver.js +55 -39
  101. package/dist/esm/decorators/mutationObserver.js.map +3 -3
  102. package/dist/esm/decorators/parameter.js +92 -9
  103. package/dist/esm/decorators/parameter.js.map +2 -2
  104. package/dist/esm/decorators/publishSwcAppMessage.js +2 -2
  105. package/dist/esm/decorators/publishSwcAppMessage.js.map +2 -2
  106. package/dist/esm/decorators/query.js +11 -5
  107. package/dist/esm/decorators/query.js.map +2 -2
  108. package/dist/esm/decorators/requestAnimationFrame.js +8 -17
  109. package/dist/esm/decorators/requestAnimationFrame.js.map +2 -2
  110. package/dist/esm/decorators/resizeObserver.js +41 -17
  111. package/dist/esm/decorators/resizeObserver.js.map +2 -2
  112. package/dist/esm/decorators/setInterval.js +37 -26
  113. package/dist/esm/decorators/setInterval.js.map +2 -2
  114. package/dist/esm/decorators/setTimeout.js +37 -26
  115. package/dist/esm/decorators/setTimeout.js.map +2 -2
  116. package/dist/esm/decorators/state.js +13 -10
  117. package/dist/esm/decorators/state.js.map +2 -2
  118. package/dist/esm/decorators/subscribeSwcAppMessage.js +53 -0
  119. package/dist/esm/decorators/subscribeSwcAppMessage.js.map +7 -0
  120. package/dist/esm/decorators/{subscribeSwcAppRouteChangeWhileConnected.js → subscribeSwcAppRouteChange.js} +20 -16
  121. package/dist/esm/decorators/subscribeSwcAppRouteChange.js.map +7 -0
  122. package/dist/esm/decorators/swcAppRoute.js +130 -0
  123. package/dist/esm/decorators/swcAppRoute.js.map +7 -0
  124. package/dist/esm/elements/SwcApp.js +70 -0
  125. package/dist/esm/elements/SwcApp.js.map +2 -2
  126. package/dist/esm/elements/SwcAppMixin.js +264 -85
  127. package/dist/esm/elements/SwcAppMixin.js.map +3 -3
  128. package/dist/esm/types/index.js +5 -1
  129. package/dist/esm/types/index.js.map +2 -2
  130. package/dist/esm/utils/Utils.js +36 -15
  131. package/dist/esm/utils/Utils.js.map +2 -2
  132. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +1893 -1000
  133. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  134. package/dist/types/SwcAppEngine.d.ts +1 -0
  135. package/dist/types/SwcAppEngine.d.ts.map +1 -1
  136. package/dist/types/decorators/addEventListener.d.ts +923 -960
  137. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  138. package/dist/types/decorators/applyAttribute.d.ts +26 -38
  139. package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
  140. package/dist/types/decorators/applyClass.d.ts +1 -3
  141. package/dist/types/decorators/applyClass.d.ts.map +1 -1
  142. package/dist/types/decorators/applyNode.d.ts +15 -17
  143. package/dist/types/decorators/applyNode.d.ts.map +1 -1
  144. package/dist/types/decorators/applyProperty.d.ts +25 -45
  145. package/dist/types/decorators/applyProperty.d.ts.map +1 -1
  146. package/dist/types/decorators/applyStyle.d.ts +1 -3
  147. package/dist/types/decorators/applyStyle.d.ts.map +1 -1
  148. package/dist/types/decorators/around.d.ts +87 -0
  149. package/dist/types/decorators/around.d.ts.map +1 -0
  150. package/dist/types/decorators/changedAttribute.d.ts +21 -1
  151. package/dist/types/decorators/changedAttribute.d.ts.map +1 -1
  152. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  153. package/dist/types/decorators/emitCustomEvent.d.ts +0 -1
  154. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  155. package/dist/types/decorators/eventMedia.d.ts +24 -1
  156. package/dist/types/decorators/eventMedia.d.ts.map +1 -1
  157. package/dist/types/decorators/fetch.d.ts +139 -0
  158. package/dist/types/decorators/fetch.d.ts.map +1 -0
  159. package/dist/types/decorators/index.d.ts +5 -2
  160. package/dist/types/decorators/index.d.ts.map +1 -1
  161. package/dist/types/decorators/intersectionObserver.d.ts +24 -11
  162. package/dist/types/decorators/intersectionObserver.d.ts.map +1 -1
  163. package/dist/types/decorators/lifecycles.d.ts +8 -7
  164. package/dist/types/decorators/lifecycles.d.ts.map +1 -1
  165. package/dist/types/decorators/mutationObserver.d.ts +26 -13
  166. package/dist/types/decorators/mutationObserver.d.ts.map +1 -1
  167. package/dist/types/decorators/parameter.d.ts +84 -6
  168. package/dist/types/decorators/parameter.d.ts.map +1 -1
  169. package/dist/types/decorators/publishSwcAppMessage.d.ts +0 -1
  170. package/dist/types/decorators/publishSwcAppMessage.d.ts.map +1 -1
  171. package/dist/types/decorators/query.d.ts +7 -7
  172. package/dist/types/decorators/query.d.ts.map +1 -1
  173. package/dist/types/decorators/requestAnimationFrame.d.ts +1 -1
  174. package/dist/types/decorators/requestAnimationFrame.d.ts.map +1 -1
  175. package/dist/types/decorators/resizeObserver.d.ts +26 -13
  176. package/dist/types/decorators/resizeObserver.d.ts.map +1 -1
  177. package/dist/types/decorators/setInterval.d.ts +10 -0
  178. package/dist/types/decorators/setInterval.d.ts.map +1 -1
  179. package/dist/types/decorators/setTimeout.d.ts +10 -0
  180. package/dist/types/decorators/setTimeout.d.ts.map +1 -1
  181. package/dist/types/decorators/state.d.ts.map +1 -1
  182. package/dist/types/decorators/subscribeSwcAppMessage.d.ts +44 -0
  183. package/dist/types/decorators/subscribeSwcAppMessage.d.ts.map +1 -0
  184. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts +60 -0
  185. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts.map +1 -0
  186. package/dist/types/decorators/swcAppRoute.d.ts +94 -0
  187. package/dist/types/decorators/swcAppRoute.d.ts.map +1 -0
  188. package/dist/types/elements/SwcApp.d.ts.map +1 -1
  189. package/dist/types/elements/SwcAppMixin.d.ts +423 -388
  190. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  191. package/dist/types/types/index.d.ts +43 -4
  192. package/dist/types/types/index.d.ts.map +1 -1
  193. package/dist/types/utils/Utils.d.ts +15 -5
  194. package/dist/types/utils/Utils.d.ts.map +1 -1
  195. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +1893 -1000
  196. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  197. package/package.json +8 -6
  198. package/src/SwcAppEngine.ts +1 -0
  199. package/src/decorators/addEventListener.ts +102 -96
  200. package/src/decorators/applyAttribute.ts +76 -138
  201. package/src/decorators/applyClass.ts +8 -14
  202. package/src/decorators/applyNode.ts +33 -41
  203. package/src/decorators/applyProperty.ts +51 -99
  204. package/src/decorators/applySlot.ts +3 -3
  205. package/src/decorators/applyStyle.ts +8 -14
  206. package/src/decorators/around.ts +196 -0
  207. package/src/decorators/changedAttribute.ts +39 -15
  208. package/src/decorators/elementDefine.ts +270 -219
  209. package/src/decorators/emitCustomEvent.ts +17 -13
  210. package/src/decorators/eventMedia.ts +50 -9
  211. package/src/decorators/fetch.ts +454 -0
  212. package/src/decorators/index.ts +5 -2
  213. package/src/decorators/intersectionObserver.ts +38 -24
  214. package/src/decorators/lifecycles.ts +24 -22
  215. package/src/decorators/mutationObserver.ts +71 -52
  216. package/src/decorators/parameter.ts +145 -14
  217. package/src/decorators/publishSwcAppMessage.ts +3 -4
  218. package/src/decorators/query.ts +22 -14
  219. package/src/decorators/requestAnimationFrame.ts +10 -19
  220. package/src/decorators/resizeObserver.ts +50 -29
  221. package/src/decorators/setInterval.ts +37 -26
  222. package/src/decorators/setTimeout.ts +37 -26
  223. package/src/decorators/state.ts +19 -13
  224. package/src/decorators/subscribeSwcAppMessage.ts +106 -0
  225. package/src/decorators/subscribeSwcAppRouteChange.ts +131 -0
  226. package/src/decorators/swcAppRoute.ts +231 -0
  227. package/src/elements/SwcApp.ts +40 -10
  228. package/src/elements/SwcAppMixin.ts +340 -101
  229. package/src/types/index.ts +38 -5
  230. package/src/utils/Utils.ts +50 -18
  231. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
  232. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +0 -7
  233. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js +0 -54
  234. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
  235. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +0 -7
  236. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts +0 -23
  237. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +0 -1
  238. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +0 -31
  239. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +0 -1
  240. package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +0 -91
  241. package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +0 -107
@@ -13,17 +13,19 @@ import {DOM_EVENT_NAMES, HTML_TAG_ENTRIES} from '../config/config';
13
13
  import {SituationTypeContainer, SituationTypeContainers} from '@dooboostore/simple-boot/decorators/inject/Inject';
14
14
  import {FirstCheckMaker} from '@dooboostore/simple-boot';
15
15
  import {buildSwcParameterArgs, getParameterMetadata} from './parameter';
16
- import {ElementDefineLifeCycler, HelperHostSet, HostSet, InjectSituationType, IntersectionObserverSet, MutationObserverSet, OnConnectedResult, ResizeObserverSet, ResizeObserverSetEntry, SwcRootType} from '../types';
16
+ import {ElementDefineLifeCycler, HelperHostSet, HostSet, InjectSituationType, IntersectionObserverSet, MutationObserverSet, MutationObserverSetEntry, ObserverScope, OnConnectedResult, ResizeObserverSet, ResizeObserverSetEntry, SwcRootType} from '../types';
17
17
  import {ConvertUtils, ElementApply} from '@dooboostore/core-web';
18
18
  import {isSSR} from "../elements/SwcAppMixin";
19
19
  import {findAllStateMetadata} from "./state";
20
+ import {findAllAroundMetadata} from "./around";
20
21
  import {findAllPropertyMetadata} from "./applyProperty";
21
- import {MessageSubscribeLifeCycler} from "./subscribeSwcAppMessageWhileConnected";
22
- import {RouteSubscribeLifeCycler} from "./subscribeSwcAppRouteChangeWhileConnected";
22
+ import {MessageSubscribeLifeCycler} from "./subscribeSwcAppMessage";
23
+ import {RouteSubscribeLifeCycler} from "./subscribeSwcAppRouteChange";
23
24
  import {SetIntervalLifeCycler} from "./setInterval";
24
25
  import {SetTimeoutLifeCycler} from "./setTimeout";
25
26
  import {RequestAnimationFrameLifeCycler} from "./requestAnimationFrame";
26
27
  import {EventMediaLifeCycler} from "./eventMedia";
28
+ import {FetchLifeCycler} from "./fetch";
27
29
 
28
30
  // --- Core Interfaces & Types ---
29
31
 
@@ -43,7 +45,7 @@ export interface ElementMetadata extends Omit<ElementConfig, 'window'> {
43
45
  }
44
46
 
45
47
  export const getElementConfig = (target: any): ElementMetadata | undefined => {
46
- const constructor = target instanceof Function ? target : target.constructor;
48
+ const constructor = typeof target === 'function' ? target : target.constructor;
47
49
  return ReflectUtils.getMetadata(ELEMENT_CONFIG_KEY, constructor);
48
50
  };
49
51
 
@@ -57,7 +59,7 @@ export const ensureInit = (inst: any) => { // HTMLElement
57
59
  // delete (inst as any)[meta.propertyKey];
58
60
  // });
59
61
 
60
- const target = inst instanceof Function ? inst : inst.constructor;
62
+ const target = typeof inst === 'function' ? inst : inst.constructor;
61
63
 
62
64
  // ╔════════════════════════════════════════════════════════════════════════════════╗
63
65
  // ║ decorator 필드의 own property 삭제 ║
@@ -116,6 +118,19 @@ export const ensureInit = (inst: any) => { // HTMLElement
116
118
  const propertyAllList = findAllPropertyMetadata(target).filter(it => it.type === 'property');
117
119
  if (propertyAllList) {
118
120
  propertyAllList.forEach(meta => {
121
+ // getter가 없으면(순수 필드: declare·하이드레이션 값 등) 지우지 않음.
122
+ // getter가 있을 때만 own 값을 지워 getter가 DOM을 새로 읽게 한다.
123
+ let proto = Object.getPrototypeOf(inst);
124
+ let hasGetter = false;
125
+ while (proto && proto !== Object.prototype) {
126
+ const d = Object.getOwnPropertyDescriptor(proto, meta.propertyKey);
127
+ if (d) {
128
+ hasGetter = typeof d.get === 'function';
129
+ break;
130
+ }
131
+ proto = Object.getPrototypeOf(proto);
132
+ }
133
+ if (!hasGetter) return;
119
134
  delete (inst as any)[meta.propertyKey];
120
135
  });
121
136
  }
@@ -123,9 +138,31 @@ export const ensureInit = (inst: any) => { // HTMLElement
123
138
  const stateAllList = findAllStateMetadata(target);
124
139
  if (stateAllList) {
125
140
  stateAllList.forEach(meta => {
126
- const initUserData = (inst as any)[meta.propertyKey];
127
- delete (inst as any)[meta.propertyKey];
128
- (inst as any)[meta.propertyKey] = initUserData;
141
+ // getter로 읽으면 안 됨 — own이 남아있을 때 getter가 이주까지 해버려
142
+ // setter 재대입과 합치면 DOM 동기화가 중복 실행됨. descriptor에서 raw를 꺼낸다.
143
+ // own이 없으면(이미 정리됨) 스킵.
144
+ const own = Object.getOwnPropertyDescriptor(inst, meta.propertyKey);
145
+ if (own && 'value' in own) {
146
+ const initUserData = own.value;
147
+ delete (inst as any)[meta.propertyKey];
148
+ (inst as any)[meta.propertyKey] = initUserData;
149
+ }
150
+ });
151
+ }
152
+
153
+ // @around 필드의 own property 정리 → setter 경유로 재대입 (초기값에 set() 적용).
154
+ // attribute/query처럼 plain delete하면 초기값이 날아간다 — 반드시 살려서 setter로 넣는다.
155
+ // getter로 읽으면 안 됨(before 변환값이 섞임) — descriptor에서 raw를 꺼낸다.
156
+ // own이 없으면(이미 정리됨) 스킵 — 그래야 중복 변환이 안 생긴다.
157
+ const aroundAllList = findAllAroundMetadata(target);
158
+ if (aroundAllList) {
159
+ aroundAllList.forEach(meta => {
160
+ const own = Object.getOwnPropertyDescriptor(inst, meta.propertyKey);
161
+ if (own && 'value' in own) {
162
+ const initUserData = own.value;
163
+ delete (inst as any)[meta.propertyKey];
164
+ (inst as any)[meta.propertyKey] = initUserData;
165
+ }
129
166
  });
130
167
  }
131
168
 
@@ -155,7 +192,7 @@ const handleGlobalSwcEvent = async (event: Event) => {
155
192
  if (!(node instanceof HTMLElement)) continue;
156
193
  const script = node.getAttribute?.(attrName);
157
194
  if (script && !getElementConfig(node)) {
158
- const host: any = SwcUtils.findNearestSwcHostIncludingSelf(node);
195
+ const host: any = SwcUtils.findNearestSwcAncestorHost(node);
159
196
  if (host && host.isConnected && typeof host.__swc_executeAttributeEvent === 'function') {
160
197
  await host.__swc_executeAttributeEvent(node, attrName, script, event);
161
198
  (event as any).__swc_handled = true;
@@ -213,8 +250,7 @@ const setupPrototype = (proto: any, win: Window) => {
213
250
  const script = this.getAttribute(attrName);
214
251
  if (script) {
215
252
  try {
216
- const conf = getElementConfig(this);
217
- const currentWin = (this as any)._resolveWindow(conf);
253
+ const currentWin = SwcUtils.resolveWindow(this);
218
254
  const helpers = SwcUtils.getHelperSet(currentWin);
219
255
  const args = {...hostSet, ...helpers, ...extraArgs, $el: this, $root: this.getRootNode()};
220
256
  FunctionUtils.execute({script, context: this, args});
@@ -224,21 +260,10 @@ const setupPrototype = (proto: any, win: Window) => {
224
260
  }
225
261
  };
226
262
 
227
- proto._resolveWindow = function (localConfig?: ElementConfig): Window {
228
- ensureInit(this);
229
- if (localConfig?.window) return localConfig.window;
230
- const ancestors = SwcUtils.findAllSwcHostsIncludingSelf(this as any);
231
- for (let i = ancestors.length - 1; i >= 0; i--) {
232
- const aConf = getElementConfig(ancestors[i]);
233
- if (aConf?.window) return aConf.window;
234
- }
235
- return getElementConfig(this).window ?? ((typeof window !== 'undefined' ? window : undefined) as Window);
236
- };
237
-
238
263
  proto._invokeLifecycleMethod = function (methodName: string | symbol, hostSet?: HostSet, extraArgs: any[] = []) {
239
264
  ensureInit(this);
240
265
  if (typeof (this as any)[methodName] !== 'function') return;
241
- const useHostSet = hostSet ?? SwcUtils.getHelperAndHostSet(win, this);
266
+ const useHostSet = hostSet ?? SwcUtils.getHelperAndHostSet(this);
242
267
  const app = useHostSet?.$appHost?.simpleApplication;
243
268
 
244
269
  // @hostSet/@helperHostSet/@helperSet(parameter.ts)를 위한 값. lifecycle 메서드는
@@ -262,10 +287,13 @@ const setupPrototype = (proto: any, win: Window) => {
262
287
  // @hostSet/@helperHostSet/@helperSet가 붙은 파라미터는 @Inject/situationType 기반
263
288
  // 해석보다 먼저 처리되고(firstCheckMaker), 그 외 파라미터는 기존처럼 @Inject/타입
264
289
  // 기반으로 정상 해석된다 — 같은 메서드에서 두 방식을 섞어 써도 된다.
290
+ // swc 파라미터 데코레이터가 붙은 자리는 DI 로 넘기지 않는다. lifecycle 이 못 채우는 kind
291
+ // (예: @fetchSettled — @fetch 가 나중에 채움)는 null. undefined 를 돌려주면
292
+ // "처리 안 함"으로 보고 DI 가 파라미터 타입(Object 등)을 resolve 하려다 SimNoSuch 로 죽는다.
265
293
  const firstCheckMaker: FirstCheckMaker = ({target, targetKey}, token, idx) => {
266
294
  const saves = getParameterMetadata(target, targetKey!);
267
295
  const found = saves.find(s => s.index === idx);
268
- return found ? (kindValues as any)[found.kind] : undefined;
296
+ return found ? ((kindValues as any)[found.kind] ?? null) : undefined;
269
297
  };
270
298
 
271
299
  return app.simstanceManager.executeBindParameterSim(
@@ -304,8 +332,7 @@ const setupPrototype = (proto: any, win: Window) => {
304
332
 
305
333
  const handler = async (event: any) => {
306
334
  const hostSet = SwcUtils.getHostSet(el);
307
- const conf = getElementConfig(this);
308
- const currentWin = (this as any)._resolveWindow(conf);
335
+ const currentWin = SwcUtils.resolveWindow(this);
309
336
  const helpers = SwcUtils.getHelperSet(currentWin);
310
337
  const args = {
311
338
  event,
@@ -326,8 +353,7 @@ const setupPrototype = (proto: any, win: Window) => {
326
353
  ensureInit(this);
327
354
 
328
355
  const hostSet = SwcUtils.getHostSet(el);
329
- const conf = getElementConfig(this);
330
- const currentWin = (this as any)._resolveWindow(conf);
356
+ const currentWin = SwcUtils.resolveWindow(this);
331
357
  const currentHelpers = SwcUtils.getHelperSet(currentWin);
332
358
  const detail = (event as CustomEvent).detail;
333
359
  const args = {
@@ -344,6 +370,11 @@ const setupPrototype = (proto: any, win: Window) => {
344
370
  };
345
371
  };
346
372
 
373
+ // 부모 클래스도 elementDefine 됐으면 proto.xxxCallback 은 부모의 swc 래퍼다 — 그걸 원래 함수로 부르면 라이프사이클이 두 번 돈다.
374
+ // 래퍼에 기억해 둔 사용자 원래 함수(없으면 undefined)를 쓴다.
375
+ const SWC_ORIGINAL_CALLBACK = Symbol.for('simple-web-component:original-callback');
376
+ const userCallback = (fn: any) => (typeof fn === 'function' && SWC_ORIGINAL_CALLBACK in fn ? fn[SWC_ORIGINAL_CALLBACK] : fn);
377
+
347
378
  export const elementDefine =
348
379
  (name: string, config: Partial<ElementConfig> = {}): ClassDecorator =>
349
380
  (constructor: any) => {
@@ -382,36 +413,87 @@ export const elementDefine =
382
413
  const proto = constructor.prototype;
383
414
  setupPrototype(proto, win);
384
415
 
385
- // 값을 기대하지않는다.
386
- let helperHostSet: HelperHostSet | null = null;
416
+ // helperHostSet 은 인스턴스마다 다르다 — 클래스 단위 변수로 두면 다른 인스턴스의 connect 가 덮어써서
417
+ // disconnect 때 엉뚱한 인스턴스가 정리된다. connect 시점 값을 인스턴스에 저장해 disconnect/adopt 에서 쓴다.
418
+ const connectedHelperHostSet = (inst: any): HelperHostSet => inst.__swc_helperHostSet ?? SwcUtils.getHelperAndHostSet(inst);
387
419
  // ── cyclers: 데코레이터별 라이프사이클 위임 클래스 (elementDefine 시 1회 생성, 계속 재사용) ──
388
420
  const eventCycler = new EventListenerLifeCycler();
421
+ const resizeCycler = new ResizeObserverLifeCycler();
422
+ const intersectionCycler = new IntersectionObserverLifeCycler();
423
+ const mutationCycler = new MutationObserverLifeCycler();
389
424
  const cyclers: ElementDefineLifeCycler[] = [
390
425
  eventCycler,
391
426
  new ChangedAttributeLifeCycler(),
392
427
  new EmitCustomEventLifeCycler(),
393
- new ResizeObserverLifeCycler(),
394
- new IntersectionObserverLifeCycler(),
395
- new MutationObserverLifeCycler(),
428
+ resizeCycler,
429
+ intersectionCycler,
430
+ mutationCycler,
396
431
  new MessageSubscribeLifeCycler(),
397
432
  new RouteSubscribeLifeCycler(),
398
433
  new SetIntervalLifeCycler(),
399
434
  new SetTimeoutLifeCycler(),
400
435
  new RequestAnimationFrameLifeCycler(),
401
436
  new EventMediaLifeCycler(),
437
+ new FetchLifeCycler(),
402
438
  ];
403
439
 
404
440
  // observedAttributes 기여 cycler (define-time, constructor 기반)
405
441
  const cyclerObservedAttributes = cyclers.flatMap(c => c.getObservedAttributeNames?.(constructor) ?? []);
406
442
  const mergedObservedAttributes = [...new Set([...(metadata.observedAttributes ?? []), ...originalStaticObservedAttributes, ...cyclerObservedAttributes, ...attributeApplyNames, ...hostAttributes, ...swcLifecycleAttributes, ...swcOnEvents])];
407
443
 
408
- // 렌더 후 모든 시클러 onConnected 호출 → Set 수집 후 observer 생성
409
- const buildObservers = async (helperHostSet: HelperHostSet): Promise<void> => {
444
+ // ── observer 빌드 (connect 1회당 상태 1개) ──
445
+ // MutationObserver·ResizeObserver 는 인스턴스당 1개씩만 만들고 모든 데코레이터가 공유한다 (delegate 추적 포함).
446
+ // 'self'(렌더 전): 자기 자신 대상 mutation/resize 만 붙여 첫 렌더부터 본다.
447
+ // 'rest'(렌더 후): 나머지 전부를 같은 옵저버에 덧붙인다.
448
+ // 시클러가 scope 별로 자기 메타만 담은 콜백을 주고, 메타는 자기 대상 레코드/엔트리만 받으므로 따로 나눌 필요가 없다.
449
+ // IntersectionObserver 는 옵션 그룹별로 1개 ('rest' 전용).
450
+ type IntersectionDelegate = { target: string; delegateRoot?: SwcRootType; observe: (el: Element) => void; unobserve: (el: Element) => void };
451
+ interface ObserverState {
452
+ observers: Array<MutationObserver | ResizeObserver | IntersectionObserver>;
453
+ ro?: ResizeObserver;
454
+ roCallbacks: Set<ResizeObserverSetEntry['callback']>;
455
+ roObserved: WeakSet<Element>;
456
+ resizeDelegates: Array<ResizeObserverSetEntry & { target: string }>;
457
+ intersectionDelegates: IntersectionDelegate[];
458
+ mo?: MutationObserver;
459
+ moCallbacks: Set<MutationObserverSetEntry['callback']>;
460
+ moTargets: Map<Element | ShadowRoot, MutationObserverInit>;
461
+ }
462
+ const newObserverState = (): ObserverState => ({
463
+ observers: [], roCallbacks: new Set(), roObserved: new WeakSet(), resizeDelegates: [], intersectionDelegates: [],
464
+ moCallbacks: new Set(), moTargets: new Map(),
465
+ });
466
+
467
+ // 같은 target 이 여러 소스에서 지정되면 MutationObserverInit 을 합집합(최대 감지범위)으로 병합한다.
468
+ const mergeMutationInit = (existing: MutationObserverInit | undefined, next: MutationObserverInit): MutationObserverInit => {
469
+ if (!existing) return next;
470
+ const mergedFilter = existing.attributeFilter || next.attributeFilter
471
+ ? [...new Set([...(existing.attributeFilter ?? []), ...(next.attributeFilter ?? [])])]
472
+ : undefined;
473
+ return {
474
+ childList: existing.childList || next.childList,
475
+ attributes: existing.attributes || next.attributes,
476
+ characterData: existing.characterData || next.characterData,
477
+ subtree: existing.subtree || next.subtree,
478
+ attributeOldValue: existing.attributeOldValue || next.attributeOldValue,
479
+ characterDataOldValue: existing.characterDataOldValue || next.characterDataOldValue,
480
+ attributeFilter: mergedFilter,
481
+ };
482
+ };
483
+
484
+ // delegate 셀렉터에 맞는 요소(자신 또는 하위)를 찾아 observe/unobserve — MutationObserver 콜백에서 동적 추적용
485
+ const forDelegateMatches = (n: Node, selector: string, fn: (el: Element) => void) => {
486
+ if (n.nodeType !== 1) return;
487
+ const el = n as HTMLElement;
488
+ const isThis = selector === '$this' || selector === '';
489
+ if (isThis || el.matches?.(selector) || el.closest?.(selector)) fn(el);
490
+ else el.querySelectorAll(selector).forEach(fn);
491
+ };
492
+
493
+ const buildObservers = async (helperHostSet: HelperHostSet, scope: ObserverScope, st: ObserverState, alive: () => boolean): Promise<void> => {
410
494
  const inst = helperHostSet.$this;
411
- const observers: Array<MutationObserver | ResizeObserver | IntersectionObserver> = [];
412
- let resizeObserverSet: ResizeObserverSet = [];
413
- let mutationObserverSet: MutationObserverSet = [];
414
- let intersectionObserverSet: IntersectionObserverSet = [];
495
+ // 'self' 는 순수 수집기(observer 시클러)만 돌린다 — 리스너·타이머 등 부작용 시클러는 'rest' 에서 한 번만.
496
+ const activeCyclers = scope === 'self' ? [resizeCycler, mutationCycler] : cyclers;
415
497
 
416
498
  // delegate 추적 root 해석: shadow/light/auto/all → 실제 root 목록
417
499
  const resolveDelegateRoots = (delegateRoot: SwcRootType | undefined): (HTMLElement | ShadowRoot)[] => {
@@ -424,225 +506,143 @@ export const elementDefine =
424
506
  return roots;
425
507
  };
426
508
 
427
- // 1. 모든 시클러 onConnected → observer Set 수집 (여러 시클러가 반환한 Set 을 전부 누적)
428
- for (const c of cyclers) {
429
- const result = (await c.onConnected?.(helperHostSet, resizeObserverSet)) as OnConnectedResult | void;
509
+ // 1. 시클러 onConnected → observer Set 수집 (여러 시클러가 반환한 Set 을 전부 누적)
510
+ let resizeObserverSet: ResizeObserverSet = [];
511
+ let mutationObserverSet: MutationObserverSet = [];
512
+ let intersectionObserverSet: IntersectionObserverSet = [];
513
+ for (const c of activeCyclers) {
514
+ const result = (await c.onConnected?.(helperHostSet, resizeObserverSet, scope)) as OnConnectedResult | void;
515
+ // await 사이에 떼어졌으면(새 연결이 차례를 가져갔으면) 여기서 멈춘다 — 옵저버를 만들면 아무도 disconnect 하지 않아 샌다.
516
+ if (!alive()) return;
430
517
  if (!result) continue;
431
518
  if (result.resizeObserverSet?.length) resizeObserverSet = [...resizeObserverSet, ...result.resizeObserverSet];
432
519
  if (result.mutationObserverSet?.length) mutationObserverSet = [...mutationObserverSet, ...result.mutationObserverSet];
433
520
  if (result.intersectionObserverSet?.length) intersectionObserverSet = [...intersectionObserverSet, ...result.intersectionObserverSet];
434
521
  }
435
522
 
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
- });
523
+ // 2. ResizeObserver (1개). delegate 항목은 셀렉터 문자열이라 초기 observe 하지 않고 MutationObserver 로 동적 추적한다.
524
+ const resizeObserve = (el: Element, opts?: ResizeObserverOptions) => {
525
+ if (!st.ro || st.roObserved.has(el)) return;
526
+ st.ro.observe(el, opts);
527
+ st.roObserved.add(el);
528
+ };
529
+ const resizeUnobserve = (el: Element) => {
530
+ if (!st.ro || !st.roObserved.has(el)) return;
531
+ st.ro.unobserve(el);
532
+ st.roObserved.delete(el);
533
+ };
534
+ const newResizeDelegates: ObserverState['resizeDelegates'] = [];
535
+ if (resizeObserverSet.length) {
536
+ if (!st.ro) {
537
+ st.ro = new win.ResizeObserver((entries, obs) => { for (const cb of st.roCallbacks) cb(entries, obs); });
538
+ st.observers.push(st.ro);
539
+ }
449
540
  for (const e of resizeObserverSet) {
450
- if (e.delegate) continue;
541
+ st.roCallbacks.add(e.callback);
542
+ if (e.delegate) {
543
+ if (typeof e.target === 'string') newResizeDelegates.push(e as ResizeObserverSetEntry & { target: string });
544
+ continue;
545
+ }
451
546
  if (typeof e.target === 'string') {
452
547
  // 문자열(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
- }
548
+ for (const r of resolveDelegateRoots(e.delegateRoot)) r.querySelectorAll(e.target).forEach(el => resizeObserve(el, e.options));
459
549
  continue;
460
550
  }
461
- ro.observe(e.target as Element, e.options);
462
- if (e.target instanceof Element) resizeObserved.add(e.target);
551
+ st.ro.observe(e.target as Element, e.options);
552
+ if (e.target instanceof Element) st.roObserved.add(e.target);
463
553
  }
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);
554
+ st.resizeDelegates.push(...newResizeDelegates);
475
555
  }
476
556
 
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 }> = [];
557
+ // 3. IntersectionObserver: 옵션 그룹별로 1개. delegate 항목은 MutationObserver 로 동적 추적한다.
558
+ const newIntersectionDelegates: IntersectionDelegate[] = [];
481
559
  for (const group of intersectionObserverSet) {
482
560
  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);
561
+ const io = new win.IntersectionObserver((entries, obs) => { for (const cb of ioCallbacks) cb(entries, obs); }, group.options);
486
562
  const ioObserved = new WeakSet<Element>();
563
+ const observe = (el: Element) => { if (ioObserved.has(el)) return; io.observe(el); ioObserved.add(el); };
564
+ const unobserve = (el: Element) => { if (!ioObserved.has(el)) return; io.unobserve(el); ioObserved.delete(el); };
487
565
  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
- }
566
+ if (e.delegate) { newIntersectionDelegates.push({ target: e.target as string, delegateRoot: e.delegateRoot, observe, unobserve }); continue; }
503
567
  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
- }
568
+ for (const r of resolveDelegateRoots(e.delegateRoot)) r.querySelectorAll(e.target).forEach(observe);
511
569
  continue;
512
570
  }
513
- io.observe(e.target as Element);
514
- if (e.target instanceof Element) ioObserved.add(e.target);
571
+ observe(e.target as Element);
515
572
  }
516
- observers.push(io);
573
+ st.observers.push(io);
517
574
  }
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
- }
575
+ st.intersectionDelegates.push(...newIntersectionDelegates);
576
+
577
+ // 4. MutationObserver (1개): mutation 콜백 + resize/intersection delegate 동적 추적을 함께 처리한다.
578
+ if (mutationObserverSet.length || newResizeDelegates.length || newIntersectionDelegates.length) {
579
+ if (!st.mo) {
580
+ st.mo = new win.MutationObserver((mutations, obs) => {
581
+ for (const mut of mutations) {
582
+ for (const [nodes, on] of [[mut.addedNodes, true], [mut.removedNodes, false]] as const) {
583
+ for (const n of Array.from(nodes)) {
584
+ for (const d of st.resizeDelegates) forDelegateMatches(n, d.target, el => on ? resizeObserve(el, d.options) : resizeUnobserve(el));
585
+ for (const d of st.intersectionDelegates) forDelegateMatches(n, d.target, el => on ? d.observe(el) : d.unobserve(el));
586
+ }
587
+ }
588
+ }
589
+ for (const cb of st.moCallbacks) cb(mutations, obs);
590
+ });
591
+ st.observers.push(st.mo);
558
592
  }
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
593
  const addObserveTarget = (target: Element | ShadowRoot, options: MutationObserverInit) => {
591
- moObserveTargets.set(target, mergeMutationInit(moObserveTargets.get(target), options));
594
+ st.moTargets.set(target, mergeMutationInit(st.moTargets.get(target), options));
592
595
  };
593
-
594
- // 1) mutationObserverSet 엔트리
595
- for (const e of mutationObserverSet ?? []) {
596
+ for (const e of mutationObserverSet) {
597
+ st.moCallbacks.add(e.callback);
596
598
  if (e.delegate) continue;
597
599
  const init = e.options ?? { childList: true };
598
600
  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
- }
601
+ for (const r of resolveDelegateRoots(e.delegateRoot)) r.querySelectorAll(e.target).forEach(el => addObserveTarget(el, init));
604
602
  continue;
605
603
  }
606
604
  addObserveTarget(e.target, init);
607
605
  }
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) {
606
+ for (const d of [...newResizeDelegates, ...newIntersectionDelegates]) {
614
607
  for (const r of resolveDelegateRoots(d.delegateRoot)) addObserveTarget(r, { childList: true, subtree: true });
615
608
  }
616
-
617
- for (const [target, options] of moObserveTargets) {
618
- mo.observe(target, options);
619
- }
620
- observers.push(mo);
609
+ // 같은 노드를 다시 observe 하면 옵션만 교체된다 (등록·전달 중복 없음) → 'self' 분과 합쳐진 옵션으로 갱신
610
+ for (const [target, options] of st.moTargets) st.mo.observe(target, options);
621
611
  }
622
612
 
623
- inst.__swc_observers = observers;
613
+ inst.__swc_observers = st.observers;
624
614
  };
625
615
 
626
- const originalConnected = proto.connectedCallback;
616
+ const originalConnected = userCallback(proto.connectedCallback);
627
617
  proto.connectedCallback = async function () {
628
618
  ensureInit(this);
629
619
  // 재연결 시 누적 방지 — cycler 내부 리소스 초기화
630
- helperHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
620
+ const helperHostSet = SwcUtils.getHelperAndHostSet(this as any);
621
+ (this as any).__swc_helperHostSet = helperHostSet;
631
622
  const appHost = helperHostSet.$appHost;
632
623
  const useSsr = isSSR(this);
624
+ // 연결 처리는 await 를 거친다. 그 사이 떨어지거나 다시 붙으면(disconnect / 새 connect 가 차례 번호를 올리면) 이 연결은 멈춘다 —
625
+ // 안 그러면 떼어진 뒤에 리스너·타이머가 등록돼 새고, 다시 붙은 연결과 겹쳐 두 번씩 돈다.
626
+ // (isConnected 는 보지 않는다 — 직접 호출이나 SSR 처럼 붙어 있지 않아도 라이프사이클은 돌아야 한다)
627
+ const gen = ((this as any).__swc_connectGen = ((this as any).__swc_connectGen ?? 0) + 1);
628
+ const alive = () => (this as any).__swc_connectGen === gen;
629
+ const obsState = newObserverState();
633
630
  try {
634
631
  if (appHost && typeof (appHost as any)._connected === 'function') {
635
632
  await (appHost as any)._connected(this);
633
+ if (!alive()) return;
636
634
  } else if (appHost && typeof (appHost as any)) {
637
635
  (appHost as any)._connected_safari_and_standby ??= [];
638
636
  (appHost as any)._connected_safari_and_standby.push(this);
639
637
  }
640
638
 
641
639
  const conf = getElementConfig(this);
642
- const currentWin = (this as any)._resolveWindow(conf);
643
640
 
644
641
  // before-connected 라이프사이클 메서드 (@onConnectedBefore)
645
- for (const m of findAllOnConnectedBeforeMetadata(this)) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
642
+ for (const m of findAllOnConnectedBeforeMetadata(this)) {
643
+ await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
644
+ if (!alive()) return;
645
+ }
646
646
  (this as any)._executeSwcScript('swc-on-before-connected', helperHostSet);
647
647
 
648
648
  // ── 렌더 (@onConnected / @onConnectedBody*) — elementDefine 책임 ──
@@ -653,6 +653,10 @@ export const elementDefine =
653
653
  this.attachShadow({ mode: mode as ShadowRootMode });
654
654
  }
655
655
 
656
+ // 자기 자신 대상 mutation/resize 는 렌더 전에 붙여 첫 렌더부터 본다 (shadow root 가 생긴 뒤라 $this 가 맞는 root 를 잡는다)
657
+ await buildObservers(helperHostSet, 'self', obsState, alive);
658
+ if (!alive()) return;
659
+
656
660
  const stateContext: any = { ...helperHostSet };
657
661
  findAllStateMetadata(this).forEach(it => {
658
662
  stateContext[it.name] = this[it.propertyKey];
@@ -662,8 +666,40 @@ export const elementDefine =
662
666
  const lightChildren: Node[] = [];
663
667
 
664
668
  if (targetConnectedList.length > 0) {
669
+ const toNodes = (value: any): Node[] => {
670
+ if (!value) return [];
671
+ if (typeof value === 'string') {
672
+ const htmlTemplateElement = doc.createElement('template');
673
+ htmlTemplateElement.innerHTML = value;
674
+ return Array.from(htmlTemplateElement.content.childNodes);
675
+ }
676
+ if (Array.isArray(value)) return value;
677
+ return [value];
678
+ };
679
+ // fallback: 비동기 렌더 대기 중에 먼저 보여준다. 결과는 모든 렌더 메서드가 끝난 뒤 한 번에 들어가므로
680
+ // 그때 같이 걷어낸다 (메서드별로 걷으면 다음 메서드를 기다리는 동안 빈 화면이 된다).
681
+ const shownFallbacks: Node[] = [];
682
+ const removeFallbacks = () => shownFallbacks.splice(0).forEach(n => (n as ChildNode).remove());
665
683
  for (const meta of targetConnectedList) {
684
+ const useShadowRoot = meta.options.useShadow || conf?.useShadow;
685
+ if (meta.options.fallback) {
686
+ const fb = typeof meta.options.fallback === 'function'
687
+ ? (meta.options.fallback as any)(helperHostSet)
688
+ : meta.options.fallback;
689
+ const fallbackNodes = toNodes(fb);
690
+ const parent = useShadowRoot ? this.shadowRoot : this;
691
+ if (fallbackNodes.length && parent) {
692
+ try {
693
+ const processed = SwcUtils.projectProcessHtml(this._swcId, fallbackNodes, doc);
694
+ parent.append(...processed);
695
+ shownFallbacks.push(...processed);
696
+ } catch (e) {
697
+ console.error('[ElementDefine] fallback setting error:', e);
698
+ }
699
+ }
700
+ }
666
701
  let res = await (this as any)._invokeLifecycleMethod(meta.propertyKey, helperHostSet);
702
+ if (!alive()) { removeFallbacks(); return; }
667
703
  if (typeof res === 'string') {
668
704
  const htmlTemplateElement = doc.createElement('template');
669
705
  htmlTemplateElement.innerHTML = res;
@@ -678,6 +714,7 @@ export const elementDefine =
678
714
  }
679
715
  }
680
716
  }
717
+ removeFallbacks();
681
718
  try {
682
719
  if (this.shadowRoot) {
683
720
  const applyShadowChildren = SwcUtils.projectProcessHtml(this._swcId, shadowChildren, doc);
@@ -704,22 +741,27 @@ export const elementDefine =
704
741
  globalDelegatedRoots.add(root);
705
742
  }
706
743
 
707
- // ── cycler 실행 (렌더 후): 모든 시클러 onConnected → observer Set 수집 → observer 생성 ──
708
- // helperHostSet 은 클로저 변수라 async 흐름에서 다른 인스턴스로 덮어써질 수 있다.
709
- // 반드시 현재 인스턴스(this)로 재계산한 값을 사용한다.
710
- await buildObservers(SwcUtils.getHelperAndHostSet(win, this as any));
744
+ // ── cycler 실행 (렌더 후): 나머지 시클러 onConnected → observer Set 수집 → 같은 옵저버에 덧붙임 ──
745
+ await buildObservers(helperHostSet, 'rest', obsState, alive);
746
+ if (!alive()) return;
711
747
 
712
748
  if (originalConnected) await originalConnected.apply(this);
749
+ if (!alive()) return;
713
750
 
714
751
  // after-connected 라이프사이클 메서드 (@onConnectedAfter)
715
- for (const m of findAllOnConnectedAfterMetadata(this)) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
752
+ for (const m of findAllOnConnectedAfterMetadata(this)) {
753
+ await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
754
+ if (!alive()) return;
755
+ }
716
756
  (this as any)._executeSwcScript('swc-on-connected', helperHostSet);
717
757
  (this as any)._executeSwcScript('swc-on-after-connected', helperHostSet);
718
758
  (this as any).__swc_connected = true;
719
759
  } finally {
720
- for (const m of findAllLifecycleMetadata(this, ON_CONNECTED_COMPLETED_METADATA_KEY)) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
760
+ // 멈춘 연결은 완료 훅을 부르지 않고, 앱 host 에는 중단만 알린다 (자식 연결 수만 맞추고 재생은 안 함)
761
+ const aborted = !alive();
762
+ if (!aborted) for (const m of findAllLifecycleMetadata(this, ON_CONNECTED_COMPLETED_METADATA_KEY)) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
721
763
  if (appHost && typeof (appHost as any)._connectedDone === 'function') {
722
- await (appHost as any)._connectedDone(this);
764
+ await (appHost as any)._connectedDone(this, { aborted });
723
765
  }
724
766
  }
725
767
  };
@@ -727,16 +769,19 @@ export const elementDefine =
727
769
  /////////////////////////////////////////////////
728
770
  // disconnectedCallback
729
771
  ////////////////////////////////////////////////
730
- const originalDisconnected = proto.disconnectedCallback;
772
+ const originalDisconnected = userCallback(proto.disconnectedCallback);
731
773
  proto.disconnectedCallback = function () {
732
- const appHost = helperHostSet?.$appHost;
774
+ (this as any).__swc_connectGen = ((this as any).__swc_connectGen ?? 0) + 1; // 진행 중인 연결은 멈춘다
775
+ // 떨어진 뒤라 DOM 으로는 $appHost 를 다시 못 찾는다 → connect 때 저장한 이 인스턴스의 값
776
+ const helperHostSet = connectedHelperHostSet(this);
777
+ const appHost = helperHostSet.$appHost;
733
778
  if (appHost && typeof (appHost as any)._disconnected === 'function') {
734
779
  (appHost as any)._disconnected(this);
735
780
  }
736
781
 
737
782
  (this as any)._executeSwcScript('swc-on-before-disconnected', helperHostSet);
738
783
  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!);
784
+ for (const c of cyclers) c.onDisconnected?.(helperHostSet);
740
785
 
741
786
  for (const o of (this as any).__swc_observers ?? []) {
742
787
  try { (o as any)?.disconnect?.(); } catch (e) { console.error('[SWC] observer disconnect error:', e); }
@@ -754,12 +799,12 @@ export const elementDefine =
754
799
  (this as any).__swc_connected = false;
755
800
  };
756
801
 
757
- const originalAdopted = proto.adoptedCallback;
802
+ const originalAdopted = userCallback(proto.adoptedCallback);
758
803
  proto.adoptedCallback = function () {
759
804
  const hostSet = SwcUtils.getHostSet(this as any);
760
805
  (this as any)._executeSwcScript('swc-on-before-adopted', hostSet);
761
806
  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!);
807
+ for (const c of cyclers) c.onAdopted?.(connectedHelperHostSet(this));
763
808
 
764
809
  if (originalAdopted) originalAdopted.apply(this);
765
810
 
@@ -768,13 +813,13 @@ export const elementDefine =
768
813
  (this as any)._executeSwcScript('swc-on-after-adopted', hostSet);
769
814
  };
770
815
 
771
- const originalAttributeChanged = proto.attributeChangedCallback;
816
+ const originalAttributeChanged = userCallback(proto.attributeChangedCallback);
772
817
  proto.attributeChangedCallback = function (name: string, old: string | null, newVal: string | null) {
773
818
  if (originalAttributeChanged) originalAttributeChanged.apply(this, [name, old, newVal]);
774
819
 
775
820
  // attributeChangedCallback 은 connected 이전에도 호출될 수 있으므로
776
821
  // 클로저의 helperHostSet(null 가능) 대신 이 시점에 새로 계산한다.
777
- const helperAndHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
822
+ const helperAndHostSet = SwcUtils.getHelperAndHostSet(this as any);
778
823
 
779
824
  // Process expression directive before passing to handlers
780
825
  let processedVal: any = newVal;
@@ -782,8 +827,8 @@ export const elementDefine =
782
827
  const ae = new ActionExpression(newVal);
783
828
  const expr = ae.getFirstExpression('callReturn');
784
829
  if (expr) {
785
- const win = (this as any)._resolveWindow?.() || ((typeof window !== 'undefined' ? window : undefined) as any);
786
- const exprHelperAndHostSet = SwcUtils.getHelperAndHostSet(win, this as any);
830
+ const win = SwcUtils.resolveWindow(this);
831
+ const exprHelperAndHostSet = SwcUtils.getHelperAndHostSet(this as any);
787
832
  const script = ConvertUtils.decodeHtmlEntity(expr.script, win.document);
788
833
  try {
789
834
  const result = FunctionUtils.executeReturn({
@@ -815,6 +860,12 @@ export const elementDefine =
815
860
  configurable: true
816
861
  });
817
862
 
863
+ // 이 클래스를 다시 상속해 elementDefine 하면 자식이 이 래퍼 대신 원래 사용자 함수를 부르도록 기억해 둔다
864
+ proto.connectedCallback[SWC_ORIGINAL_CALLBACK] = originalConnected;
865
+ proto.disconnectedCallback[SWC_ORIGINAL_CALLBACK] = originalDisconnected;
866
+ proto.adoptedCallback[SWC_ORIGINAL_CALLBACK] = originalAdopted;
867
+ proto.attributeChangedCallback[SWC_ORIGINAL_CALLBACK] = originalAttributeChanged;
868
+
818
869
  ReflectUtils.defineMetadata(ELEMENT_CONFIG_KEY, metadata, constructor);
819
870
  const registry = metadata.customElementRegistry || (win as any)?.customElements;
820
871
  if (registry && !registry.get(metadata.name)) {