@dooboostore/simple-web-component 1.0.19 → 1.0.22

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 (174) hide show
  1. package/README.md +2759 -201
  2. package/dist/cjs/decorators/addEventListener.js +8 -4
  3. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  4. package/dist/cjs/decorators/{appendChild.js → applyNode.js} +26 -18
  5. package/dist/cjs/decorators/applyNode.js.map +7 -0
  6. package/dist/cjs/decorators/attribute.js +162 -0
  7. package/dist/cjs/decorators/attribute.js.map +7 -0
  8. package/dist/cjs/decorators/{changedAttribute.js → changedAttributeHost.js} +11 -11
  9. package/dist/cjs/decorators/changedAttributeHost.js.map +7 -0
  10. package/dist/cjs/decorators/classList.js +15 -5
  11. package/dist/cjs/decorators/classList.js.map +2 -2
  12. package/dist/cjs/decorators/elementDefine.js +124 -46
  13. package/dist/cjs/decorators/elementDefine.js.map +3 -3
  14. package/dist/cjs/decorators/emitCustomEvent.js +8 -7
  15. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  16. package/dist/cjs/decorators/index.js +3 -3
  17. package/dist/cjs/decorators/index.js.map +2 -2
  18. package/dist/cjs/decorators/lifecycles.js +8 -4
  19. package/dist/cjs/decorators/lifecycles.js.map +2 -2
  20. package/dist/cjs/decorators/onConnectedInnerHtml.js +4 -4
  21. package/dist/cjs/decorators/onConnectedInnerHtml.js.map +2 -2
  22. package/dist/cjs/decorators/query.js +9 -5
  23. package/dist/cjs/decorators/query.js.map +2 -2
  24. package/dist/cjs/decorators/queryAll.js +9 -5
  25. package/dist/cjs/decorators/queryAll.js.map +2 -2
  26. package/dist/cjs/decorators/setAttribute.js +51 -21
  27. package/dist/cjs/decorators/setAttribute.js.map +2 -2
  28. package/dist/cjs/decorators/style.js +4 -4
  29. package/dist/cjs/decorators/style.js.map +2 -2
  30. package/dist/cjs/elements/SwcAppEngine.js +8 -8
  31. package/dist/cjs/elements/SwcAppEngine.js.map +3 -3
  32. package/dist/cjs/elements/SwcAppMixin.js +28 -1
  33. package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
  34. package/dist/cjs/elements/SwcAsync.js +6 -6
  35. package/dist/cjs/elements/SwcAsync.js.map +2 -2
  36. package/dist/cjs/elements/SwcChoose.js +6 -6
  37. package/dist/cjs/elements/SwcChoose.js.map +2 -2
  38. package/dist/cjs/elements/SwcIf.js +6 -6
  39. package/dist/cjs/elements/SwcIf.js.map +2 -2
  40. package/dist/cjs/elements/SwcLoop.js +6 -6
  41. package/dist/cjs/elements/SwcLoop.js.map +2 -2
  42. package/dist/cjs/elements/register.js +19 -18
  43. package/dist/cjs/elements/register.js.map +2 -2
  44. package/dist/cjs/index.js +6 -17
  45. package/dist/cjs/index.js.map +2 -2
  46. package/dist/cjs/router/ElementRouter.js +7 -7
  47. package/dist/cjs/router/ElementRouter.js.map +2 -2
  48. package/dist/cjs/types.js.map +2 -2
  49. package/dist/esm/decorators/addEventListener.js +5 -1
  50. package/dist/esm/decorators/addEventListener.js.map +2 -2
  51. package/dist/esm/decorators/{appendChild.js → applyNode.js} +23 -15
  52. package/dist/esm/decorators/applyNode.js.map +7 -0
  53. package/dist/esm/decorators/attribute.js +143 -0
  54. package/dist/esm/decorators/attribute.js.map +7 -0
  55. package/dist/esm/decorators/{changedAttribute.js → changedAttributeHost.js} +4 -4
  56. package/dist/esm/decorators/changedAttributeHost.js.map +7 -0
  57. package/dist/esm/decorators/classList.js +12 -2
  58. package/dist/esm/decorators/classList.js.map +2 -2
  59. package/dist/esm/decorators/elementDefine.js +119 -41
  60. package/dist/esm/decorators/elementDefine.js.map +2 -2
  61. package/dist/esm/decorators/emitCustomEvent.js +5 -4
  62. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  63. package/dist/esm/decorators/index.js +3 -3
  64. package/dist/esm/decorators/index.js.map +1 -1
  65. package/dist/esm/decorators/lifecycles.js +5 -1
  66. package/dist/esm/decorators/lifecycles.js.map +2 -2
  67. package/dist/esm/decorators/onConnectedInnerHtml.js +1 -1
  68. package/dist/esm/decorators/onConnectedInnerHtml.js.map +1 -1
  69. package/dist/esm/decorators/query.js +6 -2
  70. package/dist/esm/decorators/query.js.map +2 -2
  71. package/dist/esm/decorators/queryAll.js +6 -2
  72. package/dist/esm/decorators/queryAll.js.map +2 -2
  73. package/dist/esm/decorators/setAttribute.js +47 -17
  74. package/dist/esm/decorators/setAttribute.js.map +2 -2
  75. package/dist/esm/decorators/style.js +1 -1
  76. package/dist/esm/decorators/style.js.map +1 -1
  77. package/dist/esm/elements/SwcAppEngine.js +4 -4
  78. package/dist/esm/elements/SwcAppEngine.js.map +2 -2
  79. package/dist/esm/elements/SwcAppMixin.js +28 -1
  80. package/dist/esm/elements/SwcAppMixin.js.map +2 -2
  81. package/dist/esm/elements/SwcAsync.js +3 -3
  82. package/dist/esm/elements/SwcAsync.js.map +2 -2
  83. package/dist/esm/elements/SwcChoose.js +3 -3
  84. package/dist/esm/elements/SwcChoose.js.map +2 -2
  85. package/dist/esm/elements/SwcIf.js +3 -3
  86. package/dist/esm/elements/SwcIf.js.map +2 -2
  87. package/dist/esm/elements/SwcLoop.js +3 -3
  88. package/dist/esm/elements/SwcLoop.js.map +2 -2
  89. package/dist/esm/elements/register.js +7 -6
  90. package/dist/esm/elements/register.js.map +2 -2
  91. package/dist/esm/index.js +6 -17
  92. package/dist/esm/index.js.map +2 -2
  93. package/dist/esm/router/ElementRouter.js +2 -2
  94. package/dist/esm/router/ElementRouter.js.map +1 -1
  95. package/dist/esm/types.js.map +2 -2
  96. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +5444 -3317
  97. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  98. package/dist/types/decorators/addEventListener.d.ts +6 -0
  99. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  100. package/dist/types/decorators/applyNode.d.ts +38 -0
  101. package/dist/types/decorators/applyNode.d.ts.map +1 -0
  102. package/dist/types/decorators/attribute.d.ts +29 -0
  103. package/dist/types/decorators/attribute.d.ts.map +1 -0
  104. package/dist/types/decorators/{changedAttribute.d.ts → changedAttributeHost.d.ts} +6 -2
  105. package/dist/types/decorators/changedAttributeHost.d.ts.map +1 -0
  106. package/dist/types/decorators/classList.d.ts +25 -0
  107. package/dist/types/decorators/classList.d.ts.map +1 -1
  108. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  109. package/dist/types/decorators/emitCustomEvent.d.ts +14 -2
  110. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  111. package/dist/types/decorators/index.d.ts +3 -3
  112. package/dist/types/decorators/index.d.ts.map +1 -1
  113. package/dist/types/decorators/lifecycles.d.ts +2 -0
  114. package/dist/types/decorators/lifecycles.d.ts.map +1 -1
  115. package/dist/types/decorators/query.d.ts +6 -0
  116. package/dist/types/decorators/query.d.ts.map +1 -1
  117. package/dist/types/decorators/queryAll.d.ts +6 -0
  118. package/dist/types/decorators/queryAll.d.ts.map +1 -1
  119. package/dist/types/decorators/setAttribute.d.ts +8 -0
  120. package/dist/types/decorators/setAttribute.d.ts.map +1 -1
  121. package/dist/types/elements/SwcAppEngine.d.ts +5 -4
  122. package/dist/types/elements/SwcAppEngine.d.ts.map +1 -1
  123. package/dist/types/elements/SwcAppMixin.d.ts +1 -1
  124. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  125. package/dist/types/elements/register.d.ts +1 -1
  126. package/dist/types/elements/register.d.ts.map +1 -1
  127. package/dist/types/index.d.ts +3 -14
  128. package/dist/types/index.d.ts.map +1 -1
  129. package/dist/types/router/ElementRouter.d.ts +1 -1
  130. package/dist/types/router/ElementRouter.d.ts.map +1 -1
  131. package/dist/types/types.d.ts +3 -1
  132. package/dist/types/types.d.ts.map +1 -1
  133. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +5444 -3317
  134. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  135. package/package.json +10 -5
  136. package/src/decorators/addEventListener.ts +10 -1
  137. package/src/decorators/applyNode.ts +127 -0
  138. package/src/decorators/attribute.ts +192 -0
  139. package/src/decorators/{changedAttribute.ts → changedAttributeHost.ts} +6 -2
  140. package/src/decorators/classList.ts +33 -1
  141. package/src/decorators/elementDefine.ts +148 -47
  142. package/src/decorators/emitCustomEvent.ts +14 -6
  143. package/src/decorators/index.ts +3 -3
  144. package/src/decorators/lifecycles.ts +4 -2
  145. package/src/decorators/onConnectedInnerHtml.ts +1 -1
  146. package/src/decorators/query.ts +10 -1
  147. package/src/decorators/queryAll.ts +10 -1
  148. package/src/decorators/setAttribute.ts +57 -17
  149. package/src/decorators/style.ts +1 -1
  150. package/src/elements/SwcAppEngine.ts +10 -8
  151. package/src/elements/SwcAppMixin.ts +34 -2
  152. package/src/elements/SwcAsync.ts +3 -3
  153. package/src/elements/SwcChoose.ts +3 -3
  154. package/src/elements/SwcIf.ts +3 -3
  155. package/src/elements/SwcLoop.ts +3 -3
  156. package/src/elements/register.ts +8 -6
  157. package/src/index.ts +8 -16
  158. package/src/router/ElementRouter.ts +2 -2
  159. package/src/types.ts +3 -1
  160. package/dist/cjs/decorators/appendChild.js.map +0 -7
  161. package/dist/cjs/decorators/changedAttribute.js.map +0 -7
  162. package/dist/cjs/decorators/replaceChildren.js +0 -79
  163. package/dist/cjs/decorators/replaceChildren.js.map +0 -7
  164. package/dist/esm/decorators/appendChild.js.map +0 -7
  165. package/dist/esm/decorators/changedAttribute.js.map +0 -7
  166. package/dist/esm/decorators/replaceChildren.js +0 -60
  167. package/dist/esm/decorators/replaceChildren.js.map +0 -7
  168. package/dist/types/decorators/appendChild.d.ts +0 -17
  169. package/dist/types/decorators/appendChild.d.ts.map +0 -1
  170. package/dist/types/decorators/changedAttribute.d.ts.map +0 -1
  171. package/dist/types/decorators/replaceChildren.d.ts +0 -9
  172. package/dist/types/decorators/replaceChildren.d.ts.map +0 -1
  173. package/src/decorators/appendChild.ts +0 -86
  174. package/src/decorators/replaceChildren.ts +0 -64
package/README.md CHANGED
@@ -30,321 +30,2879 @@ For **Safari** support of the `is` attribute, add this to your `<head>`:
30
30
 
31
31
  ---
32
32
 
33
- ## 💡 Core Decorators
33
+ ## 💡 Complete Decorator Reference
34
34
 
35
- ### 1. `@elementDefine(config)`
36
- Registers the class as a custom element.
35
+ ### Lifecycle Decorators
37
36
 
38
- | Option | Type | Description |
39
- |---|---|---|
40
- | `name` | `string` | The custom element tag name (e.g., `'my-el'`). |
41
- | `extends` | `string` | (Optional) The native tag to extend. |
42
- | `observedAttributes` | `string[]` | (Optional) Extra attributes to observe. |
43
- | `window` | `Window` | (Optional) Custom window object (useful for SSR). |
37
+ #### 1. `@onConstructor()`
38
+ Called during component construction (in `constructor`). Perfect for dependency injection setup.
44
39
 
45
- ### 2. `@onConnectedInnerHtml(options?)`
46
- Defines the template for the component. Executed when the component is connected to the DOM.
40
+ ```typescript
41
+ @elementDefine('my-component')
42
+ class MyComponent extends HTMLElement {
43
+ private dataService: DataService;
44
+
45
+ @onConstructor
46
+ setupDI(dataService: DataService) {
47
+ this.dataService = dataService;
48
+ console.log('Component constructed with DI');
49
+ }
50
+ }
51
+ ```
52
+
53
+ **Execution Order**: First - during `new MyComponent()`
54
+
55
+ #### 2. `@onBeforeConnected()`
56
+ **Dependency Injection & Pre-connection Setup**
57
+
58
+ Called before the component connects to the DOM. Perfect for initializing services via DI.
59
+
60
+ **⚠️ Important**: Lifecycle DI works **WITHOUT `@Sim`** - regular `@elementDefine` components can receive services here!
47
61
 
48
62
  ```typescript
49
- @elementDefine('my-el')
50
- class MyEl extends HTMLElement {
51
- @onConnectedInnerHtml({ useShadow: true })
63
+ @elementDefine('my-component') // ← NO @Sim needed for lifecycle DI
64
+ class MyComponent extends HTMLElement {
65
+ #cartService: CartService;
66
+ #subscription?: Subscription;
67
+
68
+ // Lifecycle DI injection - works even without @Sim
69
+ @onBeforeConnected
70
+ async setupComponent(cartService: CartService) {
71
+ this.#cartService = cartService;
72
+
73
+ // Can be async - waits before rendering
74
+ await this.#cartService.load();
75
+
76
+ // Subscribe to reactive updates
77
+ this.#subscription = this.#cartService.store.subscribe(state => {
78
+ this.updateUI();
79
+ });
80
+ }
81
+
82
+ disconnectedCallback() {
83
+ this.#subscription?.unsubscribe();
84
+ }
85
+
86
+ @onConnectedInnerHtml
52
87
  render() {
53
- return `<style>:host { color: blue; }</style><div>Hello World</div>`;
88
+ return `<div>Component ready</div>`;
54
89
  }
55
90
  }
56
91
  ```
57
92
 
58
- ### 3. Modern Attribute Management
59
- SWC uses a **Method-Based Reflection** approach. You control exactly how data flows.
93
+ **Key Features:**
94
+ - ✅ Services automatically injected via lifecycle decorators
95
+ - ✅ **NO `@Sim` required** for lifecycle DI injection
96
+ - ✅ Can be async - waits before rendering
97
+ - ✅ Called once, before `connectedCallback`
98
+ - ✅ Perfect for initialization and subscriptions
60
99
 
61
- - **`@setAttribute(name?)`**: Reflects the method's **return value** to the DOM attribute.
62
- - **`@changedAttribute(name?)`**: A handler triggered when the attribute changes.
100
+ **DI Injection Methods:**
63
101
 
64
- ```typescript
65
- @elementDefine('my-counter')
66
- class MyCounter extends HTMLElement {
67
- private count = 0;
68
- @query('#display') displayEl?: HTMLElement;
102
+ | Method | Requires `@Sim`? | Location |
103
+ |--------|-----------------|----------|
104
+ | Lifecycle (`@onBeforeConnected`) | ❌ NO | Lifecycle decorators |
105
+ | Constructor Injection | ✅ YES | Inside `<swc-app>` only |
106
+ | Static Registration | ✅ YES | `<swc-app>` DI container |
69
107
 
70
- @setAttribute('count')
71
- updateCount(val: number) { return val; } // Sets 'count' attribute in DOM
108
+ **Execution Order**: Before `connectedCallback()`
109
+
110
+ #### 3. `@onAfterConnected()` / `@onConnected()`
111
+ Called after the component connects to the DOM. For post-render operations.
72
112
 
73
- @changedAttribute('count')
74
- onCountChange(newVal: string, oldVal: string, name: string) {
75
- this.count = Number(newVal);
76
- // Surgical Update: Only update the specific node
77
- if (this.displayEl) this.displayEl.textContent = newVal;
113
+ ```typescript
114
+ @elementDefine('my-element')
115
+ class MyElement extends HTMLElement {
116
+ @onAfterConnected // or @onConnected (alias)
117
+ initializeAfterConnect() {
118
+ console.log('Element fully connected to DOM');
119
+ this.focus(); // Can interact with DOM now
120
+
121
+ // Measure element size after render
122
+ const rect = this.getBoundingClientRect();
123
+ console.log('Size:', rect.width, rect.height);
78
124
  }
79
125
  }
80
126
  ```
81
127
 
82
- ### 4. Powerful Queries (`@query`, `@queryAll`)
83
- Inject internal elements or host instances directly into properties. Assigning `null` or `[]` to these properties will surgically remove the target elements from the DOM.
128
+ **Execution Order**: After `connectedCallback()`
129
+
130
+ #### 4. `@onBeforeDisconnected()`
131
+ Called before the component disconnects from the DOM. For cleanup preparation.
84
132
 
85
- #### Usage with Special Targets:
86
- Special targets (starting with `:`) navigate the hierarchy and environment.
87
133
  ```typescript
88
- @query(':host') self!: HTMLElement;
89
- @query(':parentHost') parent!: HTMLElement;
90
- @query(':appHost') app!: SwcAppInterface;
91
- @query(':window') win!: Window;
92
- @query(':document') doc!: Document;
93
- @queryAll(':hosts') ancestors!: HTMLElement[];
134
+ @elementDefine('event-listener-component')
135
+ class EventListenerComponent extends HTMLElement {
136
+ private resizeObserver: ResizeObserver;
137
+
138
+ @onBeforeDisconnected
139
+ prepareForDisconnect() {
140
+ console.log('About to disconnect, saving state...');
141
+ // Save state, cancel pending requests, etc.
142
+ const state = {
143
+ scrollPosition: window.scrollY,
144
+ formData: this.getFormData()
145
+ };
146
+ localStorage.setItem('component-state', JSON.stringify(state));
147
+ }
148
+
149
+ @onAfterDisconnected
150
+ cleanup() {
151
+ this.resizeObserver?.disconnect();
152
+ console.log('Cleanup completed');
153
+ }
154
+ }
94
155
  ```
95
156
 
96
- #### Usage with Selectors & Root Options:
97
- Standard CSS selectors support precise root control.
157
+ **Execution Order**: Before `disconnectedCallback()`
158
+
159
+ #### 5. `@onAfterDisconnected()` / `@onDisconnected()`
160
+ Called after the component disconnects from the DOM. Final cleanup.
161
+
98
162
  ```typescript
99
- // root options: 'light' | 'shadow' | 'all' | 'auto' (default)
100
- @query('.btn', { root: 'shadow' }) shadowBtn!: HTMLButtonElement;
163
+ @elementDefine('resource-component')
164
+ class ResourceComponent extends HTMLElement {
165
+ private timerId: number;
166
+ private subscription: Subscription;
167
+
168
+ @onAfterDisconnected // or @onDisconnected (alias)
169
+ cleanup() {
170
+ // Cancel timers
171
+ clearTimeout(this.timerId);
172
+
173
+ // Unsubscribe from observables
174
+ this.subscription?.unsubscribe();
175
+
176
+ console.log('Resources cleaned up');
177
+ }
178
+ }
101
179
  ```
102
180
 
103
- ### 5. Smart Event Binding (`@addEventListener`)
104
- Bind events declaratively with support for **Event Delegation** and special targets.
181
+ **Execution Order**: After `disconnectedCallback()`
182
+
183
+ #### 6. `@onBeforeAdopted()`
184
+ Called before the element is adopted by a new document (useful for SSR/iframe scenarios).
105
185
 
106
186
  ```typescript
107
- @elementDefine('event-demo')
108
- class EventDemo extends HTMLElement {
109
- // 1. Direct binding (Pure Mode)
110
- @addEventListener('.static-btn', 'click')
111
- onStaticClick(event: MouseEvent) { ... }
187
+ @elementDefine('document-aware')
188
+ class DocumentAware extends HTMLElement {
189
+ @onBeforeAdopted
190
+ prepareForAdoption() {
191
+ console.log('Adopting to new document...');
192
+ // Prepare for document change
193
+ }
194
+ }
195
+ ```
196
+
197
+ #### 7. `@onAfterAdopted()` / `@onAdopted()`
198
+ Called after the element is adopted by a new document.
112
199
 
113
- // 2. Event Delegation (Handles dynamic nodes)
114
- @addEventListener('.dynamic-item', 'click', { delegate: true })
115
- onItemClick(event: MouseEvent, { $host }: HostSet, target: HTMLElement) {
116
- console.log('Clicked in:', $host.tagName);
200
+ ```typescript
201
+ @elementDefine('iframe-component')
202
+ class IframeComponent extends HTMLElement {
203
+ @onAfterAdopted // or @onAdopted (alias)
204
+ onDocumentAdopted() {
205
+ console.log('Now in new document context');
206
+ // Re-attach to new document's resources
207
+ this.initializeInNewDocument();
117
208
  }
118
209
 
119
- // 3. Global Events with Auto-Cleanup
120
- @addEventListener(':window', 'resize', { removeOnDisconnected: true })
121
- onResize() { ... }
210
+ private initializeInNewDocument() {
211
+ // Setup for new document
212
+ }
122
213
  }
123
214
  ```
124
215
 
125
- ### 6. `@replaceChildren(selector)`
126
- Surgically replaces the children of a target element with the method's return value (`string`, `Node`, or `Array<Node|string>`).
216
+ #### 8. `@onAddEventListener()`
217
+ Called when an event listener is added to the element. For event tracking/analytics.
127
218
 
128
219
  ```typescript
129
- @elementDefine('surgical-demo')
130
- class SurgicalDemo extends HTMLElement {
131
- // Replaces all children of #display-area with a new HTML fragment
132
- @replaceChildren('#display-area')
133
- updateView(title: string) {
134
- return htmlFragment`<h2>${title}</h2><p>Updated successfully.</p>`;
220
+ @elementDefine('event-tracked')
221
+ class EventTracked extends HTMLElement {
222
+ @onAddEventListener
223
+ onListenerAdded(type: string, listener: EventListener, options?: AddEventListenerOptions) {
224
+ console.log(`Event listener added: ${type}`);
225
+ // Track event listeners for debugging
135
226
  }
136
227
  }
137
228
  ```
138
229
 
139
- ### 7. `@appendChild(selector, options?)`
140
- Appends or inserts the method's return value relative to a target element.
141
- - **position**: `'firstChild'`, `'lastChild'` (default), `'nextSibling'`, `'previousSibling'`.
230
+ #### 9. `@onConnectedInnerHtml(options?)`
231
+ Defines the component's template. Renders to Shadow DOM or Light DOM based on `useShadow` option.
142
232
 
143
233
  ```typescript
144
- @elementDefine('list-manager')
145
- class ListManager extends HTMLElement {
146
- // Prepends a new item to the top of the #my-list element
147
- @appendChild('#my-list', { position: 'firstChild' })
148
- addNewItem(text: string) {
149
- return htmlDivElement`<li>${text}</li>`;
234
+ @elementDefine('styled-card')
235
+ class StyledCard extends HTMLElement {
236
+ @onConnectedInnerHtml({ useShadow: true })
237
+ render() {
238
+ return `
239
+ <style>
240
+ :host {
241
+ display: block;
242
+ padding: 20px;
243
+ border-radius: 8px;
244
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
245
+ }
246
+ </style>
247
+ <div class="card-content">
248
+ <slot></slot>
249
+ </div>
250
+ `;
150
251
  }
151
252
  }
152
253
  ```
153
254
 
255
+ **Options:**
256
+ - `useShadow: true` - Render to Shadow DOM (style encapsulation)
257
+ - `useShadow: false` - Render to Light DOM (default)
258
+
154
259
  ---
155
260
 
156
- ## 🛠 Template Utilities
261
+ ### Attribute & Property Decorators
157
262
 
158
- SWC provides powerful utilities to create DOM nodes using Tagged Template Literals. These are perfect for use with surgical decorators like `@replaceChildren`.
263
+ #### 10. `@attributeHost(name, options?)`
264
+ Bidirectional attribute binding with automatic type conversion.
159
265
 
160
- ### 1. `htmlFragment` & `textNode`
161
- - **`htmlFragment\`...\``**: Returns a `DocumentFragment` from an HTML string.
162
- - **`textNode\`...\``**: Returns a pure `Text` node.
266
+ ```typescript
267
+ @elementDefine('input-field')
268
+ class InputField extends HTMLElement {
269
+ @attributeHost('value', { connectedInitialize: true })
270
+ value: string = '';
163
271
 
164
- ### 2. Element Factories (`html***Element`)
165
- Specific factories for common tags like `htmlDivElement`, `htmlSpanElement`, `htmlButtonElement`, etc.
272
+ @attributeHost('disabled')
273
+ disabled: boolean = false;
166
274
 
167
- ```typescript
168
- import { htmlDivElement, htmlSpanElement } from '@dooboostore/simple-web-component';
275
+ @attributeHost('count')
276
+ count: number = 0;
169
277
 
170
- const el = htmlDivElement`<span>Hello ${name}</span>`; // Returns HTMLDivElement
278
+ @onConnectedInnerHtml
279
+ render() {
280
+ return `
281
+ <input type="text" value="${this.value}" ${this.disabled ? 'disabled' : ''}>
282
+ <p>Count: ${this.count}</p>
283
+ `;
284
+ }
285
+ }
171
286
  ```
172
287
 
173
- ### 3. `createElement(info, attributes)`
174
- Structured element creation.
288
+ **Options:**
289
+ - `connectedInitialize: true` - Push class value to DOM on connect
290
+ - `connectedInitialize: false` - Pull DOM value on connect (default)
291
+ - Auto-converts: `String`, `Number`, `Boolean`
292
+
293
+ #### 11. `@changedAttributeHost(name)`
294
+ Reactive handler triggered when attribute changes.
295
+
175
296
  ```typescript
176
- const el = createElement(
177
- { tagName: 'div', innerHtml: '<b>Bold</b>' },
178
- { class: 'my-class', 'data-id': '123' }
179
- );
297
+ @elementDefine('reactive-input')
298
+ class ReactiveInput extends HTMLElement {
299
+ private internalValue = '';
300
+
301
+ @changedAttributeHost('value')
302
+ onValueChange(newVal: string, oldVal: string) {
303
+ console.log(`Value changed: ${oldVal} → ${newVal}`);
304
+ this.internalValue = newVal;
305
+ this.updateDisplay();
306
+ }
307
+
308
+ updateDisplay() {
309
+ const input = this.querySelector('input') as HTMLInputElement;
310
+ if (input) input.value = this.internalValue;
311
+ }
312
+ }
180
313
  ```
181
314
 
182
- ---
315
+ #### 12. `@setAttribute(name, options?)`
316
+ Reflect method's return value to a DOM attribute. One-way binding (class → DOM).
183
317
 
184
- ## 🌐 Powerful SPA & Routing
318
+ ```typescript
319
+ @elementDefine('status-badge')
320
+ class StatusBadge extends HTMLElement {
321
+ private status: 'active' | 'inactive' | 'pending' = 'pending';
322
+
323
+ // Reflect status to DOM attribute
324
+ @setAttribute('data-status')
325
+ getStatus() {
326
+ return this.status;
327
+ }
185
328
 
186
- SWC provides a revolutionary way to build SPAs where **each component scope is an independent application**.
329
+ // Reflect computed data to attribute
330
+ @setAttribute('count')
331
+ getItemCount() {
332
+ return this.getItems().length.toString();
333
+ }
187
334
 
188
- ### 1. Isolated App Scopes (Micro-Frontends)
189
- Use the `<swc-app>` tag to create an isolated universe. Each instance has its own **DI Container** and **Router**.
335
+ // Reflect to Shadow DOM element
336
+ @setAttribute('aria-label', { root: 'shadow' })
337
+ getAriaLabel() {
338
+ return `Status is ${this.status}`;
339
+ }
190
340
 
191
- ```html
192
- <!-- index.html -->
193
- <swc-app swc-get-application-config="myAppConfig(this)"></swc-app>
341
+ private getItems() {
342
+ return [];
343
+ }
194
344
 
195
- <script>
196
- // This function returns the configuration needed to bootstrap the SWC App
197
- window.myAppConfig = (host) => {
198
- return {
199
- rootRouter: RootRouter, // The entry point class (must be @Sim)
200
- routeType: 'element', // 'element' (in-memory history), 'hash', or 'path'
201
- path: '/users/list', // Initial route path
202
- window: window // Optional: custom window object
203
- };
204
- };
345
+ @onConnectedInnerHtml
346
+ render() {
347
+ return `
348
+ <div class="badge" data-status="${this.status}">
349
+ Status: ${this.status}
350
+ </div>
351
+ `;
352
+ }
205
353
 
206
- // External Control
207
- function navigateFromOutside() {
208
- const app = document.querySelector('swc-app');
209
- app.routing('/users/detail/2');
354
+ // Change status and update DOM
355
+ setStatus(newStatus: typeof this.status) {
356
+ this.status = newStatus;
357
+ this.getStatus(); // Triggers @setAttribute update
210
358
  }
211
- </script>
359
+ }
212
360
  ```
213
361
 
214
- ### 2. DI-Powered Routing
362
+ **Key Differences:**
363
+ - **`@attributeHost`** - Two-way binding: DOM ↔ Class (Bidirectional)
364
+ - **`@setAttribute`** - One-way binding: Class → DOM (Method return reflects to attribute)
365
+ - **`@changedAttributeHost`** - Watch DOM attribute changes (Reactive handler)
366
+
367
+ **Options:**
368
+ - `root: 'light'` - Set attribute on light DOM element
369
+ - `root: 'shadow'` - Set attribute on shadow DOM element
370
+ - `root: 'auto'` - Auto-detect (default)
371
+
372
+ ---
373
+
374
+ ### Query & DOM Selection Decorators
375
+
376
+ #### 13. `@query(selector, options?)`
377
+ Inject a single element into a property.
378
+
215
379
  ```typescript
216
- @Sim
217
- @Router({
218
- path: '/users',
219
- route: { '/list': UserList, '/detail/:id': UserDetail }
220
- })
221
- @elementDefine('user-router')
222
- class UserRouter extends HTMLElement implements RouterAction.CanActivate {
380
+ @elementDefine('form-handler')
381
+ class FormHandler extends HTMLElement {
382
+ @query('#submit-btn') submitBtn?: HTMLButtonElement;
383
+ @query('.form-field', { root: 'shadow' }) formField?: HTMLElement;
384
+ @query(':host') self?: HTMLElement;
385
+
223
386
  @onConnectedInnerHtml({ useShadow: true })
224
- render() {
225
- return `<header>User System</header><main><slot></slot></main>`;
387
+ render() {
388
+ return `
389
+ <div class="form-field">
390
+ <input type="text" placeholder="Enter name">
391
+ <button id="submit-btn">Submit</button>
392
+ </div>
393
+ `;
226
394
  }
395
+ }
396
+ ```
227
397
 
228
- async canActivate(url: RoutingDataSet, data?: any) {
229
- if (data instanceof Node) this.replaceChildren(data);
398
+ **Root Options:**
399
+ - `'auto'` (default) - Search shadow DOM first, then light DOM
400
+ - `'light'` - Search only light DOM
401
+ - `'shadow'` - Search only shadow DOM
402
+ - `'all'` - Search both, return `[lightElements, shadowElements]`
403
+
404
+ **Special Targets:**
405
+ ```typescript
406
+ @query(':host') self?: HTMLElement; // Element itself
407
+ @query(':parentHost') parent?: HTMLElement; // Parent SWC component
408
+ @query(':appHost') app?: SwcAppInterface; // Nearest <swc-app>
409
+ @query(':window') win?: Window; // Global window
410
+ @query(':document') doc?: Document; // Global document
411
+ ```
412
+
413
+ #### 14. `@queryAll(selector, options?)`
414
+ Inject multiple elements as an array.
415
+
416
+ ```typescript
417
+ @elementDefine('list-renderer')
418
+ class ListRenderer extends HTMLElement {
419
+ @queryAll('.item') items?: HTMLElement[];
420
+ @queryAll('button', { root: 'shadow' }) buttons?: HTMLButtonElement[];
421
+
422
+ getItemCount() {
423
+ return this.items?.length ?? 0;
230
424
  }
231
425
  }
426
+ ```
232
427
 
233
- @Sim
234
- class UserService {
235
- async getProfile(id: string) { return { id, name: 'Standard User' }; }
428
+ ---
429
+
430
+ ### Event & Messaging Decorators
431
+
432
+ #### 15. `@addEventListener(selector, event, options?)`
433
+ Declarative event binding with automatic delegation.
434
+
435
+ ```typescript
436
+ @elementDefine('todo-app')
437
+ class TodoApp extends HTMLElement {
438
+ private todos: any[] = [];
439
+
440
+ // Direct binding to specific element
441
+ @addEventListener('#add-btn', 'click')
442
+ onAddTodo(event: MouseEvent) {
443
+ console.log('Add button clicked');
444
+ }
445
+
446
+ // Event delegation for dynamic elements
447
+ @addEventListener('.todo-item', 'click', { delegate: true })
448
+ onTodoClick(event: MouseEvent, hostSet: any, target: HTMLElement) {
449
+ const itemId = target.getAttribute('data-id');
450
+ console.log('Clicked todo:', itemId);
451
+ }
452
+
453
+ // Global window events
454
+ @addEventListener(':window', 'resize', { removeOnDisconnected: true })
455
+ onWindowResize(event: Event) {
456
+ console.log('Window resized');
457
+ }
458
+
459
+ // Parent app events
460
+ @addEventListener(':appHost', 'custom-event')
461
+ onCustomEvent(event: CustomEvent) {
462
+ console.log('Event received:', event.detail);
463
+ }
464
+
465
+ @onConnectedInnerHtml
466
+ render() {
467
+ return `
468
+ <div>
469
+ <input type="text" id="todo-input" placeholder="Add todo...">
470
+ <button id="add-btn">Add</button>
471
+ <ul class="todo-list">
472
+ ${this.todos.map(t => `
473
+ <li class="todo-item" data-id="${t.id}">${t.text}</li>
474
+ `).join('')}
475
+ </ul>
476
+ </div>
477
+ `;
478
+ }
236
479
  }
480
+ ```
237
481
 
238
- @Sim
239
- @elementDefine('user-list')
240
- class UserList extends HTMLElement {
241
- constructor(private router: Router, private userService: UserService) { super(); }
482
+ **Features:**
483
+ - ✅ Direct binding to static selectors
484
+ - ✅ Event delegation for dynamic elements
485
+ - ✅ Global events (`:window`, `:document`)
486
+ - ✅ Parent communication (`:parentHost`, `:appHost`)
487
+ - ✅ Auto-cleanup on `disconnectedCallback`
488
+
489
+ #### 16. `@emitCustomEvent(target, type, options?)`
490
+ Emit custom events to component hierarchy or global targets.
491
+
492
+ ```typescript
493
+ @elementDefine('auth-panel')
494
+ class AuthPanel extends HTMLElement {
495
+ constructor(private authService: AuthService) { super(); }
496
+
497
+ // Emit to parent app when user logs in
498
+ @emitCustomEvent(':appHost', 'user-logged-in')
499
+ async onLogin() {
500
+ const user = await this.authService.login();
501
+ return { user, timestamp: Date.now() };
502
+ }
503
+
504
+ // Emit globally when logout happens
505
+ @emitCustomEvent(':window', 'user-logged-out')
506
+ async onLogout() {
507
+ await this.authService.logout();
508
+ return { timestamp: Date.now() };
509
+ }
510
+
511
+ // Emit to direct parent
512
+ @emitCustomEvent(':parentHost', 'child-ready')
513
+ notifyParent() {
514
+ return { childElement: this };
515
+ }
242
516
 
243
517
  @onConnectedInnerHtml
244
- render() {
518
+ render() {
245
519
  return `
246
- <ul><li>User A</li><li>User B</li></ul>
247
- <button swc-on-click="$host.router.go('/users/detail/1')">Go to Detail</button>
248
- `;
520
+ <div>
521
+ <button swc-on-click="$host.onLogin()">Login</button>
522
+ <button swc-on-click="$host.onLogout()">Logout</button>
523
+ </div>
524
+ `;
249
525
  }
250
526
  }
251
527
 
252
- @Sim
253
- @elementDefine('user-detail')
254
- class UserDetail extends HTMLElement {
255
- constructor(private router: Router, private userService: UserService) { super(); }
528
+ // Listen to the emitted event
529
+ @elementDefine('user-widget')
530
+ class UserWidget extends HTMLElement {
531
+ private user: any = null;
256
532
 
257
- @onConnected
258
- async init() {
259
- const id = this.router.getIntent()?.params?.id;
260
- const profile = await this.userService.getProfile(id);
261
- console.log('Viewing User:', profile.name);
533
+ @addEventListener(':appHost', 'user-logged-in')
534
+ onUserLoggedIn(event: CustomEvent) {
535
+ this.user = event.detail.user;
536
+ this.render();
537
+ }
538
+
539
+ @onConnectedInnerHtml
540
+ render() {
541
+ return `<p>User: ${this.user?.name ?? 'Anonymous'}</p>`;
542
+ }
543
+ }
544
+ ```
545
+
546
+ **Emission Targets:**
547
+ - `':appHost'` - Nearest parent `<swc-app>` (contained within app)
548
+ - `':parentHost'` - Direct parent SWC component
549
+ - `':firstHost'` / `':lastHost'` - First/last component in hierarchy
550
+ - `':firstAppHost'` / `':lastAppHost'` - First/last `<swc-app>` in hierarchy
551
+ - `':hosts'` - All SWC ancestors
552
+ - `':appHosts'` - All `<swc-app>` ancestors
553
+ - `':window'` - Global window (all apps can listen)
554
+ - `':document'` - Global document
555
+ - `selector` - CSS selector (emit to matching elements)
556
+
557
+ **Options:**
558
+ - `bubbles: true` (default) - Event bubbles up
559
+ - `composed: true` (default) - Event crosses Shadow DOM boundary
560
+ - `cancelable: true` - Event can be canceled
561
+ - `root: 'auto' | 'light' | 'shadow'` - Where to find target elements
562
+
563
+ ---
564
+
565
+ ### DOM Manipulation Decorators
566
+
567
+ #### 17. `@applyNode(selector, options?)`
568
+ Surgically add, update, or replace DOM nodes at specific positions.
569
+
570
+ ```typescript
571
+ @elementDefine('counter-display')
572
+ class CounterDisplay extends HTMLElement {
573
+ private count = 0;
574
+
575
+ // Replace all children with new content
576
+ @applyNode('#display', { position: 'replaceChildren' })
577
+ updateDisplayCount() {
578
+ return `<span>${this.count}</span>`;
579
+ }
580
+
581
+ // Append to container
582
+ @applyNode('#history', { position: 'beforeEnd' })
583
+ addHistoryEntry() {
584
+ return `<p>Count was: ${this.count}</p>`;
585
+ }
586
+
587
+ // Set HTML content
588
+ @applyNode('#status', { position: 'innerHtml' })
589
+ setStatus() {
590
+ return `<strong>Status: Active</strong>`;
591
+ }
592
+
593
+ // Set text content
594
+ @applyNode('#counter', { position: 'innerText' })
595
+ updateCounter() {
596
+ return this.count.toString();
262
597
  }
263
598
 
264
599
  @onConnectedInnerHtml
265
- render() { return `<div>User Detail View <button swc-on-click="$host.router.back()">Back</button></div>`; }
600
+ render() {
601
+ return `
602
+ <div id="display">0</div>
603
+ <div id="counter">0</div>
604
+ <div id="status"></div>
605
+ <button swc-on-click="$host.count++; $host.updateDisplayCount?.()">+</button>
606
+ <div id="history"></div>
607
+ `;
608
+ }
266
609
  }
267
610
  ```
268
611
 
612
+ **Position Options:**
613
+ - `'beforeEnd'` (default) - Append to end
614
+ - `'afterBegin'` - Prepend to start
615
+ - `'beforeBegin'` - Insert before element
616
+ - `'afterEnd'` - Insert after element
617
+ - `'replace'` - Replace entire element
618
+ - `'replaceChildren'` - Replace all children
619
+ - `'innerHtml'` - Set `innerHTML`
620
+ - `'innerText'` - Set `innerText`
621
+
269
622
  ---
270
623
 
271
- ## ✉️ Integrated Messaging System
624
+ ### Dependency Injection & Singleton
272
625
 
273
- SWC leverages its **Special Selectors** to provide a powerful, scoped messaging system using native browser events without any external manager.
626
+ #### 18. `@Sim`
627
+ Marks a class as a **Singleton** in the `<swc-app>` DI container for **constructor injection**.
274
628
 
275
- ### Scoped Messaging Example
276
- - **`:appHost`**: Communicate within the same Micro-App (Isolated scope).
277
- - **`:window` / `:document`**: Broadcast to the entire page (Global scope).
278
- - **`:parentHost`**: Send a direct message to the immediate parent host.
279
- - **`:firstHost` / `:firstAppHost`**: Target the absolute root component or the top-level SPA.
629
+ **Two DI Mechanisms:**
630
+
631
+ 1. **Lifecycle DI** (`@onBeforeConnected`, etc.) - ❌ NO `@Sim` needed
632
+ - Works on any `@elementDefine` component
633
+ - Services injected via method parameters
634
+ - Can be used outside `<swc-app>`
635
+
636
+ 2. **Constructor DI** - ✅ `@Sim` REQUIRED
637
+ - Only works inside `<swc-app>` boundary
638
+ - Both provider AND consumer need `@Sim`
639
+ - Singleton pattern within the app
640
+
641
+ **Example: Both Approaches**
280
642
 
281
643
  ```typescript
282
- @elementDefine('message-sender')
283
- class Sender extends HTMLElement {
284
- // Dispatches a message to the nearest parent <swc-app>
285
- @emitCustomEvent(':appHost', 'user-login')
286
- login() { return { id: 1, name: 'John' }; }
644
+ // ============ SERVICE ============
645
+ @Sim
646
+ class CartService {
647
+ private items: any[] = [];
648
+
649
+ addItem(item: any) {
650
+ this.items.push(item);
651
+ }
652
+
653
+ getItems() {
654
+ return this.items;
655
+ }
656
+ }
657
+
658
+ // ============ APPROACH 1: Lifecycle DI (NO @Sim needed) ============
659
+ @elementDefine('cart-header') // ← NO @Sim
660
+ class CartHeader extends HTMLElement {
661
+ #cartService: CartService;
662
+
663
+ @onBeforeConnected
664
+ async setup(cartService: CartService) { // ← Injected here
665
+ this.#cartService = cartService;
666
+ await this.#cartService.load?.();
667
+ }
668
+
669
+ @onConnectedInnerHtml
670
+ render() {
671
+ return `<div>Cart: ${this.#cartService?.getItems().length}</div>`;
672
+ }
673
+ }
674
+
675
+ // ============ APPROACH 2: Constructor DI (NEEDS @Sim) ============
676
+ @Sim // ← Required for constructor injection
677
+ @elementDefine('cart-component')
678
+ class CartComponent extends HTMLElement {
679
+ // Constructor injection - CartService is auto-injected
680
+ constructor(private cartService: CartService) { super(); }
681
+
682
+ @onConnectedInnerHtml
683
+ render() {
684
+ const itemCount = this.cartService.getItems().length;
685
+ return `<div>Items: ${itemCount}</div>`;
686
+ }
287
687
  }
288
688
 
289
- @elementDefine('message-receiver')
290
- class Receiver extends HTMLElement {
291
- // Listens for messages from the parent <swc-app>
292
- @addEventListener(':appHost', 'user-login')
293
- onLogin(e: CustomEvent) {
294
- console.log('User logged in within my app:', e.detail.name);
689
+ // ============ ROUTING & BOOTSTRAP ============
690
+ @Sim
691
+ @Router({
692
+ path: '/',
693
+ route: {
694
+ '/home': HomePage,
695
+ '/cart': CartComponent // Constructor DI works here
696
+ }
697
+ })
698
+ @elementDefine('app-router')
699
+ class AppRouter extends HTMLElement {
700
+ @onConnectedInnerHtml
701
+ render() {
702
+ return `<div><slot></slot></div>`;
295
703
  }
296
704
  }
705
+
706
+ // ============ HTML ============
707
+ <!-- Lifecycle DI works OUTSIDE <swc-app> -->
708
+ <cart-header></cart-header>
709
+
710
+ <!-- Constructor DI works INSIDE <swc-app> -->
711
+ <swc-app swc-get-application-config="appConfig()">
712
+ <app-router></app-router>
713
+ </swc-app>
714
+
715
+ <script>
716
+ window.appConfig = () => ({
717
+ rootRouter: AppRouter,
718
+ routeType: 'element',
719
+ path: '/'
720
+ });
721
+ </script>
297
722
  ```
298
723
 
724
+ **When to Use Each:**
725
+
726
+ | Scenario | Use | Mark |
727
+ |----------|-----|------|
728
+ | Component needs services anywhere | Lifecycle DI in `@onBeforeConnected` | NO `@Sim` |
729
+ | Component inside `<swc-app>` route | Constructor DI | YES `@Sim` |
730
+ | Service/Singleton registration | Register in DI container | YES `@Sim` |
731
+ | Standalone component | Lifecycle DI | NO `@Sim` |
732
+
733
+ **Key Points:**
734
+ - `@Sim` = Mark as singleton for **constructor injection inside `<swc-app>`**
735
+ - Lifecycle DI = Inject via method parameters (works everywhere, no `@Sim`)
736
+ - Each `<swc-app>` has its own isolated DI container
737
+ - Services are local to their `<swc-app>` instance (for constructor DI)
738
+
299
739
  ---
300
740
 
301
- ## 🏰 Host Hierarchy & Context Hierarchy & Declarative Events
302
- SWC automatically traverses the DOM to provide meaningful context to your scripts and listeners:
741
+ ## Core Decorators
303
742
 
304
- - **`$host`**: The current component Handling the event (**Self**).
305
- - **`$parentHost`**: The direct parent SWC component.
306
- - **`$appHost`**: The nearest parent SPA boundary.
307
- - **`$hosts`**: Array of all SWC ancestors `[Root, ..., Parent, Self]`.
743
+ #### Root Options
744
+ The `root` option controls which DOM tree is searched:
745
+ - **`'auto'`** (default): Searches shadow DOM first, then light DOM
746
+ - **`'light'`**: Searches only light DOM
747
+ - **`'shadow'`**: Searches only shadow DOM
748
+ - **`'all'`**: Searches all ancestors, returning `[lightNodes, shadowNodes]` array
749
+
750
+ #### Usage with Special Targets:
751
+ Special targets (starting with `:`) navigate the hierarchy and environment.
752
+ ```typescript
753
+ @query(':host') self!: HTMLElement;
754
+ @query(':parentHost') parent!: HTMLElement;
755
+ @query(':appHost') app!: SwcAppInterface;
756
+ @query(':window') win!: Window;
757
+ @query(':document') doc!: Document;
758
+ @queryAll(':hosts') ancestors!: HTMLElement[];
759
+ ```
308
760
 
309
- ### Declarative Inline Events (`swc-on-*`)
310
- Bind events directly in HTML. These scripts have full access to the context above and the actual element via **`$el`**.
761
+ #### Usage with Selectors & Root Options:
762
+ Standard CSS selectors support precise root control.
763
+ ```typescript
764
+ // root options: 'light' | 'shadow' | 'all' | 'auto' (default)
765
+ @query('.btn', { root: 'shadow' }) shadowBtn!: HTMLButtonElement;
766
+ ```
311
767
 
312
- ```html
313
- <template is="swc-loop" on-get-value="users">
314
- <!-- The script can access $item, $index, $host, and $el -->
315
- <button swc-on-click="console.log('Clicked:', $el); $host.deleteUser($index)">
316
- Delete Item
317
- </button>
318
- </template>
768
+ ### 5. Event Binding (`@addEventListener`)
769
+
770
+ Bind events declaratively with built-in **Event Delegation**, **Auto-Cleanup**, and **Context Injection**. Events flow through the component hierarchy with full access to parent scopes.
771
+
772
+ #### Core Features:
773
+ - **Direct Binding**: Bind to static selectors present at initialization
774
+ - **Event Delegation**: Automatically handle dynamically added elements
775
+ - **Auto-Cleanup**: Automatically remove listeners on `disconnectedCallback`
776
+ - **Context Injection**: Receive `$host`, `$parentHost`, `$appHost` automatically
777
+ - **Special Targets**: `:window`, `:document`, `:parentHost`, `:appHost`
778
+
779
+ #### 6. Surgical DOM Manipulation (`@applyNode`)
780
+
781
+ Surgically add, prepend, insert, or replace nodes with a single decorator. Perfect for dynami content updates without imperatively manipulating the DOM.
782
+
783
+ ```typescript
784
+ @elementDefine('list-manager')
785
+ class ListManager extends HTMLElement {
786
+ @applyNode('#list') addItem() {
787
+ return '<li>New Item</li>';
788
+ }
789
+
790
+ @applyNode('#header', { position: 'afterBegin' }) prependTitle() {
791
+ return '<h1>Title</h1>';
792
+ }
793
+
794
+ @applyNode('#item', { position: 'replace' }) replaceItem() {
795
+ return '<div>Replaced Content</div>';
796
+ }
797
+ }
319
798
  ```
320
799
 
800
+ **Positions:**
801
+ - `'beforeEnd'` (default): Append to end of target
802
+ - `'afterBegin'`: Prepend to start of target
803
+ - `'beforeBegin'`: Insert before target
804
+ - `'afterEnd'`: Insert after target
805
+ - `'replace'`: Replace entire target element
806
+ - `'replaceChildren'`: Replace all children of target element
807
+ - `'innerHtml'`: Set target's `innerHTML` with string content
808
+ - `'innerText'`: Set target's `innerText` with string content
809
+
321
810
  ---
322
811
 
323
- ## 🔄 Structural Directives (Advanced)
812
+ ### 6. DOM Manipulation (`@applyNode`)
324
813
 
325
- SWC provides 4 core templates that empower your HTML without wrapper elements: `swc-loop`, `swc-if`, `swc-choose`, and `swc-async`. They all share a consistent, high-performance API.
814
+ Surgically add, prepend, insert, or replace nodes. Perfect for reactive updates without full re-renders.
326
815
 
327
- ### Common Attributes:
328
- - **`on-get-value`**: Script to fetch data (supports return-less expressions).
329
- - **`on-clone-nodes`**: Callback after a group of nodes is cloned (Args: `$index`, `$item`, `$nodes`, `$elements`, `$firstElement`, `$state`).
330
- - **`on-clone-node`**: Callback for each individual node (Args: `$node`, `$nodeIndex`, and all group args).
331
- - **`on-get-portal`**: Script that returns an `Element` or selector string to render content in a different part of the DOM.
816
+ #### Pattern 1: Direct Binding & Static Selectors
332
817
 
333
- ### 1. `swc-if` (Conditional Rendering)
334
- ```html
335
- <template is="swc-if" on-get-value="isAdmin">
336
- <template is="swc-default"><p>Please login as admin.</p></template>
337
- <div class="admin-panel">Welcome, Master!</div>
338
- </template>
818
+ ```typescript
819
+ @Sim
820
+ @elementDefine('form-handler')
821
+ class FormHandler extends HTMLElement {
822
+ @query('#submit-btn') submitBtn?: HTMLButtonElement;
823
+ @query('#form') form?: HTMLFormElement;
824
+
825
+ // Bind to a specific static element
826
+ @addEventListener('#submit-btn', 'click')
827
+ onSubmit(event: MouseEvent) {
828
+ event.preventDefault();
829
+ console.log('Form submitted');
830
+ this.form?.submit();
831
+ }
832
+
833
+ // Bind to multiple elements with selector
834
+ @addEventListener('input[type="text"]', 'input')
835
+ onTextInput(event: Event) {
836
+ console.log('Input value:', (event.target as HTMLInputElement).value);
837
+ }
838
+
839
+ @onConnectedInnerHtml
840
+ render() {
841
+ return `
842
+ <form id="form">
843
+ <input type="text" placeholder="Enter name">
844
+ <input type="email" placeholder="Enter email">
845
+ <button id="submit-btn">Submit</button>
846
+ </form>
847
+ `;
848
+ }
849
+ }
339
850
  ```
340
851
 
341
- ### 2. `swc-loop` (High-Performance Iteration)
342
- ```html
343
- <template is="swc-loop" on-get-value="users"
344
- on-clone-node="if($isElement) $node.querySelector('.name').textContent = $item.name">
345
- <template is="swc-default"><p>No users found.</p></template>
346
- <div class="user-row"><span class="name"></span></div>
347
- </template>
852
+ #### Pattern 2: Event Delegation (Dynamic Content)
853
+
854
+ Automatically handle elements added **after component initialization**.
855
+
856
+ ```typescript
857
+ @Sim
858
+ @elementDefine('todo-app')
859
+ class TodoApp extends HTMLElement {
860
+ private todos: any[] = [];
861
+
862
+ constructor(private todoService: TodoService) { super(); }
863
+
864
+ // Delegate: Handles all current AND future .todo-item elements
865
+ @addEventListener('.todo-item', 'click', { delegate: true })
866
+ onTodoClick(event: MouseEvent, hostSet: any, target: HTMLElement) {
867
+ const todoId = target.getAttribute('data-id');
868
+ console.log('Clicked todo:', todoId);
869
+ this.selectTodo(todoId);
870
+ }
871
+
872
+ // Event delegation with checkbox - even newly added items react
873
+ @addEventListener('.todo-checkbox', 'change', { delegate: true })
874
+ onTodoToggle(event: Event, hostSet: any, target: HTMLInputElement) {
875
+ const todoId = target.closest('.todo-item')?.getAttribute('data-id');
876
+ const isChecked = target.checked;
877
+ console.log(`Todo ${todoId} ${isChecked ? 'completed' : 'uncompleted'}`);
878
+ }
879
+
880
+ async addTodo(text: string) {
881
+ const newTodo = await this.todoService.create(text);
882
+ this.todos.push(newTodo);
883
+ this.render();
884
+ // New .todo-item elements automatically get event listeners!
885
+ }
886
+
887
+ selectTodo(id: string) {
888
+ const todo = this.todos.find(t => t.id === id);
889
+ console.log('Selected:', todo);
890
+ }
891
+
892
+ @onConnectedInnerHtml
893
+ render() {
894
+ return `
895
+ <div>
896
+ <input type="text" placeholder="Add todo">
897
+ <button swc-on-click="$host.addTodo(this.previousElementSibling.value)">Add</button>
898
+ <ul>
899
+ ${this.todos.map(t => `
900
+ <li class="todo-item" data-id="${t.id}">
901
+ <input type="checkbox" class="todo-checkbox" ${t.done ? 'checked' : ''}>
902
+ <span>${t.text}</span>
903
+ </li>
904
+ `).join('')}
905
+ </ul>
906
+ </div>
907
+ `;
908
+ }
909
+ }
910
+ ```
911
+
912
+ #### Pattern 3: Global Events with Context Access
913
+
914
+ ```typescript
915
+ @Sim
916
+ @elementDefine('responsive-layout')
917
+ class ResponsiveLayout extends HTMLElement {
918
+ private isMobile = false;
919
+
920
+ // Listen to window resize with auto-cleanup on disconnect
921
+ @addEventListener(':window', 'resize', { removeOnDisconnected: true })
922
+ onWindowResize() {
923
+ this.isMobile = window.innerWidth < 768;
924
+ this.updateLayout();
925
+ }
926
+
927
+ // Listen to global scroll with host context
928
+ @addEventListener(':window', 'scroll', { removeOnDisconnected: true })
929
+ onScroll(event: Event, { $appHost }: any) {
930
+ const scrollPos = window.scrollY;
931
+ console.log('Scrolled in app:', $appHost?.tagName, 'Position:', scrollPos);
932
+ }
933
+
934
+ updateLayout() {
935
+ this.className = this.isMobile ? 'mobile-layout' : 'desktop-layout';
936
+ }
937
+
938
+ @onConnectedInnerHtml
939
+ render() {
940
+ return `<div class="${this.isMobile ? 'mobile' : 'desktop'}"><slot></slot></div>`;
941
+ }
942
+ }
943
+ ```
944
+
945
+ ### 7. Dynamic Content Updates with `@applyNode`
946
+
947
+ #### Advanced Patterns
948
+
949
+ **Pattern 1: Simple Content Replacement with `replaceChildren`**
950
+
951
+ ```typescript
952
+ @Sim
953
+ @elementDefine('search-results')
954
+ class SearchResults extends HTMLElement {
955
+ private results: any[] = [];
956
+ private isLoading = false;
957
+
958
+ // Replace #results-container with new search results
959
+ @replaceChildren('#results-container')
960
+ async displayResults(query: string) {
961
+ this.isLoading = true;
962
+ const startTime = Date.now();
963
+
964
+ try {
965
+ this.results = await fetch(`/api/search?q=${query}`)
966
+ .then(r => r.json());
967
+
968
+ return htmlFragment`
969
+ <div class="results-header">
970
+ <p>Found ${this.results.length} results in ${Date.now() - startTime}ms</p>
971
+ </div>
972
+ <div class="results-list">
973
+ ${this.results.map(item => `
974
+ <div class="result-item" data-id="${item.id}">
975
+ <h3>${item.title}</h3>
976
+ <p>${item.description}</p>
977
+ <a href="${item.url}">View</a>
978
+ </div>
979
+ `).join('')}
980
+ </div>
981
+ `;
982
+ } catch (error) {
983
+ return htmlFragment`<p class="error">Search failed: ${error.message}</p>`;
984
+ }
985
+ }
986
+
987
+ @onConnectedInnerHtml
988
+ render() {
989
+ return `
990
+ <div>
991
+ <input type="text" class="search-input" placeholder="Search...">
992
+ <button swc-on-click="$host.search($el.previousElementSibling.value)">Search</button>
993
+ <div id="results-container">
994
+ <p>Enter search term...</p>
995
+ </div>
996
+ </div>
997
+ `;
998
+ }
999
+
1000
+ search(query: string) {
1001
+ if (query.trim()) {
1002
+ this.displayResults(query);
1003
+ }
1004
+ }
1005
+ }
1006
+ ```
1007
+
1008
+ **Pattern 2: Setting HTML Content**
1009
+
1010
+ ```typescript
1011
+ @applyNode('#message', { position: 'innerHtml' })
1012
+ displayMessage(text: string) {
1013
+ return `<p style="color: blue;">${text}</p>`;
1014
+ }
1015
+
1016
+ @applyNode('#count', { position: 'innerText' })
1017
+ updateCounter() {
1018
+ return this.count.toString();
1019
+ }
1020
+ ```
1021
+
1022
+ **Pattern 3: Tab Content Switching
1023
+
1024
+ ```typescript
1025
+ @Sim
1026
+ @elementDefine('tabbed-panel')
1027
+ class TabbedPanel extends HTMLElement {
1028
+ private activeTab = 'overview';
1029
+ private tabData: any = {
1030
+ overview: '<h3>Overview</h3><p>General information...</p>',
1031
+ details: '<h3>Details</h3><p>Detailed specifications...</p>',
1032
+ reviews: '<h3>Reviews</h3><p>Customer reviews...</p>'
1033
+ };
1034
+
1035
+ // Click tab and automatically replace content
1036
+ @replaceChildren('.tab-content')
1037
+ switchTab(tabName: string) {
1038
+ this.activeTab = tabName;
1039
+
1040
+ // Update tab buttons visual state
1041
+ Array.from(this.querySelectorAll('.tab-btn')).forEach(btn => {
1042
+ btn.classList.toggle('active', btn.getAttribute('data-tab') === tabName);
1043
+ });
1044
+
1045
+ // Return new content
1046
+ return htmlFragment`${this.tabData[tabName]}`;
1047
+ }
1048
+
1049
+ @onConnectedInnerHtml
1050
+ render() {
1051
+ return `
1052
+ <div class="tabs">
1053
+ <button class="tab-btn active" data-tab="overview"
1054
+ swc-on-click="$host.switchTab('overview')">Overview</button>
1055
+ <button class="tab-btn" data-tab="details"
1056
+ swc-on-click="$host.switchTab('details')">Details</button>
1057
+ <button class="tab-btn" data-tab="reviews"
1058
+ swc-on-click="$host.switchTab('reviews')">Reviews</button>
1059
+ </div>
1060
+ <div class="tab-content">${this.tabData.overview}</div>
1061
+ `;
1062
+ }
1063
+ }
1064
+ ```
1065
+
1066
+ ### 8. `@appendChild(selector, options?)`
1067
+
1068
+ Appends or inserts the method's return value relative to a target element. Perfect for dynamically building lists, stacking notifications, or adding children without replacing existing content.
1069
+
1070
+ **Position Options:**
1071
+ - **`'lastChild'`** (default): Append to end
1072
+ - **`'firstChild'`**: Prepend to beginning
1073
+ - **`'nextSibling'`**: Insert after element
1074
+ - **`'previousSibling'`**: Insert before element
1075
+
1076
+ #### Pattern 1: Dynamic List Building
1077
+
1078
+ ```typescript
1079
+ @Sim
1080
+ @elementDefine('task-list')
1081
+ class TaskList extends HTMLElement {
1082
+ private tasks: any[] = [];
1083
+ private taskIdCounter = 0;
1084
+
1085
+ // Append new task to bottom of list
1086
+ @appendChild('#task-list', { position: 'lastChild' })
1087
+ addTask(title: string, priority: 'low' | 'medium' | 'high' = 'medium') {
1088
+ const id = ++this.taskIdCounter;
1089
+ const bgColor = { low: '#e8f5e9', medium: '#fff3e0', high: '#ffebee' }[priority];
1090
+
1091
+ return htmlFragment`
1092
+ <div class="task-item" data-id="${id}" style="background: ${bgColor}; padding: 12px; margin: 8px 0; border-radius: 4px;">
1093
+ <div class="task-header">
1094
+ <input type="checkbox" class="task-checkbox" data-id="${id}">
1095
+ <span class="task-title">${title}</span>
1096
+ <span class="task-priority">${priority.toUpperCase()}</span>
1097
+ </div>
1098
+ <button class="task-delete" swc-on-click="$host.deleteTask(${id})">🗑️ Delete</button>
1099
+ </div>
1100
+ `;
1101
+ }
1102
+
1103
+ // Prepend urgent task to top of list
1104
+ @appendChild('#task-list', { position: 'firstChild' })
1105
+ addUrgentTask(title: string) {
1106
+ return htmlFragment`
1107
+ <div class="task-item urgent" style="background: #ffcdd2; padding: 12px; border: 2px solid #f44336;">
1108
+ <strong>🚨 URGENT: ${title}</strong>
1109
+ <button swc-on-click="$host.deleteTask()">Close</button>
1110
+ </div>
1111
+ `;
1112
+ }
1113
+
1114
+ deleteTask(id?: number) {
1115
+ console.log('Delete task:', id);
1116
+ }
1117
+
1118
+ @onConnectedInnerHtml
1119
+ render() {
1120
+ return `
1121
+ <div>
1122
+ <div class="task-input">
1123
+ <input type="text" id="new-task" placeholder="Enter task...">
1124
+ <select id="priority">
1125
+ <option value="low">Low</option>
1126
+ <option value="medium" selected>Medium</option>
1127
+ <option value="high">High</option>
1128
+ </select>
1129
+ <button swc-on-click="
1130
+ const input = document.getElementById('new-task');
1131
+ const priority = document.getElementById('priority').value;
1132
+ if (priority === 'high') {
1133
+ $host.addUrgentTask(input.value);
1134
+ } else {
1135
+ $host.addTask(input.value, priority);
1136
+ }
1137
+ input.value = '';
1138
+ input.focus();
1139
+ ">Add Task</button>
1140
+ </div>
1141
+ <div id="task-list"></div>
1142
+ </div>
1143
+ `;
1144
+ }
1145
+ }
1146
+ ```
1147
+
1148
+ #### Pattern 2: Notification Stack
1149
+
1150
+ ```typescript
1151
+ @Sim
1152
+ @elementDefine('notification-stack')
1153
+ class NotificationStack extends HTMLElement {
1154
+ private notificationId = 0;
1155
+
1156
+ // Append notifications to stack (they auto-dismiss)
1157
+ @appendChild('.notifications', { position: 'lastChild' })
1158
+ notify(message: string, type: 'info' | 'success' | 'warning' | 'error' = 'info', duration = 5000) {
1159
+ const id = ++this.notificationId;
1160
+ const bgColor = { info: '#e3f2fd', success: '#e8f5e9', warning: '#fff3e0', error: '#ffebee' }[type];
1161
+ const borderColor = { info: '#2196f3', success: '#4caf50', warning: '#ff9800', error: '#f44336' }[type];
1162
+
1163
+ // Auto-remove after duration
1164
+ setTimeout(() => {
1165
+ const el = document.querySelector(`[data-notify-id="${id}"]`);
1166
+ if (el) el.remove();
1167
+ }, duration);
1168
+
1169
+ return htmlFragment`
1170
+ <div class="notification" data-notify-id="${id}"
1171
+ style="background: ${bgColor}; border-left: 4px solid ${borderColor}; padding: 16px; margin: 8px 0; border-radius: 4px; display: flex; justify-content: space-between;">
1172
+ <span>${message}</span>
1173
+ <button swc-on-click="this.closest('[data-notify-id]').remove()"
1174
+ style="background: none; border: none; cursor: pointer; font-size: 18px;">×</button>
1175
+ </div>
1176
+ `;
1177
+ }
1178
+
1179
+ @onConnectedInnerHtml
1180
+ render() {
1181
+ return `
1182
+ <div class="notifications" style="position: fixed; top: 20px; right: 20px; width: 300px; z-index: 1000;"></div>
1183
+ `;
1184
+ }
1185
+ }
1186
+
1187
+ // Usage
1188
+ const notifier = document.querySelector('notification-stack');
1189
+ notifier.notify('Operation successful!', 'success');
1190
+ notifier.notify('Something went wrong', 'error');
1191
+ ```
1192
+
1193
+ ---
1194
+
1195
+ ## 🛠 Template Utilities
1196
+
1197
+ SWC provides powerful utilities to create DOM nodes using **Tagged Template Literals**. These are perfect for use with surgical decorators like `@replaceChildren`, `@appendChild`, and `@replaceChildren`.
1198
+
1199
+ ### Complete Factory List
1200
+
1201
+ SWC provides element factories for **all common HTML tags**:
1202
+
1203
+ ```typescript
1204
+ import {
1205
+ htmlFragment, textNode,
1206
+ htmlDivElement, htmlSpanElement, htmlButtonElement,
1207
+ htmlInputElement, htmlFormElement,
1208
+ htmlTableElement, htmlTrElement, htmlTdElement,
1209
+ htmlUlElement, htmlLiElement,
1210
+ htmlHeadingElement, htmlImageElement,
1211
+ // ... 50+ more factories
1212
+ } from '@dooboostore/simple-web-component';
1213
+ ```
1214
+
1215
+ ### 1. `htmlFragment` - DOM Fragments
1216
+
1217
+ Returns a `DocumentFragment` from an HTML string. Perfect for multi-element insertion.
1218
+
1219
+ ```typescript
1220
+ @Sim
1221
+ @elementDefine('fragment-demo')
1222
+ class FragmentDemo extends HTMLElement {
1223
+ @replaceChildren('#container')
1224
+ renderMultipleElements() {
1225
+ // All elements wrapped in one fragment for efficient insertion
1226
+ return htmlFragment`
1227
+ <h2>Title</h2>
1228
+ <p>First paragraph</p>
1229
+ <p>Second paragraph</p>
1230
+ <ul>
1231
+ <li>Item 1</li>
1232
+ <li>Item 2</li>
1233
+ </ul>
1234
+ `;
1235
+ }
1236
+
1237
+ @onConnectedInnerHtml
1238
+ render() {
1239
+ return `<div id="container"></div>`;
1240
+ }
1241
+ }
1242
+ ```
1243
+
1244
+ ### 2. `textNode` - Pure Text Nodes
1245
+
1246
+ Create raw text nodes (useful for escaping HTML or building text content).
1247
+
1248
+ ```typescript
1249
+ @Sim
1250
+ @elementDefine('text-renderer')
1251
+ class TextRenderer extends HTMLElement {
1252
+ @appendChild('.text-list', { position: 'lastChild' })
1253
+ addPlainText(userInput: string) {
1254
+ // Create pure text node - prevents XSS
1255
+ return textNode`User wrote: ${userInput}`;
1256
+ }
1257
+
1258
+ @onConnectedInnerHtml
1259
+ render() {
1260
+ return `
1261
+ <input type="text" id="user-input" placeholder="Enter text...">
1262
+ <button swc-on-click="
1263
+ const input = document.getElementById('user-input');
1264
+ $host.addPlainText(input.value);
1265
+ input.value = '';
1266
+ ">Add Text</button>
1267
+ <div class="text-list"></div>
1268
+ `;
1269
+ }
1270
+ }
1271
+ ```
1272
+
1273
+ ### 3. Element Factories (`html***Element`)
1274
+
1275
+ Specific factories for common tags. Each returns a **typed element instance**.
1276
+
1277
+ ```typescript
1278
+ @Sim
1279
+ @elementDefine('element-factory-demo')
1280
+ class ElementFactoryDemo extends HTMLElement {
1281
+ // htmlDivElement returns HTMLDivElement (typed)
1282
+ @appendChild('#container')
1283
+ addCard(title: string, description: string) {
1284
+ return htmlDivElement`
1285
+ <div class="card">
1286
+ <h3>${title}</h3>
1287
+ <p>${description}</p>
1288
+ <button class="card-btn">Learn More</button>
1289
+ </div>
1290
+ `;
1291
+ }
1292
+
1293
+ // htmlTableElement for building tables
1294
+ @replaceChildren('#table-container')
1295
+ buildTable(data: Array<{ name: string; age: number; role: string }>) {
1296
+ const headerRow = htmlTrElement`
1297
+ <tr style="background: #f0f0f0; font-weight: bold;">
1298
+ <td>Name</td>
1299
+ <td>Age</td>
1300
+ <td>Role</td>
1301
+ </tr>
1302
+ `;
1303
+
1304
+ const rows = data.map(item =>
1305
+ htmlTrElement`
1306
+ <tr>
1307
+ <td>${item.name}</td>
1308
+ <td>${item.age}</td>
1309
+ <td>${item.role}</td>
1310
+ </tr>
1311
+ `
1312
+ );
1313
+
1314
+ return htmlTableElement`
1315
+ ${headerRow}
1316
+ ${rows}
1317
+ `;
1318
+ }
1319
+
1320
+ // htmlUlElement for lists
1321
+ @appendChild('.sidebar')
1322
+ addMenuList(items: string[]) {
1323
+ const listItems = items.map(item =>
1324
+ htmlLiElement`<li><a href="#">${item}</a></li>`
1325
+ );
1326
+
1327
+ return htmlUlElement`
1328
+ <ul class="menu">
1329
+ ${listItems}
1330
+ </ul>
1331
+ `;
1332
+ }
1333
+
1334
+ @onConnectedInnerHtml
1335
+ render() {
1336
+ return `
1337
+ <div id="container"></div>
1338
+ <div id="table-container"></div>
1339
+ <div class="sidebar"></div>
1340
+ `;
1341
+ }
1342
+ }
1343
+ ```
1344
+
1345
+ ---
1346
+
1347
+ ## ⚡ Quick Start
1348
+
1349
+ ```ts
1350
+ import 'reflect-metadata';
1351
+ import swcRegister, { elementDefine, onConnectedInnerHtml } from '@dooboostore/simple-web-component';
1352
+
1353
+ swcRegister(window);
1354
+
1355
+ @elementDefine('hello-swc')
1356
+ class HelloSwc extends HTMLElement {
1357
+ @onConnectedInnerHtml({ useShadow: true })
1358
+ render() {
1359
+ return `<style>:host{display:block;padding:12px;background:#111;color:#fefefe}</style><slot>Hello SWC</slot>`;
1360
+ }
1361
+ }
1362
+
1363
+ // <hello-swc> contents render automatically once the element is connected.
1364
+ ```
1365
+
1366
+ - Call `swcRegister(window)` once per window to register structural elements (`swc-if`, `swc-loop`, etc.) globally.
1367
+ - Decorators rely on `reflect-metadata`, so make sure it is imported before defining elements.
1368
+
1369
+ ---
1370
+
1371
+ ## 🧪 Interactive Test Cases
1372
+
1373
+ Every decorator and directive ships with runnable playgrounds under [test/case/src](test/case/src). They double as living documentation.
1374
+
1375
+ 1. Install dependencies at the repo root: `pnpm install`.
1376
+ 2. Start the playground server:
1377
+ ```bash
1378
+ cd packages/@dooboostore/simple-web-component/test/case
1379
+ pnpm run dev
1380
+ ```
1381
+ 3. Open `http://localhost:3005` and pick a scenario from the menu.
1382
+
1383
+ | Scenario | Folder | Highlights |
1384
+ | --- | --- | --- |
1385
+ | Lifecycle hooks | `lifecycle/` | All pre/post connect, disconnect, adopt hooks with parent-child ordering. |
1386
+ | Attribute flow | `attr/` and `attribute-test/` | DOM-first attribute sync, `@setAttribute`, `@changedAttribute`, `connectedInitialize` edge cases. |
1387
+ | Events | `event/` and `event-attribute/` | Direct binding, delegation, special selectors, `swc-on-*`, custom event emission. |
1388
+ | Queries | `query/` | `@query`, `@queryAll`, special selectors like `:appHost`, element injection safety. |
1389
+ | Structural directives | `swc-loop/`, `swc-if/`, `swc-choose/`, `swc-async/` | Surgical list handling, portal rendering, async template states. |
1390
+ | SPA & DI | `spa/` | Multiple `<swc-app>` instances, DI containers, nested routers. |
1391
+ | Performance | `load-test/` | 1,000+ node stress tests showcasing zero MutationObserver overhead. |
1392
+
1393
+ Each folder contains an `index.ts` that demonstrates the decorator usage and an `index.html` you can load standalone if needed.
1394
+
1395
+ ---
1396
+
1397
+ ## 🧱 Project Layout
1398
+
1399
+ - [src/](src) holds the production TypeScript sources (decorators, structural elements, router, utilities).
1400
+ - [test/case/src](test/case/src) aggregates example applications used in documentation and regression checks.
1401
+ - [test/case/src/README.md](test/case/src/README.md) provides an overview of every scenario along with the architectural ideas they validate.
1402
+
1403
+ ---
1404
+
1405
+ ## 🛠 Developer Scripts
1406
+
1407
+ Inside [package.json](package.json) you will find task shortcuts:
1408
+
1409
+ - `pnpm run build` – produce ESM, CJS, and bundle artifacts (`dist/`).
1410
+ - `pnpm run watch` – rebuild all targets on file changes.
1411
+ - `pnpm run build:esm` / `build:cjs` – individual module formats when you only care about one output.
1412
+ - `pnpm run test:bundle` – launches the bundle regression playground (`@dooboostore/simple-web-component-bundle-test`).
1413
+ - `pnpm run typecheck` – validate TypeScript types without emitting output.
1414
+
1415
+ Publishing uses `publish:npm`, which runs a clean build, packs artifacts, and pushes them with the correct metadata.
1416
+
1417
+ ---
1418
+
1419
+ ## 🌐 Server-Side Rendering (SSR) & Hydration
1420
+
1421
+ SWC provides seamless SSR support with automatic tag-name preservation and client-side hydration strategies.
1422
+
1423
+ ### SSR Features
1424
+ - **Tag Name Preservation**: Custom element tags (e.g., `<user-route>`, `<index-router>`) are preserved through the SSR virtual DOM layer.
1425
+ - **Metadata-Based Registration**: The `CustomElementRegistry` stores tagname metadata during `define()`, allowing `HTMLElementBase` to auto-infer correct tag names during construction.
1426
+ - **Virtual Window Support**: Pass a custom window object to `@elementDefine()` for SSR rendering environments.
1427
+
1428
+ ### Hydration Modes
1429
+ When connecting a server-rendered app to the client, choose a connection strategy via the `connectMode` config:
1430
+
1431
+ ```typescript
1432
+ const config = {
1433
+ rootRouter: RootRouter,
1434
+ routeType: 'element',
1435
+ connectMode: 'swap' // or 'direct' (default)
1436
+ };
1437
+
1438
+ app.connect(config);
1439
+ ```
1440
+
1441
+ #### Mode Behaviors:
1442
+ - **`'direct'`** (default): Directly connects the existing rendered DOM tree.
1443
+ - **`'swap'`**: Creates a clone of the rendered app, connects it, and smoothly replaces the original DOM (Zero FOUC - Flash of Unstyled Content).
1444
+
1445
+ The swap mode is ideal for SSR workflows where you want to:
1446
+ 1. Render components on the server with proper tag names
1447
+ 2. Send minimal HTML to the client
1448
+ 3. Clone and connect on the client side without visible DOM flashing
1449
+
1450
+ ---
1451
+
1452
+ ## 🌐 Revolutionary SPA Architecture
1453
+
1454
+ SWC introduces a **paradigm shift** in SPA design: **Each component scope is an independent, self-contained application** with its own DI container, router, and lifecycle. This enables true micro-frontend architectures without iframe overhead while maintaining perfect encapsulation.
1455
+
1456
+ ### Core Philosophy: Multi-App Composition
1457
+ Unlike traditional SPAs that force a global router and monolithic state tree, SWC allows:
1458
+ - **Nested Independent Apps**: Multiple `<swc-app>` instances on the same page, each with zero cross-contamination
1459
+ - **Autonomous Routing**: Each app handles its own navigation, history, and component lifecycle independently
1460
+ - **Decentralized DI**: Service dependencies are resolved within app scope, enabling natural code splitting and lazy loading
1461
+ - **Island Architecture**: Apps boot only when needed, reducing initial JavaScript payload
1462
+
1463
+ ### ✨ Complete Commerce App Example
1464
+
1465
+ See the **[Commerce Example](examples/commerce/)** for a production-ready SPA implementation featuring:
1466
+
1467
+ - **Multi-page routing** (Home, Product Detail, Cart, Orders)
1468
+ - **Service layer** (ProductService, CartService, OrderService) with localStorage persistence
1469
+ - **Reactive state management** using BehaviorSubject
1470
+ - **Modern UI design** with gradient backgrounds, responsive layouts, and sticky header
1471
+ - **Product images** from dynamic CDN (picsum.photos)
1472
+ - **DI integration** with `@onBeforeConnected` lifecycle hooks
1473
+ - **Shadow DOM** for style encapsulation
1474
+ - **Real-time updates** with cart count synchronization
1475
+
1476
+ ```bash
1477
+ cd packages/@dooboostore/simple-web-component/examples/commerce
1478
+ pnpm install
1479
+ pnpm build
1480
+ ```
1481
+
1482
+ This example demonstrates all decorators working together in a real-world e-commerce scenario.
1483
+
1484
+ ---
1485
+
1486
+ ### Advanced Patterns
1487
+
1488
+ #### Isolated App Scopes & Micro-Frontend Pattern
1489
+
1490
+ Create self-contained apps with `<swc-app>` where routing and DI are completely isolated:
1491
+
1492
+ ```html
1493
+ <div class="dashboard">
1494
+ <swc-app swc-get-application-config="createUserModule()"></swc-app>
1495
+ <swc-app swc-get-application-config="createReportsModule()"></swc-app>
1496
+ </div>
1497
+ ```
1498
+
1499
+ Each app is independent: state, services, and routes don't cross boundaries.
1500
+
1501
+ ---
1502
+
1503
+ #### DI-Powered Routing with Lifecycle Hooks
1504
+
1505
+ SWC routing injects services directly into components using `canActivate` lifecycle hook for permission checks and data preloading:
1506
+
1507
+ ```typescript
1508
+ // ============================================
1509
+ // 1. SERVICE LAYER (Shared by all routes)
1510
+ // ============================================
1511
+ @Sim
1512
+ class AuthService {
1513
+ private user: { id: string; role: 'admin' | 'user' } | null = null;
1514
+
1515
+ async login(credentials: { username: string; password: string }) {
1516
+ // Simulate API call
1517
+ this.user = { id: 'user123', role: 'admin' };
1518
+ return this.user;
1519
+ }
1520
+
1521
+ async logout() {
1522
+ this.user = null;
1523
+ }
1524
+
1525
+ getCurrentUser() {
1526
+ return this.user;
1527
+ }
1528
+
1529
+ hasRole(role: string) {
1530
+ return this.user?.role === role;
1531
+ }
1532
+ }
1533
+
1534
+ @Sim
1535
+ class UserService {
1536
+ constructor(private auth: AuthService) {}
1537
+
1538
+ async getUsers() {
1539
+ if (!this.auth.hasRole('admin')) {
1540
+ throw new Error('Unauthorized');
1541
+ }
1542
+ return [
1543
+ { id: '1', name: 'John Doe', email: 'john@example.com' },
1544
+ { id: '2', name: 'Jane Smith', email: 'jane@example.com' }
1545
+ ];
1546
+ }
1547
+
1548
+ async getUserById(id: string) {
1549
+ return { id, name: 'User ' + id, email: `user${id}@example.com` };
1550
+ }
1551
+ }
1552
+
1553
+ // ============================================
1554
+ // 2. ROUTE COMPONENTS (Nested routing)
1555
+ // ============================================
1556
+ @Sim
1557
+ @Router({
1558
+ path: '/admin',
1559
+ route: {
1560
+ '/users': UserListPage,
1561
+ '/users/:id': UserDetailPage,
1562
+ '/settings': SettingsPage
1563
+ }
1564
+ })
1565
+ @elementDefine('admin-router')
1566
+ class AdminRouter extends HTMLElement implements RouterAction.CanActivate {
1567
+ constructor(private auth: AuthService) { super(); }
1568
+
1569
+ @onConnectedInnerHtml({ useShadow: true })
1570
+ render() {
1571
+ return `
1572
+ <style>
1573
+ :host { display: flex; height: 100vh; }
1574
+ .sidebar { width: 200px; background: #333; color: white; padding: 20px; }
1575
+ .sidebar nav a { display: block; margin: 10px 0; color: #0066cc; cursor: pointer; }
1576
+ .content { flex: 1; padding: 20px; }
1577
+ </style>
1578
+ <div class="sidebar">
1579
+ <h3>Admin Panel</h3>
1580
+ <nav>
1581
+ <a swc-on-click="$host.router.go('/admin/users')">Users</a>
1582
+ <a swc-on-click="$host.router.go('/admin/settings')">Settings</a>
1583
+ </nav>
1584
+ </div>
1585
+ <div class="content">
1586
+ <slot></slot>
1587
+ </div>
1588
+ `;
1589
+ }
1590
+
1591
+ // Lifecycle hook: Called BEFORE route transition
1592
+ // Used for permission checks, data preloading, etc.
1593
+ async canActivate(url: RoutingDataSet, data?: any) {
1594
+ // Only admin can enter this route
1595
+ if (!this.auth.hasRole('admin')) {
1596
+ console.warn('Access Denied: Admin role required');
1597
+ return false;
1598
+ }
1599
+
1600
+ // If data is a rendered component (Node), inject it into DOM
1601
+ if (data instanceof Node) {
1602
+ this.replaceChildren(data);
1603
+ }
1604
+
1605
+ return true;
1606
+ }
1607
+ }
1608
+
1609
+ // ============================================
1610
+ // 3. PAGE COMPONENTS (Leaf routes)
1611
+ // ============================================
1612
+ @Sim
1613
+ @elementDefine('user-list-page')
1614
+ class UserListPage extends HTMLElement {
1615
+ private users: any[] = [];
1616
+
1617
+ constructor(
1618
+ private userService: UserService,
1619
+ private router: Router,
1620
+ private auth: AuthService
1621
+ ) {
1622
+ super();
1623
+ }
1624
+
1625
+ @onConnected
1626
+ async loadUsers() {
1627
+ try {
1628
+ this.users = await this.userService.getUsers();
1629
+ this.render();
1630
+ } catch (error) {
1631
+ console.error('Failed to load users:', error);
1632
+ this.innerHTML = '<p style="color: red;">Failed to load users</p>';
1633
+ }
1634
+ }
1635
+
1636
+ @classList('active')
1637
+ isActive = false;
1638
+
1639
+ @onConnectedInnerHtml
1640
+ render() {
1641
+ return `
1642
+ <style>
1643
+ .user-table { width: 100%; border-collapse: collapse; }
1644
+ .user-table th, .user-table td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
1645
+ .user-table tr:hover { background: #f5f5f5; }
1646
+ </style>
1647
+ <h2>Users</h2>
1648
+ <table class="user-table">
1649
+ <thead>
1650
+ <tr><th>Name</th><th>Email</th><th>Actions</th></tr>
1651
+ </thead>
1652
+ <tbody>
1653
+ ${this.users.map(u => `
1654
+ <tr>
1655
+ <td>${u.name}</td>
1656
+ <td>${u.email}</td>
1657
+ <td>
1658
+ <button swc-on-click="$host.router.go('/admin/users/${u.id}')">View</button>
1659
+ <button swc-on-click="$host.deleteUser('${u.id}')">Delete</button>
1660
+ </td>
1661
+ </tr>
1662
+ `).join('')}
1663
+ </tbody>
1664
+ </table>
1665
+ `;
1666
+ }
1667
+
1668
+ deleteUser(id: string) {
1669
+ if (confirm('Delete this user?')) {
1670
+ this.users = this.users.filter(u => u.id !== id);
1671
+ this.render();
1672
+ }
1673
+ }
1674
+ }
1675
+
1676
+ @Sim
1677
+ @elementDefine('user-detail-page')
1678
+ class UserDetailPage extends HTMLElement {
1679
+ private user: any = null;
1680
+
1681
+ constructor(
1682
+ private userService: UserService,
1683
+ private router: Router
1684
+ ) {
1685
+ super();
1686
+ }
1687
+
1688
+ @onConnected
1689
+ async loadUser() {
1690
+ // Extract route parameter (:id)
1691
+ const id = this.router.getIntent()?.params?.id;
1692
+ if (id) {
1693
+ this.user = await this.userService.getUserById(id);
1694
+ this.render();
1695
+ }
1696
+ }
1697
+
1698
+ @onConnectedInnerHtml
1699
+ render() {
1700
+ if (!this.user) return '<p>Loading...</p>';
1701
+
1702
+ return `
1703
+ <style>
1704
+ .detail { max-width: 600px; background: #f9f9f9; padding: 20px; border-radius: 8px; }
1705
+ .detail-row { margin: 10px 0; }
1706
+ .detail-label { font-weight: bold; color: #333; }
1707
+ </style>
1708
+ <h2>User Details</h2>
1709
+ <div class="detail">
1710
+ <div class="detail-row">
1711
+ <span class="detail-label">ID:</span> ${this.user.id}
1712
+ </div>
1713
+ <div class="detail-row">
1714
+ <span class="detail-label">Name:</span> ${this.user.name}
1715
+ </div>
1716
+ <div class="detail-row">
1717
+ <span class="detail-label">Email:</span> ${this.user.email}
1718
+ </div>
1719
+ </div>
1720
+ <button swc-on-click="$host.router.back()">Back</button>
1721
+ `;
1722
+ }
1723
+ }
1724
+
1725
+ // ============================================
1726
+ // 4. ROOT ROUTER (Entry point)
1727
+ // ============================================
1728
+ @Sim
1729
+ @Router({
1730
+ path: '/',
1731
+ route: {
1732
+ '/': HomePage,
1733
+ '/login': LoginPage,
1734
+ '/admin': AdminRouter
1735
+ }
1736
+ })
1737
+ @elementDefine('app-router')
1738
+ class RootRouter extends HTMLElement implements RouterAction.CanActivate {
1739
+ constructor(private auth: AuthService) { super(); }
1740
+
1741
+ @onConnectedInnerHtml({ useShadow: true })
1742
+ render() {
1743
+ const user = this.auth.getCurrentUser();
1744
+ return `
1745
+ <style>
1746
+ :host { display: block; font-family: Arial, sans-serif; }
1747
+ header { background: #222; color: white; padding: 15px; }
1748
+ header .user-info { float: right; font-size: 12px; }
1749
+ </style>
1750
+ <header>
1751
+ <h1>SWC App</h1>
1752
+ <div class="user-info">
1753
+ ${user ? `Logged in as: ${user.id} (${user.role})` : 'Not logged in'}
1754
+ </div>
1755
+ </header>
1756
+ <main><slot></slot></main>
1757
+ `;
1758
+ }
1759
+
1760
+ async canActivate(url: RoutingDataSet, data?: any) {
1761
+ if (data instanceof Node) {
1762
+ this.replaceChildren(data);
1763
+ }
1764
+ return true;
1765
+ }
1766
+ }
1767
+ ```
1768
+
1769
+ ---
1770
+
1771
+ ### 3. Route Lifecycle & Guard Patterns
1772
+
1773
+ **The `canActivate` lifecycle hook** is called **before** entering a route. Use it for:
1774
+ - **Permission Checks**: Verify user roles and access rights
1775
+ - **Data Preloading**: Fetch required data before rendering
1776
+ - **Route Interception**: Redirect to login or error pages
1777
+ - **Analytics**: Track user navigation
1778
+
1779
+ ```typescript
1780
+ @Router({ path: '/protected', route: { '/data': ProtectedDataPage } })
1781
+ @elementDefine('protected-router')
1782
+ class ProtectedRouter extends HTMLElement implements RouterAction.CanActivate {
1783
+ constructor(private auth: AuthService) { super(); }
1784
+
1785
+ async canActivate(url: RoutingDataSet, data?: any) {
1786
+ // Guard: Redirect if not authenticated
1787
+ if (!this.auth.getCurrentUser()) {
1788
+ console.log('Redirecting to login...');
1789
+ this.parentElement?.querySelector('app-router')?.routing('/login');
1790
+ return false;
1791
+ }
1792
+
1793
+ // Data injection: Component receives pre-fetched data
1794
+ if (data instanceof Node) {
1795
+ this.replaceChildren(data);
1796
+ }
1797
+
1798
+ return true;
1799
+ }
1800
+ }
1801
+ ```
1802
+
1803
+ ---
1804
+
1805
+ ### 4. Advanced Patterns: Nested Routers & State Management
1806
+
1807
+ ```typescript
1808
+ // Real-world: Multi-level nested routing with shared state
1809
+ @Sim
1810
+ class GridStateService {
1811
+ private sortBy = 'name';
1812
+ private filterRole = '';
1813
+
1814
+ setSortBy(field: string) { this.sortBy = field; }
1815
+ setFilterRole(role: string) { this.filterRole = role; }
1816
+ getSortBy() { return this.sortBy; }
1817
+ getFilterRole() { return this.filterRole; }
1818
+ }
1819
+
1820
+ @Sim
1821
+ @Router({
1822
+ path: '/crm',
1823
+ route: {
1824
+ '/contacts': ContactsRouter,
1825
+ '/accounts': AccountsRouter
1826
+ }
1827
+ })
1828
+ @elementDefine('crm-app')
1829
+ class CRMApp extends HTMLElement implements RouterAction.CanActivate {
1830
+ async canActivate(url: RoutingDataSet, data?: any) {
1831
+ if (data instanceof Node) this.replaceChildren(data);
1832
+ return true;
1833
+ }
1834
+
1835
+ @onConnectedInnerHtml
1836
+ render() {
1837
+ return `<div><slot></slot></div>`;
1838
+ }
1839
+ }
1840
+ ```
1841
+
1842
+ ---
1843
+
1844
+ ## ✉️ Declarative & Scoped Messaging System
1845
+
1846
+ SWC provides a **hierarchical messaging architecture** using native browser events with intelligent scoping. No external event bus or state manager needed—messages flow naturally through the DOM hierarchy.
1847
+
1848
+ ### Message Scope Hierarchy
1849
+
1850
+ Messages can target different scopes using **Special Selectors**:
1851
+
1852
+ | Selector | Scope | Use Case |
1853
+ |----------|-------|----------|
1854
+ | **`:appHost`** | Same `<swc-app>` instance | Module-to-module communication (confined) |
1855
+ | **`:parentHost`** | Direct parent SWC component | Parent-child coordination |
1856
+ | **`:window` / `:document`** | Entire page | Global broadcasts (Cross-app signals) |
1857
+ | **`:firstHost` / `:firstAppHost`** | Root of hierarchy | Communication with top-level container |
1858
+ | **`selector`** (CSS) | Direct query result | Point-to-point messaging |
1859
+
1860
+ ### Pattern 1: App-Scoped Communication
1861
+
1862
+ Communicate within a micro-app without affecting siblings.
1863
+
1864
+ ```typescript
1865
+ // ========== SENDER COMPONENT ==========
1866
+ @Sim
1867
+ @elementDefine('auth-panel')
1868
+ class AuthPanel extends HTMLElement {
1869
+ constructor(private auth: AuthService) { super(); }
1870
+
1871
+ @onConnectedInnerHtml
1872
+ render() {
1873
+ return `
1874
+ <div style="padding: 10px; background: #f0f0f0; border-radius: 4px;">
1875
+ <button swc-on-click="$host.onLogin()">Login</button>
1876
+ <button swc-on-click="$host.onLogout()">Logout</button>
1877
+ </div>
1878
+ `;
1879
+ }
1880
+
1881
+ // Emit event to the nearest parent <swc-app>
1882
+ @emitCustomEvent(':appHost', 'auth-changed')
1883
+ async onLogin() {
1884
+ await this.auth.login({ username: 'demo', password: 'demo' });
1885
+ return { user: this.auth.getCurrentUser(), timestamp: Date.now() };
1886
+ }
1887
+
1888
+ @emitCustomEvent(':appHost', 'auth-changed')
1889
+ async onLogout() {
1890
+ await this.auth.logout();
1891
+ return { user: null, timestamp: Date.now() };
1892
+ }
1893
+ }
1894
+
1895
+ // ========== RECEIVER COMPONENT (SAME APP) ==========
1896
+ @Sim
1897
+ @elementDefine('user-widget')
1898
+ class UserWidget extends HTMLElement {
1899
+ private user: any = null;
1900
+
1901
+ @onConnectedInnerHtml
1902
+ render() {
1903
+ return `
1904
+ <div style="padding: 10px; background: #e8f4f8; border-radius: 4px;">
1905
+ ${this.user ? `<p>Welcome, ${this.user.name}!</p>` : '<p>Not logged in</p>'}
1906
+ </div>
1907
+ `;
1908
+ }
1909
+
1910
+ // Listen for auth changes within the same app
1911
+ @addEventListener(':appHost', 'auth-changed')
1912
+ onAuthChanged(event: CustomEvent) {
1913
+ const { detail } = event;
1914
+ this.user = detail.user;
1915
+ this.render();
1916
+ console.log('Auth state changed at:', new Date(detail.timestamp).toLocaleTimeString());
1917
+ }
1918
+ }
1919
+
1920
+ // HTML
1921
+ <swc-app swc-get-application-config="dashboardConfig()">
1922
+ <auth-panel></auth-panel>
1923
+ <user-widget></user-widget> <!-- Reacts to auth-changed event -->
1924
+ </swc-app>
1925
+ ```
1926
+
1927
+ **Result**: When user clicks "Login" in `<auth-panel>`, `<user-widget>` is notified and updates automatically. No explicit imperative connection needed.
1928
+
1929
+ ---
1930
+
1931
+ ### Pattern 2: Cross-Module (Global) Broadcasting
1932
+
1933
+ Send a signal that reaches all apps on the page.
1934
+
1935
+ ```typescript
1936
+ // ========== SENDER (In Module A) ==========
1937
+ @Sim
1938
+ @elementDefine('notification-sender')
1939
+ class NotificationSender extends HTMLElement {
1940
+ @emitCustomEvent(':window', 'system-notification')
1941
+ announceSystemMaintenance() {
1942
+ return {
1943
+ title: 'System Maintenance',
1944
+ message: 'Server will be down for 2 hours',
1945
+ severity: 'warning',
1946
+ timestamp: new Date().toISOString()
1947
+ };
1948
+ }
1949
+
1950
+ @onConnectedInnerHtml
1951
+ render() {
1952
+ return `
1953
+ <button swc-on-click="$host.announceSystemMaintenance()">
1954
+ 📢 Broadcast Maintenance Alert
1955
+ </button>
1956
+ `;
1957
+ }
1958
+ }
1959
+
1960
+ // ========== RECEIVER (In Module B, Different App) ==========
1961
+ @Sim
1962
+ @elementDefine('notification-display')
1963
+ class NotificationDisplay extends HTMLElement {
1964
+ private notifications: any[] = [];
1965
+
1966
+ @addEventListener(':window', 'system-notification')
1967
+ onSystemNotification(event: CustomEvent) {
1968
+ this.notifications.push(event.detail);
1969
+ this.render();
1970
+ }
1971
+
1972
+ @onConnectedInnerHtml
1973
+ render() {
1974
+ return `
1975
+ <div style="position: fixed; top: 10px; right: 10px; font-size: 12px;">
1976
+ ${this.notifications.map(n => `
1977
+ <div style="background: ${n.severity === 'warning' ? '#fff3cd' : '#fff'};
1978
+ border: 1px solid #ffc107; padding: 10px; margin: 5px 0;
1979
+ border-radius: 4px; max-width: 300px;">
1980
+ <strong>${n.title}</strong><br>
1981
+ ${n.message}
1982
+ </div>
1983
+ `).join('')}
1984
+ </div>
1985
+ `;
1986
+ }
1987
+ }
1988
+
1989
+ <!-- Two independent apps receiving the same broadcast -->
1990
+ <swc-app swc-get-application-config="moduleAConfig()">
1991
+ <notification-sender></notification-sender>
1992
+ </swc-app>
1993
+
1994
+ <swc-app swc-get-application-config="moduleBConfig()">
1995
+ <notification-display></notification-display>
1996
+ </swc-app>
1997
+ ```
1998
+
1999
+ **Result**: Clicking the button in Module A instantly triggers the notification in Module B, even though they're in separate `<swc-app>` instances.
2000
+
2001
+ ---
2002
+
2003
+ ### Pattern 3: Hierarchical Parent-Child Messaging
2004
+
2005
+ Communicate up and down the component tree with `$parentHost` and `:hosts` selector.
2006
+
2007
+ ```typescript
2008
+ // ========== PARENT CONTAINER ==========
2009
+ @Sim
2010
+ @elementDefine('modal-container')
2011
+ class ModalContainer extends HTMLElement {
2012
+ private modals: any[] = [];
2013
+
2014
+ @onConnectedInnerHtml
2015
+ render() {
2016
+ return `
2017
+ <div style="position: relative;">
2018
+ <slot></slot>
2019
+ ${this.modals.map(m => `
2020
+ <div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
2021
+ background: white; border: 2px solid #333; padding: 20px;
2022
+ border-radius: 8px; z-index: 1000;">
2023
+ ${m.content}
2024
+ <button swc-on-click="$host.closeModal()">Close</button>
2025
+ </div>
2026
+ `).join('')}
2027
+ </div>
2028
+ `;
2029
+ }
2030
+
2031
+ // Listen for child components requesting to open a modal
2032
+ @addEventListener(':hosts', 'request-modal')
2033
+ onModalRequest(event: CustomEvent) {
2034
+ this.modals.push({ id: Date.now(), content: event.detail.html });
2035
+ this.render();
2036
+ event.stopPropagation();
2037
+ }
2038
+
2039
+ closeModal() {
2040
+ this.modals.pop();
2041
+ this.render();
2042
+ }
2043
+ }
2044
+
2045
+ // ========== CHILD COMPONENT ==========
2046
+ @Sim
2047
+ @elementDefine('form-with-modal')
2048
+ class FormWithModal extends HTMLElement {
2049
+ @emitCustomEvent(':parentHost', 'request-modal')
2050
+ showHelpModal() {
2051
+ return {
2052
+ html: '<h3>Help</h3><p>Fill in all required fields and click Submit.</p>'
2053
+ };
2054
+ }
2055
+
2056
+ @onConnectedInnerHtml
2057
+ render() {
2058
+ return `
2059
+ <form>
2060
+ <input type="text" placeholder="Full Name" required>
2061
+ <button type="button" swc-on-click="$host.showHelpModal()">?</button>
2062
+ <button type="submit">Submit</button>
2063
+ </form>
2064
+ `;
2065
+ }
2066
+ }
2067
+
2068
+ <!-- Structure -->
2069
+ <modal-container>
2070
+ <form-with-modal></form-with-modal>
2071
+ </modal-container>
2072
+ ```
2073
+
2074
+ **Result**: Clicking "?" in the child form opens a modal managed by the parent container.
2075
+
2076
+ ---
2077
+
2078
+ ### Pattern 4: Event Detail Payloads
2079
+
2080
+ Send rich data structures through events.
2081
+
2082
+ ```typescript
2083
+ @Sim
2084
+ @elementDefine('data-grid')
2085
+ class DataGrid extends HTMLElement {
2086
+ private rows: any[] = [];
2087
+
2088
+ @onConnected
2089
+ loadData() {
2090
+ this.rows = [
2091
+ { id: 1, name: 'Alice', age: 28, department: 'Engineering' },
2092
+ { id: 2, name: 'Bob', age: 34, department: 'Sales' }
2093
+ ];
2094
+ this.render();
2095
+ }
2096
+
2097
+ // Emit complex payload when row is clicked
2098
+ @emitCustomEvent(':appHost', 'row-selected', false) // 3rd arg: whether to bubble
2099
+ selectRow(rowId: number) {
2100
+ const row = this.rows.find(r => r.id === rowId);
2101
+ return {
2102
+ rowId,
2103
+ rowData: row,
2104
+ timestamp: Date.now(),
2105
+ gridId: this.id,
2106
+ userAgent: navigator.userAgent
2107
+ };
2108
+ }
2109
+
2110
+ @onConnectedInnerHtml
2111
+ render() {
2112
+ return `
2113
+ <table>
2114
+ <tbody>
2115
+ ${this.rows.map(row => `
2116
+ <tr swc-on-click="$host.selectRow(${row.id})">
2117
+ <td>${row.name}</td>
2118
+ <td>${row.age}</td>
2119
+ <td>${row.department}</td>
2120
+ </tr>
2121
+ `).join('')}
2122
+ </tbody>
2123
+ </table>
2124
+ `;
2125
+ }
2126
+ }
2127
+
2128
+ // Listener receives all payload details
2129
+ @Sim
2130
+ @elementDefine('detail-panel')
2131
+ class DetailPanel extends HTMLElement {
2132
+ private selectedData: any = null;
2133
+
2134
+ @addEventListener(':appHost', 'row-selected')
2135
+ onRowSelect(event: CustomEvent) {
2136
+ const { rowData, timestamp } = event.detail;
2137
+ this.selectedData = rowData;
2138
+ console.log(`Selected at ${new Date(timestamp).toLocaleTimeString()}`);
2139
+ this.render();
2140
+ }
2141
+
2142
+ @onConnectedInnerHtml
2143
+ render() {
2144
+ if (!this.selectedData) return '<p>No selection</p>';
2145
+ return `
2146
+ <div>
2147
+ <h3>${this.selectedData.name}</h3>
2148
+ <p>Age: ${this.selectedData.age}</p>
2149
+ <p>Department: ${this.selectedData.department}</p>
2150
+ </div>
2151
+ `;
2152
+ }
2153
+ }
2154
+ ```
2155
+
2156
+ ---
2157
+
2158
+ ## 🏰 Host Hierarchy, Context Propagation & Template Events
2159
+
2160
+ SWC automatically traverses the component tree to provide meaningful **context** to scripts and event handlers. This eliminates the need for prop drilling and global state managers.
2161
+
2162
+ ### Context Variables (In Templates & Event Handlers)
2163
+
2164
+ When you use `swc-on-*` inline events or callback functions, these variables are automatically injected:
2165
+
2166
+ | Variable | Type | Description |
2167
+ |----------|------|-------------|
2168
+ | **`$host`** | `HTMLElement` | The component that owns the event handler (the element itself) |
2169
+ | **`$parentHost`** | `HTMLElement` | The direct parent SWC component |
2170
+ | **`$appHost`** | `SwcAppInterface` | The nearest parent `<swc-app>` boundary |
2171
+ | **`$hosts`** | `HTMLElement[]` | Array of ALL SWC ancestors: `[Root, Parent, ..., Self]` |
2172
+ | **`$el`** | `HTMLElement` | The actual DOM element that triggered the event |
2173
+ | **`$item`** | `any` | (In `swc-loop`) Current iteration item |
2174
+ | **`$index`** | `number` | (In `swc-loop`) Current iteration index |
2175
+
2176
+ ### Pattern 1: Declarative Inline Events (`swc-on-*`)
2177
+
2178
+ Bind events directly in HTML templates. The context is automatically available.
2179
+
2180
+ ```typescript
2181
+ @Sim
2182
+ @elementDefine('todo-item')
2183
+ class TodoItem extends HTMLElement {
2184
+ private title = '';
2185
+ private done = false;
2186
+
2187
+ constructor(private todoService: TodoService) { super(); }
2188
+
2189
+ @onConnectedInnerHtml
2190
+ render() {
2191
+ return `
2192
+ <style>
2193
+ .completed { text-decoration: line-through; color: #ccc; }
2194
+ button { margin-left: 10px; padding: 2px 8px; cursor: pointer; }
2195
+ </style>
2196
+ <li class="${this.done ? 'completed' : ''}">
2197
+ <!-- Access $host (self), $appHost (parent app), and $item -->
2198
+ <span>${this.title}</span>
2199
+
2200
+ <!-- Inline script with full context -->
2201
+ <button swc-on-click="
2202
+ $host.toggleDone();
2203
+ console.log('Current item:', $item);
2204
+ console.log('App instance:', $appHost);
2205
+ ">
2206
+ ${this.done ? '✓ Done' : '○ Pending'}
2207
+ </button>
2208
+
2209
+ <button swc-on-click="$host.deleteTodo($index)">
2210
+ 🗑️ Delete
2211
+ </button>
2212
+
2213
+ <!-- Access element coordinates -->
2214
+ <button swc-on-click="
2215
+ console.log('Clicked at:', $el.getBoundingClientRect());
2216
+ ">
2217
+ Info
2218
+ </button>
2219
+ </li>
2220
+ `;
2221
+ }
2222
+
2223
+ toggleDone() {
2224
+ this.done = !this.done;
2225
+ this.render();
2226
+ }
2227
+
2228
+ deleteTodo(index: number) {
2229
+ console.log('Deleting todo at index:', index);
2230
+ }
2231
+ }
2232
+ ```
2233
+
2234
+ ### Pattern 2: Loop Iteration Context
2235
+
2236
+ `swc-loop` provides special iteration variables you can access in inline events.
2237
+
2238
+ ```typescript
2239
+ @Sim
2240
+ @elementDefine('todo-list')
2241
+ class TodoList extends HTMLElement {
2242
+ private todos = [
2243
+ { id: 1, title: 'Learn SWC', done: false },
2244
+ { id: 2, title: 'Build an app', done: false }
2245
+ ];
2246
+
2247
+ @onConnectedInnerHtml
2248
+ render() {
2249
+ return `
2250
+ <ul>
2251
+ <template is="swc-loop" on-get-value="todos"
2252
+ on-clone-node="
2253
+ if ($isElement) {
2254
+ console.log('Cloning item:', $item, 'at index:', $index);
2255
+ $node.querySelector('.index').textContent = $index + 1;
2256
+ }
2257
+ ">
2258
+ <li class="todo-item">
2259
+ <span class="index">0</span>.
2260
+ <span class="title">${'${item.title}'}</span>
2261
+ <!-- Callback receives full context -->
2262
+ <button swc-on-click="
2263
+ $host.editTodo($item.id, $index, $el);
2264
+ console.log('Hosts hierarchy:', $hosts.map(h => h.tagName));
2265
+ ">
2266
+ Edit
2267
+ </button>
2268
+ </li>
2269
+ </template>
2270
+ </ul>
2271
+ `;
2272
+ }
2273
+
2274
+ editTodo(id: number, index: number, element: HTMLElement) {
2275
+ console.log(`Editing todo #${id} (index: ${index}):`, element);
2276
+ }
2277
+ }
2278
+ ```
2279
+
2280
+ ### Pattern 3: Accessing Ancestor Services via Context
2281
+
2282
+ Use `$appHost` to access services from the parent app's DI container.
2283
+
2284
+ ```typescript
2285
+ @Sim
2286
+ @elementDefine('child-component')
2287
+ class ChildComponent extends HTMLElement {
2288
+ @onConnectedInnerHtml
2289
+ render() {
2290
+ return `
2291
+ <div>
2292
+ <!-- Access parent app's router to navigate -->
2293
+ <button swc-on-click="$appHost.routing('/dashboard')">
2294
+ Go to Dashboard
2295
+ </button>
2296
+
2297
+ <!-- Access other components in the app -->
2298
+ <button swc-on-click="
2299
+ const sibling = document.querySelector('other-component');
2300
+ sibling.triggerUpdate();
2301
+ ">
2302
+ Notify Sibling
2303
+ </button>
2304
+
2305
+ <!-- Emit event up the tree to parent app -->
2306
+ <button swc-on-click="
2307
+ $appHost.dispatchEvent(new CustomEvent('child-ready', {
2308
+ detail: { child: $host, timestamp: Date.now() }
2309
+ }));
2310
+ ">
2311
+ Notify Parent App
2312
+ </button>
2313
+ </div>
2314
+ `;
2315
+ }
2316
+ }
2317
+ ```
2318
+
2319
+ ### Pattern 4: Host Hierarchy Traversal
2320
+
2321
+ Manually traverse the component tree to find ancestors or siblings.
2322
+
2323
+ ```typescript
2324
+ @Sim
2325
+ @elementDefine('deep-child')
2326
+ class DeepChild extends HTMLElement {
2327
+ findRootApp() {
2328
+ // Traverse up until you find <swc-app> or root
2329
+ let current = this.parentElement as any;
2330
+ while (current && current.tagName !== 'SWC-APP') {
2331
+ current = current.parentElement;
2332
+ }
2333
+ return current;
2334
+ }
2335
+
2336
+ getSiblingCount() {
2337
+ // Count siblings at same level
2338
+ return this.parentElement?.querySelectorAll(':scope > *').length || 0;
2339
+ }
2340
+
2341
+ @onConnectedInnerHtml
2342
+ render() {
2343
+ return `
2344
+ <div>
2345
+ <p>Root app: ${this.findRootApp()?.getAttribute('id')}</p>
2346
+ <p>Sibling count: ${this.getSiblingCount()}</p>
2347
+ </div>
2348
+ `;
2349
+ }
2350
+ }
2351
+ ```
2352
+
2353
+ ### Real-World Example: Multi-Level Navigation with Context
2354
+
2355
+ ```typescript
2356
+ // ============ ROOT ROUTER ============
2357
+ @Sim
2358
+ @Router({ path: '/', route: { '/dashboard': Dashboard, '/profile': Profile } })
2359
+ @elementDefine('app-root')
2360
+ class AppRoot extends HTMLElement {
2361
+ private breadcrumbs: string[] = [];
2362
+
2363
+ @onConnectedInnerHtml
2364
+ render() {
2365
+ return `
2366
+ <div>
2367
+ <header>
2368
+ <h1>My App</h1>
2369
+ <nav>
2370
+ <button swc-on-click="$host.router.go('/dashboard')">Dashboard</button>
2371
+ <button swc-on-click="$host.router.go('/profile')">Profile</button>
2372
+ </nav>
2373
+ <p>Breadcrumbs: ${this.breadcrumbs.join(' > ')}</p>
2374
+ </header>
2375
+ <main><slot></slot></main>
2376
+ </div>
2377
+ `;
2378
+ }
2379
+ }
2380
+
2381
+ // ============ CHILD ROUTE ============
2382
+ @Sim
2383
+ @elementDefine('dashboard-page')
2384
+ class DashboardPage extends HTMLElement {
2385
+ @onConnectedInnerHtml
2386
+ render() {
2387
+ return `
2388
+ <div>
2389
+ <h2>Dashboard</h2>
2390
+ <button swc-on-click="
2391
+ console.log('App router:', $appHost.routing);
2392
+ console.log('My tag:', $host.tagName);
2393
+ console.log('All ancestors:', $hosts.map(h => h.tagName).join(' -> '));
2394
+ ">
2395
+ Debug Context
2396
+ </button>
2397
+ </div>
2398
+ `;
2399
+ }
2400
+ }
2401
+ ```
2402
+
2403
+ ---
2404
+
2405
+ ## 🏰 Advanced Host Context: Query & Portal Patterns
2406
+
2407
+ ### Custom Query with Root Options
2408
+
2409
+ Combine `@query/@queryAll` with the `root` option from host methods to precision-target elements.
2410
+
2411
+ ```typescript
2412
+ @Sim
2413
+ @elementDefine('form-validator')
2414
+ class FormValidator extends HTMLElement {
2415
+ @query('form', { root: 'light' }) lightForm?: HTMLFormElement;
2416
+ @query('.hidden-form', { root: 'shadow' }) shadowForm?: HTMLFormElement;
2417
+ @queryAll('input', { root: 'all' }) allInputs?: HTMLInputElement[];
2418
+
2419
+ @onConnectedInnerHtml({ useShadow: true })
2420
+ render() {
2421
+ return `
2422
+ <form class="shadow-form">
2423
+ <input type="text" placeholder="Shadow DOM input">
2424
+ </form>
2425
+ <slot></slot>
2426
+ `;
2427
+ }
2428
+
2429
+ validateAllForms() {
2430
+ // Validate both light and shadow DOM forms
2431
+ const allInputs = Array.from(this.allInputs || []);
2432
+ return allInputs.every(input => input.value.trim() !== '');
2433
+ }
2434
+ }
2435
+ ```
2436
+
2437
+ ### Portal Pattern: Render Content Anywhere
2438
+
2439
+ Use `on-get-portal` to render content at a completely different location in the DOM.
2440
+
2441
+ ```typescript
2442
+ @Sim
2443
+ @elementDefine('tooltip-manager')
2444
+ class TooltipManager extends HTMLElement {
2445
+ private tooltips: any[] = [];
2446
+
2447
+ @onConnectedInnerHtml
2448
+ render() {
2449
+ return `
2450
+ <style>
2451
+ .tooltip { position: fixed; background: #222; color: white;
2452
+ padding: 8px 12px; border-radius: 4px; font-size: 12px; }
2453
+ </style>
2454
+ <template is="swc-loop" on-get-value="tooltips"
2455
+ on-get-portal="document.body">
2456
+ <div class="tooltip" style="left: ${'${item.x}px'}; top: ${'${item.y}px'}">
2457
+ ${'${item.text}'}
2458
+ </div>
2459
+ </template>
2460
+ `;
2461
+ }
2462
+
2463
+ addTooltip(text: string, x: number, y: number) {
2464
+ this.tooltips.push({ text, x, y });
2465
+ this.render();
2466
+ }
2467
+ }
2468
+
2469
+ // Usage: Tooltip renders outside component hierarchy
2470
+ const tooltipMgr = document.querySelector('tooltip-manager');
2471
+ tooltipMgr.addTooltip('Hover info', 100, 50);
2472
+ ```
2473
+
2474
+
2475
+
2476
+ ## 🔄 Structural Directives (Advanced)
2477
+
2478
+ SWC provides 4 core templates that empower your HTML without wrapper elements: `swc-loop`, `swc-if`, `swc-choose`, and `swc-async`. They all share a consistent, high-performance API with **zero wrapper elements** and **surgical DOM updates**.
2479
+
2480
+ ### Common Attributes:
2481
+ - **`on-get-value`**: Script to fetch data (evaluated in component context).
2482
+ - **`on-clone-node`**: Callback fired for **each individual node clone** (Args: `$node`, `$item`, `$index`, `$isElement`, etc.).
2483
+ - **`on-clone-nodes`**: Callback fired **once after all nodes cloned** (Args: `$nodes`, `$elements`, `$state`, etc.).
2484
+ - **`on-get-portal`**: Render nodes at a different DOM location using a selector.
2485
+ - **`on-state-changed`**: Track rendering state changes.
2486
+
2487
+ ### 1. `swc-if` - Binary Conditional Rendering
2488
+
2489
+ Show/hide content based on a boolean expression. No DOM wrapper.
2490
+
2491
+ ```typescript
2492
+ @Sim
2493
+ @elementDefine('access-control')
2494
+ class AccessControl extends HTMLElement {
2495
+ private user: any = null;
2496
+ private isLoading = false;
2497
+
2498
+ @onConnectedInnerHtml
2499
+ render() {
2500
+ return `
2501
+ <div class="access-panel">
2502
+ <!-- Show ONLY if loading (no wrapper) -->
2503
+ <template is="swc-if" on-get-value="isLoading">
2504
+ <div class="loading-spinner">
2505
+ <spinner-component></spinner-component>
2506
+ <p>Verifying access...</p>
2507
+ </div>
2508
+ </template>
2509
+
2510
+ <!-- Show ONLY if user is authenticated -->
2511
+ <template is="swc-if" on-get-value="user">
2512
+ <template is="swc-default">
2513
+ <p class="not-authorized">⛔ You are not authorized to view this content.</p>
2514
+ </template>
2515
+ <div class="admin-content">
2516
+ <h2>Welcome, \${user?.name}! 🎉</h2>
2517
+ <p>VIP Access Level: \${user?.role}</p>
2518
+ </div>
2519
+ </template>
2520
+ </div>
2521
+ `;
2522
+ }
2523
+ }
2524
+ ```
2525
+
2526
+ ### 2. `swc-loop` - High-Performance List Rendering
2527
+
2528
+ Iterate over arrays with **index-preserving stability** and zero virtual DOM overhead.
2529
+
2530
+ ```typescript
2531
+ @Sim
2532
+ @elementDefine('product-grid')
2533
+ class ProductGrid extends HTMLElement {
2534
+ private products = [
2535
+ { id: 1, name: 'Laptop', price: 999 },
2536
+ { id: 2, name: 'Mouse', price: 29 }
2537
+ ];
2538
+
2539
+ @onConnectedInnerHtml
2540
+ render() {
2541
+ return `
2542
+ <template is="swc-loop" on-get-value="products"
2543
+ on-clone-node="
2544
+ if ($isElement) {
2545
+ $node.querySelector('.price').textContent = '$' + $item.price;
2546
+ }
2547
+ ">
2548
+ <div class="product-card">
2549
+ <h3>\${item.name}</h3>
2550
+ <p class="price">$\${item.price}</p>
2551
+ </div>
2552
+ </template>
2553
+ `;
2554
+ }
2555
+ }
2556
+ ```
2557
+
2558
+ ### 3. `swc-choose` - Multi-Branch Conditional
2559
+
2560
+ Switch-like behavior for multiple conditions.
2561
+
2562
+ ```typescript
2563
+ @templateIsSim
2564
+ @elementDefine('order-status')
2565
+ class OrderStatus extends HTMLElement {
2566
+ private order = { status: 'shipped' };
2567
+
2568
+ @onConnectedInnerHtml
2569
+ render() {
2570
+ return `
2571
+ <template is="swc-choose" on-get-value="order.status">
2572
+ <template is="swc-when" on-get-value="'pending'">
2573
+ <p>⏳ Your order is being prepared...</p>
2574
+ </template>
2575
+ <template is="swc-when" on-get-value="'shipped'">
2576
+ <p>📦 Your order has shipped!</p>
2577
+ </template>
2578
+ <template is="swc-default">
2579
+ <p>❌ Order status unknown</p>
2580
+ </template>
2581
+ </template>
2582
+ `;
2583
+ }
2584
+ }
2585
+ ```
2586
+
2587
+ ### 4. `swc-async` - Async Content Loading
2588
+
2589
+ Automatically handle Promise rendering states.
2590
+
2591
+ ```typescript
2592
+ @Sim
2593
+ @elementDefine('async-data')
2594
+ class AsyncData extends HTMLElement {
2595
+ async fetchUserData(userId: string) {
2596
+ const response = await fetch(`/api/users/\${userId}`);
2597
+ return response.json();
2598
+ }
2599
+
2600
+ @onConnectedInnerHtml
2601
+ render() {
2602
+ return `
2603
+ <template is="swc-async" on-get-value="fetchUserData('user123')">
2604
+ <template is="swc-pending">
2605
+ <p>Loading...</p>
2606
+ </template>
2607
+ <template is="swc-fulfilled">
2608
+ <h2>\${data.name}</h2>
2609
+ <p>Email: \${data.email}</p>
2610
+ </template>
2611
+ <template is="swc-rejected">
2612
+ <p>❌ Error: \${error.message}</p>
2613
+ </template>
2614
+ </template>
2615
+ `;
2616
+ }
2617
+ }
2618
+ ```
2619
+
2620
+ ---
2621
+
2622
+ ## 🚀 Server-Side Rendering (SSR) Project Example
2623
+
2624
+ SWC integrates seamlessly with `@dooboostore/simple-boot-http-server-ssr` to provide **full-stack SSR** applications with automatic tag-name preservation, hydration, and isomorphic routing.
2625
+
2626
+ ### Project Structure
2627
+
2628
+ ```
2629
+ ssr-project/
2630
+ ├── back-end/ # Express/HTTP server with SSR
2631
+ │ └── index.ts # SSR entry point
2632
+ ├── front-end/ # Client-side app
2633
+ │ └── index.ts # Client bootstrap
2634
+ ├── src/
2635
+ │ ├── bootfactory.ts # Universal app configuration
2636
+ │ ├── components/ # Shared components
2637
+ │ └── pages/ # Route components (routers + pages)
2638
+ ```
2639
+
2640
+ ### 1. Backend SSR Setup
2641
+
2642
+ The backend renders components on the server and sends HTML to the client:
2643
+
2644
+ ```typescript
2645
+ // back-end/index.ts
2646
+ import { SimpleBootHttpSSRServer } from '@dooboostore/simple-boot-http-server-ssr';
2647
+ import { SSRSimpleWebComponentFilter } from '@dooboostore/simple-boot-http-server-ssr';
2648
+ import swcRegister from '@dooboostore/simple-web-component';
2649
+ import bootfactory from '@swc-src/bootfactory';
2650
+
2651
+ const swcFilter = new SSRSimpleWebComponentFilter({
2652
+ frontDistPath: path.resolve(__dirname, '../dist-front-end'),
2653
+ frontDistIndexFileName: 'index.html',
2654
+ registerComponents: async (window: any) => {
2655
+ // 1. Register core SWC elements (swc-if, swc-loop, etc.)
2656
+ swcRegister(window);
2657
+
2658
+ // 2. Register and bootstrap your app with routing
2659
+ const urlPath = window.location.pathname;
2660
+ bootfactory(window, urlPath);
2661
+
2662
+ console.log('✅ Components registered for SSR');
2663
+ }
2664
+ });
2665
+
2666
+ const server = new SimpleBootHttpSSRServer({
2667
+ listen: { port: 8080 },
2668
+ filters: [swcFilter]
2669
+ });
2670
+
2671
+ server.listen();
2672
+ console.log('🚀 SSR Server running on http://localhost:8080');
2673
+ ```
2674
+
2675
+ ### 2. Universal Bootfactory
2676
+
2677
+ The bootfactory runs on **both server and client** with different connection modes:
2678
+
2679
+ ```typescript
2680
+ // src/bootfactory.ts
2681
+ import register from '@dooboostore/simple-web-component';
2682
+ import { IndexRouter } from './pages/index.router';
2683
+
2684
+ export default (window: Window, initialPath?: string) => {
2685
+ // Register all components globally
2686
+ register(window, [...componentFactories, ...pageFactories]);
2687
+
2688
+ const app = window.document.querySelector('#app') as any;
2689
+
2690
+ const config = {
2691
+ rootRouter: IndexRouter.SYMBOL,
2692
+ routeType: 'path', // Use browser path: /users, /pages/about
2693
+ path: initialPath ?? '/',
2694
+ window: window
2695
+ };
2696
+
2697
+ // Detect client vs server environment
2698
+ const isClientWindow = typeof window !== 'undefined' && window === globalThis.window;
2699
+
2700
+ if (isClientWindow) {
2701
+ // CLIENT: Use 'swap' mode for zero FOUC hydration
2702
+ app.connect({
2703
+ ...config,
2704
+ connectMode: 'swap' // Clone → Connect → Replace original
2705
+ });
2706
+ } else {
2707
+ // SERVER: Use 'direct' mode (no swap needed)
2708
+ app.connect(config);
2709
+ }
2710
+
2711
+ return app;
2712
+ };
2713
+ ```
2714
+
2715
+ ### 3. Isomorphic Router & Pages
2716
+
2717
+ Define routers that work the same way on server and client:
2718
+
2719
+ ```typescript
2720
+ // src/pages/index.router.ts
2721
+ import { Sim } from '@dooboostore/simple-boot';
2722
+ import { Router } from '@dooboostore/simple-boot';
2723
+ import { elementDefine, onConnectedInnerHtml, setAttribute } from '@dooboostore/simple-web-component';
2724
+ import { RouterAction } from '@dooboostore/simple-boot';
2725
+
2726
+ @Sim
2727
+ @Router({
2728
+ path: '',
2729
+ route: {
2730
+ '/': HomePage.SYMBOL,
2731
+ '/user': UserPage.SYMBOL
2732
+ }
2733
+ })
2734
+ @elementDefine('index-router')
2735
+ class IndexRouter extends HTMLElement implements RouterAction.CanActivate {
2736
+ constructor(private router: Router) {
2737
+ super();
2738
+ }
2739
+
2740
+ // Lifecycle hook: Called BEFORE entering route
2741
+ async canActivate(url: RoutingDataSet, data?: any) {
2742
+ // Inject rendered page component
2743
+ if (data instanceof Node) {
2744
+ console.log(`✅ Rendering: \${data.nodeName}`);
2745
+ this.replaceChildren(data);
2746
+ }
2747
+ }
2748
+
2749
+ @onConnectedInnerHtml({ useShadow: true })
2750
+ render() {
2751
+ return `
2752
+ <style>
2753
+ :host { display: block; }
2754
+ header { background: #222; color: white; padding: 20px; }
2755
+ nav button { margin: 5px; padding: 10px 15px; cursor: pointer; }
2756
+ section { padding: 20px; }
2757
+ </style>
2758
+ <header>
2759
+ <h1>SWC + SSR App</h1>
2760
+ <nav>
2761
+ <button swc-on-click="$host.router.go('/')">🏠 Home</button>
2762
+ <button swc-on-click="$host.router.go('/user')">👤 Users</button>
2763
+ </nav>
2764
+ </header>
2765
+ <section><slot></slot></section>
2766
+ `;
2767
+ }
2768
+ }
2769
+ ```
2770
+
2771
+ ### 4. Page Components
2772
+
2773
+ ```typescript
2774
+ // src/pages/home.page.ts - Leaf route (no children)
2775
+ @Sim
2776
+ @elementDefine('home-page')
2777
+ class HomePage extends HTMLElement {
2778
+ @onConnectedInnerHtml
2779
+ render() {
2780
+ return `
2781
+ <h2>Welcome! 👋</h2>
2782
+ <p>This page was rendered on the server and hydrated on the client.</p>
2783
+ <template is="swc-loop" on-get-value="[1,2,3]">
2784
+ <div style="padding: 10px; background: #f5f5f5; margin: 5px;">
2785
+ Item \${item}
2786
+ </div>
2787
+ </template>
2788
+ `;
2789
+ }
2790
+ }
2791
+
2792
+ // src/pages/user.router.ts - Nested router with sub-routes
2793
+ @Sim
2794
+ @Router({
2795
+ path: '/user',
2796
+ route: {
2797
+ '': UserListPage.SYMBOL,
2798
+ '/:id': UserDetailPage.SYMBOL
2799
+ }
2800
+ })
2801
+ @elementDefine('user-router')
2802
+ class UserRouter extends HTMLElement implements RouterAction.CanActivate {
2803
+ async canActivate(url: RoutingDataSet, data?: any) {
2804
+ if (data instanceof Node) {
2805
+ this.replaceChildren(data);
2806
+ }
2807
+ }
2808
+
2809
+ @onConnectedInnerHtml({ useShadow: true })
2810
+ render() {
2811
+ return `
2812
+ <div style="margin-left: 20px;">
2813
+ <h2>User Management</h2>
2814
+ <slot></slot>
2815
+ </div>
2816
+ `;
2817
+ }
2818
+ }
2819
+ ```
2820
+
2821
+ ### 5. Client Entry Point
2822
+
2823
+ The client reuses the same bootfactory:
2824
+
2825
+ ```typescript
2826
+ // front-end/index.ts
2827
+ import bootfactory from '@swc-src/bootfactory';
2828
+ import { UrlUtils } from '@dooboostore/core/url';
2829
+
2830
+ console.log('🚀 Client-side app initializing...');
2831
+
2832
+ // Get current URL path from browser
2833
+ const currentPath = UrlUtils.getUrlPath(window.location);
2834
+
2835
+ // Bootstrap with swap mode (zero FOUC)
2836
+ bootfactory(window, currentPath);
2837
+
2838
+ console.log('✅ Client app hydrated');
2839
+ ```
2840
+
2841
+ ### 6. Flow Diagram
2842
+
2843
+ ```
2844
+ ┌─────────────────────────────────────────────────────────┐
2845
+ │ User Request │
2846
+ │ GET /user/123 │
2847
+ └──────────────────────┬──────────────────────────────────┘
2848
+ │
2849
+ ┌──────────────▼──────────────┐
2850
+ │ Backend SSR Renderer │
2851
+ ├─────────────────────────────┤
2852
+ │ 1. bootfactory(virtualWin) │
2853
+ │ 2. IndexRouter routes │
2854
+ │ 3. UserRouter renders │
2855
+ │ 4. UserDetailPage → HTML │
2856
+ └──────────────┬──────────────┘
2857
+ │
2858
+ ┌──────────────▼──────────────┐
2859
+ │ Sent to Client as HTML │
2860
+ │ <swc-app> │
2861
+ │ <index-router> │
2862
+ │ <user-router> │
2863
+ │ <user-detail-page> │
2864
+ │ </user-detail-page> │
2865
+ │ </user-router> │
2866
+ │ </index-router> │
2867
+ │ </swc-app> │
2868
+ └──────────────┬──────────────┘
2869
+ │
2870
+ ┌──────────────▼──────────────┐
2871
+ │ Client Hydration Swap │
2872
+ ├─────────────────────────────┤
2873
+ │ 1. Clone SSR HTML │
2874
+ │ 2. Connect listeners │
2875
+ │ 3. Replace original DOM │
2876
+ │ 4. Fully interactive │
2877
+ └─────────────────────────────┘
2878
+ ```
2879
+
2880
+ ### 7. Key Benefits
2881
+
2882
+ | Aspect | Benefit |
2883
+ |--------|---------|
2884
+ | **SEO** | Server renders initial HTML (searchable) |
2885
+ | **Performance** | Faster First Contentful Paint (FCP) |
2886
+ | **Tag Preservation** | Custom element tags survive SSR → client |
2887
+ | **Zero FOUC** | Swap mode clones & replaces seamlessly |
2888
+ | **Code Sharing** | Same router code on server & client |
2889
+ | **Island Architecture** | Multiple independent `<swc-app>` instances |
2890
+ | **Progressive Enhancement** | Works without JavaScript (graceful fallback) |
2891
+
2892
+ ### 8. Build & Run
2893
+
2894
+ ```bash
2895
+ # Build backend (Node.js server)
2896
+ cd back-end && npm run build
2897
+
2898
+ # Build frontend (browser bundle)
2899
+ cd front-end && npm run build
2900
+
2901
+ # Run SSR server on http://localhost:8080
2902
+ npm start
2903
+
2904
+ # Visit http://localhost:8080/user/123
2905
+ # Server renders UserDetailPage → Client hydrates with swap mode
348
2906
  ```
349
2907
 
350
2908
  ---