@dooboostore/simple-web-component 1.0.50 → 1.0.51

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 +256 -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 +258 -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 +1886 -999
  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 +1886 -999
  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 +330 -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
@@ -55,7 +55,7 @@ export const resolvePropertyTargets = (inst: any, selector: PropertySelector, op
55
55
  // Resolve selector if it's a function
56
56
  let resolvedSelector: string | Node | Element | NodeList | Element[] | null = selector as any;
57
57
  if (typeof selector === 'function') {
58
- const hostSet = SwcUtils.getHelperAndHostSet(currentWin, inst);
58
+ const hostSet = SwcUtils.getHelperAndHostSet(inst);
59
59
  resolvedSelector = selector(inst, hostSet);
60
60
  }
61
61
 
@@ -182,9 +182,7 @@ export function applyProperty(selector: PropertySelector, targetPropertyKeyOrOpt
182
182
  if (resolvedValue === undefined) return resolvedValue;
183
183
 
184
184
  const targetEls = resolvePropertyTargets(inst, selector, finalOptions);
185
- const conf = getElementConfig(inst);
186
- const currentWin = conf.window;
187
- const hostSet = SwcUtils.getHelperAndHostSet(currentWin, inst);
185
+ const hostSet = SwcUtils.getHelperAndHostSet(inst);
188
186
 
189
187
  targetEls.forEach(targetEl => {
190
188
  // Apply filter if provided
@@ -219,7 +217,7 @@ export function applyProperty(selector: PropertySelector, targetPropertyKeyOrOpt
219
217
  * use that value. Otherwise use the entire return value.
220
218
  *
221
219
  * Example:
222
- * @setProperty('selector')
220
+ * @property('selector')
223
221
  * myMethod() {
224
222
  * return {
225
223
  * [PROPERTY_METADATA_KEY]: 'property-value',
@@ -251,7 +249,13 @@ export function applyProperty(selector: PropertySelector, targetPropertyKeyOrOpt
251
249
  return;
252
250
  }
253
251
 
254
- // Property decorator: define getter/setter to proxy to element properties
252
+ // Property decorator: define getter/setter to proxy to element properties.
253
+ // selector가 '$this' + 타겟 키가 자기 필드면 getter/setter 만들지 않고 리턴.
254
+ // (메타데이터는 위에서 이미 등록됨 → 하이드레이션 수집은 됨.)
255
+ // 순수 필드로 두는 게 업그레이드 때 값 유지에 안전 (setter 경로 타면 생성자·폴리필에 날아감).
256
+ if (selector === '$this' && targetPropertyKey === propertyKey) {
257
+ return;
258
+ }
255
259
  Object.defineProperty(target, propertyKey, {
256
260
  configurable: true,
257
261
  enumerable: true,
@@ -259,9 +263,7 @@ export function applyProperty(selector: PropertySelector, targetPropertyKeyOrOpt
259
263
  ensureInit(this);
260
264
  // Read from matched elements
261
265
  const targetEls = resolvePropertyTargets(this, selector, finalOptions);
262
- const conf = getElementConfig(this);
263
- const currentWin = conf.window;
264
- const hostSet = SwcUtils.getHelperAndHostSet(currentWin, this);
266
+ const hostSet = SwcUtils.getHelperAndHostSet(this);
265
267
 
266
268
  // Apply filter
267
269
  const filtered = targetEls.filter(el => {
@@ -292,76 +294,42 @@ export function applyProperty(selector: PropertySelector, targetPropertyKeyOrOpt
292
294
  // ============================================
293
295
 
294
296
  /**
295
- * @property - Field decorator for binding element properties (read/write)
296
- *
297
- * Usage:
298
- * - @property('selector', 'propertyName') - Bind to specific property
299
- * - @property('selector') - Use field name as property name
300
- * - @property('selector', options) - Bind with options
301
- * - @property((this, helper) => 'selector', 'propertyName') - Function-based selector
302
- * - @property - Bare decorator (binds to $this with field name)
303
- *
304
- * Note: This decorator is for FIELDS ONLY. For methods, use @setProperty.
297
+ * @property - 필드/메서드 공용. 붙은 곳에 따라 동작이 갈린다.
298
+ * - 필드: 대상 엘리먼트 프로퍼티를 읽고 쓰는 getter/setter ($this + 같은 이름이면 순수 필드로 두고 하이드레이션만 등록)
299
+ * - 메서드: 리턴값을 대상 엘리먼트 프로퍼티에 대입 (undefined 면 무시)
300
+ *
301
+ * 첫 문자열은 항상 셀렉터. 자기 자신의 프로퍼티는 필드 그 자체라 이름만 받는 형태는 의미가 없다.
302
+ * - @property - $this, 이름은 필드/메서드 이름
303
+ * - @property('selector') / @property('selector', options) - 대상의 같은 이름 프로퍼티
304
+ * - @property('selector', 'propertyName', options?) - 대상의 propertyName
305
+ * - @property((this, helper) => el, 'propertyName') - 함수 셀렉터
306
+ * - @property(options) - $this, 이름은 options.name 또는 필드/메서드 이름
307
+ *
308
+ * Example:
309
+ * @property('#chart', 'data') chartData: number[];
310
+ *
311
+ * @property('button', 'disabled')
312
+ * lock() { return true; }
305
313
  */
306
- export function property(selector: string, targetPropertyKey: string | symbol, options?: PropertyQueryOptions): PropertyDecorator;
307
- export function property(selector: PropertyFnSelector, targetPropertyKey: string | symbol, options?: PropertyNonQueryOptions): PropertyDecorator;
308
- export function property(selector: string, options?: PropertyQueryOptions): PropertyDecorator;
309
- export function property(selector: PropertyFnSelector, options?: PropertyNonQueryOptions): PropertyDecorator;
314
+ export function property(selector: string, targetPropertyKey: string | symbol, options?: PropertyQueryOptions): MethodDecorator & PropertyDecorator;
315
+ export function property(selector: PropertyFnSelector, targetPropertyKey: string | symbol, options?: PropertyNonQueryOptions): MethodDecorator & PropertyDecorator;
316
+ export function property(selector: string, options?: PropertyQueryOptions): MethodDecorator & PropertyDecorator;
317
+ export function property(selector: PropertyFnSelector, options?: PropertyNonQueryOptions): MethodDecorator & PropertyDecorator;
318
+ export function property(options: PropertyOptions): MethodDecorator & PropertyDecorator;
319
+ // bare(@property) 시그니처는 맨 뒤 — 앞에 있으면 문자열 두 개짜리 호출이 (target, key) 로 먼저 잡혀 TS1240
310
320
  export function property(target: Object, propertyKey: string | symbol): void;
311
321
  export function property(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
312
- export function property(selectorOrTarget?: PropertySelector | Object, targetPropertyKeyOrOptions?: any, optionsOrDescriptor?: any): any {
313
- // Bare decorator direct invocation: @property on field/method passes (target, key[, descriptor]).
314
- // NOTE: (selector-string, key, options) factory form must NOT enter here — target is never string/function.
315
- if (optionsOrDescriptor !== undefined
316
- && typeof selectorOrTarget !== 'string' && typeof selectorOrTarget !== 'function'
317
- && (typeof targetPropertyKeyOrOptions === 'string' || typeof targetPropertyKeyOrOptions === 'symbol')) {
318
- return applyProperty('$this', undefined as any, {})(selectorOrTarget as Object, targetPropertyKeyOrOptions, optionsOrDescriptor as PropertyDescriptor);
319
- }
320
- // With selector
321
- if (typeof selectorOrTarget === 'string') {
322
- return applyProperty(selectorOrTarget, targetPropertyKeyOrOptions as any, optionsOrDescriptor as PropertyOptions);
323
- }
324
- // Function selector
325
- if (typeof selectorOrTarget === 'function') {
326
- return applyProperty(selectorOrTarget as PropertyFnSelector, targetPropertyKeyOrOptions as any, optionsOrDescriptor as PropertyOptions);
327
- }
328
- // Without selector (defaults to $this)
329
- return applyProperty('$this', undefined as any, selectorOrTarget as PropertyOptions);
330
- }
331
-
332
- /**
333
- * @setProperty - Method decorator for setting element properties from method return value
334
- *
335
- * Usage:
336
- * - @setProperty('selector', 'propertyName') - Set specific property
337
- * - @setProperty('selector') - Use method name as property name
338
- * - @setProperty('selector', options) - Set with options
339
- * - @setProperty((this, helper) => 'selector', 'propertyName') - Function-based selector
340
- * - @setProperty - Bare decorator (sets on $this with method name)
341
- *
342
- * Note: This decorator is for METHODS ONLY. For fields, use @property.
343
- *
344
- * Example:
345
- * @setProperty('selector', 'disabled')
346
- * disableElement() {
347
- * return true;
348
- * }
349
- */
350
- export function setProperty(selector: string, targetPropertyKey: string | symbol, options?: PropertyQueryOptions): MethodDecorator;
351
- export function setProperty(selector: PropertyFnSelector, targetPropertyKey: string | symbol, options?: PropertyNonQueryOptions): MethodDecorator;
352
- export function setProperty(selector: string, options?: PropertyQueryOptions): MethodDecorator;
353
- export function setProperty(selector: PropertyFnSelector, options?: PropertyNonQueryOptions): MethodDecorator;
354
- export function setProperty(selectorOrOptions?: PropertySelector | PropertyOptions, targetPropertyKeyOrOptions?: any, optionsOrUndefined?: PropertyOptions): MethodDecorator {
355
- // With selector
356
- if (typeof selectorOrOptions === 'string') {
357
- return applyProperty(selectorOrOptions, targetPropertyKeyOrOptions as any, optionsOrUndefined as PropertyOptions) as MethodDecorator;
322
+ export function property(selectorOrTarget?: PropertySelector | PropertyOptions | Object, targetPropertyKeyOrOptions?: any, optionsOrDescriptor?: any): any {
323
+ // 셀렉터 (문자열 / 함수)
324
+ if (typeof selectorOrTarget === 'string' || typeof selectorOrTarget === 'function') {
325
+ return applyProperty(selectorOrTarget as PropertySelector, targetPropertyKeyOrOptions as any, optionsOrDescriptor as PropertyOptions);
358
326
  }
359
- // Function selector
360
- if (typeof selectorOrOptions === 'function') {
361
- return applyProperty(selectorOrOptions as PropertyFnSelector, targetPropertyKeyOrOptions as any, optionsOrUndefined as PropertyOptions) as MethodDecorator;
327
+ // bare: @property — 데코레이터로 바로 호출됨 (target=prototype, key[, descriptor])
328
+ if (selectorOrTarget && (typeof targetPropertyKeyOrOptions === 'string' || typeof targetPropertyKeyOrOptions === 'symbol')) {
329
+ return applyProperty('$this', {})(selectorOrTarget as Object, targetPropertyKeyOrOptions, optionsOrDescriptor as PropertyDescriptor);
362
330
  }
363
- // Without selector (defaults to $this)
364
- return applyProperty('$this', undefined as any, selectorOrOptions as PropertyOptions) as MethodDecorator;
331
+ // 옵션만: $this
332
+ return applyProperty('$this', selectorOrTarget as PropertyOptions);
365
333
  }
366
334
 
367
335
  /**
@@ -415,7 +383,7 @@ export function callPropertyAll(selector: string, targetMethodKey?: string | sym
415
383
  // ============================================
416
384
 
417
385
  export const findAllPropertyMetadata = (target: any): PropertyMetadata[] => {
418
- const actualTarget = target instanceof Function ? target : target.constructor;
386
+ const actualTarget = typeof target === 'function' ? target : target.constructor;
419
387
  return ReflectUtils.findAllMetadata<PropertyMetadata[]>(PROPERTY_METADATA_KEY, actualTarget).flat();
420
388
  };
421
389
 
@@ -429,7 +397,7 @@ export const findAllPropertyApplyMetadata = (target: any): Map<string | symbol,
429
397
  };
430
398
 
431
399
  // ─── Aliases ───
432
- // property(필드) / setProperty(메서드)와 구분되는 일반 데코레이터 단축명
400
+ // applyProperty 단축명
433
401
  export const prop = applyProperty;
434
402
 
435
403
  // ─── 편의 헬퍼 (selector/root 생략) ───
@@ -446,22 +414,17 @@ const dispatchPropConvenience = (
446
414
  return applyProperty(selector, targetPropertyKeyOrTarget as any, optionsOrPropertyKey as PropertyQueryOptions);
447
415
  };
448
416
 
449
- export function propThis(targetPropertyKey?: string | symbol, options?: PropertyQueryOptions): MethodDecorator;
450
- export function propThis(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
451
- export function propThis(targetPropertyKeyOrTarget?: string | symbol | Object, optionsOrPropertyKey?: PropertyQueryOptions | string | symbol, descriptor?: PropertyDescriptor): any {
452
- return dispatchPropConvenience('$this', targetPropertyKeyOrTarget, optionsOrPropertyKey, descriptor);
453
- }
454
- export function propAppHost(targetPropertyKey?: string | symbol, options?: PropertyQueryOptions): MethodDecorator;
417
+ export function propAppHost(targetPropertyKey: string | symbol, options?: PropertyQueryOptions): MethodDecorator;
455
418
  export function propAppHost(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
456
419
  export function propAppHost(targetPropertyKeyOrTarget?: string | symbol | Object, optionsOrPropertyKey?: PropertyQueryOptions | string | symbol, descriptor?: PropertyDescriptor): any {
457
420
  return dispatchPropConvenience('$appHost', targetPropertyKeyOrTarget, optionsOrPropertyKey, descriptor);
458
421
  }
459
- export function propWindow(targetPropertyKey?: string | symbol, options?: PropertyQueryOptions): MethodDecorator;
422
+ export function propWindow(targetPropertyKey: string | symbol, options?: PropertyQueryOptions): MethodDecorator;
460
423
  export function propWindow(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
461
424
  export function propWindow(targetPropertyKeyOrTarget?: string | symbol | Object, optionsOrPropertyKey?: PropertyQueryOptions | string | symbol, descriptor?: PropertyDescriptor): any {
462
425
  return dispatchPropConvenience('$window', targetPropertyKeyOrTarget, optionsOrPropertyKey, descriptor);
463
426
  }
464
- export function propDocument(targetPropertyKey?: string | symbol, options?: PropertyQueryOptions): MethodDecorator;
427
+ export function propDocument(targetPropertyKey: string | symbol, options?: PropertyQueryOptions): MethodDecorator;
465
428
  export function propDocument(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
466
429
  export function propDocument(targetPropertyKeyOrTarget?: string | symbol | Object, optionsOrPropertyKey?: PropertyQueryOptions | string | symbol, descriptor?: PropertyDescriptor): any {
467
430
  return dispatchPropConvenience('$document', targetPropertyKeyOrTarget, optionsOrPropertyKey, descriptor);
@@ -476,25 +439,14 @@ export function propAll(selector: string, targetPropertyKey?: string | symbol, o
476
439
  return applyProperty(selector, targetPropertyKey, {...options ?? {}, root: 'all'});
477
440
  }
478
441
 
479
- // ─── setProperty root 별칭 (메서드 리턴값 → 타깃 프로퍼티) ───
480
- export function setPropertyLight(selector: string, targetPropertyKey?: string | symbol, options?: Omit<PropertyQueryOptions, 'root'>): MethodDecorator {
481
- return applyProperty(selector, targetPropertyKey, {...options ?? {}, root: 'light'});
482
- }
483
- export function setPropertyShadow(selector: string, targetPropertyKey?: string | symbol, options?: Omit<PropertyQueryOptions, 'root'>): MethodDecorator {
484
- return applyProperty(selector, targetPropertyKey, {...options ?? {}, root: 'shadow'});
485
- }
486
- export function setPropertyAll(selector: string, targetPropertyKey?: string | symbol, options?: Omit<PropertyQueryOptions, 'root'>): MethodDecorator {
487
- return applyProperty(selector, targetPropertyKey, {...options ?? {}, root: 'all'});
488
- }
489
-
490
- // ─── property root 별칭 (필드 프록시) ───
491
- export function propertyLight(selector: string, targetPropertyKey?: string | symbol, options?: Omit<PropertyQueryOptions, 'root'>): PropertyDecorator {
442
+ // ─── property root 별칭 ───
443
+ export function propertyLight(selector: string, targetPropertyKey?: string | symbol, options?: Omit<PropertyQueryOptions, 'root'>): MethodDecorator & PropertyDecorator {
492
444
  return applyProperty(selector, targetPropertyKey, {...options ?? {}, root: 'light'});
493
445
  }
494
- export function propertyShadow(selector: string, targetPropertyKey?: string | symbol, options?: Omit<PropertyQueryOptions, 'root'>): PropertyDecorator {
446
+ export function propertyShadow(selector: string, targetPropertyKey?: string | symbol, options?: Omit<PropertyQueryOptions, 'root'>): MethodDecorator & PropertyDecorator {
495
447
  return applyProperty(selector, targetPropertyKey, {...options ?? {}, root: 'shadow'});
496
448
  }
497
- export function propertyAll(selector: string, targetPropertyKey?: string | symbol, options?: Omit<PropertyQueryOptions, 'root'>): PropertyDecorator {
449
+ export function propertyAll(selector: string, targetPropertyKey?: string | symbol, options?: Omit<PropertyQueryOptions, 'root'>): MethodDecorator & PropertyDecorator {
498
450
  return applyProperty(selector, targetPropertyKey, {...options ?? {}, root: 'all'});
499
451
  }
500
452
 
@@ -114,7 +114,7 @@ export function applySlot(targetId: string, opt: OptionalType<ApplySlotOptions,
114
114
  const currentWin = conf.window;
115
115
  const swcId = this._swcId;
116
116
  const currentDoc = currentWin.document;
117
- const hostSet = SwcUtils.getHelperAndHostSet(currentWin, this);
117
+ const hostSet = SwcUtils.getHelperAndHostSet(this);
118
118
  const pos = options.position;
119
119
 
120
120
  const resToNodes = (res: string | Node) => {
@@ -216,6 +216,6 @@ export function replaceChildrenTextSlot(targetId: string): MethodDecorator {
216
216
 
217
217
  // export const findAllApplySlotMetadata = (target: any): Map<string | symbol, ApplySlotMetadata> => {
218
218
  export const findAllApplySlotMetadata = (target: any): ApplySlotMetadata[] => {
219
- const constructor = target instanceof Function ? target : target.constructor;
220
- return ReflectUtils.getMetadata(APPLY_SLOT_METADATA_KEY, constructor);
219
+ const constructor = typeof target === 'function' ? target : target.constructor;
220
+ return ReflectUtils.findAllMetadata<any[]>(APPLY_SLOT_METADATA_KEY, constructor).flat();
221
221
  };
@@ -39,7 +39,7 @@ export const STYLE_METADATA_KEY = Symbol.for('simple-web-component:style');
39
39
  */
40
40
  const resolveSelector = (selector: StyleSelector, inst: any, win: Window): string | HTMLElement[] => {
41
41
  if (typeof selector === 'function') {
42
- const hostSet = SwcUtils.getHelperAndHostSet(win, inst);
42
+ const hostSet = SwcUtils.getHelperAndHostSet(inst);
43
43
  const result = selector(inst, hostSet);
44
44
 
45
45
  if (result instanceof win.Element) {
@@ -75,7 +75,7 @@ const getTargetElements = (selector: StyleSelector, inst: any, win: Window, root
75
75
  const stringSelector = resolved as string;
76
76
  const targetEls: HTMLElement[] = [];
77
77
  const host = inst as unknown as HTMLElement;
78
- const hostSet = SwcUtils.getHelperAndHostSet(win, inst);
78
+ const hostSet = SwcUtils.getHelperAndHostSet(inst);
79
79
 
80
80
  const applyRoot = (t: any) => {
81
81
  if (!t || !(t instanceof win.HTMLElement)) return;
@@ -139,7 +139,7 @@ function createStyleDecorator(action: StyleAction) {
139
139
  if (resolvedValue !== undefined) {
140
140
  const win = (this as any).ownerDocument?.defaultView || window;
141
141
  const host = this as unknown as HTMLElement;
142
- const hostSet = { ...SwcUtils.getHelperAndHostSet(win, host), $this: this };
142
+ const hostSet = { ...SwcUtils.getHelperAndHostSet(host), $this: this };
143
143
  const r = options.root || 'auto';
144
144
  const targetEls = getTargetElements(selector, this, win, r);
145
145
 
@@ -183,9 +183,11 @@ function createStyleDecorator(action: StyleAction) {
183
183
  };
184
184
 
185
185
  if (res instanceof Promise) {
186
- return res.then((v: any) => handleResult(extractValue(v)));
186
+ // 자기 몫(valueKey)만 적용하고 원래 반환값은 그대로 위로 넘긴다 — 위에 쌓인 다른 아웃풋도 자기 키를 찾게
187
+ return res.then((v: any) => { handleResult(extractValue(v)); return v; });
187
188
  } else {
188
- return handleResult(extractValue(res));
189
+ handleResult(extractValue(res));
190
+ return res;
189
191
  }
190
192
  };
191
193
  return descriptor;
@@ -266,15 +268,7 @@ export const style = applyStyle;
266
268
 
267
269
  // ─── 편의 헬퍼 (selector/root 생략) ───
268
270
 
269
- export function styleThis(action?: StyleAction, options?: StyleQueryOptions): MethodDecorator;
270
- export function styleThis(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
271
- export function styleThis(actionOrTarget?: StyleAction | Object, optionsOrPropertyKey?: StyleQueryOptions | string | symbol, descriptor?: PropertyDescriptor): any {
272
- if ((typeof optionsOrPropertyKey === 'string' || typeof optionsOrPropertyKey === 'symbol') && descriptor !== undefined) {
273
- return (applyStyle('$this', undefined as any, {}) as any)(actionOrTarget, optionsOrPropertyKey, descriptor);
274
- }
275
- return applyStyle('$this', actionOrTarget as any, optionsOrPropertyKey as StyleQueryOptions);
276
- }
277
- export function styleAppHost(action?: StyleAction, options?: StyleQueryOptions): MethodDecorator;
271
+ export function styleAppHost(action: StyleAction, options?: StyleQueryOptions): MethodDecorator;
278
272
  export function styleAppHost(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
279
273
  export function styleAppHost(actionOrTarget?: StyleAction | Object, optionsOrPropertyKey?: StyleQueryOptions | string | symbol, descriptor?: PropertyDescriptor): any {
280
274
  if ((typeof optionsOrPropertyKey === 'string' || typeof optionsOrPropertyKey === 'symbol') && descriptor !== undefined) {
@@ -0,0 +1,196 @@
1
+ import { ReflectUtils } from '@dooboostore/core';
2
+ import { ensureInit } from './elementDefine';
3
+ import { SwcUtils } from '../utils/Utils';
4
+ import { HelperHostSet } from '../types';
5
+
6
+ export const AROUND_METADATA_KEY = Symbol.for('simple-web-component:around');
7
+
8
+ export interface AroundMetadata {
9
+ propertyKey: string | symbol;
10
+ }
11
+
12
+ export interface AroundPropertyOptions<Value = any> {
13
+ /**
14
+ * get할 때 (helperHostSet, 저장된 값)을 받아 실제로 읽는 쪽에 돌려줄 값을 리턴한다.
15
+ * undefined 리턴이면 저장값 그대로 통과. 안 주면 그대로 통과.
16
+ */
17
+ get?: (helperHostSet: HelperHostSet, stored: Value) => Value | void;
18
+ /**
19
+ * set할 때 (helperHostSet, 대입되는 값)을 받아 실제로 저장할 값을 리턴한다.
20
+ * 안 주면 그대로 통과.
21
+ */
22
+ set?: (helperHostSet: HelperHostSet, incoming: Value) => Value | void;
23
+ }
24
+ export interface AroundOptions<Args extends any[] = any[], Return = any> {
25
+ /**
26
+ * 원본 실행 전에, (helperHostSet, 호출자가 넘긴 인자 배열)을 받아
27
+ * 실제로 원본에 넘길 인자 배열을 리턴한다. undefined 리턴이면 인자 그대로 통과.
28
+ * 원본이 Promise를 리턴하면 before도 Promise를 리턴할 수 있다 —
29
+ * resolve된 값으로 원본이 호출된다 (이 경우 래퍼도 Promise 반환).
30
+ * 원본이 sync면 before도 sync여야 한다 (async를 달면 호출 측이 모르는 Promise가 새어나감).
31
+ * 안 주면 그대로 통과.
32
+ *
33
+ * 제네릭 생략 시(Return=any) 양쪽 다 허용이라 느슨하게 동작하고,
34
+ * `@around<[number], number>`처럼 명시하면 위 규칙이 검사된다.
35
+ */
36
+ before?: (helperHostSet: HelperHostSet, args: Args) => Return extends Promise<any> ? Args | void | Promise<Args | void> : Args | void;
37
+ /**
38
+ * 원본 실행 후에, (helperHostSet, 리턴값)을 받아 실제로 호출자에게
39
+ * 돌려줄 값을 리턴한다.
40
+ * 원본이 Promise를 리턴하면 resolve된 값(Awaited<Return>)을 넘겨받고,
41
+ * after의 리턴값이 그대로 최종 Promise의 resolve 값이 된다.
42
+ * after가 Promise를 리턴해도 된다 — resolve된 값이 최종값이 된다.
43
+ * 안 주면 그대로 통과.
44
+ *
45
+ * 제네릭을 명시하면 훅 입출력이 검사된다:
46
+ * `@around<[string], Promise<string>>({ after: (h, r) => ... })`
47
+ * (r은 string으로 추론). 생략하면 any로 느슨하게 동작한다.
48
+ * 단, legacy 데코레이터 한계상 래핑된 메서드의 겉보기 시그니처는 안 바뀌므로
49
+ * async 훅을 단 sync 메서드는 호출 측에서 Promise로 취급해야 한다.
50
+ */
51
+ after?: (helperHostSet: HelperHostSet, result: Awaited<Return>) => Return | Promise<Awaited<Return>>;
52
+ /**
53
+ * before/원본/after 실행이 끝나면 성공·실패와 무관하게 항상 실행되는 정리 훅 (try/finally의 finally).
54
+ * 두 번째 인자로 컨텍스트를 받는다:
55
+ * - args: 호출자가 넘긴 원본 인자 배열(before 가공 전)
56
+ * - result: 성공 시 최종 리턴값(after 적용 후). 실패 시 없음.
57
+ * - error: throw/reject 시 그 에러. 성공 시 없음.
58
+ * 에러를 삼키지 않는다 — finally를 먼저 돌린 뒤 원래 에러/리턴값이 그대로 전파된다.
59
+ * sync 원본 + sync finally면 sync로 유지되지만, finally가 Promise를 리턴하면
60
+ * 래퍼는 Promise를 반환한다(async before/after와 동일 규칙).
61
+ */
62
+ finally?: (helperHostSet: HelperHostSet, ctx: { args: Args; result?: Awaited<Return>; error?: any }) => void | Promise<void>;
63
+ }
64
+
65
+ /** 인스턴스에서 HelperHostSet을 해석한다 — 실패(SSR/미초기화)하면 최소 폴백을 돌려준다. */
66
+ const resolveHelperHostSet = (inst: any): HelperHostSet => {
67
+ try {
68
+ ensureInit(inst);
69
+ return SwcUtils.getHelperAndHostSet(inst);
70
+ } catch {
71
+ return { $this: inst } as HelperHostSet;
72
+ }
73
+ };
74
+
75
+ /**
76
+ * 메서드에 걸면 호출을 감싸서 호출 전(인자 가공)/후(리턴값 가공) 훅을 건다 - AOP의 "around advice".
77
+ *
78
+ * @around({
79
+ * before: (h, args: [number, string]) => [args[0], args[1].toUpperCase()],
80
+ * after: (h, r: string) => r + '!'
81
+ * })
82
+ * greet(a: number, b: string) { return `${a}:${b}`; }
83
+ *
84
+ * 프로퍼티(필드)에 걸면 get/set을 가로챈다 - get은 get, set은 set이 호출된다.
85
+ * (프로퍼티 경로에서는 값이 배열이 아니라 단일값으로 넘어간다)
86
+ * 필드 이니셜라이저(`wow = 'x'`)가 있어도 된다 — ensureInit이 state처럼
87
+ * 초기값을 살려 setter 경유로 정리하므로 초기값에도 set()이 적용된다.
88
+ * (단, custom element가 아닌 일반 클래스에서는 ensureInit이 안 돌므로
89
+ * `declare` + 생성자 대입으로 쓸 것)
90
+ *
91
+ * @around({
92
+ * get: (h, stored: string) => stored?.toUpperCase(),
93
+ * set: (h, incoming: string) => incoming?.trim()
94
+ * })
95
+ * wow: string = ' hi ';
96
+ */
97
+ export function around<Args extends any[] = any[], Return = any>(options: AroundOptions<NoInfer<Args>, NoInfer<Return>>): MethodDecorator;
98
+ export function around<Value = any>(options: AroundPropertyOptions<NoInfer<Value>>): PropertyDecorator;
99
+ export function around(options: any = {}): MethodDecorator & PropertyDecorator {
100
+ return ((target: Object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => {
101
+ // 메서드: descriptor.value가 실제 함수일 때만 (이미 정의된 접근자를 감싸는 건 지원 범위 밖)
102
+ if (descriptor && typeof descriptor.value === 'function') {
103
+ const original = descriptor.value;
104
+ const runAfter = function (this: any, hhs: HelperHostSet, res: any) {
105
+ if (!options.after) return res;
106
+ const run = (v: any) => (options.after as any).call(this, hhs, v);
107
+ // after가 Promise를 리턴하면 resolve된 값이 최종값이 된다 (then 플래트닝).
108
+ return res && typeof res.then === 'function' ? res.then(run) : run(res);
109
+ };
110
+ descriptor.value = function (this: any, ...args: any[]) {
111
+ const hhs = resolveHelperHostSet(this);
112
+ const self = this;
113
+ const callOriginal = (finalArgs: any[]) => runAfter.call(self, hhs, original.apply(self, finalArgs));
114
+ const runBody = () => {
115
+ if (options.before) {
116
+ const mapped = (options.before as any).call(self, hhs, args);
117
+ if (mapped && typeof mapped.then === 'function') {
118
+ // async before — resolve된 인자로 원본 호출, 래퍼는 Promise 반환.
119
+ // (sync 원본 + sync 훅일 때만 sync 반환이 유지된다)
120
+ return mapped.then((m: any) => {
121
+ if (Array.isArray(m)) return callOriginal(m);
122
+ if (m !== undefined) return callOriginal([m]);
123
+ return callOriginal(args);
124
+ });
125
+ }
126
+ if (Array.isArray(mapped)) return callOriginal(mapped);
127
+ if (mapped !== undefined) return callOriginal([mapped]);
128
+ }
129
+ return callOriginal(args);
130
+ };
131
+ if (!options.finally) return runBody();
132
+ // sync/async 투명성을 지키는 try/finally — 에러는 삼키지 않고 finally 후 전파.
133
+ const afterFin = (settle: () => any, ctx: { args: any[]; result?: any; error?: any }) => {
134
+ const f = (options.finally as any).call(self, hhs, ctx);
135
+ return f && typeof f.then === 'function' ? f.then(settle) : settle();
136
+ };
137
+ let out: any;
138
+ try {
139
+ out = runBody();
140
+ } catch (e) {
141
+ return afterFin(() => { throw e; }, { args, error: e });
142
+ }
143
+ if (out && typeof out.then === 'function') {
144
+ return out.then(
145
+ (v: any) => afterFin(() => v, { args, result: v }),
146
+ (e: any) => afterFin(() => { throw e; }, { args, error: e })
147
+ );
148
+ }
149
+ return afterFin(() => out, { args, result: out });
150
+ };
151
+ return descriptor;
152
+ }
153
+
154
+ // 프로퍼티: ensureInit에서 own property를 정리할 수 있도록 메타데이터 등록 (state.ts와 동일 패턴).
155
+ // 필드 이니셜라이저가 있으면 own property가 accessor를 가리므로 getter가 영원히 안 불림 —
156
+ // getter 안의 자체이주는 마지막 안전망일 뿐, 실질적인 살림은 ensureInit이 한다.
157
+ const constructor = (target as any).constructor;
158
+ let aroundList = ReflectUtils.getOwnMetadata(AROUND_METADATA_KEY, constructor) as AroundMetadata[] | undefined;
159
+ if (!aroundList) {
160
+ aroundList = [];
161
+ ReflectUtils.defineMetadata(AROUND_METADATA_KEY, aroundList, constructor);
162
+ }
163
+ aroundList.push({ propertyKey });
164
+
165
+ // target은 prototype이라 여기 값을 직접 두면 모든 인스턴스가 공유해버린다.
166
+ // 인스턴스별로 따로 저장되도록 인스턴스 전용 Symbol 슬롯(this[storageKey])에 실제 값을 둔다.
167
+ const storageKey = Symbol(`around:${String(propertyKey)}`);
168
+ const getFn = (options as AroundPropertyOptions).get as unknown as ((hhs: HelperHostSet, value: any) => any) | undefined;
169
+ const setFn = (options as AroundPropertyOptions).set as unknown as ((hhs: HelperHostSet, value: any) => any) | undefined;
170
+ Object.defineProperty(target, propertyKey, {
171
+ get(this: any) {
172
+ // 안전망: 누군가 plain delete로 own을 걷어낸 뒤라면 여기서 슬롯으로 회수한다.
173
+ // (정상 경로에서는 ensureInit이 이미 setter 경유로 정리했으므로 own이 없음)
174
+ const own = Object.getOwnPropertyDescriptor(this, propertyKey);
175
+ if (own && 'value' in own) {
176
+ delete this[propertyKey];
177
+ this[storageKey] = setFn ? setFn.call(this, resolveHelperHostSet(this), own.value) : own.value;
178
+ }
179
+ const raw = this[storageKey];
180
+ if (!getFn) return raw;
181
+ const mapped = getFn.call(this, resolveHelperHostSet(this), raw);
182
+ return mapped === undefined ? raw : mapped;
183
+ },
184
+ set(this: any, value: any) {
185
+ this[storageKey] = setFn ? setFn.call(this, resolveHelperHostSet(this), value) : value;
186
+ },
187
+ enumerable: true,
188
+ configurable: true
189
+ });
190
+ }) as MethodDecorator & PropertyDecorator;
191
+ }
192
+
193
+ export const findAllAroundMetadata = (target: any): AroundMetadata[] => {
194
+ const constructor = typeof target === 'function' ? target : target.constructor;
195
+ return ReflectUtils.findAllMetadata<any[]>(AROUND_METADATA_KEY, constructor).flat();
196
+ };
@@ -5,6 +5,12 @@ export const ON_ATTRIBUTE_CHANGED_METADATA_KEY = Symbol.for('simple-web-componen
5
5
  export interface ChangedAttributeThisOptions {
6
6
  type?: typeof Number | typeof Boolean | typeof String;
7
7
  while?: 'connected';
8
+ /** 속성 변경 시 핸들러 실행 여부 게이트. Promise<boolean>도 되어 async 판정 가능. false면 스킵. */
9
+ filter?: (value: any, meta: { currentThis: any; helper: HelperHostSet }) => boolean | Promise<boolean>;
10
+ /** filter 통과 후 핸들러 직전 훅. await되고, 리턴값은 @changedAttributeBeforeReturn 으로 핸들러에 주입된다. */
11
+ before?: (value: any, meta: { currentThis: any; helper: HelperHostSet }) => any | Promise<any>;
12
+ /** 핸들러가 성공/실패해도 항상 실행되는 정리 훅. ctx로 인자/결과/에러를 받는다. 에러는 로깅됨. */
13
+ finally?: (value: any, meta: { currentThis: any; helper: HelperHostSet }, ctx: { args: any[]; result?: any; error?: any }) => any | Promise<any>;
8
14
  }
9
15
 
10
16
  export interface ChangedAttributeThisMetadata {
@@ -13,13 +19,6 @@ export interface ChangedAttributeThisMetadata {
13
19
  options: ChangedAttributeThisOptions;
14
20
  }
15
21
 
16
- const convertValue = (val: any, type: any): any => {
17
- if (val === null || val === undefined) return val;
18
- if (type === Number) return Number(val);
19
- if (type === Boolean) return val === 'false' || val === '0' ? false : true;
20
- return val;
21
- };
22
-
23
22
  const applyChangedAttribute = (attributeName: string | undefined, options: ChangedAttributeThisOptions, target: Object, propertyKey: string | symbol): void => {
24
23
  const constructor = target.constructor;
25
24
  let metaList = ReflectUtils.getOwnMetadata(ON_ATTRIBUTE_CHANGED_METADATA_KEY, constructor) as ChangedAttributeThisMetadata[];
@@ -36,7 +35,7 @@ const applyChangedAttribute = (attributeName: string | undefined, options: Chang
36
35
  });
37
36
  };
38
37
 
39
- export function changedAttribute(attributeName?: string, options?: ChangedAttributeThisOptions): MethodDecorator;
38
+ export function changedAttribute(attributeName: string, options?: ChangedAttributeThisOptions): MethodDecorator;
40
39
  export function changedAttribute(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
41
40
  /**
42
41
  * @changedAttributeThis decorator - fires when any attribute on $this changes.
@@ -48,7 +47,7 @@ export function changedAttribute(attributeNameOrTarget?: string | Object, option
48
47
  applyChangedAttribute(undefined, {}, attributeNameOrTarget as Object, optionsOrPropertyKey);
49
48
  return;
50
49
  }
51
- // 옵션과 함께: @changedAttribute() / @changedAttribute('name', options?)
50
+ // 옵션과 함께: @changedAttribute('name', options?)
52
51
  const attributeName = attributeNameOrTarget as string | undefined;
53
52
  const options = (optionsOrPropertyKey as ChangedAttributeThisOptions) ?? {};
54
53
  return (target: Object, propertyKey: string | symbol) => {
@@ -57,12 +56,12 @@ export function changedAttribute(attributeNameOrTarget?: string | Object, option
57
56
  }
58
57
 
59
58
  export const getChangedAttributeMetadata = (target: any): ChangedAttributeThisMetadata[] | undefined => {
60
- const constructor = target instanceof Function ? target : target.constructor;
61
- return ReflectUtils.getMetadata(ON_ATTRIBUTE_CHANGED_METADATA_KEY, constructor);
59
+ const constructor = typeof target === 'function' ? target : target.constructor;
60
+ return ReflectUtils.findAllMetadata<any[]>(ON_ATTRIBUTE_CHANGED_METADATA_KEY, constructor).flat();
62
61
  };
63
62
 
64
63
  export const findAllAttributeChangedMetadata = (target: any): Map<string, ChangedAttributeThisMetadata[]> => {
65
- const constructor = target instanceof Function ? target : target.constructor;
64
+ const constructor = typeof target === 'function' ? target : target.constructor;
66
65
  const metaList = ReflectUtils.findAllMetadata<ChangedAttributeThisMetadata[]>(ON_ATTRIBUTE_CHANGED_METADATA_KEY, constructor) || [];
67
66
 
68
67
  const result = new Map<string, ChangedAttributeThisMetadata[]>();
@@ -85,7 +84,9 @@ export const convertAttributeValue = (val: any, type?: typeof Number | typeof Bo
85
84
  // ChangedAttributeLifeCycler
86
85
  // ─────────────────────────────────────────────────────────────────────────────
87
86
  import { ElementDefineLifeCycler, HelperHostSet } from '../types';
88
- import { getAttributeValue } from './applyAttribute';
87
+ import { getAttributeValue, convertValue } from './applyAttribute';
88
+ import { SwcUtils } from '../utils/Utils';
89
+ import { buildSwcParameterArgs } from './parameter';
89
90
 
90
91
  export class ChangedAttributeLifeCycler implements ElementDefineLifeCycler {
91
92
  private attrChangeMap: Map<string, ChangedAttributeThisMetadata[]> | null = null;
@@ -109,7 +110,7 @@ export class ChangedAttributeLifeCycler implements ElementDefineLifeCycler {
109
110
  for (const meta of metaList) {
110
111
  if (meta.options.while === 'connected') {
111
112
  const val = getAttributeValue(inst, name, { type: meta.options.type });
112
- if (val !== null) inst[meta.propertyKey](val, null, name, helperHostSet);
113
+ if (val !== null) this.runChanged(helperHostSet, meta, val, null, name);
113
114
  }
114
115
  }
115
116
  }
@@ -122,7 +123,30 @@ export class ChangedAttributeLifeCycler implements ElementDefineLifeCycler {
122
123
  if (!metaList) return;
123
124
  for (const meta of metaList) {
124
125
  if (meta.options.while === 'connected' && !inst.__swc_connected) continue;
125
- inst[meta.propertyKey](convertAttributeValue(newVal, meta.options.type), old, name, helperHostSet);
126
+ this.runChanged(helperHostSet, meta, convertAttributeValue(newVal, meta.options.type), old, name);
126
127
  }
127
128
  }
129
+
130
+ // filter(async)/before/finally + @changedAttributeBeforeReturn 주입. fire-and-forget.
131
+ private runChanged(helperHostSet: HelperHostSet, meta: ChangedAttributeThisMetadata, value: any, old: string | null, name: string): void {
132
+ const inst = helperHostSet.$this;
133
+ const opts = meta.options;
134
+ void (async () => {
135
+ const helper = SwcUtils.getHelperAndHostSet(inst);
136
+ if (opts.filter && !(await opts.filter(value, { currentThis: inst, helper }))) return;
137
+ const hostSet = SwcUtils.getHostSet(inst);
138
+ const helperSet = SwcUtils.getHelperSet(helperHostSet.$w);
139
+ const legacyArgs = [value, old, name, helper];
140
+ const buildArgs = (beforeReturn: any) => buildSwcParameterArgs(inst, meta.propertyKey, {
141
+ hostSet, helperHostSet: helper, helperSet, changedAttributeBeforeReturn: beforeReturn
142
+ }, [...legacyArgs, beforeReturn]);
143
+ let args = buildArgs(undefined);
144
+ if (opts.before) { const br = await opts.before(value, { currentThis: inst, helper }); args = buildArgs(br); }
145
+ let result: any, error: any;
146
+ try { result = await inst[meta.propertyKey](...args); }
147
+ catch (e) { error = e; }
148
+ finally { if (opts.finally) await opts.finally(value, { currentThis: inst, helper }, { args, result, error }); }
149
+ if (error) throw error;
150
+ })().catch(e => console.error('[SWC] changedAttribute handler error:', e));
151
+ }
128
152
  }