@dooboostore/simple-web-component 1.0.37 → 1.0.39
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.
- package/README.md +280 -791
- package/dist/cjs/SwcAppEngine.js +3 -0
- package/dist/cjs/SwcAppEngine.js.map +2 -2
- package/dist/cjs/decorators/addEventListener.js +20 -4
- package/dist/cjs/decorators/addEventListener.js.map +2 -2
- package/dist/cjs/decorators/applyAttribute.js.map +1 -1
- package/dist/cjs/decorators/applyClass.js.map +1 -1
- package/dist/cjs/decorators/applyNode.js +104 -35
- package/dist/cjs/decorators/applyNode.js.map +2 -2
- package/dist/cjs/decorators/applySlot.js +69 -57
- package/dist/cjs/decorators/applySlot.js.map +3 -3
- package/dist/cjs/decorators/applyStyle.js.map +2 -2
- package/dist/cjs/decorators/attribute.js.map +1 -1
- package/dist/cjs/decorators/elementDefine.js +65 -42
- package/dist/cjs/decorators/elementDefine.js.map +2 -2
- package/dist/cjs/decorators/emitCustomEvent.js.map +1 -1
- package/dist/cjs/decorators/index.js +1 -0
- package/dist/cjs/decorators/index.js.map +2 -2
- package/dist/cjs/decorators/lifecycles.js +47 -31
- package/dist/cjs/decorators/lifecycles.js.map +3 -3
- package/dist/cjs/decorators/publishSwcAppMessage.js.map +2 -2
- package/dist/cjs/decorators/query.js +1 -1
- package/dist/cjs/decorators/query.js.map +2 -2
- package/dist/cjs/decorators/queryAll.js.map +1 -1
- package/dist/cjs/decorators/setProperty.js.map +1 -1
- package/dist/cjs/decorators/state.js +83 -0
- package/dist/cjs/decorators/state.js.map +7 -0
- package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +1 -1
- package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
- package/dist/cjs/elements/SwcApp.js +193 -305
- package/dist/cjs/elements/SwcApp.js.map +3 -3
- package/dist/cjs/elements/SwcAppMixin.js +291 -0
- package/dist/cjs/elements/SwcAppMixin.js.map +7 -0
- package/dist/cjs/elements/index.js +3 -21
- package/dist/cjs/elements/index.js.map +3 -3
- package/dist/cjs/index.js +1 -3
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/{types.js → types/index.js} +1 -1
- package/dist/cjs/types/index.js.map +7 -0
- package/dist/cjs/types/window.d.js.map +1 -1
- package/dist/cjs/utils/Utils.js +159 -20
- package/dist/cjs/utils/Utils.js.map +3 -3
- package/dist/esm/SwcAppEngine.js +3 -0
- package/dist/esm/SwcAppEngine.js.map +2 -2
- package/dist/esm/decorators/addEventListener.js +20 -4
- package/dist/esm/decorators/addEventListener.js.map +2 -2
- package/dist/esm/decorators/applyAttribute.js.map +1 -1
- package/dist/esm/decorators/applyClass.js.map +1 -1
- package/dist/esm/decorators/applyNode.js +104 -35
- package/dist/esm/decorators/applyNode.js.map +2 -2
- package/dist/esm/decorators/applySlot.js +69 -57
- package/dist/esm/decorators/applySlot.js.map +3 -3
- package/dist/esm/decorators/applyStyle.js.map +2 -2
- package/dist/esm/decorators/attribute.js.map +1 -1
- package/dist/esm/decorators/elementDefine.js +69 -46
- package/dist/esm/decorators/elementDefine.js.map +2 -2
- package/dist/esm/decorators/emitCustomEvent.js.map +1 -1
- package/dist/esm/decorators/index.js +1 -0
- package/dist/esm/decorators/index.js.map +2 -2
- package/dist/esm/decorators/lifecycles.js +47 -31
- package/dist/esm/decorators/lifecycles.js.map +3 -3
- package/dist/esm/decorators/publishSwcAppMessage.js.map +2 -2
- package/dist/esm/decorators/query.js +1 -1
- package/dist/esm/decorators/query.js.map +2 -2
- package/dist/esm/decorators/queryAll.js.map +1 -1
- package/dist/esm/decorators/setProperty.js.map +1 -1
- package/dist/esm/decorators/state.js +64 -0
- package/dist/esm/decorators/state.js.map +7 -0
- package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +1 -1
- package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
- package/dist/esm/elements/SwcApp.js +195 -307
- package/dist/esm/elements/SwcApp.js.map +3 -3
- package/dist/esm/elements/SwcAppMixin.js +273 -0
- package/dist/esm/elements/SwcAppMixin.js.map +7 -0
- package/dist/esm/elements/index.js +2 -7
- package/dist/esm/elements/index.js.map +2 -2
- package/dist/esm/index.js +2 -4
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/{types.js → types/index.js} +1 -1
- package/dist/esm/types/index.js.map +7 -0
- package/dist/esm/utils/Utils.js +159 -20
- package/dist/esm/utils/Utils.js.map +3 -3
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +3448 -2584
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
- package/dist/types/SwcAppEngine.d.ts +1 -1
- package/dist/types/SwcAppEngine.d.ts.map +1 -1
- package/dist/types/decorators/addEventListener.d.ts +5 -2
- package/dist/types/decorators/addEventListener.d.ts.map +1 -1
- package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
- package/dist/types/decorators/applyClass.d.ts.map +1 -1
- package/dist/types/decorators/applyNode.d.ts +23 -7
- package/dist/types/decorators/applyNode.d.ts.map +1 -1
- package/dist/types/decorators/applySlot.d.ts +14 -12
- package/dist/types/decorators/applySlot.d.ts.map +1 -1
- package/dist/types/decorators/applyStyle.d.ts.map +1 -1
- package/dist/types/decorators/attribute.d.ts.map +1 -1
- package/dist/types/decorators/elementDefine.d.ts.map +1 -1
- package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
- package/dist/types/decorators/index.d.ts +1 -0
- package/dist/types/decorators/index.d.ts.map +1 -1
- package/dist/types/decorators/lifecycles.d.ts +10 -14
- package/dist/types/decorators/lifecycles.d.ts.map +1 -1
- package/dist/types/decorators/publishSwcAppMessage.d.ts.map +1 -1
- package/dist/types/decorators/query.d.ts +1 -1
- package/dist/types/decorators/query.d.ts.map +1 -1
- package/dist/types/decorators/queryAll.d.ts.map +1 -1
- package/dist/types/decorators/setProperty.d.ts +1 -1
- package/dist/types/decorators/setProperty.d.ts.map +1 -1
- package/dist/types/decorators/state.d.ts +9 -0
- package/dist/types/decorators/state.d.ts.map +1 -0
- package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +1 -1
- package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +5 -1
- package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +1 -1
- package/dist/types/elements/SwcApp.d.ts +35 -6
- package/dist/types/elements/SwcApp.d.ts.map +1 -1
- package/dist/types/elements/SwcAppMixin.d.ts +405 -0
- package/dist/types/elements/SwcAppMixin.d.ts.map +1 -0
- package/dist/types/elements/index.d.ts +2 -2
- package/dist/types/elements/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/{types.d.ts → types/index.d.ts} +11 -11
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/utils/Utils.d.ts +14 -2
- package/dist/types/utils/Utils.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +3431 -2567
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
- package/package.json +4 -4
- package/src/SwcAppEngine.ts +6 -2
- package/src/decorators/addEventListener.ts +21 -5
- package/src/decorators/applyAttribute.ts +1 -1
- package/src/decorators/applyClass.ts +1 -1
- package/src/decorators/applyNode.ts +183 -73
- package/src/decorators/applySlot.ts +99 -128
- package/src/decorators/applyStyle.ts +4 -4
- package/src/decorators/attribute.ts +1 -1
- package/src/decorators/elementDefine.ts +112 -60
- package/src/decorators/emitCustomEvent.ts +1 -1
- package/src/decorators/index.ts +2 -0
- package/src/decorators/lifecycles.ts +50 -26
- package/src/decorators/publishSwcAppMessage.ts +2 -3
- package/src/decorators/query.ts +4 -4
- package/src/decorators/queryAll.ts +1 -1
- package/src/decorators/setProperty.ts +1 -1
- package/src/decorators/state.ts +83 -0
- package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +1 -1
- package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +4 -3
- package/src/elements/SwcApp.ts +239 -441
- package/src/elements/SwcAppMixin.ts +328 -0
- package/src/elements/index.ts +2 -9
- package/src/index.ts +2 -4
- package/src/{types.ts → types/index.ts} +13 -10
- package/src/types/window.d.ts +3 -0
- package/src/utils/Utils.ts +205 -20
- package/dist/cjs/types.js.map +0 -7
- package/dist/esm/types.js.map +0 -7
- package/dist/types/types.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Inject services into Web Components using the `@onInitialize` decorator.
|
|
|
30
30
|
class Dashboard extends HTMLElement {
|
|
31
31
|
@onInitialize
|
|
32
32
|
onconstructor(
|
|
33
|
-
@
|
|
33
|
+
@inject(UserService.SYMBOL) userService: UserService
|
|
34
34
|
) {
|
|
35
35
|
this.userService = userService;
|
|
36
36
|
}
|
|
@@ -43,7 +43,7 @@ class Dashboard extends HTMLElement {
|
|
|
43
43
|
Render HTML automatically when component connects to DOM.
|
|
44
44
|
|
|
45
45
|
```typescript
|
|
46
|
-
@
|
|
46
|
+
@onConnectedShadowDom
|
|
47
47
|
render() {
|
|
48
48
|
return `<div>Hello, <span>${this.name}</span>!</div>`;
|
|
49
49
|
}
|
|
@@ -53,7 +53,7 @@ render() {
|
|
|
53
53
|
Apply HTML to specific host element.
|
|
54
54
|
|
|
55
55
|
```typescript
|
|
56
|
-
@
|
|
56
|
+
@applyThisInnerHtmlNodeLightDom
|
|
57
57
|
updateContent() {
|
|
58
58
|
return `<p>Updated content</p>`;
|
|
59
59
|
}
|
|
@@ -63,48 +63,204 @@ updateContent() {
|
|
|
63
63
|
Replace child nodes with new content.
|
|
64
64
|
|
|
65
65
|
```typescript
|
|
66
|
-
@
|
|
66
|
+
@applyNodeThisReplaceChildrenLightDom
|
|
67
67
|
replaceChildren(node: Node) {
|
|
68
68
|
return node;
|
|
69
69
|
}
|
|
70
70
|
```
|
|
71
|
+
### 3.5 **Slot Management with @applySlot**
|
|
72
|
+
|
|
73
|
+
Manage dynamic content insertion into named slots using the `@applySlot` decorator. Slots are placeholders in templates that can be updated dynamically.
|
|
71
74
|
|
|
72
|
-
### slot
|
|
73
75
|
```typescript
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
@elementDefine('content-manager')
|
|
77
|
+
class ContentManager extends HTMLElement {
|
|
78
|
+
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
@
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// Method: Replace children in slot with HTML
|
|
81
|
+
@addEventListener('.update-btn', 'click')
|
|
82
|
+
@applySlotReplaceChildren('main-content')
|
|
83
|
+
updateContent() {
|
|
84
|
+
return '<div>Updated content here</div>';
|
|
85
|
+
}
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
// Method: Append HTML to slot
|
|
88
|
+
@applySlotAppendHtml('main-content')
|
|
89
|
+
addMoreContent() {
|
|
90
|
+
return '<p>Additional content</p>';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Method: Append text to slot
|
|
94
|
+
@applySlotAppendText('sidebar')
|
|
95
|
+
addSidebarText() {
|
|
96
|
+
return 'Sidebar text: ' + new Date().toISOString();
|
|
97
|
+
}
|
|
89
98
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
// Method: Clear slot content
|
|
100
|
+
@addEventListener('.clear-btn', 'click')
|
|
101
|
+
@applySlotClear('main-content')
|
|
102
|
+
clearContent() {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@onConnectedInnerHtml
|
|
109
|
+
render() {
|
|
110
|
+
return `
|
|
111
|
+
<div>
|
|
112
|
+
<h2>Main Content</h2>
|
|
113
|
+
<!--[[ main-content ]]-->
|
|
114
|
+
|
|
115
|
+
<aside>
|
|
116
|
+
<h3>Sidebar</h3>
|
|
117
|
+
<!--[[ sidebar ]]-->
|
|
118
|
+
</aside>
|
|
119
|
+
|
|
120
|
+
<button class="update-btn">Update Content</button>
|
|
121
|
+
<button class="clear-btn">Clear Content</button>
|
|
122
|
+
</div>
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
94
125
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Slot Syntax in Templates:**
|
|
129
|
+
- `<!--[[ slot-id ]]-->` - Named slot placeholder (HTML comment syntax)
|
|
130
|
+
|
|
131
|
+
**@applySlot Decorator Variants:**
|
|
132
|
+
- `@applySlot(id, options)` - Generic slot operation with position option
|
|
133
|
+
- `@applySlotReplaceChildren(id)` - Replace all children with HTML
|
|
134
|
+
- `@applySlotReplaceChildrenText(id)` - Replace all children with text
|
|
135
|
+
- `@applySlotAppendHtml(id)` - Append HTML to slot
|
|
136
|
+
- `@applySlotAppendText(id)` - Append text to slot
|
|
137
|
+
- `@applySlotPrependHtml(id)` - Prepend HTML to slot
|
|
138
|
+
- `@applySlotPrependText(id)` - Prepend text to slot
|
|
139
|
+
- `@applySlotClear(id)` - Clear slot content
|
|
140
|
+
|
|
141
|
+
**Position Options:**
|
|
142
|
+
```typescript
|
|
143
|
+
export type ApplySlotPosition =
|
|
144
|
+
| 'prependHtml' // Add HTML at beginning
|
|
145
|
+
| 'prependText' // Add text at beginning
|
|
146
|
+
| 'appendHtml' // Add HTML at end
|
|
147
|
+
| 'appendText' // Add text at end
|
|
148
|
+
| 'replaceChildrenHtml' // Replace all with HTML
|
|
149
|
+
| 'replaceChildrenText' // Replace all with text
|
|
150
|
+
| 'clear'; // Clear all content
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
### 3.6 **State Management with @state**
|
|
155
|
+
|
|
156
|
+
Reactive state management with automatic DOM updates when state changes. State values are accessible in templates and scripts using special syntax.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
@elementDefine('counter-app')
|
|
160
|
+
class CounterApp extends HTMLElement {
|
|
161
|
+
// Declare reactive state
|
|
162
|
+
@state('count')
|
|
163
|
+
count: number = 0;
|
|
164
|
+
|
|
165
|
+
@state('message')
|
|
166
|
+
message: string = 'Hello';
|
|
167
|
+
|
|
168
|
+
@state('isActive')
|
|
169
|
+
isActive: boolean = false;
|
|
170
|
+
|
|
171
|
+
@addEventListener('button', 'click')
|
|
172
|
+
increment() {
|
|
173
|
+
this.count++; // Triggers automatic DOM update
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@onConnectedInnerHtml
|
|
177
|
+
render() {
|
|
178
|
+
return `
|
|
179
|
+
<div>
|
|
180
|
+
<!-- HTML directive: render HTML content -->
|
|
181
|
+
<!--[html @message@ ]-->
|
|
182
|
+
|
|
183
|
+
<!-- Text directive: render as text -->
|
|
184
|
+
<!--[text Count: @count@ ]-->
|
|
185
|
+
|
|
186
|
+
<!-- Attribute binding with a: prefix -->
|
|
187
|
+
<div a:title="Count is @count@"></div>
|
|
188
|
+
|
|
189
|
+
<!-- Event binding with e: prefix -->
|
|
190
|
+
<button e:click="@increment@()">Increment (@count@)</button>
|
|
191
|
+
|
|
192
|
+
<!-- Conditional rendering -->
|
|
193
|
+
<div a:style="@isActive@ ? 'color: green' : 'color: red'">
|
|
194
|
+
Status: @isActive@
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
105
199
|
}
|
|
106
200
|
```
|
|
107
201
|
|
|
202
|
+
**State Syntax in Templates:**
|
|
203
|
+
|
|
204
|
+
**Reading State Values:**
|
|
205
|
+
- `@stateName@` - Read state value as text/attribute
|
|
206
|
+
- `@stateName@()` - Call state property as function (if it's a function)
|
|
207
|
+
- `@expression@` - Evaluate expressions with state variables
|
|
208
|
+
|
|
209
|
+
**Template Directives:**
|
|
210
|
+
- `<!--[html @state@ ]-->` - Render state value as HTML
|
|
211
|
+
- `<!--[text @state@ ]-->` - Render state value as text
|
|
212
|
+
- `a:attributeName="@state@"` - Bind state to HTML attributes
|
|
213
|
+
- `e:eventName="@methodName@()"` - Bind events to methods
|
|
214
|
+
|
|
215
|
+
**How @state Works:**
|
|
216
|
+
1. Declare property with `@state('stateName')`
|
|
217
|
+
2. When property value changes via `this.count++`, setter is triggered
|
|
218
|
+
3. Setter automatically applies state context to templates
|
|
219
|
+
4. All `@stateName@` expressions in templates are re-evaluated
|
|
220
|
+
5. DOM updates automatically with new values
|
|
221
|
+
|
|
222
|
+
**State Context Available:**
|
|
223
|
+
- `@propertyName@` - Access state properties
|
|
224
|
+
- `@methodName@()` - Call methods
|
|
225
|
+
- `@expression@` - Evaluate JavaScript expressions
|
|
226
|
+
- All state variables available in template expressions
|
|
227
|
+
|
|
228
|
+
**Advanced Example:**
|
|
229
|
+
```typescript
|
|
230
|
+
@elementDefine('user-profile')
|
|
231
|
+
class UserProfile extends HTMLElement {
|
|
232
|
+
@state('user')
|
|
233
|
+
user = { name: 'Alice', age: 30, email: 'alice@example.com' };
|
|
234
|
+
|
|
235
|
+
@state('isEditing')
|
|
236
|
+
isEditing = false;
|
|
237
|
+
|
|
238
|
+
@addEventListener('.edit-btn', 'click')
|
|
239
|
+
toggleEdit() {
|
|
240
|
+
this.isEditing = !this.isEditing;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@onConnectedShadowDom
|
|
244
|
+
render() {
|
|
245
|
+
return `
|
|
246
|
+
<div>
|
|
247
|
+
<!-- Display user info -->
|
|
248
|
+
<h2><!--[text @user.name@ ]--></h2>
|
|
249
|
+
<p a:title="Email: @user.email@">Age: @user.age@</p>
|
|
250
|
+
|
|
251
|
+
<!-- Conditional rendering based on state -->
|
|
252
|
+
<div a:style="@isEditing@ ? 'border: 1px solid blue' : 'border: none'">
|
|
253
|
+
<!--[html @isEditing@ ? '<input type="text" />' : '<span>View Mode</span>' ]-->
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<button e:click="toggleEdit">
|
|
257
|
+
@isEditing@ ? 'Save' : 'Edit'
|
|
258
|
+
</button>
|
|
259
|
+
</div>
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
108
264
|
|
|
109
265
|
### 4. **Event Handling**
|
|
110
266
|
|
|
@@ -211,7 +367,7 @@ listItems?: HTMLLIElement[];
|
|
|
211
367
|
```
|
|
212
368
|
|
|
213
369
|
#### @queryThis
|
|
214
|
-
Query the component element itself.
|
|
370
|
+
Query the component element itself. == this
|
|
215
371
|
|
|
216
372
|
```typescript
|
|
217
373
|
@queryThis
|
|
@@ -222,18 +378,6 @@ printTag() {
|
|
|
222
378
|
}
|
|
223
379
|
```
|
|
224
380
|
|
|
225
|
-
#### @queryAllThis
|
|
226
|
-
Query all direct children of the component.
|
|
227
|
-
|
|
228
|
-
```typescript
|
|
229
|
-
@queryAllThis
|
|
230
|
-
children?: HTMLElement[];
|
|
231
|
-
|
|
232
|
-
getChildCount() {
|
|
233
|
-
return this.children?.length ?? 0;
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
381
|
### 6. **Attribute Binding**
|
|
238
382
|
|
|
239
383
|
#### @attribute
|
|
@@ -317,378 +461,6 @@ SWC supports two types of expression syntax for dynamic value evaluation:
|
|
|
317
461
|
|
|
318
462
|
---
|
|
319
463
|
|
|
320
|
-
#### 8.1 **SwcChoose - Multi-Condition Rendering**
|
|
321
|
-
|
|
322
|
-
Switch-case style conditional rendering with `swc-when` and `swc-otherwise` sub-templates.
|
|
323
|
-
|
|
324
|
-
```typescript
|
|
325
|
-
// Component with conditional rendering logic
|
|
326
|
-
@elementDefine('status-display')
|
|
327
|
-
class StatusDisplay extends HTMLElement {
|
|
328
|
-
@attributeThis('status')
|
|
329
|
-
status: string = 'pending';
|
|
330
|
-
|
|
331
|
-
@setProperty('#status-template', 'value')
|
|
332
|
-
getStatus() {
|
|
333
|
-
return this.status;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
@onConnectedInnerHtml
|
|
337
|
-
render() {
|
|
338
|
-
return `
|
|
339
|
-
<template id="status-template" is="swc-choose">
|
|
340
|
-
<template is="swc-when" test="$value === 'pending'">
|
|
341
|
-
<div style="color: orange;">⏳ Processing...</div>
|
|
342
|
-
</template>
|
|
343
|
-
|
|
344
|
-
<template is="swc-when" test="$value === 'success'">
|
|
345
|
-
<div style="color: green;">✓ Completed</div>
|
|
346
|
-
</template>
|
|
347
|
-
|
|
348
|
-
<template is="swc-when" test="$value === 'error'">
|
|
349
|
-
<div style="color: red;">✗ Failed</div>
|
|
350
|
-
</template>
|
|
351
|
-
|
|
352
|
-
<!-- Fallback -->
|
|
353
|
-
<template is="swc-otherwise">
|
|
354
|
-
<div style="color: gray;">Unknown status</div>
|
|
355
|
-
</template>
|
|
356
|
-
</template>
|
|
357
|
-
`;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
**Expression Features:**
|
|
363
|
-
- `$value` contains the value passed to `swc-choose`
|
|
364
|
-
- `skip-if-same` attribute prevents re-render when same template selected
|
|
365
|
-
- **Attribute substitution with `{{= }}`**: `attribute-name="{{= expression }}"` automatically evaluated
|
|
366
|
-
|
|
367
|
-
**Attribute Substitution Example ({{= }} usage):**
|
|
368
|
-
```html
|
|
369
|
-
<template is="swc-choose">
|
|
370
|
-
<template is="swc-when" test="$value?.type === 'user'">
|
|
371
|
-
<user-card
|
|
372
|
-
data-username="{{= $value?.name }}"
|
|
373
|
-
data-email="{{= $value?.email }}"
|
|
374
|
-
data-id="{{= $value?.id }}"
|
|
375
|
-
></user-card>
|
|
376
|
-
</template>
|
|
377
|
-
|
|
378
|
-
<template is="swc-when" value="$value?.type === 'product'">
|
|
379
|
-
<product-card
|
|
380
|
-
data-product-name="{{= $value?.title }}"
|
|
381
|
-
data-price="{{= $value?.price }}"
|
|
382
|
-
></product-card>
|
|
383
|
-
</template>
|
|
384
|
-
</template>
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
---
|
|
388
|
-
|
|
389
|
-
#### 8.2 **SwcIf - Binary Conditional**
|
|
390
|
-
|
|
391
|
-
If-then-else rendering with optional `swc-default` fallback.
|
|
392
|
-
|
|
393
|
-
```typescript
|
|
394
|
-
@elementDefine('user-profile')
|
|
395
|
-
class UserProfile extends HTMLElement {
|
|
396
|
-
private isLoggedIn = false;
|
|
397
|
-
private userData = { userName: 'Alice', userId: 123 };
|
|
398
|
-
|
|
399
|
-
@setProperty('#content', 'value')
|
|
400
|
-
checkAuth() {
|
|
401
|
-
return this.isLoggedIn;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
@onConnectedInnerHtml
|
|
405
|
-
render() {
|
|
406
|
-
return `
|
|
407
|
-
<!-- True condition: renders main template using {{ }} -->
|
|
408
|
-
<template id="content" is="swc-if" value="{{ $host.isLoggedIn }}">
|
|
409
|
-
<!-- Attribute substitution with {{= }} expressions -->
|
|
410
|
-
<user-card
|
|
411
|
-
data-username="{{= $host.userData.userName }}"
|
|
412
|
-
data-userid="{{= $host.userData.userId }}"
|
|
413
|
-
></user-card>
|
|
414
|
-
<div>
|
|
415
|
-
<h2>Welcome, {{ $host.userData.userName }}</h2>
|
|
416
|
-
<button on-click="logout">Logout</button>
|
|
417
|
-
</div>
|
|
418
|
-
|
|
419
|
-
<!-- Fallback template: renders if condition is false -->
|
|
420
|
-
<template is="swc-default">
|
|
421
|
-
<div>
|
|
422
|
-
<p>Please log in</p>
|
|
423
|
-
<button on-click="login">Login</button>
|
|
424
|
-
</div>
|
|
425
|
-
</template>
|
|
426
|
-
</template>
|
|
427
|
-
`;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
**Expression Features:**
|
|
433
|
-
- Binary true/false logic with `{{ }} condition`
|
|
434
|
-
- Main template renders when `value` is truthy
|
|
435
|
-
- `swc-default` sub-template renders when value is falsy
|
|
436
|
-
- Dynamic attribute substitution with `{{= }}` supported
|
|
437
|
-
- Access `$host`, `$value`, and other context variables
|
|
438
|
-
- `{{= functionCall() }}` executes functions and returns results for attributes
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
#### 8.3 **SwcAsync - Promise/Loading State**
|
|
443
|
-
|
|
444
|
-
Automatically handle async operations with loading, success, and error states.
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
@elementDefine('data-loader')
|
|
448
|
-
class DataLoader extends HTMLElement {
|
|
449
|
-
private data: any = null;
|
|
450
|
-
|
|
451
|
-
async fetchData() {
|
|
452
|
-
return new Promise((resolve, reject) => {
|
|
453
|
-
setTimeout(() => {
|
|
454
|
-
resolve({ id: 1, name: 'Sample Data', description: 'API response' });
|
|
455
|
-
}, 2000);
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
@setProperty('#async-content', 'value')
|
|
460
|
-
loadData() {
|
|
461
|
-
return this.fetchData();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
@onConnectedInnerHtml
|
|
465
|
-
render() {
|
|
466
|
-
return `
|
|
467
|
-
<template id="async-content" is="swc-async" value="{{= $host.fetchData() }}">
|
|
468
|
-
<!-- Loading State: Shows while Promise is pending -->
|
|
469
|
-
<template is="swc-loading">
|
|
470
|
-
<div>🔄 Loading data...</div>
|
|
471
|
-
</template>
|
|
472
|
-
|
|
473
|
-
<!-- Success State: Shows when Promise resolves -->
|
|
474
|
-
<!-- Using {{= }} to extract data from resolved Promise -->
|
|
475
|
-
<template is="swc-success">
|
|
476
|
-
<data-card
|
|
477
|
-
data-id="{{= $value.id }}"
|
|
478
|
-
data-name="{{= $value.name }}"
|
|
479
|
-
></data-card>
|
|
480
|
-
<div>
|
|
481
|
-
<p>Data ID: {{ $value.id }}</p>
|
|
482
|
-
<p>Data Name: {{ $value.name }}</p>
|
|
483
|
-
<p>Description: {{= $value.description }}</p>
|
|
484
|
-
</div>
|
|
485
|
-
</template>
|
|
486
|
-
|
|
487
|
-
<!-- Error State: Shows if Promise rejects -->
|
|
488
|
-
<template is="swc-error">
|
|
489
|
-
<div style="color: red;">❌ Failed to load data</div>
|
|
490
|
-
</template>
|
|
491
|
-
</template>
|
|
492
|
-
`;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
**Expression Features:**
|
|
498
|
-
- Promise lifecycle management with `{{ }} conditions`
|
|
499
|
-
- `{{= $host.fetchData() }}` - function call expression that executes and returns Promise
|
|
500
|
-
- `{{= $value.propertyName }}` - extract properties from resolved value
|
|
501
|
-
- `swc-loading` - displays during pending state
|
|
502
|
-
- `swc-success` - displays on successful resolution, `$value` contains resolved result
|
|
503
|
-
- `swc-error` - displays on Promise rejection, `$value` contains error
|
|
504
|
-
- Dynamic attribute substitution with `{{= }}` in all states
|
|
505
|
-
- `on-clone-node` and `on-clone-nodes` callbacks for DOM manipulation
|
|
506
|
-
|
|
507
|
-
---
|
|
508
|
-
|
|
509
|
-
#### 8.4 **SwcLoop - List Rendering**
|
|
510
|
-
|
|
511
|
-
Iterate over arrays and render templates for each item.
|
|
512
|
-
|
|
513
|
-
```typescript
|
|
514
|
-
@elementDefine('product-list')
|
|
515
|
-
class ProductList extends HTMLElement {
|
|
516
|
-
private products = [
|
|
517
|
-
{ id: 1, name: 'Laptop', price: 1200, category: 'electronics' },
|
|
518
|
-
{ id: 2, name: 'Phone', price: 800, category: 'electronics' },
|
|
519
|
-
{ id: 3, name: 'Tablet', price: 500, category: 'electronics' }
|
|
520
|
-
];
|
|
521
|
-
|
|
522
|
-
@setProperty('#products-list', 'value')
|
|
523
|
-
getProducts() {
|
|
524
|
-
return this.products;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
@onConnectedInnerHtml
|
|
528
|
-
render() {
|
|
529
|
-
return `
|
|
530
|
-
<template id="products-list" is="swc-loop" value="{{= $host.products }}">
|
|
531
|
-
<!-- Loop iteration with {{ }} for display and {{= }} for attributes -->
|
|
532
|
-
<div style="margin: 10px; padding: 10px; border: 1px solid #ddd;">
|
|
533
|
-
<!-- Using {{ }} for template content rendering -->
|
|
534
|
-
<h3>{{ $item.name }}</h3>
|
|
535
|
-
<p>Price: {{ $item.price }}</p>
|
|
536
|
-
<p>ID: {{ $item.id }} (Index: {{ $index }})</p>
|
|
537
|
-
|
|
538
|
-
<!-- Using {{= }} for dynamic attribute substitution -->
|
|
539
|
-
<product-card
|
|
540
|
-
data-product-id="{{= $item.id }}"
|
|
541
|
-
data-product-name="{{= $item.name }}"
|
|
542
|
-
data-product-price="{{= $item.price }}"
|
|
543
|
-
data-product-category="{{= $item.category }}"
|
|
544
|
-
></product-card>
|
|
545
|
-
|
|
546
|
-
<!-- Computed expressions with {{= }} -->
|
|
547
|
-
<button data-id="{{= $item.id }}" data-price="{{= $item.price }}">
|
|
548
|
-
Add to Cart - {{ $item.price }}
|
|
549
|
-
</button>
|
|
550
|
-
</div>
|
|
551
|
-
|
|
552
|
-
<!-- Empty fallback: renders when array is empty -->
|
|
553
|
-
<template is="swc-default">
|
|
554
|
-
<p>No products available</p>
|
|
555
|
-
</template>
|
|
556
|
-
</template>
|
|
557
|
-
`;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
**Context Variables (Available in loop):**
|
|
563
|
-
- `$item` - current item in iteration
|
|
564
|
-
- `$index` - zero-based index (0, 1, 2, ...)
|
|
565
|
-
- `$value` - entire array being iterated
|
|
566
|
-
- `$nodes` - cloned DOM nodes
|
|
567
|
-
- `$elements` - cloned HTML elements
|
|
568
|
-
- `$firstElement` - first element in cloned nodes
|
|
569
|
-
|
|
570
|
-
**Expression Features:**
|
|
571
|
-
- `{{= $host.products }}` - function call expression that returns the array to iterate
|
|
572
|
-
- `{{ $item.propertyName }}` - display item properties using standard `{{ }}`
|
|
573
|
-
- `{{= $item.propertyName }}` - extract item properties for attributes using `{{= }}`
|
|
574
|
-
- `{{= computedValue }}` - compute values in attributes (e.g., calculations, method calls)
|
|
575
|
-
- `swc-default` fallback renders when array is empty
|
|
576
|
-
- All cloned attributes support `{{ }}` and `{{= }}` substitution
|
|
577
|
-
- `on-clone-node` callback fires for each cloned node
|
|
578
|
-
- `on-clone-nodes` callback fires after all nodes cloned
|
|
579
|
-
|
|
580
|
-
**Common Patterns:**
|
|
581
|
-
```html
|
|
582
|
-
<!-- Template functions with {{= }} -->
|
|
583
|
-
<template is="swc-loop" value="{{= filterProducts($host.products) }}">
|
|
584
|
-
<!-- Dynamic calculations -->
|
|
585
|
-
<span>Total: {{= $item.price * $item.quantity }}</span>
|
|
586
|
-
|
|
587
|
-
<!-- Conditional attributes -->
|
|
588
|
-
<div class="{{= $item.inStock ? 'available' : 'unavailable' }}"></div>
|
|
589
|
-
|
|
590
|
-
<!-- Method calls as attributes -->
|
|
591
|
-
<button data-formatted="{{= formatPrice($item.price) }}">
|
|
592
|
-
Buy - {{ formatPrice($item.price) }}
|
|
593
|
-
</button>
|
|
594
|
-
</template>
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
**Advanced: replace-wrap-start replace-wrap-end for Custom Expression Syntax**
|
|
598
|
-
|
|
599
|
-
Override default `{{ }}` and `{{= }}` syntax with custom wrappers:
|
|
600
|
-
|
|
601
|
-
```typescript
|
|
602
|
-
@elementDefine('custom-loop')
|
|
603
|
-
class CustomLoop extends HTMLElement {
|
|
604
|
-
private items = [{ id: 1, name: 'Item 1' }];
|
|
605
|
-
|
|
606
|
-
@onConnectedInnerHtml
|
|
607
|
-
render() {
|
|
608
|
-
return `
|
|
609
|
-
<!-- Using custom wrapper syntax [(= ... )] instead of {{= ... }} -->
|
|
610
|
-
<template id="items" is="swc-loop" replace-wrap-start="[(" replace-wrap-end=")]" value="{{= $host.items }}">
|
|
611
|
-
<div>
|
|
612
|
-
<p>Name: [( $item.name )]</p>
|
|
613
|
-
<button data-id="[( $item.id )]">Click me</button>
|
|
614
|
-
</div>
|
|
615
|
-
</template>
|
|
616
|
-
`;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
**replace-wrap attribute options:**
|
|
622
|
-
- Change expression wrapper from default `{#{ }#}` to custom pattern
|
|
623
|
-
- Supported on: `SwcLoop`, `SwcChoose`, `SwcIf`, `SwcAsync`, `register()` directives
|
|
624
|
-
|
|
625
|
-
---
|
|
626
|
-
|
|
627
|
-
#### 8.5 **Declarative Routing with swc-choose**
|
|
628
|
-
|
|
629
|
-
Combine `@subscribeSwcAppRouteChangeWhileConnected` with `swc-choose` for clean, declarative routing without boilerplate.
|
|
630
|
-
|
|
631
|
-
```typescript
|
|
632
|
-
@elementDefine('root-router')
|
|
633
|
-
class RootRouter extends HTMLElement {
|
|
634
|
-
private router: Router;
|
|
635
|
-
|
|
636
|
-
@onInitialize
|
|
637
|
-
onconstructor(router: Router) {
|
|
638
|
-
this.router = router;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
@setProperty('#router', 'value')
|
|
642
|
-
@subscribeSwcAppRouteChangeWhileConnected(['/', '/users', '/users/{id}', '/products/{id}'])
|
|
643
|
-
routeChanged(route: RouterEventType) {
|
|
644
|
-
return route;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
navigate(path: string): void {
|
|
648
|
-
this.router?.go(path);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
@onConnectedInnerHtml
|
|
652
|
-
render() {
|
|
653
|
-
return `
|
|
654
|
-
<template id="router" is="swc-choose">
|
|
655
|
-
<template is="swc-when" test="['', '/'].includes($value?.path)">
|
|
656
|
-
<home-page/>
|
|
657
|
-
</template>
|
|
658
|
-
|
|
659
|
-
<!-- Users List Page -->
|
|
660
|
-
<template is="swc-when" test="$value?.path === '/users'">
|
|
661
|
-
<users-list-page/>
|
|
662
|
-
</template>
|
|
663
|
-
|
|
664
|
-
<!-- User Detail Page: Using {{= }} for dynamic attributes from route params -->
|
|
665
|
-
<template is="swc-when" test="$value?.path.startsWith('/users/')">
|
|
666
|
-
<user-detail-page user-id="{{= $value?.pathData?.id }}"></user-detail-page>
|
|
667
|
-
</template>
|
|
668
|
-
|
|
669
|
-
<!-- Product Detail Page: Using {{= }} for dynamic route parameters -->
|
|
670
|
-
<template is="swc-when" test="$value?.path.startsWith('/products/')">
|
|
671
|
-
<product-detail-page product-id="{{= $value?.pathData?.id }}"></product-detail-page>
|
|
672
|
-
</template>
|
|
673
|
-
|
|
674
|
-
<!-- Not Found -->
|
|
675
|
-
<template is="swc-otherwise">
|
|
676
|
-
<not-found-page/>
|
|
677
|
-
</template>
|
|
678
|
-
</template>
|
|
679
|
-
`;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
**Benefits:**
|
|
685
|
-
- ✅ **No imperative route methods** - routes are declarative templates
|
|
686
|
-
- ✅ **Automatic re-rendering** - attributes like `user-id` update automatically
|
|
687
|
-
- ✅ **Performance optimization** - use `skip-if-same` on templates to prevent re-renders
|
|
688
|
-
- ✅ **Dynamic path parameters** - use `{{= $value?.pathData?.id }}` syntax to inject route params with `{{= }}`
|
|
689
|
-
- ✅ **Cleaner DX** - single unified routing pattern
|
|
690
|
-
- ✅ **Full expression support** - use `{{= }}` for complex parameter transformations
|
|
691
|
-
|
|
692
464
|
### 9. **Component Communication - Message Bus**
|
|
693
465
|
|
|
694
466
|
SWC provides a powerful message bus system for inter-component communication through SwcApp. Components can publish and subscribe to typed messages while connected.
|
|
@@ -699,7 +471,7 @@ Subscribe to messages while component is connected to DOM.
|
|
|
699
471
|
```typescript
|
|
700
472
|
@elementDefine('notification-panel')
|
|
701
473
|
class NotificationPanel extends HTMLElement {
|
|
702
|
-
@subscribeSwcAppMessageWhileConnected
|
|
474
|
+
@subscribeSwcAppMessageWhileConnected
|
|
703
475
|
onAnyMessage(message: SwcAppMessage) {
|
|
704
476
|
console.log('Received message:', message);
|
|
705
477
|
}
|
|
@@ -783,363 +555,50 @@ class ProductDetail extends HTMLElement {
|
|
|
783
555
|
|
|
784
556
|
---
|
|
785
557
|
|
|
786
|
-
### 10. **
|
|
787
|
-
Factory-based component registration with explicit DI.
|
|
558
|
+
### 10. **SwcApp - Multiple Element Types with Mixin Pattern**
|
|
788
559
|
|
|
789
|
-
|
|
790
|
-
// Component factory
|
|
791
|
-
export default (w: Window) => {
|
|
792
|
-
const tagName = 'my-component';
|
|
793
|
-
const existing = w.customElements.get(tagName);
|
|
794
|
-
if (existing) return tagName;
|
|
560
|
+
SWC provides a flexible Mixin-based architecture for creating SwcApp elements that extend different HTMLElement types. This enables building SPAs with any semantic HTML element as the root.
|
|
795
561
|
|
|
796
|
-
|
|
797
|
-
class MyComponent extends w.HTMLElement {
|
|
798
|
-
@onInitialize
|
|
799
|
-
onconstructor(service: MyService) {
|
|
800
|
-
this.service = service;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
return tagName;
|
|
805
|
-
};
|
|
806
|
-
|
|
807
|
-
// Boot factory
|
|
808
|
-
export default (w: Window, container: symbol) => {
|
|
809
|
-
serviceFactories.forEach(s => s(container));
|
|
810
|
-
register(w, [...pageFactories, ...componentFactories]);
|
|
811
|
-
};
|
|
562
|
+
#### Available SwcApp Variants
|
|
812
563
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
564
|
+
```typescript
|
|
565
|
+
import {
|
|
566
|
+
SwcApp, // swc-app (HTMLElement)
|
|
567
|
+
SwcAppBody, // swc-app-body (HTMLBodyElement, is="body")
|
|
568
|
+
SwcAppDiv, // swc-app-div (HTMLDivElement, is="div")
|
|
569
|
+
SwcAppSection, // swc-app-section (HTMLElement, is="section")
|
|
570
|
+
SwcAppMain, // swc-app-main (HTMLElement, is="main")
|
|
571
|
+
SwcAppArticle, // swc-app-article (HTMLElement, is="article")
|
|
572
|
+
SwcAppHeader, // swc-app-header (HTMLElement, is="header")
|
|
573
|
+
SwcAppFooter, // swc-app-footer (HTMLElement, is="footer")
|
|
574
|
+
SwcAppNav, // swc-app-nav (HTMLElement, is="nav")
|
|
575
|
+
SwcAppAside, // swc-app-aside (HTMLElement, is="aside")
|
|
576
|
+
defineSwcAppAll, // Register all SwcApp variants at once
|
|
577
|
+
swcAppFactories // Array of all definition functions
|
|
578
|
+
} from '@dooboostore/simple-web-component';
|
|
823
579
|
```
|
|
824
580
|
|
|
825
|
-
|
|
581
|
+
#### Using SwcAppBody (Recommended for SPAs)
|
|
826
582
|
|
|
827
|
-
Complete example of setting up a production SPA with routing, services, and components.
|
|
828
|
-
|
|
829
|
-
#### Step 1. HTML Entry Point
|
|
830
583
|
```html
|
|
831
584
|
<!DOCTYPE html>
|
|
832
585
|
<html>
|
|
833
586
|
<head>
|
|
834
587
|
<meta charset="UTF-8">
|
|
835
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
836
588
|
<title>My SPA</title>
|
|
837
|
-
<!-- Web Components polyfill for older browsers -->
|
|
838
|
-
<script src="https://unpkg.com/@ungap/custom-elements"></script>
|
|
839
589
|
</head>
|
|
590
|
+
<!-- Use is="swc-app-body" for semantic HTML -->
|
|
840
591
|
<body id="app" is="swc-app-body">
|
|
841
|
-
<!-- Root router will render pages here -->
|
|
842
592
|
<root-router></root-router>
|
|
843
593
|
</body>
|
|
844
594
|
<script src="bundle.js"></script>
|
|
845
595
|
</html>
|
|
846
596
|
```
|
|
847
597
|
|
|
848
|
-
#### Step 2. Services (Dependency Injection)
|
|
849
|
-
```typescript
|
|
850
|
-
// services/UserService.ts
|
|
851
|
-
import { Sim } from '@dooboostore/simple-boot';
|
|
852
|
-
import { ConstructorType } from '@dooboostore/core';
|
|
853
|
-
|
|
854
|
-
export namespace UserService {
|
|
855
|
-
export const SYMBOL = Symbol('UserService');
|
|
856
|
-
|
|
857
|
-
export interface User {
|
|
858
|
-
id: number;
|
|
859
|
-
name: string;
|
|
860
|
-
email: string;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
const users: User[] = [
|
|
864
|
-
{ id: 1, name: 'Alice', email: 'alice@example.com' },
|
|
865
|
-
{ id: 2, name: 'Bob', email: 'bob@example.com' }
|
|
866
|
-
];
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
export interface UserService {
|
|
870
|
-
getUser(id: number): UserService.User | undefined;
|
|
871
|
-
getAllUsers(): UserService.User[];
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
// Factory function: accepts container and returns service class
|
|
875
|
-
export default (container: symbol): ConstructorType<any> => {
|
|
876
|
-
@Sim({ symbol: UserService.SYMBOL, container: container })
|
|
877
|
-
class UserServiceImpl implements UserService {
|
|
878
|
-
getUser(id: number) {
|
|
879
|
-
return UserService.users.find(u => u.id === id);
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
getAllUsers() {
|
|
883
|
-
return [...UserService.users];
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
return UserServiceImpl;
|
|
888
|
-
};
|
|
889
|
-
```
|
|
890
|
-
|
|
891
|
-
```typescript
|
|
892
|
-
// services/index.ts
|
|
893
|
-
import userServiceFactory from './UserService';
|
|
894
|
-
|
|
895
|
-
export const serviceFactories = [userServiceFactory];
|
|
896
|
-
```
|
|
897
|
-
|
|
898
|
-
#### Step 3. Router Configuration (Declarative with swc-choose)
|
|
899
|
-
```typescript
|
|
900
|
-
// pages/RootRouter.ts
|
|
901
|
-
import { elementDefine, subscribeSwcAppRouteChangeWhileConnected, setProperty, onConnectedInnerHtml, onInitialize } from '@dooboostore/simple-web-component';
|
|
902
|
-
import { Router, RouterEventType } from '@dooboostore/core-web';
|
|
903
|
-
|
|
904
|
-
const rootRouterFactory = (w: Window) => {
|
|
905
|
-
const tagName = 'root-router';
|
|
906
|
-
const existing = w.customElements.get(tagName);
|
|
907
|
-
if (existing) return tagName;
|
|
908
|
-
|
|
909
|
-
@elementDefine(tagName, { window: w })
|
|
910
|
-
class RootRouter extends w.HTMLElement {
|
|
911
|
-
private router: Router;
|
|
912
|
-
|
|
913
|
-
@onInitialize
|
|
914
|
-
onconstructor(router: Router) {
|
|
915
|
-
this.router = router;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
@setProperty('#router', 'value')
|
|
919
|
-
@subscribeSwcAppRouteChangeWhileConnected(['/', '/users', '/users/{id}'])
|
|
920
|
-
routeChanged(route: RouterEventType) {
|
|
921
|
-
return route;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
navigate(path: string): void {
|
|
925
|
-
this.router?.go(path);
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
@onConnectedInnerHtml
|
|
929
|
-
render() {
|
|
930
|
-
return `
|
|
931
|
-
<template id="router" is="swc-choose">
|
|
932
|
-
<!-- Home Page -->
|
|
933
|
-
<template is="swc-when" test="['', '/'].includes($value?.path)">
|
|
934
|
-
<home-page/>
|
|
935
|
-
</template>
|
|
936
|
-
|
|
937
|
-
<!-- Users List -->
|
|
938
|
-
<template is="swc-when" test="$value?.path === '/users'">
|
|
939
|
-
<users-list-page/>
|
|
940
|
-
</template>
|
|
941
|
-
|
|
942
|
-
<!-- User Detail -->
|
|
943
|
-
<template is="swc-when" test="$value?.path.startsWith('/users/')">
|
|
944
|
-
<user-detail-page user-id="{{$value?.pathData?.id}}"/>
|
|
945
|
-
</template>
|
|
946
|
-
|
|
947
|
-
<!-- Not Found -->
|
|
948
|
-
<template is="swc-otherwise">
|
|
949
|
-
<not-found-page/>
|
|
950
|
-
</template>
|
|
951
|
-
</template>
|
|
952
|
-
`;
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
return tagName;
|
|
957
|
-
};
|
|
958
|
-
|
|
959
|
-
export default rootRouterFactory;
|
|
960
|
-
```
|
|
961
|
-
|
|
962
|
-
#### Step 4. Page Components
|
|
963
|
-
```typescript
|
|
964
|
-
// pages/HomePage.ts
|
|
965
|
-
import { elementDefine, onConnectedInnerHtml } from '@dooboostore/simple-web-component';
|
|
966
|
-
|
|
967
|
-
const HomePage = (w: Window) => {
|
|
968
|
-
const tagName = 'home-page';
|
|
969
|
-
const existing = w.customElements.get(tagName);
|
|
970
|
-
if (existing) return tagName;
|
|
971
|
-
|
|
972
|
-
@elementDefine(tagName, { window: w })
|
|
973
|
-
class HomePage extends w.HTMLElement {
|
|
974
|
-
@onConnectedInnerHtml({ useShadow: true })
|
|
975
|
-
render() {
|
|
976
|
-
return `
|
|
977
|
-
<style>
|
|
978
|
-
:host { display: block; padding: 20px; }
|
|
979
|
-
h1 { color: #333; }
|
|
980
|
-
a { color: #0066cc; text-decoration: none; margin-right: 20px; }
|
|
981
|
-
</style>
|
|
982
|
-
<h1>Welcome to My SPA</h1>
|
|
983
|
-
<nav>
|
|
984
|
-
<a href="/users">View All Users</a>
|
|
985
|
-
</nav>
|
|
986
|
-
`;
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
return tagName;
|
|
991
|
-
};
|
|
992
|
-
|
|
993
|
-
export default HomePage;
|
|
994
|
-
```
|
|
995
|
-
|
|
996
|
-
```typescript
|
|
997
|
-
// pages/UsersListPage.ts
|
|
998
|
-
import { elementDefine, onConnectedInnerHtml, onConnectedBefore } from '@dooboostore/simple-web-component';
|
|
999
|
-
import { Inject } from '@dooboostore/simple-boot';
|
|
1000
|
-
import { UserService } from '../services/UserService';
|
|
1001
|
-
|
|
1002
|
-
const UsersListPage = (w: Window) => {
|
|
1003
|
-
const tagName = 'users-list-page';
|
|
1004
|
-
const existing = w.customElements.get(tagName);
|
|
1005
|
-
if (existing) return tagName;
|
|
1006
|
-
|
|
1007
|
-
@elementDefine(tagName, { window: w })
|
|
1008
|
-
class UsersListPage extends w.HTMLElement {
|
|
1009
|
-
private users: any[] = [];
|
|
1010
|
-
|
|
1011
|
-
@onConnectedBefore
|
|
1012
|
-
onconstructor(@Inject({ symbol: UserService.SYMBOL }) userService: UserService) {
|
|
1013
|
-
this.users = userService.getAllUsers();
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
@onConnectedInnerHtml({ useShadow: true })
|
|
1017
|
-
render() {
|
|
1018
|
-
const usersList = this.users
|
|
1019
|
-
.map(u => `<li><a href="/users/${u.id}">${u.name}</a></li>`)
|
|
1020
|
-
.join('');
|
|
1021
|
-
|
|
1022
|
-
return `
|
|
1023
|
-
<style>
|
|
1024
|
-
:host { display: block; padding: 20px; }
|
|
1025
|
-
h1 { color: #333; }
|
|
1026
|
-
ul { list-style: none; padding: 0; }
|
|
1027
|
-
li { margin: 10px 0; }
|
|
1028
|
-
a { color: #0066cc; text-decoration: none; }
|
|
1029
|
-
</style>
|
|
1030
|
-
<h1>Users</h1>
|
|
1031
|
-
<ul>${usersList}</ul>
|
|
1032
|
-
<a href="/">Back to Home</a>
|
|
1033
|
-
`;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
return tagName;
|
|
1038
|
-
};
|
|
1039
|
-
|
|
1040
|
-
export default UsersListPage;
|
|
1041
|
-
```
|
|
1042
|
-
|
|
1043
|
-
#### Step 4-1. Reusable Components
|
|
1044
|
-
```typescript
|
|
1045
|
-
// components/UserCard.ts
|
|
1046
|
-
import { elementDefine, onConnectedInnerHtml, attributeHost } from '@dooboostore/simple-web-component';
|
|
1047
|
-
|
|
1048
|
-
const UserCard = (w: Window) => {
|
|
1049
|
-
const tagName = 'user-card';
|
|
1050
|
-
const existing = w.customElements.get(tagName);
|
|
1051
|
-
if (existing) return tagName;
|
|
1052
|
-
|
|
1053
|
-
@elementDefine(tagName, { window: w })
|
|
1054
|
-
class UserCard extends w.HTMLElement {
|
|
1055
|
-
@attributeThis('data-username')
|
|
1056
|
-
username: string;
|
|
1057
|
-
|
|
1058
|
-
@attributeThis('data-email')
|
|
1059
|
-
email: string;
|
|
1060
|
-
|
|
1061
|
-
@onConnectedInnerHtml({ useShadow: true })
|
|
1062
|
-
render() {
|
|
1063
|
-
return `
|
|
1064
|
-
<style>
|
|
1065
|
-
:host { display: block; }
|
|
1066
|
-
.card { border: 1px solid #ddd; border-radius: 4px; padding: 12px; }
|
|
1067
|
-
.name { font-weight: bold; font-size: 14px; }
|
|
1068
|
-
.email { color: #666; font-size: 12px; }
|
|
1069
|
-
</style>
|
|
1070
|
-
<div class="card">
|
|
1071
|
-
<div class="name">${this.username}</div>
|
|
1072
|
-
<div class="email">${this.email}</div>
|
|
1073
|
-
</div>
|
|
1074
|
-
`;
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
return tagName;
|
|
1079
|
-
};
|
|
1080
|
-
|
|
1081
|
-
export default UserCard;
|
|
1082
|
-
```
|
|
1083
|
-
|
|
1084
|
-
#### Step 5. Export Factories
|
|
1085
|
-
|
|
1086
|
-
**services/index.ts** - Export all service factories
|
|
1087
|
-
```typescript
|
|
1088
|
-
// services/index.ts
|
|
1089
|
-
import userServiceFactory from './UserService';
|
|
1090
|
-
|
|
1091
|
-
export const serviceFactories = [userServiceFactory];
|
|
1092
|
-
```
|
|
1093
|
-
|
|
1094
|
-
**components/index.ts** - Export all component factories
|
|
1095
|
-
```typescript
|
|
1096
|
-
// components/index.ts
|
|
1097
|
-
import UserCard from './UserCard';
|
|
1098
|
-
|
|
1099
|
-
export const componentFactories = [
|
|
1100
|
-
UserCard,
|
|
1101
|
-
];
|
|
1102
|
-
```
|
|
1103
|
-
|
|
1104
|
-
**pages/index.ts** - Export all page factories with router
|
|
1105
|
-
```typescript
|
|
1106
|
-
// pages/index.ts
|
|
1107
|
-
import rootRouterFactory from './RootRouter';
|
|
1108
|
-
import HomePage from './HomePage';
|
|
1109
|
-
import UsersListPage from './UsersListPage';
|
|
1110
|
-
import UserDetailPage from './UserDetailPage';
|
|
1111
|
-
|
|
1112
|
-
export const pageFactories = [
|
|
1113
|
-
rootRouterFactory, // Router first
|
|
1114
|
-
HomePage,
|
|
1115
|
-
UsersListPage,
|
|
1116
|
-
UserDetailPage
|
|
1117
|
-
];
|
|
1118
|
-
```
|
|
1119
|
-
|
|
1120
|
-
#### Step 6. Application Bootstrap
|
|
1121
|
-
```typescript
|
|
1122
|
-
// bootFactory.ts
|
|
1123
|
-
import { register } from '@dooboostore/simple-web-component';
|
|
1124
|
-
import { serviceFactories } from './services';
|
|
1125
|
-
import { componentFactories } from './components';
|
|
1126
|
-
import { pageFactories } from './pages';
|
|
1127
|
-
|
|
1128
|
-
export default (w: Window, container: symbol) => {
|
|
1129
|
-
// Initialize services with container (dependency injection)
|
|
1130
|
-
// Each factory receives the container symbol and registers its service in the DI container
|
|
1131
|
-
serviceFactories.forEach(serviceFactory => serviceFactory(container));
|
|
1132
|
-
|
|
1133
|
-
// Register pages and components
|
|
1134
|
-
register(w, [...pageFactories, ...componentFactories]);
|
|
1135
|
-
};
|
|
1136
|
-
```
|
|
1137
|
-
|
|
1138
|
-
#### Step 7. Application Entry Point
|
|
1139
598
|
```typescript
|
|
1140
599
|
// index.ts
|
|
1141
600
|
import 'reflect-metadata';
|
|
1142
|
-
import { SwcAppInterface } from '@dooboostore/simple-web-component';
|
|
601
|
+
import { defineSwcAppBody, SwcAppInterface, defineSwcAppAll } from '@dooboostore/simple-web-component';
|
|
1143
602
|
import bootFactory from './bootFactory';
|
|
1144
603
|
|
|
1145
604
|
const w = window;
|
|
@@ -1148,64 +607,94 @@ w.document.addEventListener('DOMContentLoaded', async () => {
|
|
|
1148
607
|
const container = Symbol('app');
|
|
1149
608
|
|
|
1150
609
|
// Initialize services and components
|
|
1151
|
-
|
|
610
|
+
await defineSwcAppBody(w);
|
|
1152
611
|
|
|
1153
|
-
// Wait for customized built-in element to be defined
|
|
1154
|
-
await w.customElements.whenDefined('swc-app-body');
|
|
1155
612
|
|
|
1156
613
|
// Get app root element
|
|
1157
614
|
const appElement = w.document.querySelector('#app') as SwcAppInterface;
|
|
1158
615
|
|
|
1159
616
|
if (appElement && typeof appElement.connect === 'function') {
|
|
1160
|
-
// Connect SPA with routing and DI
|
|
1161
617
|
appElement.connect({
|
|
1162
618
|
path: '/',
|
|
1163
619
|
routeType: 'path',
|
|
1164
620
|
container: container,
|
|
621
|
+
onStartedLazyDefineComponent: [yourComponentFactory1, yourComponentFactor2],
|
|
1165
622
|
window: w,
|
|
1166
623
|
onEngineStarted: () => {
|
|
1167
624
|
console.log('🚀 Application started successfully');
|
|
1168
|
-
// Root router automatically initialized via @elementDefine
|
|
1169
625
|
}
|
|
1170
626
|
});
|
|
1171
|
-
} else {
|
|
1172
|
-
console.error('Failed to initialize SPA');
|
|
1173
627
|
}
|
|
1174
628
|
});
|
|
1175
629
|
```
|
|
1176
630
|
|
|
1177
|
-
####
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
631
|
+
#### Factory Pattern - Component Registration
|
|
632
|
+
|
|
633
|
+
```typescript
|
|
634
|
+
// Component factory
|
|
635
|
+
export default (w: Window) => {
|
|
636
|
+
const tagName = 'my-component';
|
|
637
|
+
const existing = w.customElements.get(tagName);
|
|
638
|
+
if (existing) return tagName;
|
|
639
|
+
|
|
640
|
+
@elementDefine(tagName, { window: w })
|
|
641
|
+
class MyComponent extends w.HTMLElement {
|
|
642
|
+
@onInitialize
|
|
643
|
+
onconstructor(service: MyService) {
|
|
644
|
+
this.service = service;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return tagName;
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
// Boot factory
|
|
652
|
+
export default (w: Window, container: symbol) => {
|
|
653
|
+
serviceFactories.forEach(s => s(container));
|
|
654
|
+
register(w, [...pageFactories, ...componentFactories]);
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
// Entry point
|
|
658
|
+
const container = Symbol('app');
|
|
659
|
+
bootFactory(w, container);
|
|
660
|
+
appElement.connect({
|
|
661
|
+
container,
|
|
662
|
+
window: w,
|
|
663
|
+
onEngineStarted: () => {
|
|
664
|
+
appElement.innerHTML = '<root-router></root-router>';
|
|
665
|
+
}
|
|
666
|
+
});
|
|
1195
667
|
```
|
|
1196
668
|
|
|
1197
|
-
**
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
669
|
+
### 11. **Accommodation Pattern**
|
|
670
|
+
Factory-based component registration with explicit DI.
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
// Component factory
|
|
674
|
+
import {SwcUtils, createElement, CreateElementConfig} from '@dooboostore/simple-web-component';
|
|
675
|
+
const tagName = 'my-component';
|
|
676
|
+
export interface MyComponent extends HTMLElement {
|
|
677
|
+
value: string;
|
|
678
|
+
}
|
|
679
|
+
export const MyComponent = (w: Window|Document, data?: CreateElementConfig) => {
|
|
680
|
+
return createElement<HTMLElement>(w, tagName, data);
|
|
681
|
+
};
|
|
682
|
+
export default (w: Window) => {
|
|
683
|
+
const existing = w.customElements.get(tagName);
|
|
684
|
+
if (existing) return existing;
|
|
1207
685
|
|
|
686
|
+
@elementDefine(tagName, { window: w })
|
|
687
|
+
class MyComponentImp extends w.HTMLElement implements MyComponent{
|
|
688
|
+
value: string = 'value';
|
|
689
|
+
@onInitialize
|
|
690
|
+
onconstructor(service: MyService) {
|
|
691
|
+
this.service = service;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return w.customElements.whenDefined(swcAppTagName);
|
|
695
|
+
};
|
|
1208
696
|
|
|
697
|
+
```
|
|
1209
698
|
---
|
|
1210
699
|
|
|
1211
700
|
## ⚠️ Critical Rules
|
|
@@ -1227,17 +716,17 @@ class UserWidget extends w.HTMLElement { }
|
|
|
1227
716
|
class UserWidget extends w.HTMLElement { }
|
|
1228
717
|
```
|
|
1229
718
|
|
|
1230
|
-
### Factory Always Returns
|
|
719
|
+
### Factory Always Returns
|
|
1231
720
|
```typescript
|
|
1232
721
|
export default (w: Window) => {
|
|
1233
722
|
const tagName = 'my-element';
|
|
1234
723
|
const existing = w.customElements.get(tagName);
|
|
1235
|
-
if (existing) return
|
|
724
|
+
if (existing) return existing;
|
|
1236
725
|
|
|
1237
726
|
@elementDefine(tagName, { window: w })
|
|
1238
727
|
class MyElement extends w.HTMLElement { }
|
|
1239
728
|
|
|
1240
|
-
return
|
|
729
|
+
return existing;
|
|
1241
730
|
};
|
|
1242
731
|
```
|
|
1243
732
|
|
|
@@ -1245,7 +734,7 @@ export default (w: Window) => {
|
|
|
1245
734
|
```typescript
|
|
1246
735
|
// ✅ CORRECT
|
|
1247
736
|
@onInitialize
|
|
1248
|
-
onconstructor(@Inject(
|
|
737
|
+
onconstructor(@Inject(symbol: Service.SYMBOL) service: Service) {
|
|
1249
738
|
this.service = service;
|
|
1250
739
|
}
|
|
1251
740
|
|