@dooboostore/simple-web-component 1.0.49 → 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 (251) hide show
  1. package/README.md +790 -337
  2. package/dist/cjs/SwcAppEngine.js.map +2 -2
  3. package/dist/cjs/decorators/addEventListener.js +1925 -22
  4. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  5. package/dist/cjs/decorators/applyAttribute.js +41 -47
  6. package/dist/cjs/decorators/applyAttribute.js.map +2 -2
  7. package/dist/cjs/decorators/applyClass.js +14 -11
  8. package/dist/cjs/decorators/applyClass.js.map +2 -2
  9. package/dist/cjs/decorators/applyNode.js +19 -21
  10. package/dist/cjs/decorators/applyNode.js.map +2 -2
  11. package/dist/cjs/decorators/applyProperty.js +24 -48
  12. package/dist/cjs/decorators/applyProperty.js.map +2 -2
  13. package/dist/cjs/decorators/applySlot.js +9 -8
  14. package/dist/cjs/decorators/applySlot.js.map +2 -2
  15. package/dist/cjs/decorators/applyStyle.js +14 -11
  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 +61 -24
  20. package/dist/cjs/decorators/changedAttribute.js.map +2 -2
  21. package/dist/cjs/decorators/elementDefine.js +240 -196
  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 +112 -0
  26. package/dist/cjs/decorators/eventMedia.js.map +7 -0
  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 +8 -2
  30. package/dist/cjs/decorators/index.js.map +2 -2
  31. package/dist/cjs/decorators/intersectionObserver.js +76 -65
  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 +97 -90
  36. package/dist/cjs/decorators/mutationObserver.js.map +3 -3
  37. package/dist/cjs/decorators/parameter.js +156 -0
  38. package/dist/cjs/decorators/parameter.js.map +7 -0
  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 +115 -0
  44. package/dist/cjs/decorators/requestAnimationFrame.js.map +7 -0
  45. package/dist/cjs/decorators/resizeObserver.js +84 -69
  46. package/dist/cjs/decorators/resizeObserver.js.map +3 -3
  47. package/dist/cjs/decorators/setInterval.js +65 -22
  48. package/dist/cjs/decorators/setInterval.js.map +2 -2
  49. package/dist/cjs/decorators/setTimeout.js +68 -22
  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 -67
  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/types/window.d.js.map +1 -1
  66. package/dist/cjs/utils/Utils.js +36 -15
  67. package/dist/cjs/utils/Utils.js.map +2 -2
  68. package/dist/esm/SwcAppEngine.js.map +2 -2
  69. package/dist/esm/decorators/addEventListener.js +1925 -22
  70. package/dist/esm/decorators/addEventListener.js.map +2 -2
  71. package/dist/esm/decorators/applyAttribute.js +41 -47
  72. package/dist/esm/decorators/applyAttribute.js.map +2 -2
  73. package/dist/esm/decorators/applyClass.js +14 -11
  74. package/dist/esm/decorators/applyClass.js.map +2 -2
  75. package/dist/esm/decorators/applyNode.js +20 -22
  76. package/dist/esm/decorators/applyNode.js.map +2 -2
  77. package/dist/esm/decorators/applyProperty.js +24 -48
  78. package/dist/esm/decorators/applyProperty.js.map +2 -2
  79. package/dist/esm/decorators/applySlot.js +9 -8
  80. package/dist/esm/decorators/applySlot.js.map +2 -2
  81. package/dist/esm/decorators/applyStyle.js +14 -11
  82. package/dist/esm/decorators/applyStyle.js.map +2 -2
  83. package/dist/esm/decorators/around.js +105 -0
  84. package/dist/esm/decorators/around.js.map +7 -0
  85. package/dist/esm/decorators/changedAttribute.js +61 -24
  86. package/dist/esm/decorators/changedAttribute.js.map +2 -2
  87. package/dist/esm/decorators/elementDefine.js +238 -194
  88. package/dist/esm/decorators/elementDefine.js.map +3 -3
  89. package/dist/esm/decorators/emitCustomEvent.js +15 -13
  90. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  91. package/dist/esm/decorators/eventMedia.js +94 -0
  92. package/dist/esm/decorators/eventMedia.js.map +7 -0
  93. package/dist/esm/decorators/fetch.js +259 -0
  94. package/dist/esm/decorators/fetch.js.map +7 -0
  95. package/dist/esm/decorators/index.js +8 -2
  96. package/dist/esm/decorators/index.js.map +2 -2
  97. package/dist/esm/decorators/intersectionObserver.js +76 -65
  98. package/dist/esm/decorators/intersectionObserver.js.map +2 -2
  99. package/dist/esm/decorators/lifecycles.js +11 -11
  100. package/dist/esm/decorators/lifecycles.js.map +2 -2
  101. package/dist/esm/decorators/mutationObserver.js +97 -90
  102. package/dist/esm/decorators/mutationObserver.js.map +3 -3
  103. package/dist/esm/decorators/parameter.js +137 -0
  104. package/dist/esm/decorators/parameter.js.map +7 -0
  105. package/dist/esm/decorators/publishSwcAppMessage.js +2 -2
  106. package/dist/esm/decorators/publishSwcAppMessage.js.map +2 -2
  107. package/dist/esm/decorators/query.js +11 -5
  108. package/dist/esm/decorators/query.js.map +2 -2
  109. package/dist/esm/decorators/requestAnimationFrame.js +96 -0
  110. package/dist/esm/decorators/requestAnimationFrame.js.map +7 -0
  111. package/dist/esm/decorators/resizeObserver.js +84 -69
  112. package/dist/esm/decorators/resizeObserver.js.map +2 -2
  113. package/dist/esm/decorators/setInterval.js +65 -23
  114. package/dist/esm/decorators/setInterval.js.map +2 -2
  115. package/dist/esm/decorators/setTimeout.js +68 -23
  116. package/dist/esm/decorators/setTimeout.js.map +2 -2
  117. package/dist/esm/decorators/state.js +13 -10
  118. package/dist/esm/decorators/state.js.map +2 -2
  119. package/dist/esm/decorators/subscribeSwcAppMessage.js +53 -0
  120. package/dist/esm/decorators/subscribeSwcAppMessage.js.map +7 -0
  121. package/dist/esm/decorators/{subscribeSwcAppRouteChangeWhileConnected.js → subscribeSwcAppRouteChange.js} +20 -16
  122. package/dist/esm/decorators/subscribeSwcAppRouteChange.js.map +7 -0
  123. package/dist/esm/decorators/swcAppRoute.js +130 -0
  124. package/dist/esm/decorators/swcAppRoute.js.map +7 -0
  125. package/dist/esm/elements/SwcApp.js +70 -0
  126. package/dist/esm/elements/SwcApp.js.map +2 -2
  127. package/dist/esm/elements/SwcAppMixin.js +258 -69
  128. package/dist/esm/elements/SwcAppMixin.js.map +3 -3
  129. package/dist/esm/types/index.js +5 -1
  130. package/dist/esm/types/index.js.map +2 -2
  131. package/dist/esm/utils/Utils.js +36 -15
  132. package/dist/esm/utils/Utils.js.map +2 -2
  133. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +4395 -1266
  134. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  135. package/dist/types/SwcAppEngine.d.ts +1 -0
  136. package/dist/types/SwcAppEngine.d.ts.map +1 -1
  137. package/dist/types/decorators/addEventListener.d.ts +936 -11
  138. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  139. package/dist/types/decorators/applyAttribute.d.ts +27 -37
  140. package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
  141. package/dist/types/decorators/applyClass.d.ts +2 -2
  142. package/dist/types/decorators/applyClass.d.ts.map +1 -1
  143. package/dist/types/decorators/applyNode.d.ts +16 -16
  144. package/dist/types/decorators/applyNode.d.ts.map +1 -1
  145. package/dist/types/decorators/applyProperty.d.ts +28 -44
  146. package/dist/types/decorators/applyProperty.d.ts.map +1 -1
  147. package/dist/types/decorators/applySlot.d.ts.map +1 -1
  148. package/dist/types/decorators/applyStyle.d.ts +2 -2
  149. package/dist/types/decorators/applyStyle.d.ts.map +1 -1
  150. package/dist/types/decorators/around.d.ts +87 -0
  151. package/dist/types/decorators/around.d.ts.map +1 -0
  152. package/dist/types/decorators/changedAttribute.d.ts +22 -4
  153. package/dist/types/decorators/changedAttribute.d.ts.map +1 -1
  154. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  155. package/dist/types/decorators/emitCustomEvent.d.ts +0 -1
  156. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  157. package/dist/types/decorators/eventMedia.d.ts +48 -0
  158. package/dist/types/decorators/eventMedia.d.ts.map +1 -0
  159. package/dist/types/decorators/fetch.d.ts +139 -0
  160. package/dist/types/decorators/fetch.d.ts.map +1 -0
  161. package/dist/types/decorators/index.d.ts +8 -2
  162. package/dist/types/decorators/index.d.ts.map +1 -1
  163. package/dist/types/decorators/intersectionObserver.d.ts +32 -10
  164. package/dist/types/decorators/intersectionObserver.d.ts.map +1 -1
  165. package/dist/types/decorators/lifecycles.d.ts +8 -7
  166. package/dist/types/decorators/lifecycles.d.ts.map +1 -1
  167. package/dist/types/decorators/mutationObserver.d.ts +34 -12
  168. package/dist/types/decorators/mutationObserver.d.ts.map +1 -1
  169. package/dist/types/decorators/parameter.d.ts +113 -0
  170. package/dist/types/decorators/parameter.d.ts.map +1 -0
  171. package/dist/types/decorators/publishSwcAppMessage.d.ts +0 -1
  172. package/dist/types/decorators/publishSwcAppMessage.d.ts.map +1 -1
  173. package/dist/types/decorators/query.d.ts +7 -7
  174. package/dist/types/decorators/query.d.ts.map +1 -1
  175. package/dist/types/decorators/requestAnimationFrame.d.ts +47 -0
  176. package/dist/types/decorators/requestAnimationFrame.d.ts.map +1 -0
  177. package/dist/types/decorators/resizeObserver.d.ts +34 -12
  178. package/dist/types/decorators/resizeObserver.d.ts.map +1 -1
  179. package/dist/types/decorators/setInterval.d.ts +27 -5
  180. package/dist/types/decorators/setInterval.d.ts.map +1 -1
  181. package/dist/types/decorators/setTimeout.d.ts +27 -5
  182. package/dist/types/decorators/setTimeout.d.ts.map +1 -1
  183. package/dist/types/decorators/state.d.ts.map +1 -1
  184. package/dist/types/decorators/subscribeSwcAppMessage.d.ts +44 -0
  185. package/dist/types/decorators/subscribeSwcAppMessage.d.ts.map +1 -0
  186. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts +60 -0
  187. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts.map +1 -0
  188. package/dist/types/decorators/swcAppRoute.d.ts +94 -0
  189. package/dist/types/decorators/swcAppRoute.d.ts.map +1 -0
  190. package/dist/types/elements/SwcApp.d.ts.map +1 -1
  191. package/dist/types/elements/SwcAppMixin.d.ts +423 -388
  192. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  193. package/dist/types/types/index.d.ts +43 -4
  194. package/dist/types/types/index.d.ts.map +1 -1
  195. package/dist/types/utils/Utils.d.ts +15 -5
  196. package/dist/types/utils/Utils.d.ts.map +1 -1
  197. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +4395 -1266
  198. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  199. package/package.json +8 -6
  200. package/src/SwcAppEngine.ts +1 -0
  201. package/src/decorators/addEventListener.ts +1086 -28
  202. package/src/decorators/applyAttribute.ts +82 -134
  203. package/src/decorators/applyClass.ts +14 -10
  204. package/src/decorators/applyNode.ts +39 -37
  205. package/src/decorators/applyProperty.ts +72 -100
  206. package/src/decorators/applySlot.ts +9 -8
  207. package/src/decorators/applyStyle.ts +14 -10
  208. package/src/decorators/around.ts +196 -0
  209. package/src/decorators/changedAttribute.ts +65 -26
  210. package/src/decorators/elementDefine.ts +298 -220
  211. package/src/decorators/emitCustomEvent.ts +17 -13
  212. package/src/decorators/eventMedia.ts +119 -0
  213. package/src/decorators/fetch.ts +454 -0
  214. package/src/decorators/index.ts +8 -2
  215. package/src/decorators/intersectionObserver.ts +108 -78
  216. package/src/decorators/lifecycles.ts +24 -22
  217. package/src/decorators/mutationObserver.ts +140 -104
  218. package/src/decorators/parameter.ts +212 -0
  219. package/src/decorators/publishSwcAppMessage.ts +3 -4
  220. package/src/decorators/query.ts +22 -14
  221. package/src/decorators/requestAnimationFrame.ts +149 -0
  222. package/src/decorators/resizeObserver.ts +119 -82
  223. package/src/decorators/setInterval.ts +90 -23
  224. package/src/decorators/setTimeout.ts +93 -23
  225. package/src/decorators/state.ts +19 -13
  226. package/src/decorators/subscribeSwcAppMessage.ts +106 -0
  227. package/src/decorators/subscribeSwcAppRouteChange.ts +131 -0
  228. package/src/decorators/swcAppRoute.ts +231 -0
  229. package/src/elements/SwcApp.ts +40 -10
  230. package/src/elements/SwcAppMixin.ts +329 -85
  231. package/src/types/index.ts +38 -5
  232. package/src/types/window.d.ts +2 -18
  233. package/src/utils/Utils.ts +50 -18
  234. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
  235. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +0 -7
  236. package/dist/cjs/examples/RxJSOperatorsExample.js +0 -285
  237. package/dist/cjs/examples/RxJSOperatorsExample.js.map +0 -7
  238. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js +0 -54
  239. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
  240. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +0 -7
  241. package/dist/esm/examples/RxJSOperatorsExample.js +0 -266
  242. package/dist/esm/examples/RxJSOperatorsExample.js.map +0 -7
  243. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts +0 -23
  244. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +0 -1
  245. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +0 -31
  246. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +0 -1
  247. package/dist/types/examples/RxJSOperatorsExample.d.ts +0 -94
  248. package/dist/types/examples/RxJSOperatorsExample.d.ts.map +0 -1
  249. package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +0 -91
  250. package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +0 -107
  251. package/src/examples/RxJSOperatorsExample.ts +0 -279
package/README.md CHANGED
@@ -10,6 +10,8 @@
10
10
 
11
11
  ## 🎯 Core Features
12
12
 
13
+ > **No arguments → no parentheses.** Every decorator that can run without arguments is used bare (`@query`, `@innerHtml`, `@eventClick`, `@swcAppRouteGo`, ...). The empty-call form `@x()` is a type error; pass arguments only when you have them (`@innerHtml('.wrap')`, `@eventClick({ once: true })`).
14
+
13
15
  ### 1. **@elementDefine** - Component Registration
14
16
  Register Web Components with automatic lifecycle management and DI support.
15
17
 
@@ -22,6 +24,22 @@ class MyComponent extends HTMLElement {
22
24
  }
23
25
  ```
24
26
 
27
+ **`@elementDefine(name, options?)` options:** `window`, `extends` (customized built-in, e.g. `'body'`), `useShadow` (`true | 'open' | 'closed'`), `observedAttributes`, `customElementRegistry`.
28
+ `window` may be another window than the one running the code (an iframe's, or a server-side DOM window) — the class then extends that window's `HTMLElement` (`class X extends w.HTMLElement`).
29
+
30
+ **Inheritance:** decorators on a base class apply to its subclasses — lifecycle hooks, events, observers, timers, message/route subscriptions, `@changedAttribute`, `@query`, `@state` …
31
+ - hooks on both run parent first, then child
32
+ - a subclass's own decorators never leak into the base class or into sibling subclasses
33
+ - a subclass of a class that is itself `@elementDefine`d runs each lifecycle once (not once per level)
34
+
35
+ ```typescript
36
+ class BasePage extends HTMLElement {
37
+ @onInitialize init(@inject(AuthService.SYMBOL) auth: AuthService) { this.auth = auth; }
38
+ @onDisconnected cleanup() { /* … */ }
39
+ }
40
+ @elementDefine('my-page') class MyPage extends BasePage {} // init / cleanup run for <my-page>
41
+ ```
42
+
25
43
  ### 2. **Dependency Injection (@onInitialize)**
26
44
  Inject services into Web Components using the `@onInitialize` decorator.
27
45
 
@@ -64,8 +82,8 @@ Replace child nodes with new content.
64
82
 
65
83
  ```typescript
66
84
  @replaceChildrenLight
67
- replaceChildren(node: Node) {
68
- return node;
85
+ renderNode() {
86
+ return this.buildNode(); // Node, Node[] or HTML string
69
87
  }
70
88
  ```
71
89
  ### 3.5 **Slot Management with @applySlot**
@@ -76,7 +94,7 @@ Manage dynamic content insertion into named slots using slot decorators. Slots a
76
94
  @elementDefine('content-manager')
77
95
  class ContentManager extends HTMLElement {
78
96
  // Method: Replace children in slot with HTML
79
- @addEventListener('.update-btn', 'click')
97
+ @event('.update-btn', 'click')
80
98
  @replaceChildrenHtmlSlot('main-content')
81
99
  updateContent() {
82
100
  return '<div>Updated content here</div>';
@@ -95,7 +113,7 @@ class ContentManager extends HTMLElement {
95
113
  }
96
114
 
97
115
  // Method: Clear slot content
98
- @addEventListener('.clear-btn', 'click')
116
+ @event('.clear-btn', 'click')
99
117
  @clearSlot('main-content')
100
118
  clearContent() {
101
119
  return true;
@@ -166,7 +184,7 @@ class CounterApp extends HTMLElement {
166
184
  @state('isActive')
167
185
  isActive: boolean = false;
168
186
 
169
- @addEventListener('button', 'click')
187
+ @event('button', 'click')
170
188
  increment() {
171
189
  this.count++; // Triggers automatic DOM update
172
190
  }
@@ -179,7 +197,7 @@ class CounterApp extends HTMLElement {
179
197
  <!--[html @message@ ]-->
180
198
 
181
199
  <!-- Text directive: render as text -->
182
- <!--[text Count: @count@ ]-->
200
+ <!--[text 'Count: ' + @count@ ]-->
183
201
 
184
202
  <!-- Attribute binding with a:: prefix -->
185
203
  <div a::title="'Count is'+@count@"></div>
@@ -227,7 +245,7 @@ State values accessed in templates via `@stateName@` syntax are read-only. You c
227
245
 
228
246
  ```typescript
229
247
  // ✅ CORRECT - Update state from method
230
- @addEventListener('button', 'click')
248
+ @event('button', 'click')
231
249
  increment() {
232
250
  this.count++; // Direct property assignment in code
233
251
  }
@@ -249,7 +267,7 @@ class UserProfile extends HTMLElement {
249
267
  @state('isEditing')
250
268
  isEditing = false;
251
269
 
252
- @addEventListener('.edit-btn', 'click')
270
+ @event('.edit-btn', 'click')
253
271
  toggleEdit() {
254
272
  this.isEditing = !this.isEditing;
255
273
  }
@@ -276,19 +294,19 @@ class UserProfile extends HTMLElement {
276
294
 
277
295
  ### 4. **Event Handling**
278
296
 
279
- #### @addEventListener
297
+ #### @event
280
298
  Attach event listeners to elements with optional filter support.
281
299
 
282
300
  ```typescript
283
- @addEventListener('#submit-btn', 'click')
301
+ @event('#submit-btn', 'click')
284
302
  onSubmit(event: Event) {
285
303
  console.log('Submitted');
286
304
  }
287
305
 
288
306
  // Filter events - only process matching events
289
- @addEventListener('button', 'click', {
290
- filter: (event, helper) => {
291
- return event.target?.id === 'critical-button';
307
+ @event('button', 'click', {
308
+ filter: (event, meta) => {
309
+ return (event.target as HTMLElement)?.id === 'critical-button';
292
310
  }
293
311
  })
294
312
  onCriticalClick(event: Event) {
@@ -296,25 +314,112 @@ onCriticalClick(event: Event) {
296
314
  }
297
315
  ```
298
316
 
299
- **@addEventListener Decorator Variants:**
300
- - `addEventListener(selector, type, options)` - Full form
301
- - `event(selector, type, options)` - Short alias
302
- - `addEventListenerThis(type, options)` - Listen on $this element
303
- - `addEventListenerAppHost(type, options)` - Listen on $appHost
304
- - `addEventListenerWindow(type, options)` - Listen on window
305
- - `addEventListenerDocument(type, options)` - Listen on document
306
- - `addEventListenerDelegate(selector, type, options)` - Event delegation
307
- - `eventDelegate(selector, type, options)` - Short alias
308
- - `addEventListenerDelegateLight(selector, type, options)` - Delegate in light DOM
309
- - `eventDelegateLight(selector, type, options)` - Short alias
310
- - `addEventListenerDelegateShadow(selector, type, options)` - Delegate in shadow DOM
311
- - `eventDelegateShadow(selector, type, options)` - Short alias
312
- - `addEventListenerDelegateAll(selector, type, options)` - Delegate in all DOM
313
- - `eventDelegateAll(selector, type, options)` - Short alias
314
- - `addEventListenerLight(selector, type, options)` / `eventLight(...)` - Bind (non-delegated) in light DOM
315
- - `addEventListenerShadow(selector, type, options)` / `eventShadow(...)` - Bind (non-delegated) in shadow DOM
316
- - `addEventListenerAll(selector, type, options)` / `eventAll(...)` - Bind (non-delegated) in both light & shadow DOM
317
- - `addEventListenerMutation(selector, type, options)` / `eventMutation(...)` - Delegate via `MutationObserver` instead of event bubbling (useful for non-bubbling events like `focus`/`blur`); `*Light`/`*Shadow`/`*All` variants also available
317
+ **@event Decorator Variants:**
318
+ - `event(selector, type, options)` - Full form
319
+ - `event(type, options)` (no selector) - Listen on $this element. With the default `root: 'auto'` it binds to the shadowRoot when the element has one (clicks inside the shadow bubble to it, but events dispatched on the element itself — `el.click()`, a parent's `el.dispatchEvent(...)` — never go down into it); use `{ root: 'light' }` to bind the element itself, `{ root: 'all' }` for both
320
+ - `eventAppHost(type, options)` - Listen on $appHost
321
+ - `eventWindow(type, options)` - Listen on window
322
+ - `eventDocument(type, options)` - Listen on document
323
+ - `eventDelegate(selector, type, options)` - Event delegation
324
+ - `eventDelegateLight(selector, type, options)` - Delegate in light DOM
325
+ - `eventDelegateShadow(selector, type, options)` - Delegate in shadow DOM
326
+ - `eventDelegateAll(selector, type, options)` - Delegate in all DOM
327
+ - `eventLight(selector, type, options)` - Bind (non-delegated) in light DOM
328
+ - `eventShadow(selector, type, options)` - Bind (non-delegated) in shadow DOM
329
+ - `eventAll(selector, type, options)` - Bind (non-delegated) in both light & shadow DOM
330
+ - `eventMutation(selector, type, options)` - Delegate via `MutationObserver` instead of event bubbling (useful for non-bubbling events like `focus`/`blur`); `*Light`/`*Shadow`/`*All` variants also available
331
+
332
+ **@event Options:**
333
+ - `capture` / `once` / `passive` - standard listener options
334
+ - `preventDefault` / `stopPropagation` / `stopImmediatePropagation` - applied before the handler runs
335
+ - `filter: (event, { currentThis, helper }) => boolean | Promise<boolean>` - skip the handler when it returns false
336
+ - `before: (event, meta, args) => any` - runs after `filter`; its return value is injectable with `@eventBeforeReturn`
337
+ - `finally: (event, meta, { args, result, error }) => any` - always runs after the handler
338
+ - `debounceTime` / `throttleTime` (ms) / `distinctUntilChanged` - rate-limit the event stream
339
+ - `removeListener: (target, options) => void` - cleanup callback on disconnect
340
+
341
+ **Event-Type-Specific Aliases (eventClick, eventInputDelegateShadow, ...):**
342
+
343
+ Beyond the generic `event(selector, type, options)` form, every scope/delegate variant above is also pre-bound to ~59 common DOM event types, so you don't have to repeat the type string:
344
+
345
+ ```typescript
346
+ @eventClick('.logo')
347
+ onLogoClick() { ... }
348
+
349
+ // No selector → the element itself (same as @event('click')); bare form, no parens
350
+ @eventClick
351
+ onClick(event: MouseEvent) { ... }
352
+
353
+ // Options without a selector → still the element itself
354
+ @eventKeydown({ filter: (e, meta) => !meta.currentThis.isLocked })
355
+ onKeydown(event: KeyboardEvent) { ... }
356
+
357
+ // Same as: @eventDelegateLight('.item', 'click')
358
+ @eventClickDelegateLight('.item')
359
+ onItemClick(event: Event) { ... }
360
+ ```
361
+
362
+ Naming pattern: `event` + `PascalCase(type)` + scope suffix (`''`(base) / `DelegateLight` / `DelegateShadow` / `DelegateAll` / `Delegate` / `MutationLight` / `MutationShadow` / `MutationAll` / `Mutation` / `Light` / `Shadow` / `All` / `AppHost` / `Window` / `Document`) — e.g. `eventClick`, `eventClickDelegateShadow`, `eventDblclick`, `eventPointerdownDelegateAll`, `eventKeydownWindow`.
363
+
364
+ The base alias (`eventClick`) takes an optional selector: bare `@eventClick` or `@eventClick({...})` listens on the element itself, `@eventClick('.btn', {...})` on the selector (there is no separate `…This` variant). The `AppHost`/`Window`/`Document` variants take no selector — only options — so they are used bare (`@eventClickWindow`) or with options. Never use empty parens. The `Delegate*`/`Light`/`Shadow`/`All` variants require a `selector`.
365
+
366
+ Covered event types include mouse (`click`, `dblclick`, `mousedown`, `mouseup`, `mousemove`, `mouseover`, `mouseout`, `mouseenter`, `mouseleave`, `contextmenu`, `wheel`), keyboard (`keydown`, `keyup`, `keypress`), form (`input`, `change`, `submit`, `reset`, `invalid`, `select`), focus (`focus`, `blur`, `focusin`, `focusout`), drag & drop (`dragstart`, `drag`, `dragend`, `dragenter`, `dragleave`, `dragover`, `drop`), touch (`touchstart`, `touchmove`, `touchend`, `touchcancel`), pointer (`pointerdown`, `pointerup`, `pointermove`, `pointerover`, `pointerout`, `pointerenter`, `pointerleave`, `pointercancel`), clipboard (`copy`, `cut`, `paste`), animation/transition (`animationstart/end/iteration/cancel`, `transitionstart/end/cancel/run`), and misc (`scroll`, `resize`, `load`, `error`, `toggle`).
367
+
368
+ These aliases are generated in `addEventListener.ts` from a small internal `makeTypedEventAliases<TEvent>(type)` factory — adding a new event type is a one-line addition, not hand-written boilerplate. Any event type not covered still works via the generic `@event(selector, 'your-type', options)` / `@eventDelegateLight(selector, 'your-type', options)` etc.
369
+
370
+ #### Order-Independent Parameter Decorators (@eventObject, @matchedElement, @hostSet, @helperHostSet, @helperSet)
371
+
372
+ `@event`-bound handlers normally receive fixed positional arguments (`(event, legacyHelper1, legacyHelper2)`). If you'd rather not memorize that order, decorate individual parameters instead — order and position no longer matter, and any parameter left undecorated in a method that uses none of these decorators still gets the legacy positional arguments (fully backward-compatible):
373
+
374
+ ```typescript
375
+ import { event, eventObject, matchedElement, hostSet, helperHostSet, helperSet } from '@dooboostore/simple-web-component';
376
+
377
+ @elementDefine('product-list')
378
+ class ProductList extends HTMLElement {
379
+ // Order can be anything you like
380
+ @eventDelegateLight('.item', 'click')
381
+ onItemClick(@matchedElement $item: Element, @eventObject event: Event) {
382
+ console.log('clicked item:', $item, event);
383
+ }
384
+
385
+ // Only need the host tree info? Just ask for @hostSet.
386
+ @eventClick
387
+ onHostClick(@hostSet hs: HostSet) {
388
+ console.log(hs.$appHost, hs.$hosts);
389
+ }
390
+
391
+ // Need DOM helpers ($q, $qa, ...) too? Use @helperHostSet (superset of @hostSet) or @helperSet (helpers only).
392
+ @eventClick
393
+ onHostClickFull(@helperHostSet full: HelperHostSet, @helperSet helpers: HelperSet) { ... }
394
+ }
395
+ ```
396
+
397
+ - `@eventObject` - the original DOM `Event`
398
+ - `@matchedElement` - the delegate-matched element (or `event.currentTarget` for direct bindings)
399
+ - `@hostSet` - host-ancestor-tree info only (`$host`, `$parentHost`, `$hosts`, `$appHost`, `$appHosts`, `$firstHost`, `$lastHost`, `$firstAppHost`, `$lastAppHost`)
400
+ - `@helperHostSet` - `@hostSet` + DOM/window helpers (`$d`, `$w`, `$q`, `$qa`, `$qi`) + `$this`
401
+ - `@helperSet` - DOM/window helpers only (`$d`, `$w`, `$q`, `$qa`, `$qi`), no host-tree info
402
+
403
+ `$w`/`$d` (and the `helper` passed to every decorator's `filter`/`before`/`finally`) come from the element's window, resolved the same way everywhere: its own `@elementDefine({ window })` → the nearest ancestor SWC host's → the global `window`. Route subscribers (`@subscribeSwcAppRouteChange`) and app messages use the SwcApp's runtime `window` instead (the per-request window under SSR).
404
+
405
+ The same mechanism (and the same 5 decorators, plus two more) also applies to:
406
+ - **`@onInitialize`/`@onConnectedBefore`/`@onConnectedAfter`/... lifecycle methods** — can freely mix `@hostSet`/`@helperHostSet`/`@helperSet` with `@inject(...)` on the same method.
407
+ - **`@subscribeSwcAppRouteChange`** — add `@swcAppRouterEvent` to receive the `RouterEventType` regardless of position.
408
+ - **`@subscribeSwcAppMessage`** — add `@appMessage` to receive the `SwcAppMessage` regardless of position.
409
+ - **`before` hook results** — `@eventBeforeReturn`, `@routeChangeBeforeReturn`, `@appMessageBeforeReturn`, `@changedAttributeBeforeReturn`, `@mutationObserverBeforeReturn`, `@resizeObserverBeforeReturn`, `@intersectionObserverBeforeReturn`, `@eventMediaBeforeReturn`, `@setIntervalBeforeReturn`, `@setTimeoutBeforeReturn` inject the value returned by that decorator's `before` option.
410
+
411
+ Note: once any parameter of a method is decorated, only decorated parameters receive values (the legacy positional arguments are no longer passed).
412
+
413
+ ```typescript
414
+ @onConnectedAfter
415
+ onconstructor(@inject(UserService.SYMBOL) userService: UserService, @hostSet hs: HostSet) { ... }
416
+
417
+ @subscribeSwcAppRouteChange
418
+ onRouteChanged(@helperSet helpers: HelperSet, @swcAppRouterEvent re: RouterEventType) { ... }
419
+
420
+ @subscribeSwcAppMessage
421
+ onMessage(@hostSet hs: HostSet, @appMessage msg: SwcAppMessage) { ... }
422
+ ```
318
423
 
319
424
  #### @emitCustomEvent
320
425
  Emit custom events with data.
@@ -330,13 +435,16 @@ async onLogin() {
330
435
  **@emitCustomEvent Decorator Variants:**
331
436
  - `emitCustomEvent(target, type, options)` - Full form
332
437
  - `emit(target, type, options)` - Short alias
333
- - `emitCustomEventThis(type, options)` - Emit from $this element
438
+ - `emitCustomEvent(type, options)` - Without a selector it emits from the element itself (`emitAppHost` / `emitWindow` / `emitDocument` take `(type, options)`; `emitLight` / `emitShadow` / `emitAll` take `(selector, type, options)`)
439
+ - Options: `bubbles`, `composed`, `cancelable`, `filter`, `valueKey`, `attributeName`, `root`
440
+ - `valueKey`: if the returned object has that key, only its value becomes `detail` (otherwise the whole return value); default key `EMIT_CUSTOM_EVENT_METADATA_KEY`. The method still returns the full value, so several emit/publish decorators can share one return object
334
441
 
335
442
  #### @publishSwcAppMessage
336
443
  Publish messages through the message bus when method completes.
337
444
 
338
445
  ```typescript
339
- @publishSwcAppMessage()
446
+ // No arguments needed → bare form
447
+ @publishSwcAppMessage
340
448
  async onLogin() {
341
449
  const user = await this.authService.login();
342
450
  return user; // Published as message with data: user
@@ -350,13 +458,13 @@ updateProfile() {
350
458
  ```
351
459
 
352
460
  **@publishSwcAppMessage Decorator Variants:**
353
- - `publishSwcAppMessage()` - Publish without message type
461
+ - `publishSwcAppMessage` - Bare decorator, publish without message type
354
462
  - `publishSwcAppMessage(messageType)` - Publish with specific message type
355
463
  - `publishSwcAppMessage(messageType, { valueKey: 'customKey' })` - Publish with custom value extraction
356
464
  - `publishSwcAppMessage({ messageType: 'type', valueKey: 'customKey' })` - Publish with options object
357
- - `publish()` - Short alias
358
- - `publish(messageType)` - Short alias with message type
359
- - `publish(messageType, options)` - Short alias with options
465
+ - `publishMessage` - Short alias
466
+ - `publishMessage(messageType)` - Short alias with message type
467
+ - `publishMessage(messageType, options)` - Short alias with options
360
468
 
361
469
  **Using valueKey for Multiple Decorators:**
362
470
 
@@ -371,38 +479,40 @@ handleMultipleEvents() {
371
479
  }
372
480
  ```
373
481
 
374
- #### @emitCustomEventThis
375
- Emit events from a method with custom event name mapping.
482
+ #### Emitting from the element itself
483
+ `@emitCustomEvent(type, options)` without a selector emits from $this. Pass `attributeName` so a parent can attach a handler through that attribute
484
+ (there is no default attribute name; set it explicitly). The attribute script gets `event` and `$data` (= `event.detail`).
376
485
 
377
486
  ```typescript
378
- @emitCustomEventThis('navigate', { attributeName: 'on-navigate' })
487
+ @emitCustomEvent('navigate', { attributeName: 'on-emit-navigate' })
379
488
  onNavClick(e: any) {
380
489
  return { path: e.target.dataset.path };
381
490
  }
491
+ // Parent: <app-header on-emit-navigate="$host.onHeaderNavigate(event, $data)"></app-header>
382
492
  ```
383
493
 
384
- #### @addEventListenerThis
385
- Listen to events on the component element itself (`$this` selector).
494
+ #### Listening on the element itself
495
+ `@event(type, options)` without a selector (or a typed alias without one, e.g. bare `@eventClick`) listens on the component element itself (`$this`). If the element has a shadow root, the default `root: 'auto'` binds to the shadowRoot — clicks inside the shadow are received, events dispatched on the element itself are not. Use `@event('click', { root: 'light' })` (or `'all'`) to also catch those.
386
496
 
387
497
  ```typescript
388
- @addEventListenerThis('click')
498
+ @event('click')
389
499
  onHostClick(event: Event) {
390
500
  console.log('Host element clicked');
391
501
  }
392
502
  ```
393
503
 
394
- #### @addEventListenerAppHost
504
+ #### @eventAppHost
395
505
  Listen to events on the app root host element (`$appHost` selector). Enables selective event handling with filters.
396
506
 
397
507
  ```typescript
398
508
  // Listen to all user-action events from $appHost
399
- @addEventListenerAppHost('user-action')
509
+ @eventAppHost('user-action')
400
510
  onUserAction(e: CustomEvent) {
401
511
  console.log('User action:', e.detail);
402
512
  }
403
513
 
404
514
  // Filter specific events - loose coupling pattern
405
- @addEventListenerAppHost('user-action', {
515
+ @eventAppHost('user-action', {
406
516
  filter: (event, helper) => event.detail?.type === 'login'
407
517
  })
408
518
  onUserLogin(e: CustomEvent) {
@@ -410,7 +520,7 @@ onUserLogin(e: CustomEvent) {
410
520
  }
411
521
 
412
522
  // Different component filtering same event differently
413
- @addEventListenerAppHost('user-action', {
523
+ @eventAppHost('user-action', {
414
524
  filter: (event, helper) => event.detail?.type === 'logout'
415
525
  })
416
526
  onUserLogout(e: CustomEvent) {
@@ -421,7 +531,7 @@ onUserLogout(e: CustomEvent) {
421
531
  ### 5. **DOM Querying**
422
532
 
423
533
  #### @query
424
- Query single element (Light DOM by default).
534
+ Query a single element. The default `root: 'auto'` searches the shadow root if one exists, otherwise the light DOM.
425
535
 
426
536
  ```typescript
427
537
  @query('#form-input')
@@ -442,91 +552,86 @@ textInputs?: HTMLInputElement[];
442
552
  listItems?: HTMLLIElement[];
443
553
  ```
444
554
 
445
- #### @queryThis
446
- Query the component element itself. == this
555
+ #### Special selectors, `pick`, and shorthands
556
+ Special selectors: `$this`, `$host`, `$parentHost`, `$appHost`, `$firstHost`, `$lastHost`, `$firstAppHost`, `$lastAppHost`, `$hosts`, `$appHosts`, `$window`, `$document`. A function selector `(currentThis, helper) => Element | Element[] | NodeList | null` is also accepted (no `root` option).
447
557
 
448
558
  ```typescript
449
- @queryThis
450
- self?: HTMLElement;
559
+ @query // bare = the component itself (same as @query('$this')); @queryAll → [itself]
560
+ self?: HTMLElement; // no arguments → no parentheses: @query() / @queryAll() are type errors
451
561
 
452
- printTag() {
453
- console.log(this.self?.tagName); // CUSTOM-CARD
454
- }
562
+ @query('.item', { pick: 'last' }) // 'first' (default) | 'last' | number → single; 'all' | 'even' | 'odd' → array
563
+ lastItem?: HTMLElement;
564
+
565
+ @(queryIn('shadow', 'even')('.row')) // root + pick factory — the chained call needs the outer parentheses
566
+ evenRows?: HTMLElement[];
455
567
  ```
456
568
 
569
+ - Shorthands: `queryLight` / `queryShadow` / `queryAllRoots` (single), `queryAllLight` / `queryAllShadow` / `queryAllAll` (arrays)
570
+ - `filter: (el, { currentThis, helper }) => boolean` narrows the matches
571
+ - Assigning `null` / `undefined` / `[]` to a `@query` field removes the matched elements from the DOM
572
+
457
573
  ### 6. **Attribute Binding**
458
574
 
459
- #### @attribute (Field Decorator)
460
- Bind HTML attributes to properties with automatic read/write synchronization.
575
+ #### @attribute (Field / Method Decorator)
576
+ Works on both fields and methods; behavior depends on where it is applied.
577
+ - **Field**: getter/setter that reads/writes the element attribute (`type` converts to Number/Boolean)
578
+ - **Method**: applies the return value to the attribute. `null` **removes** it, `undefined` does nothing
461
579
 
462
- Intelligently distinguishes between attribute names and CSS selectors:
580
+ **One string = attribute name on `$this`**, **two strings = (selector, attribute name)**.
463
581
 
464
582
  ```typescript
465
583
  @elementDefine('product-card')
466
584
  class ProductCard extends HTMLElement {
467
- // Attribute name on $this (auto-detected)
585
+ // Field — product-id on $this
468
586
  @attribute('product-id')
469
587
  productId: string;
470
588
 
471
- // Attribute name on selector
589
+ // Field — data-id on the selector target
472
590
  @attribute('#user', 'data-id')
473
591
  userId: string;
474
592
 
475
- // Bare decorator (uses field name as attribute)
593
+ // Field — bare (field name = attribute name)
476
594
  @attribute
477
595
  title: string;
478
596
 
479
- @onInitialize
480
- onconstructor() {
481
- if (this.productId) {
482
- this.loadProduct(this.productId);
483
- }
597
+ // Method — return value goes to nav[data-active] (null → removed)
598
+ @attribute('nav', 'data-active')
599
+ updateNav() {
600
+ return this.section; // 'releases' | null
484
601
  }
485
- }
486
- ```
487
602
 
488
- #### @setAttribute (Method Decorator)
489
- Set element attributes from method return values.
490
-
491
- ```typescript
492
- @elementDefine('status-updater')
493
- class StatusUpdater extends HTMLElement {
494
- // Set attribute on $this
495
- @setAttribute('data-status')
496
- updateStatus() {
497
- return this.isActive ? 'active' : 'inactive';
498
- }
499
-
500
- // Set attribute on selector
501
- @setAttribute('#user', 'data-name')
603
+ // Method — data-name on $this
604
+ @attribute('data-name')
502
605
  setUserName() {
503
606
  return this.userName;
504
607
  }
505
-
506
- // Bare decorator (uses method name as attribute)
507
- @setAttribute
508
- setValue() {
509
- return this.computedValue;
510
- }
511
608
  }
512
609
  ```
513
610
 
514
- **Usage Patterns:**
515
- - `@attribute('attr-name')` - Attribute name on $this (auto-detected)
516
- - `@attribute('#selector', 'attr-name')` - Attribute name on selector
517
- - `@attribute('attr-name', options)` - Attribute name on $this with options
518
- - `@attribute('#selector', 'attr-name', options)` - Attribute name on selector with options
519
- - `@attribute` - Bare decorator (uses field name as attribute on $this)
611
+ **Patterns:**
612
+ - `@attribute` - `$this`, name = field/method name
613
+ - `@attribute('attr-name', options?)` - attr-name on `$this`
614
+ - `@attribute('selector', 'attr-name', options?)` - selector target (`nav`, `#user`, `.card`, `$appHost`, ...)
615
+ - `@attribute((this, helper) => el, 'attr-name')` - function selector
616
+ - `@attribute('selector', 'attr-name', { valueKey: 'k' })` - (method) set only `k` from the returned object
617
+ - Options: `type`, `filter`, `valueKey`, `root`. Shorthands: `attrAppHost`, `attrLight`, `attrShadow`, `attrAll` (for $this just give the name: `@attribute('name')`)
618
+ - A field getter evaluates a `{{= expr }}` attribute value (see "Attribute Expressions")
520
619
 
521
- **@setAttribute Patterns:**
522
- - `@setAttribute('attr-name')` - Set attribute on $this
523
- - `@setAttribute('#selector', 'attr-name')` - Set attribute on selector
524
- - `@setAttribute('attr-name', options)` - Set attribute on $this with options
525
- - `@setAttribute('#selector', 'attr-name', options)` - Set attribute on selector with options
526
- - `@setAttribute` - Bare decorator (uses method name as attribute on $this)
620
+ #### @removeAttribute (Method Decorator)
621
+ Always removes the attribute when the method runs. The return value passes through unchanged,
622
+ so it is safe on subscriber handlers that must not return a value (e.g. route changes).
623
+
624
+ ```typescript
625
+ // Runs before the other route handlers — reset the nav highlight
626
+ @subscribeSwcAppRouteChange({ order: -1 })
627
+ @removeAttribute('nav', 'href')
628
+ handleNavReset() {
629
+ return undefined; // keep the chain going
630
+ }
631
+ ```
527
632
 
528
633
  #### @changedAttribute (Method Decorator)
529
- Listen for attribute changes on the component this element.
634
+ Listen for attribute changes on the component itself (`attributeChangedCallback`). Handler arguments: `(newValue, oldValue, name, helperHostSet)`; `newValue` is converted by `type` (and a `{{= expr }}` value is evaluated first).
530
635
 
531
636
  ```typescript
532
637
  @elementDefine('reactive-component')
@@ -542,80 +647,59 @@ class ReactiveComponent extends HTMLElement {
542
647
  console.log(`Status is now: ${newValue}`);
543
648
  }
544
649
 
545
- // Listen to any attribute change
546
- @changedAttribute()
547
- onAnyAttributeChanged(newValue: any, oldValue: any) {
548
- console.log('An attribute changed');
549
- }
650
+ // Bare (or no name) → the method name is used as the attribute name
651
+ @changedAttribute
652
+ status(newValue: string) { }
550
653
  }
551
654
  ```
552
655
 
553
656
  **@changedAttribute Options:**
554
657
  - `attributeName` - Attribute name to listen for (optional, defaults to method name)
555
658
  - `type` - Type converter: `Number`, `Boolean`, or `String`
556
- - `while` - Execution condition: `'connected'` (only while connected to DOM)
659
+ - `while` - `'connected'`: skip changes while disconnected, and run once on connect with the current value (if set)
660
+ - `filter` / `before` / `finally` - same hook pattern as `@event` (`before` result → `@changedAttributeBeforeReturn`)
557
661
 
558
662
  ### 6.5 **Property Binding**
559
663
 
560
- #### @property (Field Decorator)
561
- Bind element properties to component fields with automatic read/write synchronization.
562
-
563
- ```typescript
564
- @elementDefine('input-wrapper')
565
- class InputWrapper extends HTMLElement {
566
- // Property on $this (auto-detected)
567
- @property('value')
568
- inputValue: string;
664
+ #### @property (Field / Method Decorator)
665
+ Works on both fields and methods.
666
+ - **Field**: getter/setter that reads/writes a property of the target element
667
+ - Bare `@property` (= `$this` + same name) stays a plain field with no getter/setter and is only registered as an **SSR hydration target**
668
+ - **Method**: assigns the return value to the target element's property (`undefined` is ignored)
569
669
 
570
- // Property on selector
571
- @property('#submit-btn', 'disabled')
572
- isSubmitDisabled: boolean;
573
-
574
- // Bare decorator (uses field name as property)
575
- @property
576
- checked: boolean;
577
- }
578
- ```
579
-
580
- #### @setProperty (Method Decorator)
581
- Set element properties from method return values.
670
+ Unlike `@attribute`, **the first string is always a selector**. A property on the element itself is just the field, so a name-only form would be meaningless.
582
671
 
583
672
  ```typescript
584
673
  @elementDefine('form-handler')
585
674
  class FormHandler extends HTMLElement {
586
- // Set property on $this
587
- @setProperty('disabled')
588
- disableForm() {
589
- return !this.isValid();
590
- }
675
+ // Field — bare: own field (SSR hydration target)
676
+ @property
677
+ declare rows: Row[];
591
678
 
592
- // Set property on selector
593
- @setProperty('#submit-btn', 'disabled')
679
+ // Field — property of the selector target
680
+ @property('#chart', 'data')
681
+ chartData: number[];
682
+
683
+ // Method — return value goes to #submit-btn.disabled
684
+ @property('#submit-btn', 'disabled')
594
685
  updateSubmitState() {
595
686
  return this.hasErrors;
596
687
  }
597
688
 
598
- // Bare decorator (uses method name as property)
599
- @setProperty
600
- setValue() {
601
- return this.computedValue;
689
+ // Method — same-named property on the selector target (input.value)
690
+ @property('input')
691
+ value() {
692
+ return this.initialValue;
602
693
  }
603
694
  }
604
695
  ```
605
696
 
606
- **Usage Patterns:**
607
- - `@property('propertyName')` - Property on $this
608
- - `@property('#selector', 'propertyName')` - Property on selector
609
- - `@property('propertyName', options)` - Property on $this with options
610
- - `@property('#selector', 'propertyName', options)` - Property on selector with options
611
- - `@property` - Bare decorator (uses field name as property on $this)
612
-
613
- **@setProperty Patterns:**
614
- - `@setProperty('propertyName')` - Set property on $this
615
- - `@setProperty('#selector', 'propertyName')` - Set property on selector
616
- - `@setProperty('propertyName', options)` - Set property on $this with options
617
- - `@setProperty('#selector', 'propertyName', options)` - Set property on selector with options
618
- - `@setProperty` - Bare decorator (uses method name as property on $this)
697
+ **Patterns:**
698
+ - `@property` - `$this`, name = field/method name
699
+ - `@property('selector', options?)` - same-named property on the target
700
+ - `@property('selector', 'propertyName', options?)` - propertyName on the target
701
+ - `@property((this, helper) => el, 'propertyName')` - function selector
702
+ - To call a method on the target, use `@callProperty('selector', 'methodName')` (an array return value is spread as arguments; `callPropertyLight` / `callPropertyShadow` / `callPropertyAll` also exist)
619
703
 
620
704
  ### 6.6 **DOM Observers (@mutationObserver, @resizeObserver, @intersectionObserver)**
621
705
 
@@ -642,8 +726,10 @@ class ListObserver extends HTMLElement {
642
726
  ```
643
727
 
644
728
  **Callback signature:**
645
- - `matchedEls: HTMLElement[]` - Elements matching the selector (1st arg)
646
- - `mutations: MutationRecord[]` - Original mutation records (2nd arg)
729
+ - `matchedEls: HTMLElement[]` - Elements matching the selector (1st arg). For the element itself (bare `@mutationObserver` / `$this`) it is just `[element]` — the added/removed nodes are in `mutations`
730
+ - `mutations: MutationRecord[]` - Mutation records for this method's own targets (2nd arg)
731
+
732
+ Bare `@mutationObserver` (no arguments) watches the element's whole subtree (`childList` + `subtree`) and is attached **before** the first render, so it sees the render itself. Pass options to narrow it (e.g. `@mutationObserver({ childList: true })` = direct children only).
647
733
  - `observer: MutationObserver` - The observer instance (3rd arg)
648
734
 
649
735
  **How delegate works:**
@@ -674,7 +760,7 @@ Detect element size changes and react automatically.
674
760
  @elementDefine('chart-widget')
675
761
  class ChartWidget extends HTMLElement {
676
762
  // Re-render when component size changes (selector defaults to $this)
677
- @resizeObserverLight()
763
+ @resizeObserverLight
678
764
  onResize(matchedEls: HTMLElement[], entries: ResizeObserverEntry[], observer: ResizeObserver) {
679
765
  this.redraw(entries[0]?.contentRect);
680
766
  }
@@ -766,20 +852,20 @@ Surgically add, replace, or remove nodes in the DOM with fine-grained control.
766
852
  ```typescript
767
853
  @elementDefine('content-updater')
768
854
  class ContentUpdater extends HTMLElement {
769
- @addEventListener('button', 'click')
770
- @replaceChildren()
855
+ @event('button', 'click')
856
+ @replaceChildren
771
857
  updateContent() {
772
858
  return `<div>New content</div>`;
773
859
  }
774
860
 
775
- @addEventListener('.append-btn', 'click')
776
- @beforeEndNode()
861
+ @event('.append-btn', 'click')
862
+ @insertBeforeEnd
777
863
  appendContent() {
778
864
  return `<p>Appended content</p>`;
779
865
  }
780
866
 
781
- @addEventListener('.prepend-btn', 'click')
782
- @afterBegin()
867
+ @event('.prepend-btn', 'click')
868
+ @insertAfterBegin
783
869
  prependContent() {
784
870
  return `<p>Prepended content</p>`;
785
871
  }
@@ -798,15 +884,14 @@ class ContentUpdater extends HTMLElement {
798
884
  ```
799
885
 
800
886
  **@applyNode Decorator Variants:**
801
- - `replaceChildren()` - Replace all children
802
- - `replaceChildrenLight()` - Replace children in light DOM
803
- - `beforeEnd()` - Append to end
804
- - `beforeEndLight()` - Append to light DOM end
805
- - `afterBegin()` - Prepend to beginning
806
- - `afterBeginLight()` - Prepend to light DOM beginning
807
- - `innerHtml()` - Set innerHTML
808
- - `innerHtmlLight()` - Set innerHTML in light DOM
809
- - `innerText()` - Set innerText
887
+ - `applyNode(selector?, options?)` / `apply` / `node` - Full form (selector defaults to `$this`). Position: with no options at all it is `replaceChildren`; once you pass an options object without `position` it is `beforeEnd` (append) — bare `@applyAppHost` appends too. Pass `position` explicitly when it matters; `applyAppHost` / `applyLight` / `applyShadow` / `applyAll`
888
+ - `@replaceChildren` / `@replaceChildrenLight` - Replace all children
889
+ - `@insertBeforeEnd` / `@insertBeforeEndLight` / `@insertBeforeEndShadow` - Append to end
890
+ - `@insertAfterBegin` / `@insertAfterBeginLight` - Prepend to beginning
891
+ - `@innerHtml` / `@innerHtmlLight` / `@innerHtmlShadow` - Set innerHTML
892
+ - `@innerText` / `@innerTextLight` / `@innerTextShadow` - Set innerText
893
+ - `@clearChildrenNode` / `@clearChildrenLight` - Remove all children
894
+ - Options: `position`, `root`, `filter`, `fallback`, `valueKey`. Ready-made `filter` helpers: `skipIfSameTagPresent`, `skipIfExists(selector)`, `skipIfEmpty`, `applyIfChanged` (e.g. `@innerHtmlLight({ filter: skipIfExists('my-page') })`)
810
895
 
811
896
  **Position Options:**
812
897
  ```typescript
@@ -819,7 +904,8 @@ export type ApplyNodePosition =
819
904
  | 'replaceChildren' // Replace children
820
905
  | 'innerHtml' // Set innerHTML
821
906
  | 'innerText' // Set innerText
822
- | 'remove'; // Remove element
907
+ | 'remove' // Remove element
908
+ | 'clearChildren'; // Remove all children
823
909
  ```
824
910
 
825
911
  ### 8. **Style & Class Management**
@@ -832,8 +918,8 @@ class StyledComponent extends HTMLElement {
832
918
  @state('isActive')
833
919
  isActive = false;
834
920
 
835
- @addEventListener('button', 'click')
836
- @updateStyle()
921
+ @event('button', 'click')
922
+ @updateStyle
837
923
  toggleStyle() {
838
924
  return {
839
925
  color: this.isActive ? 'green' : 'red',
@@ -841,9 +927,9 @@ class StyledComponent extends HTMLElement {
841
927
  };
842
928
  }
843
929
 
844
- @addEventListener('.toggle-btn', 'click')
845
- @updateClass()
846
- toggleClass() {
930
+ @event('.toggle-btn', 'click')
931
+ @updateClass
932
+ toggleState() {
847
933
  return {
848
934
  'active': this.isActive,
849
935
  'disabled': !this.isActive
@@ -862,17 +948,18 @@ class StyledComponent extends HTMLElement {
862
948
  }
863
949
  ```
864
950
 
865
- **@applyStyle Variants:**
866
- - `setStyleThis()` - Clear and set styles
867
- - `updateStyleThis()` - Update/merge styles
868
- - `removeStyleThis()` - Remove specific styles
951
+ **@applyStyle Variants:** (each takes `(selector?, options?)`; bare form targets `$this`)
952
+ - `setStyle` - Clear and set styles
953
+ - `updateStyle` - Update/merge styles
954
+ - `removeStyle` - Remove specific styles
955
+ - `applyStyle(selector, action?)` / `style` - action `'set' | 'update' | 'remove'` (default `'update'`); `styleAppHost` / `styleLight` / `styleShadow` / `styleAll` (for $this use bare `@setStyle` / `@updateStyle` / `@removeStyle`)
869
956
 
870
- **@applyClass Variants:**
871
- - `setClassThis()` - Replace all classes
872
- - `updateClassThis()` - Toggle classes
873
- - `addClassThis()` - Add classes
874
- - `removeClassThis()` - Remove classes
875
- - `toggleClassThis()` - Toggle classes
957
+ **@applyClass Variants:** (each takes `(selector?, classMapOrOptions?, options?)`; bare form targets `$this`)
958
+ - `setClass` - Replace all classes
959
+ - `updateClass` - Toggle classes by a `{ className: boolean }` map
960
+ - `addClass` / `removeClass` / `toggleClass`
961
+ - `applyClass(selector, action?)` / `cls` - action `'set' | 'update' | 'add' | 'remove' | 'toggle'` (default `'update'`); `clsAppHost` / `clsLight` / `clsShadow` / `clsAll` (for $this use bare `@setClass` / `@updateClass` / `@addClass` / `@removeClass` / `@toggleClass`)
962
+ - Note: for the class decorators, an options object in the 2nd position must contain `root` (e.g. `{ root: 'auto', valueKey: 'k' }`); an object without `root` is read as a class map
876
963
 
877
964
  ### 8.5 **Lifecycle Hooks**
878
965
 
@@ -889,17 +976,18 @@ class MyComponent extends HTMLElement {
889
976
 
890
977
  @onConnectedBefore({ order: 0 })
891
978
  beforeConnected() {
892
- // Called before element connects to DOM
979
+ // Called on connect, before rendering
893
980
  }
894
981
 
895
982
  @onConnectedAfter({ order: 1 })
896
983
  afterConnected() {
897
- // Called after element connects to DOM
984
+ // Called on connect, after rendering and observer setup
898
985
  }
899
986
 
900
- @onConnected
901
- onConnected() {
902
- // Called when element enters DOM
987
+ @onConnectedBody
988
+ render() {
989
+ // Called when element enters DOM — renders the returned HTML (variants: @onConnectedBodyShadow / @onConnectedBodyLight)
990
+ return `<div>...</div>`;
903
991
  }
904
992
 
905
993
  @onDisconnectedBefore
@@ -907,7 +995,7 @@ class MyComponent extends HTMLElement {
907
995
  // Called before element disconnects from DOM
908
996
  }
909
997
 
910
- @onDisconnected
998
+ @onDisconnected // alias of @onDisconnectedAfter
911
999
  onDisconnected() {
912
1000
  // Called when element leaves DOM
913
1001
  }
@@ -917,15 +1005,15 @@ class MyComponent extends HTMLElement {
917
1005
  // Called before element is adopted into new document
918
1006
  }
919
1007
 
920
- @onAdopted
1008
+ @onAdopted // alias of @onAdoptedAfter
921
1009
  onAdopted() {
922
1010
  // Called when element is adopted into new document
923
1011
  }
924
1012
 
925
1013
  @onConnectedSwcApp({ order: 0 })
926
1014
  onSwcAppConnected() {
927
- // Called after SwcApp.connect() completes
928
- // Full DI support available through hostSet
1015
+ // Called when the element registers with a connected SwcApp host
1016
+ // (deferred until SwcApp.connect() if the app is not connected yet)
929
1017
  }
930
1018
 
931
1019
  @onConnectedCompleted({ order: 0 })
@@ -935,17 +1023,16 @@ class MyComponent extends HTMLElement {
935
1023
  }
936
1024
  ```
937
1025
 
938
- **Lifecycle Execution Order:**
939
- - `@onInitialize` - Component construction
940
- - `@onConnectedBefore` - Before DOM connection
941
- - `@onConnectedAfter` - After DOM connection
942
- - `@onConnected` - DOM connection (with HTML rendering)
943
- - `@onConnectedSwcApp` - After SwcApp initialization
944
- - `@onConnectedCompleted` - All connected hooks done
945
- - `@onDisconnectedBefore` - Before DOM disconnection
946
- - `@onDisconnected` - DOM disconnection
947
- - `@onAdoptedBefore` - Before document adoption
948
- - `@onAdopted` - Document adoption
1026
+ **Lifecycle Execution Order (connectedCallback):**
1027
+ 1. `@onInitialize` - once, on first init (DI via `@inject` parameters)
1028
+ 2. `@onConnectedSwcApp` - the element registers with its app host (deferred until `connect()` if needed)
1029
+ 3. `@onConnectedBefore` - before rendering
1030
+ 4. `@onConnectedBody` (`Shadow` / `Light`) - render the returned HTML/Node (skipped for SSR-rendered elements)
1031
+ 5. Observers / timers / listeners are wired up
1032
+ 6. `@onConnectedAfter` - after rendering
1033
+ 7. `@onConnectedCompleted` - always runs last (in a `finally` block), then `trigger: 'connectedDone'` replays fire
1034
+
1035
+ Disconnect: `@onDisconnectedBefore` → cleanup → `@onDisconnected` (`@onDisconnectedAfter`). Adopt: `@onAdoptedBefore` → `@onAdopted` (`@onAdoptedAfter`).
949
1036
 
950
1037
  **Order Parameter:**
951
1038
  All lifecycle decorators support `order?: number` for controlling execution sequence:
@@ -957,17 +1044,19 @@ onFirst() { }
957
1044
  onSecond() { }
958
1045
  ```
959
1046
 
960
- ### 8.6 **Timers (@setInterval, @setTimeout)**
1047
+ ### 8.6 **Timers (@setInterval, @setTimeout, @requestAnimationFrame)**
961
1048
 
962
- Declaratively run a method on a repeating interval or after a one-time delay, tied to the component's connected lifecycle. Both are pure metadata-collecting decorators — they never touch the method itself (no `descriptor.value` wrapping) — so they compose safely with any other decorator stacked on the same method, regardless of declaration order. All the actual scheduling/cleanup is handled by `SetIntervalLifeCycler`/`SetTimeoutLifeCycler` (`ElementDefineLifeCycler` implementations, same plugin pattern as the observers above):
1049
+ Declaratively schedule work tied to the component. Every timer the decorator starts is cleared automatically on disconnect. There are two modes (`type` option):
963
1050
 
964
- - Starts automatically when the component connects.
965
- - Automatically `clearInterval`/`clearTimeout`s when the component disconnects — no manual cleanup needed.
1051
+ - `type: 'returnValue'` (**default**) - nothing starts automatically. When you call the method, its return value (a function, or the function under `valueKey`) becomes the repeating/delayed callback, called with `(id: number)`. The original return value passes through.
1052
+ - `type: 'onConnected'` - starts on connect and calls the decorated method itself on every tick/fire.
966
1053
 
967
1054
  ```typescript
968
1055
  @elementDefine('live-clock')
969
1056
  class LiveClock extends HTMLElement {
1057
+ // Auto-start on connect
970
1058
  @setInterval(1000, {
1059
+ type: 'onConnected',
971
1060
  parameter: (set) => [Date.now()],
972
1061
  created: (set, id) => console.log('interval started, timer id:', id)
973
1062
  })
@@ -975,94 +1064,94 @@ class LiveClock extends HTMLElement {
975
1064
  this.textContent = new Date(now).toLocaleTimeString();
976
1065
  }
977
1066
 
978
- // No options - called with no arguments, once per second
979
- @setInterval(1000)
980
- onTick() {
981
- console.log('tick');
982
- }
983
-
984
1067
  // Fires once, 3 seconds after connect
985
- @setTimeout(3000)
1068
+ @setTimeout(3000, { type: 'onConnected' })
986
1069
  onceAfter3s() {
987
1070
  console.log('3 seconds have passed');
988
1071
  }
1072
+
1073
+ // Default 'returnValue': starts when you call startPolling()
1074
+ @setInterval(900)
1075
+ startPolling() {
1076
+ return (id: number) => console.log('interval', id, 'ticked');
1077
+ }
989
1078
  }
990
1079
  ```
991
1080
 
992
1081
  **`@setInterval(interval, options?)` / `@setTimeout(delay, options?)`:**
993
1082
  - `interval` / `delay` - milliseconds. `setInterval` repeats; `setTimeout` fires once.
994
- - `parameter?: (set: HelperHostSet) => any[]` - computes the arguments passed to the method on each tick/fire. Omitted → method is called with no arguments.
995
- - `created?: (set: HelperHostSet, id: number) => void` - fires once, right after the timer is armed, with the real `setInterval`/`setTimeout` return value. For logging/debugging only — cleanup is still handled automatically, this is not a manual-clear hook.
996
- - `valueKey?: symbol | string` - same convention as `@applyAttribute`'s `valueKey` (see "Multiple Decorators with Shared Return Value" below): if the method's return value is an object and this key's value is a function, that function is called on every tick/fire — but note the callback signature is just `(id: number)`, not `(set, id)` like `created`.
1083
+ - `type` - `'returnValue'` (default) | `'onConnected'`
1084
+ - `parameter?: (set: HelperHostSet) => any[]` - (`onConnected`) arguments for each tick/fire; omitted → no arguments.
1085
+ - `created?: (set: HelperHostSet, id: number) => void` - called once right after the timer is armed. For logging only; cleanup is automatic.
1086
+ - `filter` / `before` / `finally` - (`onConnected`) per-tick hooks; `before` result → `@setIntervalBeforeReturn` / `@setTimeoutBeforeReturn`.
1087
+ - `valueKey?: symbol | string` - which key of the returned object holds the callback function (defaults to `SET_INTERVAL_METADATA_KEY` / `SET_TIMEOUT_METADATA_KEY`).
1088
+
1089
+ **`@requestAnimationFrame(options?)`** (bare form also works) - same two modes. The frame callback is `(timestamp, prevValue) => any`: return `undefined`/`null` to stop the loop, anything else is passed as `prevValue` to the next frame. Options: `type`, `created`, `valueKey`.
997
1090
 
998
1091
  ```typescript
999
- @setInterval(900, { valueKey: 'onTick' })
1000
- poll() {
1001
- return {
1002
- onTick: (id: number) => console.log('interval', id, 'ticked'),
1003
- };
1092
+ @requestAnimationFrame
1093
+ animate() {
1094
+ return (ts: number, prev = 0) => (prev < 100 ? prev + 1 : null); // stops at 100
1004
1095
  }
1005
1096
  ```
1006
1097
 
1007
- **Why no wrapping is needed:** unlike `@applyAttribute`/`@setAttribute` (which must wrap the method because it can be called by arbitrary code — event handlers, other decorators, etc.), the timer's own `LifeCycler` is the one calling `inst[propertyKey](...)` every tick/fire — it already has the return value in hand at that call site, so it can read `valueKey` off of it without ever needing to intercept the method.
1098
+ ### 8.7 **@around (AOP) and @eventMedia**
1008
1099
 
1009
- > `@requestAnimationFrame` is not implemented yet.
1100
+ `@around` wraps a method (`before(helper, args)` → new args, `after(helper, result)` → new result, `finally(helper, { args, result, error })`) or a field (`get(helper, stored)`, `set(helper, incoming)`). Async hooks are supported when the method is async.
1010
1101
 
1011
- ### 9. **Structural Directives**
1102
+ ```typescript
1103
+ @around({ before: (h, args: [string]) => [args[0].trim()], after: (h, r: string) => r + '!' })
1104
+ greet(name: string) { return `hi ${name}`; }
1012
1105
 
1013
- Structural Directives allow declarative conditional rendering, list iteration, async handling, and routing with automatic attribute substitution and dynamic expressions.
1106
+ @around({ set: (h, v: string) => v?.trim() })
1107
+ label: string = '';
1108
+ ```
1014
1109
 
1015
- #### 8.0 **Expression Syntax - Dynamic Evaluation**
1110
+ `@eventMedia(query, eventType, options?)` / `@eventMediaChange(query, options?)` subscribe to `window.matchMedia(query)` on connect and unsubscribe on disconnect. Handler arguments: `(event: MediaQueryListEvent, helperHostSet)`. Options: `filter`, `before`, `finally`.
1016
1111
 
1017
- SWC supports two types of expression syntax for dynamic value evaluation:
1112
+ ```typescript
1113
+ @eventMediaChange('(max-width: 600px)')
1114
+ onMobile(e: MediaQueryListEvent) { this.compact = e.matches; }
1115
+ ```
1018
1116
 
1019
- **`{{ }} - Standard Expression Evaluation**
1020
- - Used for boolean conditions and value comparisons
1021
- - Syntax: `{{ expression }}`
1022
- - Example: `{{ $value === 'pending' }}`, `{{ $host.isLoggedIn }}`
1023
- - Context variables: `$value`, `$item`, `$index`, `$host`, `$parentHost`, `$appHost`, etc.
1117
+ ### 9. **Attribute Expressions (`{{= }}`)**
1024
1118
 
1025
- **`{{= }} - Function Call & Return Expression**
1026
- - Evaluates JavaScript expressions and returns result
1027
- - Syntax: `{{= functionCall() }}` or `{{= computedValue }}`
1028
- - Automatically executes functions and captures return values
1029
- - Example: `{{= $item.name }}`, `{{= $parentHost.getData() }}`, `{{= formatDate($item.date) }}`
1030
- - Used in attributes for dynamic substitution
1031
- - Result is converted to string for DOM attributes
1119
+ An attribute value of the form `{{= expression }}` is evaluated as JavaScript instead of being used as a string:
1032
1120
 
1033
- **Context Variables Available in All Directives:**
1034
- - `$value` - the value passed to the template
1035
- - `$host` - the current Web Component instance
1036
- - `$parentHost` - parent component
1037
- - `$appHost` - root application component
1038
- - `$item` - current item (in SwcLoop)
1039
- - `$index` - current index (in SwcLoop)
1040
- - Helper functions - utility methods from `SwcUtils.getHelperAndHostSet()`
1121
+ ```html
1122
+ <product-detail product-id="{{= $host.selectedId }}"></product-detail>
1123
+ ```
1124
+
1125
+ - Evaluated when the value is read by an `@attribute` field getter and before it is passed to `@changedAttribute` handlers
1126
+ - `this` is the element that owns the attribute; the helper/host variables are in scope: `$this`, `$host`, `$parentHost`, `$hosts`, `$firstHost`, `$lastHost`, `$appHost`, `$appHosts`, `$d`, `$w`, `$q`, `$qa`, `$qi`
1127
+ - The result keeps its JavaScript type (then `type` conversion applies); on error the raw string is used
1128
+
1129
+ Template bindings inside `@onConnectedBody*` HTML (`@state@`, `<!--[html ]-->`, `<!--[text ]-->`, `a::`, `e::`, and `p::` for DOM properties) are described in the `@state` section.
1041
1130
 
1042
1131
  ---
1043
1132
 
1044
- ### 9. **Component Communication - Message Bus**
1133
+ ### 10. **Component Communication - Message Bus**
1045
1134
 
1046
1135
  SWC provides a powerful message bus system for inter-component communication through SwcApp. Components can publish and subscribe to typed messages while connected.
1047
1136
 
1048
- #### @subscribeSwcAppMessageWhileConnected
1137
+ #### @subscribeSwcAppMessage
1049
1138
  Subscribe to messages while component is connected to DOM.
1050
1139
 
1051
1140
  ```typescript
1052
1141
  @elementDefine('notification-panel')
1053
1142
  class NotificationPanel extends HTMLElement {
1054
- @subscribeSwcAppMessageWhileConnected
1143
+ @subscribeSwcAppMessage
1055
1144
  onAnyMessage(message: SwcAppMessage) {
1056
1145
  console.log('Received message:', message);
1057
1146
  }
1058
1147
 
1059
- @subscribeSwcAppMessageWhileConnected('user-login')
1148
+ @subscribeSwcAppMessage('user-login')
1060
1149
  onUserLogin(message: SwcAppMessage<{ username: string }>) {
1061
1150
  console.log(`Welcome ${message.data?.username}`);
1062
1151
  }
1063
1152
 
1064
- @subscribeSwcAppMessageWhileConnected('user-login', {
1065
- filter: (msg) => msg.data?.username === 'admin'
1153
+ @subscribeSwcAppMessage('user-login', {
1154
+ filter: (msg, currentThis) => msg.data?.username === 'admin'
1066
1155
  })
1067
1156
  onAdminLogin(message: SwcAppMessage) {
1068
1157
  console.log('Admin logged in!');
@@ -1070,13 +1159,16 @@ class NotificationPanel extends HTMLElement {
1070
1159
  }
1071
1160
  ```
1072
1161
 
1162
+ **@subscribeSwcAppMessage Options:** `filter(message, currentThis)`, `before(message, currentThis)` (result → `@appMessageBeforeReturn`), `finally(message, currentThis, { args, result, error })`, `subject`, `trigger` (see "Message Replay"). Every matching subscriber runs (fire-and-forget); return values do not stop other subscribers.
1163
+
1073
1164
  #### @publishSwcAppMessage
1074
1165
  Publish a message from a method's return value.
1075
1166
 
1076
1167
  ```typescript
1077
1168
  @elementDefine('login-form')
1078
1169
  class LoginForm extends HTMLElement {
1079
- @publishSwcAppMessage()
1170
+ // No arguments needed → bare form
1171
+ @publishSwcAppMessage
1080
1172
  async onSubmit() {
1081
1173
  const user = await this.authService.login(
1082
1174
  this.username,
@@ -1094,11 +1186,11 @@ class LoginForm extends HTMLElement {
1094
1186
  ```
1095
1187
 
1096
1188
  **@publishSwcAppMessage Decorator Variants:**
1097
- - `publishSwcAppMessage()` - Publish without message type
1189
+ - `publishSwcAppMessage` - Publish without message type (bare)
1098
1190
  - `publishSwcAppMessage(messageType)` - Publish with specific message type
1099
1191
  - `publishSwcAppMessage(messageType, { valueKey: 'customKey' })` - Publish with custom value extraction
1100
1192
  - `publishSwcAppMessage({ messageType: 'type', valueKey: 'customKey' })` - Publish with options object
1101
- - `publishMessage()` - Short alias
1193
+ - `publishMessage` - Short alias
1102
1194
  - `publishMessage(messageType)` - Short alias with message type
1103
1195
  - `publishMessage(messageType, options)` - Short alias with options
1104
1196
 
@@ -1130,7 +1222,7 @@ type SwcAppMessage<T = any> = {
1130
1222
  - ✅ **Filter support** - use custom filter functions to handle specific conditions
1131
1223
  - ✅ **Auto-publishing** - return value automatically becomes message payload
1132
1224
  - ✅ **Async support** - works with async methods (promises)
1133
- - ✅ **Centralized** - all messages routed through SwcApp host
1225
+ - ✅ **Centralized** - all messages routed through the nearest SwcApp host (`$appHost`)
1134
1226
  - ✅ **Decoupled** - components don't need to know about each other
1135
1227
 
1136
1228
  **Usage Example:**
@@ -1148,7 +1240,7 @@ class ProductList extends HTMLElement {
1148
1240
  // Subscriber component
1149
1241
  @elementDefine('product-detail')
1150
1242
  class ProductDetail extends HTMLElement {
1151
- @subscribeSwcAppMessageWhileConnected('product-selected')
1243
+ @subscribeSwcAppMessage('product-selected')
1152
1244
  onProductSelected(message: SwcAppMessage<Product>) {
1153
1245
  this.displayProduct(message.data);
1154
1246
  }
@@ -1164,6 +1256,8 @@ SWC provides a flexible Mixin-based architecture for creating SwcApp elements th
1164
1256
  #### Available SwcApp Variants
1165
1257
 
1166
1258
  ```typescript
1259
+ // SwcApp, SwcAppBody, ... are createElement helpers: SwcAppBody(w, data?) creates the element.
1260
+ // defineSwcApp, defineSwcAppBody, ... register the custom element.
1167
1261
  import {
1168
1262
  SwcApp, // swc-app (HTMLElement)
1169
1263
  SwcAppBody, // swc-app-body (HTMLBodyElement, is="body")
@@ -1220,7 +1314,7 @@ w.document.addEventListener('DOMContentLoaded', async () => {
1220
1314
  path: '/',
1221
1315
  routeType: 'path',
1222
1316
  container: container,
1223
- onStartedLazyDefineComponent: [yourComponentFactory1, yourComponentFactor2],
1317
+ onStartedLazyDefineComponent: [yourComponentFactory1, yourComponentFactory2],
1224
1318
  window: w,
1225
1319
  onEngineStarted: () => {
1226
1320
  console.log('🚀 Application started successfully');
@@ -1230,6 +1324,10 @@ w.document.addEventListener('DOMContentLoaded', async () => {
1230
1324
  });
1231
1325
  ```
1232
1326
 
1327
+ **Main `connect()` options (`SwcAttributeConfigType`):** `routeType` (`'path' | 'hash' | 'element'`), `path` (first URL), `container` (DI container symbol), `window`, `onStartedLazyDefineComponent` (component factories), `onEngineStarted(sp, app)`, `onConnected` / `onDisconnected`, `onConnectedChildBefore` / `onConnectedChildAfter`, `onDisconnectedChildBefore` / `onDisconnectedChildAfter`, `onChildrenConnectedDone`, `onChildrenRouteChanged`, `messageReplayBufferSize`, `ssr`, `otherInstanceSim`.
1328
+
1329
+ App host methods: `routing(path)`, `back()`, `forward()`, `reload()`, `publishMessage(message)`, `observeMessage(...)`, `connectedElements()`.
1330
+
1233
1331
  #### Factory Pattern - Component Registration
1234
1332
 
1235
1333
  ```typescript
@@ -1269,7 +1367,7 @@ appElement.connect({
1269
1367
  });
1270
1368
  ```
1271
1369
 
1272
- #### 12. **Routing with @subscribeSwcAppRouteChangeWhileConnected**
1370
+ #### 12. **Routing with @subscribeSwcAppRouteChange**
1273
1371
 
1274
1372
  SWC provides declarative routing with automatic path matching, order-based execution, and optional propagation control.
1275
1373
 
@@ -1278,26 +1376,26 @@ SWC provides declarative routing with automatic path matching, order-based execu
1278
1376
  ```typescript
1279
1377
  @elementDefine('root-router')
1280
1378
  class RootRouter extends HTMLElement {
1281
- @subscribeSwcAppRouteChangeWhileConnected(['', '/'], { order: 0 })
1379
+ @subscribeSwcAppRouteChange(['', '/'], { order: 0 })
1282
1380
  @innerHtmlLight
1283
1381
  handleHome(routerPathSet: RouterEventType) {
1284
1382
  return `<landing-page/>`;
1285
1383
  }
1286
1384
 
1287
- @subscribeSwcAppRouteChangeWhileConnected(['/products'], { order: 1 })
1385
+ @subscribeSwcAppRouteChange(['/products'], { order: 1 })
1288
1386
  @innerHtmlLight
1289
1387
  handleProducts(routerPathSet: RouterEventType) {
1290
1388
  return `<products-list/>`;
1291
1389
  }
1292
1390
 
1293
- @subscribeSwcAppRouteChangeWhileConnected(['/product/{id}'], { order: 2 })
1391
+ @subscribeSwcAppRouteChange(['/product/{id}'], { order: 2 })
1294
1392
  @innerHtmlLight
1295
1393
  handleProductDetail(routerPathSet: RouterEventType) {
1296
1394
  const { id } = routerPathSet.pathData;
1297
1395
  return `<product-detail product-id="${id}"/>`;
1298
1396
  }
1299
1397
 
1300
- @subscribeSwcAppRouteChangeWhileConnected(['/{tail:.*}'], { order: 999 })
1398
+ @subscribeSwcAppRouteChange(['/{tail:.*}'], { order: 999 })
1301
1399
  @innerHtmlLight
1302
1400
  handle404(routerPathSet: RouterEventType) {
1303
1401
  return `<not-found-page/>`;
@@ -1308,48 +1406,138 @@ class RootRouter extends HTMLElement {
1308
1406
  #### Route Handler Features
1309
1407
 
1310
1408
  **1. Path Matching**
1311
- - **No path pattern** (omit path): `@subscribeSwcAppRouteChangeWhileConnected({ order: -1 })` - matches all routes
1312
- - **Exact match**: `['', '/']` - matches home route only
1313
- - **Prefix match**: `['/products']` - matches `/products` and `/products/...`
1314
- - **Dynamic segments**: `['/product/{id}']` - captures `id` parameter
1315
- - **Wildcard**: `['/{tail:.*}']` - matches any remaining path (use for 404)
1409
+ - **No path pattern** (omit path): `@subscribeSwcAppRouteChange({ order: -1 })` - matches all routes
1410
+ - Patterns are matched against the **whole** path (anchored regex); a string, an array (first match wins), or a function `(currentThis) => string | string[]`
1411
+ - **Exact match**: `['', '/']` - matches home route only; `['/products']` matches `/products` only
1412
+ - **Sub paths**: `['/products/{tail:.*}']` - use a regex segment to match `/products/...`
1413
+ - **Dynamic segments**: `['/product/{id}']` - captures `id` (`[^/]+`); custom regex: `{slug:[a-z0-9-]+}`
1414
+ - **Wildcard**: `['/{tail:.*}']` - matches any path (use for 404)
1316
1415
 
1317
1416
  **2. Order-Based Execution**
1318
- Routes are executed in order of `order` value (lowest first). First matching route with a return value stops propagation:
1417
+ Handlers of one element run in `order` (lowest first, default 0). The first matching handler that returns a value stops the rest of **that element's** chain:
1319
1418
 
1320
1419
  ```typescript
1321
- @subscribeSwcAppRouteChangeWhileConnected(['', '/'], { order: 0 }) // Checked first
1322
- @subscribeSwcAppRouteChangeWhileConnected(['/admin'], { order: 1 }) // Checked second
1323
- @subscribeSwcAppRouteChangeWhileConnected(['/{tail:.*}'], { order: 999 }) // Checked last (404)
1420
+ @subscribeSwcAppRouteChange(['', '/'], { order: 0 }) // Checked first
1421
+ @subscribeSwcAppRouteChange(['/admin'], { order: 1 }) // Checked second
1422
+ @subscribeSwcAppRouteChange(['/{tail:.*}'], { order: 999 }) // Checked last (404)
1324
1423
  ```
1325
1424
 
1326
1425
  **3. Propagation Control**
1327
- - **Return a value** (HTML string) → Stops propagation to next handlers
1328
- - **Return undefined/null** → Continues to next handler
1426
+ - **Return a value** (anything other than `undefined`/`null`, after `valueKey` extraction) → stops this element's remaining handlers
1427
+ - **Return `undefined`/`null`** → continues to the next handler
1329
1428
 
1330
1429
  ```typescript
1331
1430
  // This handler stops propagation (returns HTML)
1332
- @subscribeSwcAppRouteChangeWhileConnected(['/admin'], { order: 1 })
1431
+ @subscribeSwcAppRouteChange(['/admin'], { order: 1 })
1333
1432
  @innerHtmlLight
1334
1433
  handleAdmin(routerPathSet: RouterEventType) {
1335
1434
  return `<admin-panel/>`; // ✅ Stops here
1336
1435
  }
1337
1436
 
1338
1437
  // This handler continues propagation (no return value)
1339
- @subscribeSwcAppRouteChangeWhileConnected({ order: -1 })
1438
+ @subscribeSwcAppRouteChange({ order: -1 })
1340
1439
  onRouteChange(routerPathSet: RouterEventType) {
1341
1440
  console.log('Route changed:', routerPathSet.path);
1342
1441
  // No return value → continues to next handler
1343
1442
  }
1344
1443
  ```
1345
1444
 
1445
+ **4. When to run: `on`** (default `'match'` — the behavior above)
1446
+
1447
+ | `on` | Runs when |
1448
+ |---|---|
1449
+ | `'match'` | the path matches (every route change) |
1450
+ | `'enter'` | it didn't match before and matches now (also the replay when the element connects) |
1451
+ | `'update'` | it keeps matching but the path or query changed |
1452
+ | `'leave'` | it matched before and doesn't now — only for elements that stay connected (a page that gets replaced should clean up in `onDisconnected`) |
1453
+ | `'beforeLeave'` | before leaving this route; return `false` (or `Promise<false>`) to cancel the navigation |
1454
+
1455
+ A bare `@subscribeSwcAppRouteChange` (no path) matches every route: with `'match'` it runs on every change, `'update'` on every URL change, `'enter'` once.
1456
+
1457
+ ```typescript
1458
+ @subscribeSwcAppRouteChange('/releases/{releaseSeq}/tasks/{taskSeq}', { on: 'update' })
1459
+ @innerHtml('.card')
1460
+ swapCard(@swcAppRoutePathVariable('taskSeq') taskSeq: string) { ... } // same page, only the card changes
1461
+
1462
+ @subscribeSwcAppRouteChange('/edit/{id}', { on: 'beforeLeave' })
1463
+ confirmLeave() { return this.dirty ? confirm('Discard your changes?') : true; }
1464
+ ```
1465
+
1466
+ `beforeLeave` covers `router.go(...)` and the browser back/forward buttons. Back/forward have already changed the history when the guard runs, so a cancel restores the previous URL with `pushState`. Calling `pushState`/`replaceState` directly and closing the tab (`beforeunload`) are not covered. The handler receives the route being left with `to` (the target `RouteData`).
1467
+
1468
+ **5. Route parameter decorators**
1469
+
1470
+ No prefix means *first*; `First`/`Last` pick the first/last value when a key repeats; plural forms return every value.
1471
+
1472
+ | Query (`?tag=a&tag=b`) | Value | Path (`/a/{id}/b/{id}` ← `/a/1/b/2`) | Value |
1473
+ |---|---|---|---|
1474
+ | `@swcAppRouteQueryParam('tag')` | `'a'` (`null` if absent) | `@swcAppRoutePathVariable('id')` | `'1'` |
1475
+ | `@swcAppRouteFirstQueryParam('tag')` | `'a'` | `@swcAppRouteFirstPathVariable('id')` | `'1'` |
1476
+ | `@swcAppRouteLastQueryParam('tag')` | `'b'` | `@swcAppRouteLastPathVariable('id')` | `'2'` |
1477
+ | `@swcAppRouteQueryParams('tag')` | `['a','b']` | `@swcAppRoutePathVariables('id')` | `['1','2']` |
1478
+ | `@swcAppRouteQueryParamObject` | `{ tag: 'a' }` | `@swcAppRoutePathVariableObject` | `{ id: '1' }` |
1479
+ | `@swcAppRouteFirstQueryParamObject` | `{ tag: 'a' }` | `@swcAppRouteFirstPathVariableObject` | `{ id: '1' }` |
1480
+ | `@swcAppRouteLastQueryParamObject` | `{ tag: 'b' }` | `@swcAppRouteLastPathVariableObject` | `{ id: '2' }` |
1481
+ | `@swcAppRouteQueryParamsObject` | `{ tag: ['a','b'] }` | `@swcAppRoutePathVariablesObject` | `{ id: ['1','2'] }` |
1482
+ | `@swcAppRouteURLSearchParams` | `URLSearchParams` | | |
1483
+
1484
+ ```typescript
1485
+ @subscribeSwcAppRouteChange('/releases/{releaseSeq}')
1486
+ load(@swcAppRoutePathVariable('releaseSeq') seq: string, @swcAppRouteQueryParam('tab') tab: string | null) { ... }
1487
+ ```
1488
+
1489
+ - Once any parameter is decorated, the positional route argument is no longer passed — add `@swcAppRouterEvent` for the whole event.
1490
+ - `route.pathData` keeps its old behavior: a repeated variable name holds the **last** value. Use the decorators above to pick first/last explicitly.
1491
+
1492
+ **6. Navigating: `@swcAppRoute` / `@swcAppRouteGo`**
1493
+
1494
+ The method returns where to go; the decorator calls the element's SwcApp router — no `Router` injection needed. `@swcAppRoute({ type })` picks the Router method (default `'go'`); `@swcAppRouteGo`, `@swcAppRoutePush`, ... are aliases with the type fixed.
1495
+
1496
+ ```typescript
1497
+ @eventDelegateAll('[data-href]', 'click')
1498
+ @swcAppRouteGo
1499
+ onNavigate(@matchedElement el: Element) {
1500
+ return el.getAttribute('data-href');
1501
+ }
1502
+
1503
+ @eventShadow('form', 'submit', { preventDefault: true })
1504
+ @swcAppRouteGo
1505
+ async onSubmit() {
1506
+ await this.authService.login(...);
1507
+ return '/releases'; // after an async body too
1508
+ }
1509
+ ```
1510
+
1511
+ | Return value (`go`) | Result |
1512
+ |---|---|
1513
+ | `'/path'` / `{ path, searchParams }` | `router.go(...)` |
1514
+ | `{ path, replace?, state?, scrollToTop? }` | `router.go` with those options |
1515
+ | a number | history move (`-1` = back) |
1516
+ | `undefined` / `null` / `false` | no navigation (conditional) |
1517
+
1518
+ - Other Router methods: `@swcAppRoute({ type: 'push' })` or the alias with the type fixed (`@swcAppRoutePush` / `@swcAppRoutePush({ state, valueKey })`):
1519
+
1520
+ | Alias | `type` | Return value |
1521
+ |---|---|---|
1522
+ | `@swcAppRouteGo` | `go` (default) | see the table above |
1523
+ | `@swcAppRoutePush` / `@swcAppRouteReplace` | `push` / `replace` | `RouteAction` |
1524
+ | `@swcAppRoutePushUpsertSearchParam` / `@swcAppRouteReplaceUpsertSearchParam` | `pushUpsertSearchParam` / `replaceUpsertSearchParam` | `{ key: value \| value[] }` |
1525
+ | `@swcAppRoutePushAddSearchParam` / `@swcAppRouteReplaceAddSearchParam` | `pushAddSearchParam` / `replaceAddSearchParam` | `[[key, value], ...]` |
1526
+ | `@swcAppRoutePushDeleteSearchParam` / `@swcAppRouteReplaceDeleteSearchParam` | `pushDeleteSearchParam` / `replaceDeleteSearchParam` | `string \| string[]` |
1527
+ | `@swcAppRoutePushDeleteHashSearchParam` / `@swcAppRouteReplaceDeleteHashSearchParam` | `pushDeleteHashSearchParam` / `replaceDeleteHashSearchParam` | `string \| string[]` |
1528
+
1529
+ `push`/`replace` go through `beforeLeave` guards like `go`. The search-param methods keep the path, so they don't count as leaving and skip the guards.
1530
+ - Options: `@swcAppRoute({ type, state, valueKey, filter })`, plus `replace` / `scrollToTop` for `go` (`@swcAppRouteGo({ replace: true })`). `valueKey` picks the target out of a return object when stacked with other output decorators (default key `SWC_APP_ROUTE_METADATA_KEY`).
1531
+ - `filter(router, value, { currentThis, helper })` → `false` skips the navigation (same shape as the other output decorators' `filter`; `value` is after `valueKey` extraction). E.g. `@swcAppRouteGo({ filter: (router, to) => to !== router.value.path })`.
1532
+ - The original return value still passes through, and navigation goes through `router.go`, so `beforeLeave` guards apply.
1533
+
1346
1534
  #### Advanced Example with Logging
1347
1535
 
1348
1536
  ```typescript
1349
1537
  @elementDefine('accommodation-router')
1350
1538
  class AccommodationRouter extends HTMLElement {
1351
1539
  // Global route logger (order: -1 runs first, no path pattern = matches all routes)
1352
- @subscribeSwcAppRouteChangeWhileConnected({ order: -1 })
1540
+ @subscribeSwcAppRouteChange({ order: -1 })
1353
1541
  onRouteChange(routerPathSet: RouterEventType) {
1354
1542
  console.log('[Route Change]', {
1355
1543
  path: routerPathSet.path,
@@ -1360,7 +1548,7 @@ class AccommodationRouter extends HTMLElement {
1360
1548
  }
1361
1549
 
1362
1550
  // Home route
1363
- @subscribeSwcAppRouteChangeWhileConnected(['', '/'], { order: 0 })
1551
+ @subscribeSwcAppRouteChange(['', '/'], { order: 0 })
1364
1552
  @innerHtmlLight
1365
1553
  handleHome(routerPathSet: RouterEventType) {
1366
1554
  console.log('[Route Handler] Home');
@@ -1368,7 +1556,7 @@ class AccommodationRouter extends HTMLElement {
1368
1556
  }
1369
1557
 
1370
1558
  // List route
1371
- @subscribeSwcAppRouteChangeWhileConnected(['/list'], { order: 1 })
1559
+ @subscribeSwcAppRouteChange(['/list'], { order: 1 })
1372
1560
  @innerHtmlLight
1373
1561
  handleList(routerPathSet: RouterEventType) {
1374
1562
  console.log('[Route Handler] List');
@@ -1376,7 +1564,7 @@ class AccommodationRouter extends HTMLElement {
1376
1564
  }
1377
1565
 
1378
1566
  // Detail route with dynamic parameter
1379
- @subscribeSwcAppRouteChangeWhileConnected(['/detail/{productId}'], { order: 2 })
1567
+ @subscribeSwcAppRouteChange(['/detail/{productId}'], { order: 2 })
1380
1568
  @innerHtmlLight
1381
1569
  handleDetail(routerPathSet: RouterEventType) {
1382
1570
  const { productId } = routerPathSet.pathData;
@@ -1385,7 +1573,7 @@ class AccommodationRouter extends HTMLElement {
1385
1573
  }
1386
1574
 
1387
1575
  // 404 fallback (order: 999 runs last)
1388
- @subscribeSwcAppRouteChangeWhileConnected(['/{tail:.*}'], { order: 999 })
1576
+ @subscribeSwcAppRouteChange(['/{tail:.*}'], { order: 999 })
1389
1577
  @innerHtmlLight
1390
1578
  handle404(routerPathSet: RouterEventType) {
1391
1579
  console.log('[Route Handler] 404 Not Found', routerPathSet.path);
@@ -1398,15 +1586,21 @@ class AccommodationRouter extends HTMLElement {
1398
1586
 
1399
1587
  ```typescript
1400
1588
  interface SwcAppRouteChangeOptions {
1401
- path?: RoutePathType; // Route path pattern(s)
1589
+ path?: RoutePathType; // string | string[] | (currentThis) => string | string[]
1402
1590
  order?: number; // Execution order (default: 0)
1403
- filter?: (router: Router, meta: {currentThis: any, helper: HelperHostSet}) => boolean;
1591
+ filter?: (router: Router, meta: { currentThis: any; helper: HelperHostSet }) => boolean | Promise<boolean>;
1592
+ before?: (router: Router, meta) => any; // result → @routeChangeBeforeReturn
1593
+ finally?: (router: Router, meta, ctx: { args; result?; error? }) => any;
1594
+ valueKey?: symbol | string; // value used for the stop check
1595
+ trigger?: 'connected' | 'connectedDone'; // replay timing (see "Message Replay")
1404
1596
  }
1405
1597
  ```
1406
1598
 
1599
+ Forms: `@subscribeSwcAppRouteChange` (bare, all routes), `('/path')`, `(['/a', '/b'])`, `(path, options)`, `(options)`. Alias: `@changedRoute`.
1600
+
1407
1601
  **Usage:**
1408
1602
  ```typescript
1409
- @subscribeSwcAppRouteChangeWhileConnected(
1603
+ @subscribeSwcAppRouteChange(
1410
1604
  ['/admin/{section}'],
1411
1605
  {
1412
1606
  order: 5,
@@ -1420,13 +1614,43 @@ handleAdminSection(routerPathSet: RouterEventType) {
1420
1614
  }
1421
1615
  ```
1422
1616
 
1617
+ #### Combined Example: Route + Attribute (nav highlight)
1618
+
1619
+ Stack several decorators on one method and split the returned object with `valueKey`.
1620
+ **Note:** returning a value stops the later handlers, so an observer-style handler uses `order: -1` and returns `undefined` to keep the chain going.
1621
+
1622
+ ```typescript
1623
+ @replaceChildrenLight({ valueKey: 'element' })
1624
+ @subscribeSwcAppRouteChange('/releases')
1625
+ @attribute('nav', 'href', { valueKey: 'href' })
1626
+ handleExplore() {
1627
+ return { element: ExplorePage(w), href: 'releases' }; // highlight nav + render the page
1628
+ }
1629
+
1630
+ @subscribeSwcAppRouteChange({ order: -1 })
1631
+ @removeAttribute('nav', 'href')
1632
+ handleNavReset() {
1633
+ return undefined; // reset the highlight, keep the chain going
1634
+ }
1635
+ ```
1636
+
1637
+ ```css
1638
+ /* Highlight the current section button */
1639
+ nav[href="releases"] [data-href="/releases"] { color: var(--color-text); background: var(--color-bg-alt); }
1640
+ ```
1641
+
1423
1642
  #### RouterEventType Structure
1424
1643
 
1425
1644
  ```typescript
1426
1645
  type RouterEventType = {
1427
- path: string; // Current route path
1428
- pathData: Record<string, any>; // Extracted path parameters
1429
- triggerPoint: 'start' | 'end'; // Route change phase
1646
+ path: string; // Current route path
1647
+ url: string;
1648
+ search: string;
1649
+ searchParams: URLSearchParams;
1650
+ data?: any;
1651
+ pathData?: Record<string, any>; // Extracted path parameters (filled per matched pattern)
1652
+ router: Router;
1653
+ triggerPoint: 'start' | 'end' | 'first-end'; // Route change phase
1430
1654
  };
1431
1655
  ```
1432
1656
 
@@ -1459,10 +1683,10 @@ import {
1459
1683
 
1460
1684
  @elementDefine('multi-decorator-example')
1461
1685
  class MultiDecoratorExample extends HTMLElement {
1462
- @setAttribute('selector')
1463
- @setProperty('selector')
1464
- @updateStyle()
1465
- @updateClass()
1686
+ @attribute('selector', 'data-state')
1687
+ @property('selector', 'value')
1688
+ @updateStyle
1689
+ @updateClass
1466
1690
  handleUpdate() {
1467
1691
  return {
1468
1692
  [ATTRIBUTE_METADATA_KEY]: 'attribute-value',
@@ -1481,10 +1705,10 @@ For more readable code, use the `valueKey` option to specify custom keys:
1481
1705
  ```typescript
1482
1706
  @elementDefine('custom-key-example')
1483
1707
  class CustomKeyExample extends HTMLElement {
1484
- @setAttribute('selector', { valueKey: 'attrValue' })
1485
- @setProperty('selector', { valueKey: 'propValue' })
1486
- @updateStyle({ valueKey: 'styleValue' })
1487
- @updateClass({ valueKey: 'classValue' })
1708
+ @attribute('selector', 'data-state', { valueKey: 'attrValue' })
1709
+ @property('selector', 'value', { valueKey: 'propValue' })
1710
+ @updateStyle('$this', { valueKey: 'styleValue' })
1711
+ @updateClass('$this', { root: 'auto', valueKey: 'classValue' }) // class options need `root`
1488
1712
  handleUpdate() {
1489
1713
  return {
1490
1714
  attrValue: 'attribute-value',
@@ -1503,9 +1727,9 @@ You can mix both approaches in the same method:
1503
1727
  ```typescript
1504
1728
  @elementDefine('mixed-keys-example')
1505
1729
  class MixedKeysExample extends HTMLElement {
1506
- @setAttribute('selector') // Uses default ATTRIBUTE_METADATA_KEY
1507
- @setProperty('selector', { valueKey: 'customProp' }) // Uses custom key
1508
- @updateStyle({ valueKey: 'styles' }) // Uses custom key
1730
+ @attribute('selector', 'data-state') // Uses default ATTRIBUTE_METADATA_KEY
1731
+ @property('selector', 'value', { valueKey: 'customProp' }) // Uses custom key
1732
+ @updateStyle('$this', { valueKey: 'styles' }) // Uses custom key
1509
1733
  handleUpdate() {
1510
1734
  return {
1511
1735
  [ATTRIBUTE_METADATA_KEY]: 'attr-value',
@@ -1517,15 +1741,16 @@ class MixedKeysExample extends HTMLElement {
1517
1741
  ```
1518
1742
 
1519
1743
  **Supported Decorators with valueKey:**
1520
- - `@applyAttribute` / `@setAttribute`
1521
- - `@applyProperty` / `@setProperty`
1522
- - `@applySlot` / `@clearSlot` / `@appendHtmlSlot` / etc.
1523
- - `@applyNode` / `@replaceChildrenNode` / etc.
1744
+ - `@applyAttribute` / `@attribute`
1745
+ - `@applyProperty` / `@property`
1746
+ - `@applySlot(id, { position, valueKey })` (the `clearSlot` / `appendHtmlSlot` / ... shorthands take no options)
1747
+ - `@applyNode` / `@replaceChildren` / `@innerHtml` / etc.
1524
1748
  - `@applyStyle` / `@updateStyle` / etc.
1525
1749
  - `@applyClass` / `@updateClass` / etc.
1526
1750
  - `@emitCustomEvent` / `@emit`
1527
- - `@publishSwcAppMessage` / `@publish`
1528
- - `@setInterval` / `@setTimeout` - variant: the extracted value must itself be a function, and that function is invoked with `(id: number)` on every tick/fire (see "Timers" section above) rather than being applied directly like the other decorators here.
1751
+ - `@publishSwcAppMessage` / `@publishMessage`
1752
+ - `@subscribeSwcAppRouteChange` - the extracted value decides whether the chain stops
1753
+ - `@setInterval` / `@setTimeout` / `@requestAnimationFrame` - variant: the extracted value must itself be a function, which becomes the timer/frame callback (see "Timers" above) rather than being applied directly.
1529
1754
 
1530
1755
  Each decorator will use only its corresponding value from the return object, preventing conflicts and allowing clean separation of concerns.
1531
1756
 
@@ -1576,7 +1801,7 @@ appElement.connect({
1576
1801
 
1577
1802
  ### Simplified Decorator API
1578
1803
 
1579
- The decorator API uses a consolidated pattern where all decorators support **optional selector parameter** that defaults to `$this` when omitted. This eliminates redundant `*This` functions.
1804
+ Most DOM decorators take an **optional selector** that defaults to `$this` when omitted (or when used bare). Scope shorthands (`*This`, `*AppHost`, `*Light`, `*Shadow`, `*All`) are still provided for brevity.
1580
1805
 
1581
1806
  #### Current Usage Pattern
1582
1807
  ```typescript
@@ -1584,7 +1809,7 @@ The decorator API uses a consolidated pattern where all decorators support **opt
1584
1809
  @applyClass('selector', 'update')
1585
1810
  method() { ... }
1586
1811
 
1587
- @applyClass('update') // Selector defaults to $this
1812
+ @applyClass('$this', 'update') // a single string is treated as the selector, so name $this explicitly (or use bare @updateClass)
1588
1813
  method() { ... }
1589
1814
 
1590
1815
  @applyClass // Bare decorator - selector defaults to $this
@@ -1594,7 +1819,7 @@ method() { ... }
1594
1819
  #### Decorator Functions (Current API)
1595
1820
 
1596
1821
  **applyClass.ts:**
1597
- - `applyClass(selector?, options?)` - Set/update/add/remove/toggle classes
1822
+ - `applyClass(selector?, action?, options?)` - Set/update/add/remove/toggle classes
1598
1823
  - `setClass(selector?, options?)` - Replace all classes
1599
1824
  - `updateClass(selector?, options?)` - Toggle classes
1600
1825
  - `addClass(selector?, options?)` - Add classes
@@ -1602,52 +1827,51 @@ method() { ... }
1602
1827
  - `toggleClass(selector?, options?)` - Toggle classes
1603
1828
 
1604
1829
  **applyStyle.ts:**
1605
- - `applyStyle(selector?, options?)` - Set/update/remove styles
1830
+ - `applyStyle(selector?, action?, options?)` - Set/update/remove styles
1606
1831
  - `setStyle(selector?, options?)` - Clear and set styles
1607
1832
  - `updateStyle(selector?, options?)` - Update/merge styles
1608
1833
  - `removeStyle(selector?, options?)` - Remove specific styles
1609
1834
 
1610
1835
  **applyProperty.ts:**
1611
- - `property(selector?, options?)` - Get/set element properties
1612
- - `setProperty(selector?, options?)` - Set element properties
1836
+ - `property(selector?, propertyKey?, options?)` - field: property get/set; method: return value → property
1613
1837
 
1614
1838
  **applyAttribute.ts:**
1615
- - `attribute(selector?, options?)` - Get/set element attributes
1616
- - `setAttribute(selector?, options?)` - Set element attributes
1839
+ - `attribute(attrName | selector?, attrName?, options?)` - field: attribute get/set; method: return value → attribute (one string = $this)
1617
1840
 
1618
1841
  **query.ts:**
1619
- - `query(selector?, options?)` - Query single element (supports $this, $host, $appHost, etc.)
1620
- - `queryAll(selector?, options?)` - Query multiple elements (same file as `query`)
1842
+ - `query(selector, options?)` - Query single element (supports $this, $host, $appHost, etc.; `pick` option)
1843
+ - `queryAll(selector, options?)` - Query multiple elements (`query` with `pick: 'all'`)
1621
1844
 
1622
1845
  **emitCustomEvent.ts:**
1623
1846
  - `emitCustomEvent(target, type, options?)` - Emit custom events
1624
1847
  - `emit(target, type, options?)` - Short alias
1848
+ - `emitCustomEvent(type, options?)` - Emit from `$this`
1625
1849
 
1626
1850
  #### Usage Examples
1627
1851
 
1628
1852
  ```typescript
1629
1853
  @elementDefine('my-component')
1630
1854
  class MyComponent extends HTMLElement {
1631
- // Class management - all default to $this
1632
- @updateClass()
1855
+ // Class management - bare form targets $this
1856
+ @updateClass
1633
1857
  toggleActive() {
1634
1858
  return { 'active': this.isActive };
1635
1859
  }
1636
1860
 
1637
- // Style management - all default to $this
1638
- @updateStyle()
1861
+ // Style management - bare form targets $this
1862
+ @updateStyle
1639
1863
  applyTheme() {
1640
1864
  return { color: this.theme.color, fontSize: '16px' };
1641
1865
  }
1642
1866
 
1643
- // Property binding - all default to $this
1644
- @setProperty()
1867
+ // Property binding - first string is the selector
1868
+ @property('input', 'value')
1645
1869
  updateValue() {
1646
1870
  return this.computedValue;
1647
1871
  }
1648
1872
 
1649
- // Attribute binding - all default to $this
1650
- @setAttribute()
1873
+ // Attribute binding - one string = attribute on $this
1874
+ @attribute('data-id')
1651
1875
  updateId() {
1652
1876
  return this.elementId;
1653
1877
  }
@@ -1660,14 +1884,14 @@ class MyComponent extends HTMLElement {
1660
1884
  listItems?: HTMLLIElement[];
1661
1885
 
1662
1886
  // Emit custom events
1663
- @addEventListener('button', 'click')
1887
+ @event('button', 'click')
1664
1888
  @emitCustomEvent('$this', 'item-selected')
1665
1889
  onItemSelect() {
1666
1890
  return { itemId: this.selectedId };
1667
1891
  }
1668
1892
 
1669
- // With selector
1670
- @updateClass('.card', 'update')
1893
+ // With selector and action
1894
+ @applyClass('.card', 'update')
1671
1895
  updateCardClass() {
1672
1896
  return { 'highlighted': true };
1673
1897
  }
@@ -1676,7 +1900,7 @@ class MyComponent extends HTMLElement {
1676
1900
 
1677
1901
  #### Key Features
1678
1902
 
1679
- 1. **Reduced API Surface** - Consolidated decorators eliminate redundant `*This` functions
1903
+ 1. **Optional Selector** - Omit the selector (or use the bare form) to target `$this`
1680
1904
  2. **Cleaner Code** - Single function name with multiple overloads
1681
1905
  3. **Better Discoverability** - Intuitive parameter patterns
1682
1906
  4. **Flexible Usage** - Supports bare decorators, with options, and with selectors
@@ -1703,17 +1927,16 @@ class UserWidget extends w.HTMLElement { }
1703
1927
  class UserWidget extends w.HTMLElement { }
1704
1928
  ```
1705
1929
 
1706
- ### Factory Always Returns
1930
+ ### Factory Always Returns (the tag name)
1707
1931
  ```typescript
1708
1932
  export default (w: Window) => {
1709
1933
  const tagName = 'my-element';
1710
- const existing = w.customElements.get(tagName);
1711
- if (existing) return existing;
1934
+ if (w.customElements.get(tagName)) return tagName;
1712
1935
 
1713
1936
  @elementDefine(tagName, { window: w })
1714
1937
  class MyElement extends w.HTMLElement { }
1715
-
1716
- return existing;
1938
+
1939
+ return tagName;
1717
1940
  };
1718
1941
  ```
1719
1942
 
@@ -1731,6 +1954,20 @@ constructor(private service: Service) { super(); } // Web Components can't have
1731
1954
 
1732
1955
  ---
1733
1956
 
1957
+ ## 🧪 Testing
1958
+
1959
+ `pnpm test` type-checks the tests and runs them in a real browser (Chromium via Playwright, falling back to the installed Chrome) — no jsdom. Each test gets its own same-origin iframe window, so custom elements, `history` and `location` never leak between tests.
1960
+
1961
+ ```bash
1962
+ pnpm test # type checks + all tests
1963
+ node test/unit/run.mjs query event # only files whose name contains "query" / "event"
1964
+ SWC_TEST_GREP='bare @query' node test/unit/run.mjs # only tests whose name matches
1965
+ SWC_TEST_DEBUG=1 node test/unit/run.mjs route # print the page console
1966
+ SWC_TEST_SERVE=1 node test/unit/run.mjs route # just serve the pages and open them yourself
1967
+ ```
1968
+
1969
+ Test files are plain `node:test` / `node:assert` code (`test/unit/*.test.ts`); the runner maps those imports to small in-browser versions. `test/unit/empty-parens.types.ts` is checked by `tsc` only and guards the "no arguments → no parentheses" rule.
1970
+
1734
1971
  ## 📚 Examples
1735
1972
 
1736
1973
  - **[Commerce (E-Commerce SPA)](https://github.com/dooboostore-develop/packages/tree/main/%40dooboostore/simple-web-component/examples/commerce)** - Full shopping cart example
@@ -1750,3 +1987,219 @@ constructor(private service: Service) { super(); } // Web Components can't have
1750
1987
  ## 📄 License
1751
1988
  [MIT License](https://github.com/dooboostore-develop/packages/tree/main/%40dooboostore/simple-web-component/LICENSE.md)
1752
1989
 
1990
+
1991
+ ---
1992
+
1993
+ ---
1994
+
1995
+ ## 📨 Message Replay (subject / trigger)
1996
+
1997
+ Late subscribers can receive past messages. The buffer keeps every typed message
1998
+ (`SwcConfigType.messageReplayBufferSize`, default 10, minimum 1 guaranteed).
1999
+
2000
+ ```typescript
2001
+ // 'behavior' = last message only, 'replay' = whole buffer in order.
2002
+ // Unset (or 'subject') = live only.
2003
+ @subscribeSwcAppMessage('auth-changed', { subject: 'behavior' })
2004
+ onAuth(@appMessage msg: SwcAppMessage<User | null>) { ... }
2005
+ ```
2006
+
2007
+ `trigger` controls when replay happens (default `'connected'`).
2008
+
2009
+ - `'connected'`: replay when the element registers with the app host, i.e. before its own
2010
+ `@onConnectedBody` render. For handlers that don't need DOM targets.
2011
+ - `'connectedDone'`: replay after the element finished rendering (after `@onConnectedCompleted`).
2012
+ Use it when the handler targets DOM rendered by the element itself, like `@innerHtml('.wrap')`
2013
+ (avoids the race where replay arrives before render and gets silently dropped).
2014
+
2015
+ ```typescript
2016
+ @subscribeSwcAppMessage('auth-changed', { subject: 'behavior', trigger: 'connectedDone' })
2017
+ @innerHtml('.wrap')
2018
+ async load(@appMessage msg: SwcAppMessage<User | null>) { ... }
2019
+ ```
2020
+
2021
+ Route subscribers (`@subscribeSwcAppRouteChange`) accept the same `trigger`.
2022
+ Live broadcasts always reach everyone regardless of trigger.
2023
+
2024
+ ## 🔌 App Host Hooks (onConnected / onSwcAppConnected / onDisconnected)
2025
+
2026
+ The three host hooks of `SwcAppMixin` are **abstract**: a subclass must implement them (use an empty method if there is nothing to do).
2027
+ `@inject` parameters are resolved via DI (host element itself included).
2028
+
2029
+ - `onConnected()` - at connectedCallback (every time it is attached). It runs one async step after attaching, so its order relative to `connect()` depends on when you call `connect()`:
2030
+ - `connect()` right after attaching → `connect()` → `onSwcAppConnected()` → `onConnected()` (DI already exists)
2031
+ - `connect()` later → `onConnected()` (no DI yet) → `connect()` → `onSwcAppConnected()`
2032
+
2033
+ So don't rely on DI or on ordering here — keep it to DOM/attribute work and subscriptions; call services in `onSwcAppConnected()`
2034
+ - `onSwcAppConnected()` - after `connect()` completes (DI container and router are ready). **Call services here**
2035
+ - `onDisconnected()` - at disconnectedCallback. Release subscriptions created in `onConnected`
2036
+
2037
+ ```typescript
2038
+ class MyAppBody extends SwcAppMixin(w.HTMLBodyElement) {
2039
+ onConnected() {}
2040
+ async onSwcAppConnected(@inject(AuthService.SYMBOL) auth: AuthService) {
2041
+ this.publishMessage({ type: 'auth-changed', data: await auth.me().catch(() => null) });
2042
+ }
2043
+ onDisconnected() {}
2044
+ }
2045
+ ```
2046
+
2047
+ ### observeMessage — the message bus as an Observable
2048
+
2049
+ ```typescript
2050
+ host.observeMessage() // all types, live only
2051
+ host.observeMessage('auth-changed') // one type
2052
+ host.observeMessage('auth-changed', { subject: 'behavior' }) // type + replay of the last value
2053
+ host.observeMessage({ type: 'auth-changed', subject: 'replay' }) // options-object form
2054
+ ```
2055
+
2056
+ - `subject`: `'behavior'` (last one) / `'replay'` (whole buffer) is delivered first at subscribe time, then live messages.
2057
+ - Replay only works when `type` is given; options without a type receive live messages only.
2058
+ - Returns an `Observable`; `unsubscribe()` the `Subscription` yourself (usually in `onDisconnected`).
2059
+
2060
+ When publishing from the host itself, call `this.publishMessage` directly.
2061
+ The `@publishSwcAppMessage` decorator publishes through the parent host, and the
2062
+ host itself has no parent, so the message would be dropped.
2063
+
2064
+ Use `connectedElements()` to get the currently connected child elements.
2065
+
2066
+ ## 🌐 @fetch — Declarative HTTP
2067
+
2068
+ ```typescript
2069
+ // bare form: GET by default
2070
+ @fetch('https://api.example.com/post/1')
2071
+ async load(@fetchSettled settled: PromiseSettledResult<any>) {
2072
+ return settled;
2073
+ }
2074
+
2075
+ // before (default): build request → fetch → inject settled → run method.
2076
+ // Returns the method result.
2077
+ @fetch({ url: '/api/post/1', request: 'GET' })
2078
+ async loadBefore(@fetchSettled settled: PromiseSettledResult<any>) {
2079
+ return settled.status === 'fulfilled' ? settled.value : null;
2080
+ }
2081
+
2082
+ // after: run method → use its return value as the body → fetch. Returns the fetch result.
2083
+ @fetch({ url: '/api/posts', trigger: 'after', process: 'json' })
2084
+ async save() {
2085
+ return { title: 'hello' };
2086
+ }
2087
+
2088
+ // after + valueKey: use returnValue[valueKey] as the body.
2089
+ @fetch({ url: '/api/echo', trigger: 'after', valueKey: 'form', process: 'form' })
2090
+ async saveForm() {
2091
+ return { form: { title: 'hi' } };
2092
+ }
2093
+ ```
2094
+
2095
+ **Modes**
2096
+ - **before** (default, or `trigger: 'before'`): build request → fetch → inject the settled result via
2097
+ `@fetchSettled` → run the method. Returns the method result. A failed fetch does not
2098
+ throw — it arrives as `{ status: 'rejected', reason }`.
2099
+ - **after** (`trigger: 'after'`, or just `valueKey`): run the method → its return value
2100
+ (or `returnValue[valueKey]`) becomes the body → fetch. Returns the fetch result; a failed fetch throws.
2101
+ `valueKey` is after-only — combining it with `trigger: 'before'` is a type error.
2102
+
2103
+ **Declarative vs manual** — pick one:
2104
+ - **Declarative**: describe the request with `url` / `request` / `process` and the framework fetches.
2105
+ - **Manual**: give `manual` and make the request yourself (a service call, your own `fetch`, ...).
2106
+ `url` / `request` / `process` are unused then, so combining them with `manual` is a type error.
2107
+
2108
+ ```typescript
2109
+ // service call — only the result matters
2110
+ @fetch({ manual: (self) => self.myService.getProfile({}) })
2111
+ async load(@fetchSettled settled?: PromiseSettledResult<MyService.ProfileResponse>) { ... }
2112
+
2113
+ // your own fetch — pass the signal so an abort cancels the network request too
2114
+ @fetch({ trigger: 'after', manual: (_self, _helper, _params, signal, returnValue) =>
2115
+ window.fetch('/api/save', { method: 'POST', body: JSON.stringify(returnValue), signal }).then(r => r.json()) })
2116
+ async save() { return { title: 'hi' }; }
2117
+ ```
2118
+
2119
+ `manual(this, helper, params, signal, returnValue?)` — `signal` aborts on disconnect / `abortPrevious`;
2120
+ `returnValue` is the method's return value (or `returnValue[valueKey]`) in after mode, `undefined` in before mode.
2121
+ Its return value is the fetch result.
2122
+
2123
+ **Options (declarative)**
2124
+ - `url`: a string/`URL`/`Request`, or a factory `(this, helper, params) => url`.
2125
+ - `request`: an HTTP method string (`'GET'`/`'POST'`/...) or a factory returning `RequestInit` —
2126
+ `(this, helper, params) => RequestInit` in before mode, `(this, helper, params, returnValue) => RequestInit`
2127
+ in after mode. Absent = GET, or POST when there is a body.
2128
+ - `process`: body encoding for plain values — `'json'` / `'form'` (multipart) / `'urlencoded'` / `'text'`.
2129
+ Values that already are a `BodyInit` (string, `Blob`, `ArrayBuffer`, `FormData`, `URLSearchParams`,
2130
+ `ReadableStream`) are sent as-is. Unset = pass through untouched.
2131
+ - Headers: `request` may return `headers` as an object, array or `Headers` instance. A `Content-Type`
2132
+ you set wins over the one `process` would add.
2133
+ - The request uses the element's window `fetch` (the per-request window under SSR), falling back to `globalThis.fetch`.
2134
+ - Default response handling: empty body (e.g. 204) → `undefined`, JSON content types (`application/json`,
2135
+ `*+json`) → parsed, otherwise text. A non-OK status throws `HttpResponseError` from `@dooboostore/core`
2136
+ with `response` (status, headers) and the parsed `body`:
2137
+
2138
+ ```typescript
2139
+ @fetch('/api/post/1')
2140
+ async load(@fetchSettled settled?: PromiseSettledResult<any>) {
2141
+ if (settled?.status === 'rejected' && isHttpResponseError(settled.reason)) {
2142
+ return settled.reason.response?.status; // e.g. 404
2143
+ }
2144
+ }
2145
+ ```
2146
+
2147
+ **Hooks** (same order as every other SWC decorator: `filter → before → handler → finally`)
2148
+ - `filter(this, helper, params)` → `false` skips the whole call (no method, no fetch; returns `undefined`).
2149
+ - `before(this, helper, params)` runs first after `filter` and is awaited; its return value is ignored.
2150
+ - `after(this, helper, params, settled)` runs right after the fetch settles (fulfilled or rejected).
2151
+ - `finally(this, helper, params, { args, result, error })` always runs around the whole call.
2152
+ `result` is what the call returns (the method result in before mode, the fetch result in after mode).
2153
+
2154
+ **Cancellation**
2155
+ - Every call gets its own `AbortController`. Declarative requests get its signal as `init.signal`
2156
+ (merged with a `signal` you return from `request`, via `AbortSignal.any` or a fallback); `manual` gets it as
2157
+ the `signal` argument.
2158
+ - When the element disconnects, all its in-flight `@fetch` requests are aborted. Such a call ends
2159
+ quietly with `undefined`: the method (before mode) and the `after` hook are skipped so nothing touches
2160
+ a detached DOM; `finally` still runs with `ctx.error` set to the `AbortError`.
2161
+ - A call that *starts* while the element is detached (e.g. a `setTimeout` scheduled just before the user
2162
+ navigated away) is skipped the same quiet way. Elements that were never connected are not affected.
2163
+ - `manual` should pass `signal` on to its own client (`fetch`, axios, ...) so the network
2164
+ request is actually cancelled. If it ignores the signal, the call still ends as soon as it's aborted
2165
+ (the result is raced against the abort) — the request just keeps running in the background and its
2166
+ late result is discarded.
2167
+ - `abortPrevious: true` aborts the previous in-flight call of the same method when it's called again
2168
+ (search-as-you-type) — the superseded call ends quietly the same way, so a late old response can't
2169
+ overwrite the newest result.
2170
+ - Aborting through your own signal is not quiet: it surfaces as a rejected settled result (before mode)
2171
+ or a thrown error (after mode), like any other failure.
2172
+
2173
+ **Aliases**
2174
+
2175
+ | Alias | Same as |
2176
+ |---|---|
2177
+ | `@fetchManual(fn, options?)` | `@fetch({ ...options, manual: fn })` |
2178
+ | `@fetchGet(url, options?)` / `@fetchDelete(url, options?)` | before mode, `method` fixed to GET / DELETE |
2179
+ | `@fetchPost(url, options?)` / `@fetchPut(...)` / `@fetchPatch(...)` | after mode (the method's return value is the body), `method` fixed |
2180
+ | `@fetchLatest(url \| options)` | `@fetch({ ...options, abortPrevious: true })` |
2181
+ | `@fetchBefore(options)` / `@fetchAfter(options)` | `@fetch({ ...options, trigger })` |
2182
+
2183
+ For the method aliases, `request` may only be a factory returning `RequestInit` (e.g. headers) — its `method` is overwritten.
2184
+
2185
+ ```typescript
2186
+ @fetchManual((self) => self.myService.getProfile({}))
2187
+ async load(@fetchSettled settled?: PromiseSettledResult<Profile>) { ... }
2188
+
2189
+ @fetchPost('/api/posts', { process: 'json' })
2190
+ async save() { return { title: 'hi' }; }
2191
+
2192
+ @fetchDelete((_self, _helper, params) => `/api/posts/${params[0]}`)
2193
+ async remove(id: number, @fetchSettled settled?: PromiseSettledResult<unknown>) { ... }
2194
+ ```
2195
+
2196
+ **Composition** — e.g. form submit:
2197
+ `@event('form', 'submit', { before: (e) => new FormData(e.target), preventDefault: true })`
2198
+ stacked on `@fetch({ url: '/api/echo', trigger: 'after', process: 'form' })`, with the method taking
2199
+ `@eventBeforeReturn formData: FormData` and returning it.
2200
+
2201
+ ## 💧 @property Hydration Rules
2202
+
2203
+ - Attach bare `@property` (no args) to a field to mark it as an SSR hydration target.
2204
+ - Self fields (`$this` + matching key) stay plain fields with no getter/setter.
2205
+ - Declare the field with `declare` (both `= null` and `;` initializers get overwritten on upgrade).