@dooboostore/simple-web-component 1.0.47 → 1.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/README.md +223 -27
  2. package/dist/cjs/config/config.js +1 -1
  3. package/dist/cjs/config/config.js.map +2 -2
  4. package/dist/cjs/decorators/addEventListener.js +412 -16
  5. package/dist/cjs/decorators/addEventListener.js.map +3 -3
  6. package/dist/cjs/decorators/applyAttribute.js +25 -0
  7. package/dist/cjs/decorators/applyAttribute.js.map +2 -2
  8. package/dist/cjs/decorators/applyClass.js +49 -18
  9. package/dist/cjs/decorators/applyClass.js.map +3 -3
  10. package/dist/cjs/decorators/applyNode.js +67 -1
  11. package/dist/cjs/decorators/applyNode.js.map +3 -3
  12. package/dist/cjs/decorators/applyProperty.js +101 -3
  13. package/dist/cjs/decorators/applyProperty.js.map +2 -2
  14. package/dist/cjs/decorators/applyStyle.js +36 -5
  15. package/dist/cjs/decorators/applyStyle.js.map +2 -2
  16. package/dist/cjs/decorators/changedAttribute.js +39 -0
  17. package/dist/cjs/decorators/changedAttribute.js.map +2 -2
  18. package/dist/cjs/decorators/elementDefine.js +244 -225
  19. package/dist/cjs/decorators/elementDefine.js.map +3 -3
  20. package/dist/cjs/decorators/emitCustomEvent.js +55 -0
  21. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  22. package/dist/cjs/decorators/index.js +5 -1
  23. package/dist/cjs/decorators/index.js.map +2 -2
  24. package/dist/cjs/decorators/intersectionObserver.js +209 -0
  25. package/dist/cjs/decorators/intersectionObserver.js.map +7 -0
  26. package/dist/cjs/decorators/mutationObserver.js +244 -0
  27. package/dist/cjs/decorators/mutationObserver.js.map +7 -0
  28. package/dist/cjs/decorators/query.js +129 -102
  29. package/dist/cjs/decorators/query.js.map +2 -2
  30. package/dist/cjs/decorators/resizeObserver.js +191 -0
  31. package/dist/cjs/decorators/resizeObserver.js.map +7 -0
  32. package/dist/cjs/decorators/setInterval.js +82 -0
  33. package/dist/cjs/decorators/setInterval.js.map +7 -0
  34. package/dist/cjs/decorators/setTimeout.js +82 -0
  35. package/dist/cjs/decorators/setTimeout.js.map +7 -0
  36. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js +9 -0
  37. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +2 -2
  38. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js +9 -0
  39. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
  40. package/dist/cjs/examples/RxJSOperatorsExample.js +285 -0
  41. package/dist/cjs/examples/RxJSOperatorsExample.js.map +7 -0
  42. package/dist/cjs/types/index.js.map +1 -1
  43. package/dist/esm/config/config.js +1 -1
  44. package/dist/esm/config/config.js.map +2 -2
  45. package/dist/esm/decorators/addEventListener.js +413 -16
  46. package/dist/esm/decorators/addEventListener.js.map +3 -3
  47. package/dist/esm/decorators/applyAttribute.js +25 -0
  48. package/dist/esm/decorators/applyAttribute.js.map +2 -2
  49. package/dist/esm/decorators/applyClass.js +49 -18
  50. package/dist/esm/decorators/applyClass.js.map +3 -3
  51. package/dist/esm/decorators/applyNode.js +67 -1
  52. package/dist/esm/decorators/applyNode.js.map +3 -3
  53. package/dist/esm/decorators/applyProperty.js +101 -3
  54. package/dist/esm/decorators/applyProperty.js.map +2 -2
  55. package/dist/esm/decorators/applyStyle.js +36 -5
  56. package/dist/esm/decorators/applyStyle.js.map +2 -2
  57. package/dist/esm/decorators/changedAttribute.js +40 -0
  58. package/dist/esm/decorators/changedAttribute.js.map +2 -2
  59. package/dist/esm/decorators/elementDefine.js +249 -230
  60. package/dist/esm/decorators/elementDefine.js.map +3 -3
  61. package/dist/esm/decorators/emitCustomEvent.js +55 -0
  62. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  63. package/dist/esm/decorators/index.js +5 -1
  64. package/dist/esm/decorators/index.js.map +2 -2
  65. package/dist/esm/decorators/intersectionObserver.js +191 -0
  66. package/dist/esm/decorators/intersectionObserver.js.map +7 -0
  67. package/dist/esm/decorators/mutationObserver.js +226 -0
  68. package/dist/esm/decorators/mutationObserver.js.map +7 -0
  69. package/dist/esm/decorators/query.js +129 -102
  70. package/dist/esm/decorators/query.js.map +2 -2
  71. package/dist/esm/decorators/resizeObserver.js +173 -0
  72. package/dist/esm/decorators/resizeObserver.js.map +7 -0
  73. package/dist/esm/decorators/setInterval.js +64 -0
  74. package/dist/esm/decorators/setInterval.js.map +7 -0
  75. package/dist/esm/decorators/setTimeout.js +64 -0
  76. package/dist/esm/decorators/setTimeout.js.map +7 -0
  77. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js +9 -0
  78. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +2 -2
  79. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js +9 -0
  80. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
  81. package/dist/esm/examples/RxJSOperatorsExample.js +266 -0
  82. package/dist/esm/examples/RxJSOperatorsExample.js.map +7 -0
  83. package/dist/esm/types/index.js.map +1 -1
  84. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +2413 -889
  85. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  86. package/dist/types/config/config.d.ts.map +1 -1
  87. package/dist/types/decorators/addEventListener.d.ts +93 -23
  88. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  89. package/dist/types/decorators/applyAttribute.d.ts +22 -34
  90. package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
  91. package/dist/types/decorators/applyClass.d.ts +34 -34
  92. package/dist/types/decorators/applyClass.d.ts.map +1 -1
  93. package/dist/types/decorators/applyNode.d.ts +30 -20
  94. package/dist/types/decorators/applyNode.d.ts.map +1 -1
  95. package/dist/types/decorators/applyProperty.d.ts +57 -34
  96. package/dist/types/decorators/applyProperty.d.ts.map +1 -1
  97. package/dist/types/decorators/applyStyle.d.ts +25 -12
  98. package/dist/types/decorators/applyStyle.d.ts.map +1 -1
  99. package/dist/types/decorators/changedAttribute.d.ts +11 -0
  100. package/dist/types/decorators/changedAttribute.d.ts.map +1 -1
  101. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  102. package/dist/types/decorators/emitCustomEvent.d.ts +28 -4
  103. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  104. package/dist/types/decorators/index.d.ts +5 -1
  105. package/dist/types/decorators/index.d.ts.map +1 -1
  106. package/dist/types/decorators/intersectionObserver.d.ts +49 -0
  107. package/dist/types/decorators/intersectionObserver.d.ts.map +1 -0
  108. package/dist/types/decorators/mutationObserver.d.ts +46 -0
  109. package/dist/types/decorators/mutationObserver.d.ts.map +1 -0
  110. package/dist/types/decorators/query.d.ts +63 -8
  111. package/dist/types/decorators/query.d.ts.map +1 -1
  112. package/dist/types/decorators/resizeObserver.d.ts +48 -0
  113. package/dist/types/decorators/resizeObserver.d.ts.map +1 -0
  114. package/dist/types/decorators/setInterval.d.ts +29 -0
  115. package/dist/types/decorators/setInterval.d.ts.map +1 -0
  116. package/dist/types/decorators/setTimeout.d.ts +29 -0
  117. package/dist/types/decorators/setTimeout.d.ts.map +1 -0
  118. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts +7 -1
  119. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +1 -1
  120. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +6 -0
  121. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +1 -1
  122. package/dist/types/examples/RxJSOperatorsExample.d.ts +94 -0
  123. package/dist/types/examples/RxJSOperatorsExample.d.ts.map +1 -0
  124. package/dist/types/types/index.d.ts +104 -0
  125. package/dist/types/types/index.d.ts.map +1 -1
  126. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +2413 -889
  127. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  128. package/package.json +8 -6
  129. package/src/config/config.ts +1 -1
  130. package/src/decorators/addEventListener.ts +426 -23
  131. package/src/decorators/applyAttribute.ts +50 -36
  132. package/src/decorators/applyClass.ts +76 -26
  133. package/src/decorators/applyNode.ts +114 -36
  134. package/src/decorators/applyProperty.ts +152 -40
  135. package/src/decorators/applyStyle.ts +64 -17
  136. package/src/decorators/changedAttribute.ts +46 -0
  137. package/src/decorators/elementDefine.ts +301 -317
  138. package/src/decorators/emitCustomEvent.ts +73 -5
  139. package/src/decorators/index.ts +5 -1
  140. package/src/decorators/intersectionObserver.ts +259 -0
  141. package/src/decorators/mutationObserver.ts +304 -0
  142. package/src/decorators/query.ts +182 -117
  143. package/src/decorators/resizeObserver.ts +235 -0
  144. package/src/decorators/setInterval.ts +80 -0
  145. package/src/decorators/setTimeout.ts +80 -0
  146. package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +17 -0
  147. package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +19 -0
  148. package/src/examples/RxJSOperatorsExample.ts +279 -0
  149. package/src/types/index.ts +141 -0
  150. package/dist/cjs/decorators/queryAll.js +0 -181
  151. package/dist/cjs/decorators/queryAll.js.map +0 -7
  152. package/dist/cjs/elements/tt/SwcAppMixin.js +0 -1
  153. package/dist/cjs/elements/tt/SwcAppMixin.js.map +0 -7
  154. package/dist/cjs/elements/tt/SwcAsync.js +0 -1
  155. package/dist/cjs/elements/tt/SwcAsync.js.map +0 -7
  156. package/dist/cjs/elements/tt/SwcAsyncTemplate.js +0 -1
  157. package/dist/cjs/elements/tt/SwcAsyncTemplate.js.map +0 -7
  158. package/dist/cjs/elements/tt/SwcChoose.js +0 -1
  159. package/dist/cjs/elements/tt/SwcChoose.js.map +0 -7
  160. package/dist/cjs/elements/tt/SwcChooseTemplate.js +0 -1
  161. package/dist/cjs/elements/tt/SwcChooseTemplate.js.map +0 -7
  162. package/dist/cjs/elements/tt/SwcIf.js +0 -1
  163. package/dist/cjs/elements/tt/SwcIf.js.map +0 -7
  164. package/dist/cjs/elements/tt/SwcIfTemplate.js +0 -1
  165. package/dist/cjs/elements/tt/SwcIfTemplate.js.map +0 -7
  166. package/dist/cjs/elements/tt/SwcLoop.js +0 -1
  167. package/dist/cjs/elements/tt/SwcLoop.js.map +0 -7
  168. package/dist/cjs/elements/tt/SwcLoopTemplate.js +0 -1
  169. package/dist/cjs/elements/tt/SwcLoopTemplate.js.map +0 -7
  170. package/dist/cjs/elements/tt/SwcSubTemplates.js +0 -1
  171. package/dist/cjs/elements/tt/SwcSubTemplates.js.map +0 -7
  172. package/dist/cjs/elements/tt/register.js +0 -1
  173. package/dist/cjs/elements/tt/register.js.map +0 -7
  174. package/dist/esm/decorators/queryAll.js +0 -162
  175. package/dist/esm/decorators/queryAll.js.map +0 -7
  176. package/dist/esm/elements/tt/SwcAppMixin.js +0 -1
  177. package/dist/esm/elements/tt/SwcAppMixin.js.map +0 -7
  178. package/dist/esm/elements/tt/SwcAsync.js +0 -1
  179. package/dist/esm/elements/tt/SwcAsync.js.map +0 -7
  180. package/dist/esm/elements/tt/SwcAsyncTemplate.js +0 -1
  181. package/dist/esm/elements/tt/SwcAsyncTemplate.js.map +0 -7
  182. package/dist/esm/elements/tt/SwcChoose.js +0 -1
  183. package/dist/esm/elements/tt/SwcChoose.js.map +0 -7
  184. package/dist/esm/elements/tt/SwcChooseTemplate.js +0 -1
  185. package/dist/esm/elements/tt/SwcChooseTemplate.js.map +0 -7
  186. package/dist/esm/elements/tt/SwcIf.js +0 -1
  187. package/dist/esm/elements/tt/SwcIf.js.map +0 -7
  188. package/dist/esm/elements/tt/SwcIfTemplate.js +0 -1
  189. package/dist/esm/elements/tt/SwcIfTemplate.js.map +0 -7
  190. package/dist/esm/elements/tt/SwcLoop.js +0 -1
  191. package/dist/esm/elements/tt/SwcLoop.js.map +0 -7
  192. package/dist/esm/elements/tt/SwcLoopTemplate.js +0 -1
  193. package/dist/esm/elements/tt/SwcLoopTemplate.js.map +0 -7
  194. package/dist/esm/elements/tt/SwcSubTemplates.js +0 -1
  195. package/dist/esm/elements/tt/SwcSubTemplates.js.map +0 -7
  196. package/dist/esm/elements/tt/register.js +0 -1
  197. package/dist/esm/elements/tt/register.js.map +0 -7
  198. package/dist/types/decorators/queryAll.d.ts +0 -22
  199. package/dist/types/decorators/queryAll.d.ts.map +0 -1
  200. package/dist/types/elements/tt/SwcAppMixin.d.ts +0 -1
  201. package/dist/types/elements/tt/SwcAppMixin.d.ts.map +0 -1
  202. package/dist/types/elements/tt/SwcAsync.d.ts +0 -1
  203. package/dist/types/elements/tt/SwcAsync.d.ts.map +0 -1
  204. package/dist/types/elements/tt/SwcAsyncTemplate.d.ts +0 -1
  205. package/dist/types/elements/tt/SwcAsyncTemplate.d.ts.map +0 -1
  206. package/dist/types/elements/tt/SwcChoose.d.ts +0 -1
  207. package/dist/types/elements/tt/SwcChoose.d.ts.map +0 -1
  208. package/dist/types/elements/tt/SwcChooseTemplate.d.ts +0 -1
  209. package/dist/types/elements/tt/SwcChooseTemplate.d.ts.map +0 -1
  210. package/dist/types/elements/tt/SwcIf.d.ts +0 -1
  211. package/dist/types/elements/tt/SwcIf.d.ts.map +0 -1
  212. package/dist/types/elements/tt/SwcIfTemplate.d.ts +0 -1
  213. package/dist/types/elements/tt/SwcIfTemplate.d.ts.map +0 -1
  214. package/dist/types/elements/tt/SwcLoop.d.ts +0 -1
  215. package/dist/types/elements/tt/SwcLoop.d.ts.map +0 -1
  216. package/dist/types/elements/tt/SwcLoopTemplate.d.ts +0 -1
  217. package/dist/types/elements/tt/SwcLoopTemplate.d.ts.map +0 -1
  218. package/dist/types/elements/tt/SwcSubTemplates.d.ts +0 -1
  219. package/dist/types/elements/tt/SwcSubTemplates.d.ts.map +0 -1
  220. package/dist/types/elements/tt/register.d.ts +0 -1
  221. package/dist/types/elements/tt/register.d.ts.map +0 -1
  222. package/src/decorators/queryAll.ts +0 -186
  223. package/src/elements/tt/SwcAppMixin.ts +0 -13
  224. package/src/elements/tt/SwcAsync.ts +0 -139
  225. package/src/elements/tt/SwcAsyncTemplate.ts +0 -126
  226. package/src/elements/tt/SwcChoose.ts +0 -182
  227. package/src/elements/tt/SwcChooseTemplate.ts +0 -229
  228. package/src/elements/tt/SwcIf.ts +0 -145
  229. package/src/elements/tt/SwcIfTemplate.ts +0 -158
  230. package/src/elements/tt/SwcLoop.ts +0 -222
  231. package/src/elements/tt/SwcLoopTemplate.ts +0 -255
  232. package/src/elements/tt/SwcSubTemplates.ts +0 -6
  233. package/src/elements/tt/register.ts +0 -720
@@ -0,0 +1,304 @@
1
+ import { ReflectUtils } from '@dooboostore/core';
2
+ import { SpecialSelector, SwcQueryOptions, SwcSelector, SwcFnSelector, HelperHostSet } from '../types';
3
+
4
+ // 공통 옵션 — root·delegate 없음 (MutationObserverInit + filter/removeObserver)
5
+ export interface MutationObserverBaseOptions extends MutationObserverInit {
6
+ filter?: (matchedEls: HTMLElement[], meta: { currentThis: any, helper: HelperHostSet }) => boolean;
7
+ // observer 해제(disconnected) 시 호출되는 콜백. 첫 번째 인자는 observe된 target element, 두 번째는 사용자 옵션 객체.
8
+ removeObserver?: (target: Element, optionValue: MutationObserverQueryOptions) => void;
9
+ }
10
+
11
+ // 문자열 셀렉터 전용 — root·delegate 허용 (셀렉터로 탐색/델리게이션)
12
+ export type MutationObserverQueryOptions = MutationObserverBaseOptions & SwcQueryOptions & { delegate?: boolean };
13
+ // 함수 셀렉터 전용 — root·delegate 금지 (이미 요소를 직접 반환)
14
+ export type MutationObserverNonQueryOptions = MutationObserverBaseOptions;
15
+
16
+ // 셀렉터 종류에 따라 옵션 타입 분기
17
+ export type MutationObserverOptionsOf<S extends SwcSelector> = S extends string ? MutationObserverQueryOptions : MutationObserverNonQueryOptions;
18
+
19
+ export interface MutationObserverMetadata {
20
+ propertyKey: string | symbol;
21
+ selector: SwcSelector;
22
+ options: MutationObserverQueryOptions;
23
+ }
24
+
25
+ export const MUTATION_OBSERVER_METADATA_KEY = Symbol.for('simple-web-component:mutation-observer');
26
+
27
+ export function mutationObserver(target: SpecialSelector, options?: MutationObserverQueryOptions): MethodDecorator;
28
+ export function mutationObserver(selector: string, options?: MutationObserverQueryOptions): MethodDecorator;
29
+ export function mutationObserver(selector: SwcFnSelector, options?: MutationObserverNonQueryOptions): MethodDecorator;
30
+ export function mutationObserver(options?: MutationObserverQueryOptions): MethodDecorator;
31
+ /**
32
+ * @mutationObserver decorator to observe DOM mutations.
33
+ */
34
+ export function mutationObserver(selectorOrOptions?: SwcSelector | MutationObserverQueryOptions, maybeOptions?: MutationObserverQueryOptions): MethodDecorator {
35
+ return (targetObj: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
36
+ let selector: SwcSelector = '$this';
37
+ let options: MutationObserverQueryOptions = {};
38
+
39
+ if (typeof selectorOrOptions === 'string' || typeof selectorOrOptions === 'function') {
40
+ selector = selectorOrOptions;
41
+ options = maybeOptions ?? {};
42
+ } else {
43
+ selector = '$this';
44
+ options = selectorOrOptions ?? {};
45
+ }
46
+
47
+ const constructor = targetObj.constructor;
48
+
49
+ let observers = ReflectUtils.getMetadata<MutationObserverMetadata[]>(MUTATION_OBSERVER_METADATA_KEY, constructor);
50
+ if (!observers) {
51
+ observers = [];
52
+ ReflectUtils.defineMetadata(MUTATION_OBSERVER_METADATA_KEY, observers, constructor);
53
+ }
54
+
55
+ observers.push({ propertyKey, selector, options });
56
+ };
57
+ }
58
+
59
+ export function mutationObserverThis<TEvent extends Event = Event>(options?: MutationObserverQueryOptions): MethodDecorator {
60
+ return mutationObserver('$this', options);
61
+ }
62
+
63
+ // ─── root별 delegate 헬퍼 (addEventListener와 동일 패턴) ───
64
+
65
+ export function mutationObserverDelegateLight(selector: string, options?: MutationObserverBaseOptions): MethodDecorator;
66
+ export function mutationObserverDelegateLight(options?: MutationObserverBaseOptions): MethodDecorator;
67
+ export function mutationObserverDelegateLight(selectorOrOptions?: string | Omit<MutationObserverQueryOptions, 'root'>, maybeOptions?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator {
68
+ if (typeof selectorOrOptions === 'string' || typeof selectorOrOptions === 'function') {
69
+ return mutationObserver(selectorOrOptions, {...maybeOptions ?? {}, root: 'light', delegate: true});
70
+ }
71
+ return mutationObserver({...selectorOrOptions ?? {}, root: 'light', delegate: true});
72
+ }
73
+
74
+ export function mutationObserverDelegateShadow(selector: string, options?: MutationObserverBaseOptions): MethodDecorator;
75
+ export function mutationObserverDelegateShadow(options?: MutationObserverBaseOptions): MethodDecorator;
76
+ export function mutationObserverDelegateShadow(selectorOrOptions?: string | Omit<MutationObserverQueryOptions, 'root'>, maybeOptions?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator {
77
+ if (typeof selectorOrOptions === 'string' || typeof selectorOrOptions === 'function') {
78
+ return mutationObserver(selectorOrOptions, {...maybeOptions ?? {}, root: 'shadow', delegate: true});
79
+ }
80
+ return mutationObserver({...selectorOrOptions ?? {}, root: 'shadow', delegate: true});
81
+ }
82
+
83
+ export function mutationObserverDelegateAll(selector: string, options?: MutationObserverBaseOptions): MethodDecorator;
84
+ export function mutationObserverDelegateAll(options?: MutationObserverBaseOptions): MethodDecorator;
85
+ export function mutationObserverDelegateAll(selectorOrOptions?: string | Omit<MutationObserverQueryOptions, 'root'>, maybeOptions?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator {
86
+ if (typeof selectorOrOptions === 'string' || typeof selectorOrOptions === 'function') {
87
+ return mutationObserver(selectorOrOptions, {...maybeOptions ?? {}, root: 'all', delegate: true});
88
+ }
89
+ return mutationObserver({...selectorOrOptions ?? {}, root: 'all', delegate: true});
90
+ }
91
+
92
+ export function mutationObserverDelegate(selector: string, options?: MutationObserverBaseOptions): MethodDecorator;
93
+ export function mutationObserverDelegate(options?: MutationObserverBaseOptions): MethodDecorator;
94
+ export function mutationObserverDelegate(selectorOrOptions?: string | Omit<MutationObserverQueryOptions, 'root'>, maybeOptions?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator {
95
+ if (typeof selectorOrOptions === 'string' || typeof selectorOrOptions === 'function') {
96
+ return mutationObserver(selectorOrOptions, {...maybeOptions ?? {}, root: 'auto', delegate: true});
97
+ }
98
+ return mutationObserver({...selectorOrOptions ?? {}, root: 'auto', delegate: true});
99
+ }
100
+
101
+ // ─── root별 일반 헬퍼 (delegate 없이, 셀렉터 생략 시 $this) ───
102
+
103
+ export function mutationObserverLight(selector: string, options?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator;
104
+ export function mutationObserverLight(options?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator;
105
+ export function mutationObserverLight(selectorOrOptions?: string | Omit<MutationObserverQueryOptions, 'root'>, maybeOptions?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator {
106
+ if (typeof selectorOrOptions === 'string' || typeof selectorOrOptions === 'function') {
107
+ return mutationObserver(selectorOrOptions, {...maybeOptions ?? {}, root: 'light'});
108
+ }
109
+ return mutationObserver({...selectorOrOptions ?? {}, root: 'light'});
110
+ }
111
+
112
+ export function mutationObserverShadow(selector: string, options?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator;
113
+ export function mutationObserverShadow(options?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator;
114
+ export function mutationObserverShadow(selectorOrOptions?: string | Omit<MutationObserverQueryOptions, 'root'>, maybeOptions?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator {
115
+ if (typeof selectorOrOptions === 'string' || typeof selectorOrOptions === 'function') {
116
+ return mutationObserver(selectorOrOptions, {...maybeOptions ?? {}, root: 'shadow'});
117
+ }
118
+ return mutationObserver({...selectorOrOptions ?? {}, root: 'shadow'});
119
+ }
120
+
121
+ export function mutationObserverAll(selector: string, options?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator;
122
+ export function mutationObserverAll(options?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator;
123
+ export function mutationObserverAll(selectorOrOptions?: string | Omit<MutationObserverQueryOptions, 'root'>, maybeOptions?: Omit<MutationObserverQueryOptions, 'root'>): MethodDecorator {
124
+ if (typeof selectorOrOptions === 'string' || typeof selectorOrOptions === 'function') {
125
+ return mutationObserver(selectorOrOptions, {...maybeOptions ?? {}, root: 'all'});
126
+ }
127
+ return mutationObserver({...selectorOrOptions ?? {}, root: 'all'});
128
+ }
129
+
130
+ export function getMutationObserverMetadata(target: any): MutationObserverMetadata[] | undefined {
131
+ const constructor = target instanceof Function ? target : target.constructor;
132
+ return ReflectUtils.getMetadata(MUTATION_OBSERVER_METADATA_KEY, constructor);
133
+ }
134
+
135
+ // ─────────────────────────────────────────────────────────────────────────────
136
+ // MutationObserverLifeCycler
137
+ // observer 를 직접 생성하지 않고 OnConnectedResult 를 반환한다.
138
+ // elementDefine 이 수집된 MutationObserverSet 으로 observer 를 생성한다.
139
+ // ─────────────────────────────────────────────────────────────────────────────
140
+ import { ElementDefineLifeCycler, MutationObserverSet, MutationObserverSetEntry, ObserverObserveTarget, OnConnectedResult } from '../types';
141
+ import { SwcUtils } from '../utils/Utils';
142
+
143
+ export class MutationObserverLifeCycler implements ElementDefineLifeCycler {
144
+ private readonly removeObserverCallbacksMap = new WeakMap<Element, Array<{ fn: (target: Element, opts: unknown) => void; target: any; opts: unknown }>>();
145
+
146
+ onConnected(helperHostSet: HelperHostSet): OnConnectedResult | void {
147
+ const inst = helperHostSet.$this;
148
+ const currentWin = helperHostSet.$w;
149
+
150
+ const removeObserverCallbacks: Array<{ fn: (target: Element, opts: unknown) => void; target: any; opts: unknown }> = [];
151
+ this.removeObserverCallbacksMap.set(inst, removeObserverCallbacks);
152
+
153
+ const allMeta = getMutationObserverMetadata(inst) ?? [];
154
+ // 처리할 메타가 없으면 observer 불필요
155
+ if (allMeta.length === 0) return;
156
+
157
+ // ── shadow / light 루트별 분류 ──
158
+ const shadowMetas = allMeta.filter(m => {
159
+ const r = m.options.root || 'auto';
160
+ return inst.shadowRoot && (r === 'shadow' || r === 'all' || r === 'auto');
161
+ });
162
+ const lightMetas = allMeta.filter(m => {
163
+ const r = m.options.root || 'auto';
164
+ return r === 'light' || r === 'all' || (!inst.shadowRoot && r === 'auto');
165
+ });
166
+ // ── MutationObserverSet 생성 헬퍼 (루트별) ──
167
+ const buildSet = (
168
+ root: HTMLElement | ShadowRoot,
169
+ metas: MutationObserverMetadata[],
170
+ ): { callback: MutationObserverSetEntry['callback']; observeTargets: ObserverObserveTarget<MutationObserverInit>[] } | null => {
171
+ if (metas.length === 0) return null;
172
+
173
+ const initFrom = (o: MutationObserverBaseOptions): MutationObserverInit => {
174
+ const init: MutationObserverInit = {
175
+ childList: o.childList, attributes: o.attributes, characterData: o.characterData,
176
+ subtree: o.subtree, attributeFilter: o.attributeFilter,
177
+ attributeOldValue: o.attributeOldValue, characterDataOldValue: o.characterDataOldValue,
178
+ };
179
+ if (!init.childList && !init.attributes && !init.characterData) init.childList = true;
180
+ return init;
181
+ };
182
+
183
+ const fnObservedSets = new Map<MutationObserverMetadata, WeakSet<Element>>();
184
+
185
+ const callback = (mutations: MutationRecord[], obs: MutationObserver) => {
186
+ // ── MutationObserver 메타 콜백 ──
187
+ for (const meta of metas) {
188
+ const { selector, options } = meta;
189
+ let matchedEls: HTMLElement[] = [];
190
+
191
+ const typeMatches = (m: MutationRecord): boolean => {
192
+ const has = !!(options.childList || options.attributes || options.characterData);
193
+ if (m.type === 'childList') return has ? !!options.childList : true;
194
+ if (m.type === 'attributes') return !!options.attributes;
195
+ if (m.type === 'characterData') return !!options.characterData;
196
+ return true;
197
+ };
198
+
199
+ const collect = (matchesSel: (n: any) => boolean) =>
200
+ mutations.flatMap(m => {
201
+ if (!typeMatches(m)) return [];
202
+ const els: HTMLElement[] = [];
203
+ if (matchesSel(m.target)) els.push(m.target as HTMLElement);
204
+ Array.from(m.addedNodes || []).filter(matchesSel).forEach(n => els.push(n as HTMLElement));
205
+ Array.from(m.removedNodes || []).filter(matchesSel).forEach(n => els.push(n as HTMLElement));
206
+ return els;
207
+ });
208
+
209
+ if (options.delegate && typeof selector === 'string') {
210
+ const isThis = selector === '$this' || selector === '';
211
+ matchedEls = collect(n => {
212
+ if (!n || n.nodeType !== 1) return false;
213
+ if (isThis) return true;
214
+ const el = n as HTMLElement;
215
+ return el.matches?.(selector) || !!el.closest?.(selector);
216
+ });
217
+ } else if (typeof selector === 'string') {
218
+ const isThis = selector === '$this' || selector === '';
219
+ matchedEls = collect(n => {
220
+ if (!n || n.nodeType !== 1) return false;
221
+ if (isThis) return true;
222
+ return (n as HTMLElement).matches?.(selector);
223
+ });
224
+ } else if (typeof selector === 'function') {
225
+ const set = fnObservedSets.get(meta);
226
+ matchedEls = mutations.filter(m => typeMatches(m))
227
+ .map(m => m.target as HTMLElement)
228
+ .filter(t => t && t.nodeType === 1 && (set ? set.has(t) : false));
229
+ } else {
230
+ matchedEls = mutations.filter(m => typeMatches(m))
231
+ .map(m => m.target as HTMLElement)
232
+ .filter(t => t && t.nodeType === 1);
233
+ }
234
+
235
+ if (matchedEls.length === 0) continue;
236
+ if (options.filter) {
237
+ if (!options.filter(matchedEls, { currentThis: inst, helper: helperHostSet })) continue;
238
+ }
239
+ const hostSet = SwcUtils.getHostSet(inst);
240
+ inst[meta.propertyKey](matchedEls, mutations, obs, { ...hostSet, $root: root });
241
+ }
242
+
243
+ };
244
+
245
+ // ── observe 대상 목록 구성 ──
246
+ const observeTargets: ObserverObserveTarget<MutationObserverInit>[] = [];
247
+
248
+ // delegate 있으면 루트 전체 subtree observe
249
+ if (metas.some(m => m.options.delegate)) {
250
+ observeTargets.push({ target: root, options: { childList: true, subtree: true } });
251
+ }
252
+
253
+ // non-delegate: 셀렉터 매칭 요소 직접 observe
254
+ for (const m of metas.filter(m => !m.options.delegate)) {
255
+ const { selector, options } = m;
256
+ const mInit = initFrom(options);
257
+ if (typeof selector === 'string') {
258
+ if (selector === '$this' || selector === '') {
259
+ observeTargets.push({ target: root, options: mInit });
260
+ } else {
261
+ root.querySelectorAll(selector).forEach(el => observeTargets.push({ target: el as Element, options: mInit }));
262
+ }
263
+ } else if (typeof selector === 'function') {
264
+ const res = (selector as any)(inst, helperHostSet);
265
+ let targets: HTMLElement[] = [];
266
+ if (res instanceof currentWin.HTMLElement) targets = [res as HTMLElement];
267
+ else if (res instanceof currentWin.NodeList) targets = Array.from(res as NodeList).filter((e: any) => e instanceof currentWin.HTMLElement) as HTMLElement[];
268
+ else if (Array.isArray(res)) targets = res.filter((e: any) => e instanceof currentWin.HTMLElement) as HTMLElement[];
269
+ const set = new WeakSet<Element>();
270
+ for (const el of targets) { set.add(el); observeTargets.push({ target: el, options: mInit }); }
271
+ fnObservedSets.set(m, set);
272
+ }
273
+ }
274
+
275
+ // removeObserver 콜백 수집
276
+ for (const m of metas) {
277
+ if (m.options.removeObserver) removeObserverCallbacks.push({ fn: m.options.removeObserver, target: root, opts: m.options });
278
+ }
279
+
280
+ return { callback, observeTargets };
281
+ };
282
+
283
+ const shadowSet = inst.shadowRoot ? buildSet(inst.shadowRoot, shadowMetas) : null;
284
+ const lightSet = buildSet(inst as any, lightMetas);
285
+
286
+ // 두 루트의 observe 대상 + 콜백을 엔트리 배열(MutationObserverSet)로 병합
287
+ // 같은 루트의 여러 대상은 같은 callback 을 공유한다.
288
+ const mutationObserverSet: MutationObserverSet = [
289
+ ...(shadowSet?.observeTargets ?? []).map(t => ({ ...t, callback: shadowSet!.callback })),
290
+ ...(lightSet?.observeTargets ?? []).map(t => ({ ...t, callback: lightSet!.callback })),
291
+ ];
292
+ if (mutationObserverSet.length === 0) return;
293
+
294
+ return { mutationObserverSet };
295
+ }
296
+
297
+ onDisconnected(helperHostSet: HelperHostSet): void {
298
+ const inst = helperHostSet.$this;
299
+ for (const cb of this.removeObserverCallbacksMap.get(inst) ?? []) {
300
+ try { cb.fn(cb.target, cb.opts); } catch (e) { console.error('[SWC] removeObserver error:', e); }
301
+ }
302
+ this.removeObserverCallbacksMap.delete(inst);
303
+ }
304
+ }
@@ -1,36 +1,110 @@
1
1
  import { ReflectUtils } from '@dooboostore/core';
2
2
  import { getElementConfig, ensureInit } from './elementDefine';
3
3
  import { SwcUtils } from '../utils/Utils';
4
- import { SwcQueryOptions, HelperHostSet } from '../types';
4
+ import { SwcQueryOptions, SwcRootType, SwcFnSelector, SwcSelector, HelperHostSet } from '../types';
5
5
 
6
- export interface QueryOptions extends SwcQueryOptions {
7
- /**
8
- * Filter function to determine whether to include the element.
9
- * If it returns false, the element is skipped.
10
- */
6
+ export type QueryPick = 'first' | 'last' | 'all' | 'even' | 'odd' | number;
7
+
8
+ export interface QueryBaseOptions {
9
+ // 어떤 요소를 고를지 — 기본 'first'(단일). 'all'/'even'/'odd'는 배열, number는 인덱스(단일)
10
+ pick?: QueryPick;
11
11
  filter?: (target: HTMLElement, meta: { currentThis: any, helper: HelperHostSet }) => boolean;
12
12
  }
13
+ // 문자열 셀렉터 전용 — root 허용
14
+ export type QueryOptions = QueryBaseOptions & SwcQueryOptions;
15
+ // 함수 셀렉터 전용 — root 금지
16
+ export type QueryNonQueryOptions = QueryBaseOptions;
17
+
18
+ export type QueryFnSelector = SwcFnSelector;
19
+ export type QuerySelector = SwcSelector;
20
+
21
+ // 셀렉터 종류에 따라 옵션 타입 분기
22
+ export type QueryOptionsOf<S extends QuerySelector> = S extends string ? QueryOptions : QueryNonQueryOptions;
13
23
 
14
24
  export interface QueryMetadata {
15
25
  propertyKey: string | symbol;
16
- selector: string | ((currentThis: any, helper: HelperHostSet) => Node | NodeList | Element | Element[] | null);
17
- options: QueryOptions ;
26
+ selector: QuerySelector;
27
+ options: QueryOptions;
18
28
  }
19
29
 
20
30
  export const QUERY_METADATA_KEY = Symbol.for('simple-web-component:query');
21
31
 
32
+ /**
33
+ * 셀렉터를 해석해 매칭된 모든 요소를 배열로 반환한다 (query/queryAll 공용).
34
+ * $window/$document는 요소가 아니므로 여기서 처리하지 않는다.
35
+ */
36
+ export const resolveQueryElements = (
37
+ inst: any,
38
+ selector: QuerySelector,
39
+ options: QueryOptions,
40
+ win: Window
41
+ ): HTMLElement[] => {
42
+ const hostSet = SwcUtils.getHelperAndHostSet(win, inst);
43
+ let all: HTMLElement[] = [];
44
+
45
+ if (typeof selector === 'function') {
46
+ const result = (selector as any)(inst, hostSet);
47
+ if (result instanceof win.NodeList) all = Array.from(result as NodeList).filter(e => e instanceof win.HTMLElement) as HTMLElement[];
48
+ else if (Array.isArray(result)) all = (result as any[]).filter(e => e instanceof win.HTMLElement) as HTMLElement[];
49
+ else if (result instanceof win.HTMLElement) all = [result as HTMLElement];
50
+ } else if (typeof selector === 'string') {
51
+ const r = (options as any)?.root || 'auto';
52
+ if (selector === '$this') all = [inst as HTMLElement];
53
+ else if (selector === '$host') all = hostSet.$host ? [hostSet.$host] : [];
54
+ else if (selector === '$parentHost') all = hostSet.$parentHost ? [hostSet.$parentHost] : [];
55
+ else if (selector === '$appHost') all = hostSet.$appHost ? [hostSet.$appHost as any] : [];
56
+ else if (selector === '$firstHost') all = hostSet.$firstHost ? [hostSet.$firstHost] : [];
57
+ else if (selector === '$lastHost') all = hostSet.$lastHost ? [hostSet.$lastHost] : [];
58
+ else if (selector === '$firstAppHost') all = hostSet.$firstAppHost ? [hostSet.$firstAppHost as any] : [];
59
+ else if (selector === '$lastAppHost') all = hostSet.$lastAppHost ? [hostSet.$lastAppHost as any] : [];
60
+ else if (selector === '$hosts') all = hostSet.$hosts.filter(Boolean);
61
+ else if (selector === '$appHosts') all = hostSet.$appHosts.filter(Boolean) as any[];
62
+ else {
63
+ const scopes: any[] = [];
64
+ if (r === 'auto') scopes.push(inst.shadowRoot || inst);
65
+ else if (r === 'light') scopes.push(inst);
66
+ else if (r === 'shadow' && inst.shadowRoot) scopes.push(inst.shadowRoot);
67
+ else if (r === 'all') { scopes.push(inst); if (inst.shadowRoot) scopes.push(inst.shadowRoot); }
68
+ for (const scope of scopes) {
69
+ const found = scope?.querySelectorAll?.(selector) as NodeListOf<HTMLElement> | undefined;
70
+ if (found?.length) all.push(...Array.from(found).filter(e => e instanceof win.HTMLElement));
71
+ }
72
+ }
73
+ }
74
+
75
+ if ((options as any)?.filter) {
76
+ all = all.filter(el => (options as any).filter(el, { currentThis: inst, helper: hostSet }));
77
+ }
78
+ return all;
79
+ };
80
+
81
+ /**
82
+ * 매칭된 요소 배열에서 pick 옵션에 따라 요소를 고른다.
83
+ * - 'first'(기본) / 'last' / number(인덱스) → 단일 요소
84
+ * - 'all' / 'even'(0,2,4...) / 'odd'(1,3,5...) → 배열
85
+ */
86
+ export const pickElements = (
87
+ all: HTMLElement[],
88
+ pick: QueryPick
89
+ ): HTMLElement | HTMLElement[] | null => {
90
+ if (pick === 'all') return all;
91
+ if (pick === 'even') return all.filter((_, i) => i % 2 === 0);
92
+ if (pick === 'odd') return all.filter((_, i) => i % 2 === 1);
93
+ if (typeof pick === 'number') return all[pick] ?? null;
94
+ if (pick === 'last') return all.length ? all[all.length - 1] : null;
95
+ return all.length ? all[0] : null;
96
+ };
97
+
22
98
  export function query(target: Object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): PropertyDescriptor | void;
23
- export function query(selector: string | ((currentThis: any, helper: HelperHostSet) => Node | NodeList | Element | Element[] | null), options?: QueryOptions): PropertyDecorator;
99
+ export function query(selector: string, options?: QueryOptions): PropertyDecorator;
100
+ export function query(selector: QueryFnSelector, options?: QueryNonQueryOptions): PropertyDecorator;
24
101
  export function query(options?: QueryOptions): PropertyDecorator;
25
102
  /**
26
- * @query decorator to inject a single element into a class property.
27
- *
28
- * Supports both string selectors and function-based selectors:
29
- * - String selector: @query('#my-element')
30
- * - Function selector: @query((currentThis, helper) => currentThis.shadowRoot?.querySelector('.item'))
31
- *
32
- * Supports filtering:
33
- * - @query('#my-element', { filter: (target, meta) => target.hasAttribute('active') })
103
+ * @query decorator — 클래스 필드에 요소를 주입한다.
104
+ *
105
+ * - 문자열 셀렉터: CSS / 특수 셀렉터($this,$host,$window,...), root 옵션으로 탐색 영역 지정
106
+ * - 함수 셀렉터: (currentThis, helper) => Node | NodeList | Element | Element[] | null (root 금지)
107
+ * - options.pick: 'first'(기본) | 'last' | 'all' | 'even' | 'odd' | number
34
108
  */
35
109
  export function query(selectorOrTarget?: string | ((currentThis: any, helper: HelperHostSet) => Node | NodeList | Element | Element[] | null) | Object, optionsOrPropertyKey?: QueryOptions | string | symbol, descriptor?: PropertyDescriptor): any {
36
110
  // Bare decorator: @query
@@ -50,118 +124,32 @@ export function query(selectorOrTarget?: string | ((currentThis: any, helper: He
50
124
  queries = [];
51
125
  ReflectUtils.defineMetadata(QUERY_METADATA_KEY, queries, constructor);
52
126
  }
53
- queries.push({ propertyKey, selector: selectorOrTarget as any, options: optionsOrPropertyKey as QueryOptions || {} });
127
+ queries.push({ propertyKey, selector: selectorOrTarget as any, options: (optionsOrPropertyKey as QueryOptions) || {} });
54
128
 
55
129
  Object.defineProperty(targetObj, propertyKey, {
56
130
  get(this: HTMLElement) {
57
131
  ensureInit(this);
58
132
  const config = getElementConfig(this);
59
133
  const win = config?.window || window;
60
- const hostSet = SwcUtils.getHelperAndHostSet(win, this);
61
-
62
- // Resolve selector - can be string or function
63
- let resolvedSelector: string;
64
- let resolvedElement: HTMLElement | null = null;
65
-
66
- if (typeof selectorOrTarget === 'function') {
67
- const result = (selectorOrTarget as any)(this, hostSet);
68
- // If function returns a Node/Element, return it directly
69
- if (result instanceof win.Node || result instanceof win.Element) {
70
- resolvedElement = result instanceof win.HTMLElement ? (result as HTMLElement) : null;
71
- }
72
- // If function returns NodeList or Element[], return first element
73
- else if (result instanceof win.NodeList || Array.isArray(result)) {
74
- resolvedElement = result.length > 0 && result[0] instanceof win.HTMLElement ? (result[0] as HTMLElement) : null;
75
- }
76
- } else {
77
- resolvedSelector = selectorOrTarget;
78
-
79
- const r = (optionsOrPropertyKey as QueryOptions)?.root || 'auto';
80
- const applyRoot = (t: any) => {
81
- if (!t || !(t instanceof HTMLElement)) return t;
82
- if (r === 'auto') return t.shadowRoot || t;
83
- if (r === 'shadow') return t.shadowRoot;
84
- if (r === 'light') return t;
85
- if (r === 'all') return t.shadowRoot || t;
86
- return t;
87
- };
88
-
89
- // --- Special Selectors: HostSet ---
90
- if (resolvedSelector === '$this') resolvedElement = applyRoot(this);
91
- else if (resolvedSelector === '$host') resolvedElement = applyRoot(hostSet.$host);
92
- else if (resolvedSelector === '$parentHost') resolvedElement = applyRoot(hostSet.$parentHost);
93
- else if (resolvedSelector === '$appHost') resolvedElement = applyRoot(hostSet.$appHost);
94
- else if (resolvedSelector === '$firstHost') resolvedElement = applyRoot(hostSet.$firstHost);
95
- else if (resolvedSelector === '$lastHost') resolvedElement = applyRoot(hostSet.$lastHost);
96
- else if (resolvedSelector === '$firstAppHost') resolvedElement = applyRoot(hostSet.$firstAppHost);
97
- else if (resolvedSelector === '$lastAppHost') resolvedElement = applyRoot(hostSet.$lastAppHost);
98
- // --- Special Selectors: Env ---
99
- else if (resolvedSelector === '$window') return win;
100
- else if (resolvedSelector === '$document') return win.document;
101
- // --- Standard Selectors ---
102
- else {
103
- if (r === 'shadow') resolvedElement = this.shadowRoot ? (this.shadowRoot.querySelector(resolvedSelector) as HTMLElement | null) : null;
104
- else if (r === 'light') resolvedElement = this.querySelector(resolvedSelector) as HTMLElement | null;
105
- else if (r === 'all') {
106
- const shadowMatch = this.shadowRoot ? (this.shadowRoot.querySelector(resolvedSelector) as HTMLElement | null) : null;
107
- resolvedElement = shadowMatch || (this.querySelector(resolvedSelector) as HTMLElement | null);
108
- }
109
- else resolvedElement = ((this.shadowRoot || this).querySelector(resolvedSelector) as HTMLElement | null);
110
- }
111
- }
134
+ const options = (optionsOrPropertyKey as QueryOptions) || {};
112
135
 
113
- // Apply filter if provided
114
- if (resolvedElement instanceof win.HTMLElement && (optionsOrPropertyKey as QueryOptions)?.filter) {
115
- const shouldInclude = (optionsOrPropertyKey as QueryOptions).filter!(resolvedElement, { currentThis: this, helper: hostSet });
116
- if (!shouldInclude) {
117
- return null;
118
- }
119
- }
136
+ // $window/$document는 요소가 아니므로 항상 단일 반환
137
+ if (typeof selectorOrTarget === 'string' && selectorOrTarget === '$window') return win;
138
+ if (typeof selectorOrTarget === 'string' && selectorOrTarget === '$document') return win.document;
120
139
 
121
- return resolvedElement;
140
+ const all = resolveQueryElements(this, selectorOrTarget as QuerySelector, options, win);
141
+ return pickElements(all, options.pick ?? 'first');
122
142
  },
123
143
  set(this: HTMLElement, nv: any) {
124
144
  ensureInit(this);
125
- if (nv === null || nv === undefined) {
145
+ if (nv === null || nv === undefined || (Array.isArray(nv) && nv.length === 0)) {
126
146
  const config = getElementConfig(this);
127
147
  const win = config?.window || window;
128
- const hostSet = SwcUtils.getHelperAndHostSet(win, this);
129
-
130
- // Resolve selector - can be string or function
131
- if (typeof selectorOrTarget === 'function') {
132
- // Get the element from function and remove it
133
- const result = (selectorOrTarget as any)(this, hostSet);
134
- if (result instanceof win.Element) {
135
- (result as Element).remove();
136
- } else if (result instanceof win.NodeList || Array.isArray(result)) {
137
- const el = result.length > 0 ? result[0] : null;
138
- if (el instanceof win.Element) {
139
- (el as Element).remove();
140
- }
141
- }
142
- return;
143
- }
144
-
145
- const resolvedSelector = selectorOrTarget;
146
-
147
- if (resolvedSelector.startsWith('$') && resolvedSelector !== '$this') return;
148
-
149
- const r = (optionsOrPropertyKey as QueryOptions)?.root || 'auto';
150
- const targets: Element[] = [];
151
-
152
- if (r === 'shadow') {
153
- if (this.shadowRoot?.querySelector(resolvedSelector)) targets.push(this.shadowRoot.querySelector(resolvedSelector)!);
154
- } else if (r === 'light') {
155
- if (this.querySelector(resolvedSelector)) targets.push(this.querySelector(resolvedSelector)!);
156
- } else if (r === 'all') {
157
- if (this.shadowRoot?.querySelector(resolvedSelector)) targets.push(this.shadowRoot.querySelector(resolvedSelector)!);
158
- if (this.querySelector(resolvedSelector)) targets.push(this.querySelector(resolvedSelector)!);
159
- } else {
160
- const el = (this.shadowRoot || this).querySelector(resolvedSelector);
161
- if (el) targets.push(el);
162
- }
163
-
164
- targets.forEach(t => t.remove());
148
+ const options = (optionsOrPropertyKey as QueryOptions) || {};
149
+ const all = resolveQueryElements(this, selectorOrTarget as QuerySelector, options, win);
150
+ const picked = pickElements(all, options.pick ?? 'first');
151
+ if (Array.isArray(picked)) picked.forEach(t => t.remove());
152
+ else if (picked) picked.remove();
165
153
  }
166
154
  },
167
155
  enumerable: true,
@@ -170,10 +158,87 @@ export function query(selectorOrTarget?: string | ((currentThis: any, helper: He
170
158
  };
171
159
  }
172
160
  // Without selector (defaults to $this)
173
- return query('$this', selectorOrTarget as SwcQueryOptions);
161
+ return query('$this', selectorOrTarget as QueryOptions);
174
162
  }
175
163
 
176
164
  export const getQueryMetadata = (target: any): QueryMetadata[] => {
177
165
  const constructor = target instanceof Function ? target : target.constructor;
178
166
  return ReflectUtils.getMetadata(QUERY_METADATA_KEY, constructor) ?? [];
179
167
  };
168
+
169
+ // ─── queryAll (query의 pick:'all' 편의 래퍼 — 호환용) ───
170
+ export const QUERY_ALL_METADATA_KEY = QUERY_METADATA_KEY;
171
+
172
+ export function queryAll(target: Object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): PropertyDescriptor | void;
173
+ export function queryAll(selector: string, options?: Omit<QueryOptions, 'pick'>): PropertyDecorator;
174
+ export function queryAll(selector: QueryFnSelector, options?: Omit<QueryNonQueryOptions, 'pick'>): PropertyDecorator;
175
+ export function queryAll(options?: Omit<QueryOptions, 'pick'>): PropertyDecorator;
176
+ export function queryAll(selectorOrTarget?: any, optionsOrPropertyKey?: any, descriptor?: PropertyDescriptor): any {
177
+ // bare(필드 직접 적용)는 query로 위임
178
+ if (descriptor !== undefined && (typeof optionsOrPropertyKey === 'string' || typeof optionsOrPropertyKey === 'symbol')) {
179
+ return query(selectorOrTarget, optionsOrPropertyKey, descriptor);
180
+ }
181
+ if (typeof selectorOrTarget === 'string' || typeof selectorOrTarget === 'function') {
182
+ return query(selectorOrTarget, {...(optionsOrPropertyKey ?? {}), pick: 'all'});
183
+ }
184
+ return query({...(selectorOrTarget ?? {}), pick: 'all'});
185
+ }
186
+
187
+ export const getQueryAllMetadata = getQueryMetadata;
188
+
189
+ // ─── root별 편의 데코레이터 ───
190
+
191
+ /**
192
+ * @queryShadow(selector, options?) — shadow DOM에서 요소를 쿼리
193
+ */
194
+ export function queryShadow(selector: string | QueryFnSelector, options?: QueryBaseOptions): PropertyDecorator {
195
+ return query(selector as any, {...options ?? {}, root: 'shadow'});
196
+ }
197
+
198
+ /**
199
+ * @queryLight(selector, options?) — light DOM에서 요소를 쿼리
200
+ */
201
+ export function queryLight(selector: string | QueryFnSelector, options?: QueryBaseOptions): PropertyDecorator {
202
+ return query(selector as any, {...options ?? {}, root: 'light'});
203
+ }
204
+
205
+ /**
206
+ * @queryAllRoots(selector, options?) — shadow+light 모두에서 요소를 쿼리 (pick 기본 first)
207
+ */
208
+ export function queryAllRoots(selector: string | QueryFnSelector, options?: QueryBaseOptions): PropertyDecorator {
209
+ return query(selector as any, {...options ?? {}, root: 'all'});
210
+ }
211
+
212
+ /**
213
+ * @queryAllShadow(selector, options?) — shadow DOM에서 모든 매칭 요소를 배열로 쿼리
214
+ */
215
+ export function queryAllShadow(selector: string | QueryFnSelector, options?: QueryBaseOptions): PropertyDecorator {
216
+ return query(selector as any, {...options ?? {}, root: 'shadow', pick: 'all'});
217
+ }
218
+
219
+ /**
220
+ * @queryAllLight(selector, options?) — light DOM에서 모든 매칭 요소를 배열로 쿼리
221
+ */
222
+ export function queryAllLight(selector: string | QueryFnSelector, options?: QueryBaseOptions): PropertyDecorator {
223
+ return query(selector as any, {...options ?? {}, root: 'light', pick: 'all'});
224
+ }
225
+
226
+ /**
227
+ * @queryAllAll(selector, options?) — shadow+light 모두에서 모든 매칭 요소를 배열로 쿼리
228
+ */
229
+ export function queryAllAll(selector: string | QueryFnSelector, options?: QueryBaseOptions): PropertyDecorator {
230
+ return query(selector as any, {...options ?? {}, root: 'all', pick: 'all'});
231
+ }
232
+
233
+ /**
234
+ * @queryIn(root, pick?) — root + pick 조합 팩토리.
235
+ * first/last/even/odd/all/number를 모두 지원하는 데코레이터를 생성한다.
236
+ *
237
+ * @example
238
+ * @queryIn('shadow', 'even')('.item') // shadow DOM 짝수 인덱스들
239
+ * @queryIn('light', 2)('.item') // light DOM 3번째 요소
240
+ * @queryIn('all')('.item') // root all, pick 기본 first
241
+ */
242
+ export const queryIn = (root: SwcRootType, pick?: QueryPick) =>
243
+ (selector: string | QueryFnSelector, options?: QueryBaseOptions): PropertyDecorator =>
244
+ query(selector as any, {...options ?? {}, root, ...(pick !== undefined ? {pick} : {})});