@dooboostore/simple-web-component 1.0.38 → 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 +31 -833
- package/dist/cjs/decorators/applyNode.js +26 -39
- package/dist/cjs/decorators/applyNode.js.map +2 -2
- package/dist/cjs/decorators/applySlot.js +69 -64
- package/dist/cjs/decorators/applySlot.js.map +3 -3
- package/dist/cjs/decorators/elementDefine.js +0 -7
- package/dist/cjs/decorators/elementDefine.js.map +2 -2
- package/dist/cjs/decorators/state.js +0 -1
- package/dist/cjs/decorators/state.js.map +2 -2
- package/dist/esm/decorators/applyNode.js +26 -39
- package/dist/esm/decorators/applyNode.js.map +2 -2
- package/dist/esm/decorators/applySlot.js +69 -64
- package/dist/esm/decorators/applySlot.js.map +3 -3
- package/dist/esm/decorators/elementDefine.js +0 -7
- package/dist/esm/decorators/elementDefine.js.map +2 -2
- package/dist/esm/decorators/state.js +0 -1
- package/dist/esm/decorators/state.js.map +2 -2
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +124 -139
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
- package/dist/types/decorators/applyNode.d.ts +2 -2
- 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/state.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +124 -139
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
- package/package.json +3 -3
- package/src/decorators/applyNode.ts +43 -51
- package/src/decorators/applySlot.ts +97 -86
- package/src/decorators/elementDefine.ts +6 -6
- package/src/decorators/state.ts +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,10 +63,11 @@ 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**
|
|
71
72
|
|
|
72
73
|
Manage dynamic content insertion into named slots using the `@applySlot` decorator. Slots are placeholders in templates that can be updated dynamically.
|
|
@@ -74,12 +75,7 @@ Manage dynamic content insertion into named slots using the `@applySlot` decorat
|
|
|
74
75
|
```typescript
|
|
75
76
|
@elementDefine('content-manager')
|
|
76
77
|
class ContentManager extends HTMLElement {
|
|
77
|
-
|
|
78
|
-
@applySlot('main-content')
|
|
79
|
-
mainSlot?: NodeSlot;
|
|
80
|
-
|
|
81
|
-
@applySlot('sidebar')
|
|
82
|
-
sidebarSlot?: NodeSlot;
|
|
78
|
+
|
|
83
79
|
|
|
84
80
|
// Method: Replace children in slot with HTML
|
|
85
81
|
@addEventListener('.update-btn', 'click')
|
|
@@ -107,14 +103,7 @@ class ContentManager extends HTMLElement {
|
|
|
107
103
|
return true;
|
|
108
104
|
}
|
|
109
105
|
|
|
110
|
-
|
|
111
|
-
@onConnectedAfter
|
|
112
|
-
setupSlots() {
|
|
113
|
-
// Access slot via property for manual manipulation
|
|
114
|
-
if (this.mainSlot) {
|
|
115
|
-
this.mainSlot.appendText('main-content', 'Initial text');
|
|
116
|
-
}
|
|
117
|
-
}
|
|
106
|
+
|
|
118
107
|
|
|
119
108
|
@onConnectedInnerHtml
|
|
120
109
|
render() {
|
|
@@ -161,18 +150,6 @@ export type ApplySlotPosition =
|
|
|
161
150
|
| 'clear'; // Clear all content
|
|
162
151
|
```
|
|
163
152
|
|
|
164
|
-
**Manual Slot Manipulation:**
|
|
165
|
-
```typescript
|
|
166
|
-
// Via @applySlot property
|
|
167
|
-
@applySlot('content')
|
|
168
|
-
contentSlot?: NodeSlot;
|
|
169
|
-
|
|
170
|
-
// In lifecycle or methods
|
|
171
|
-
this.contentSlot?.appendHtml('content', '<p>New content</p>');
|
|
172
|
-
this.contentSlot?.appendText('content', 'Text content');
|
|
173
|
-
this.contentSlot?.replaceChildrenHtml('content', '<div>Replaced</div>');
|
|
174
|
-
this.contentSlot?.clear('content');
|
|
175
|
-
```
|
|
176
153
|
|
|
177
154
|
### 3.6 **State Management with @state**
|
|
178
155
|
|
|
@@ -210,7 +187,7 @@ class CounterApp extends HTMLElement {
|
|
|
210
187
|
<div a:title="Count is @count@"></div>
|
|
211
188
|
|
|
212
189
|
<!-- Event binding with e: prefix -->
|
|
213
|
-
<button e:click="increment">Increment (@count@)</button>
|
|
190
|
+
<button e:click="@increment@()">Increment (@count@)</button>
|
|
214
191
|
|
|
215
192
|
<!-- Conditional rendering -->
|
|
216
193
|
<div a:style="@isActive@ ? 'color: green' : 'color: red'">
|
|
@@ -233,7 +210,7 @@ class CounterApp extends HTMLElement {
|
|
|
233
210
|
- `<!--[html @state@ ]-->` - Render state value as HTML
|
|
234
211
|
- `<!--[text @state@ ]-->` - Render state value as text
|
|
235
212
|
- `a:attributeName="@state@"` - Bind state to HTML attributes
|
|
236
|
-
- `e:eventName="methodName"` - Bind events to methods
|
|
213
|
+
- `e:eventName="@methodName@()"` - Bind events to methods
|
|
237
214
|
|
|
238
215
|
**How @state Works:**
|
|
239
216
|
1. Declare property with `@state('stateName')`
|
|
@@ -263,7 +240,7 @@ class UserProfile extends HTMLElement {
|
|
|
263
240
|
this.isEditing = !this.isEditing;
|
|
264
241
|
}
|
|
265
242
|
|
|
266
|
-
@
|
|
243
|
+
@onConnectedShadowDom
|
|
267
244
|
render() {
|
|
268
245
|
return `
|
|
269
246
|
<div>
|
|
@@ -390,7 +367,7 @@ listItems?: HTMLLIElement[];
|
|
|
390
367
|
```
|
|
391
368
|
|
|
392
369
|
#### @queryThis
|
|
393
|
-
Query the component element itself.
|
|
370
|
+
Query the component element itself. == this
|
|
394
371
|
|
|
395
372
|
```typescript
|
|
396
373
|
@queryThis
|
|
@@ -401,18 +378,6 @@ printTag() {
|
|
|
401
378
|
}
|
|
402
379
|
```
|
|
403
380
|
|
|
404
|
-
#### @queryAllThis
|
|
405
|
-
Query all direct children of the component.
|
|
406
|
-
|
|
407
|
-
```typescript
|
|
408
|
-
@queryAllThis
|
|
409
|
-
children?: HTMLElement[];
|
|
410
|
-
|
|
411
|
-
getChildCount() {
|
|
412
|
-
return this.children?.length ?? 0;
|
|
413
|
-
}
|
|
414
|
-
```
|
|
415
|
-
|
|
416
381
|
### 6. **Attribute Binding**
|
|
417
382
|
|
|
418
383
|
#### @attribute
|
|
@@ -496,321 +461,6 @@ SWC supports two types of expression syntax for dynamic value evaluation:
|
|
|
496
461
|
|
|
497
462
|
---
|
|
498
463
|
|
|
499
|
-
#### 8.1 **SwcChoose - Multi-Condition Rendering**
|
|
500
|
-
|
|
501
|
-
Switch-case style conditional rendering with `swc-when` and `swc-otherwise` sub-templates.
|
|
502
|
-
|
|
503
|
-
```typescript
|
|
504
|
-
// Component with conditional rendering logic
|
|
505
|
-
@elementDefine('status-display')
|
|
506
|
-
class StatusDisplay extends HTMLElement {
|
|
507
|
-
@attributeThis('status')
|
|
508
|
-
status: string = 'pending';
|
|
509
|
-
|
|
510
|
-
@setProperty('#status-template', 'value')
|
|
511
|
-
getStatus() {
|
|
512
|
-
return this.status;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
@onConnectedInnerHtml
|
|
516
|
-
render() {
|
|
517
|
-
return `
|
|
518
|
-
<template id="status-template" is="swc-choose">
|
|
519
|
-
<template is="swc-when" test="$value === 'pending'">
|
|
520
|
-
<div style="color: orange;">⏳ Processing...</div>
|
|
521
|
-
</template>
|
|
522
|
-
|
|
523
|
-
<template is="swc-when" test="$value === 'success'">
|
|
524
|
-
<div style="color: green;">✓ Completed</div>
|
|
525
|
-
</template>
|
|
526
|
-
|
|
527
|
-
<template is="swc-when" test="$value === 'error'">
|
|
528
|
-
<div style="color: red;">✗ Failed</div>
|
|
529
|
-
</template>
|
|
530
|
-
|
|
531
|
-
<!-- Fallback -->
|
|
532
|
-
<template is="swc-otherwise">
|
|
533
|
-
<div style="color: gray;">Unknown status</div>
|
|
534
|
-
</template>
|
|
535
|
-
</template>
|
|
536
|
-
`;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
**Expression Features:**
|
|
542
|
-
- `$value` contains the value passed to `swc-choose`
|
|
543
|
-
- `skip-if-same` attribute prevents re-render when same template selected
|
|
544
|
-
- **Attribute substitution with `{{= }}`**: `attribute-name="{{= expression }}"` automatically evaluated
|
|
545
|
-
|
|
546
|
-
**Attribute Substitution Example ({{= }} usage):**
|
|
547
|
-
```html
|
|
548
|
-
<template is="swc-choose">
|
|
549
|
-
<template is="swc-when" test="$value?.type === 'user'">
|
|
550
|
-
<user-card
|
|
551
|
-
data-username="{{= $value?.name }}"
|
|
552
|
-
data-email="{{= $value?.email }}"
|
|
553
|
-
data-id="{{= $value?.id }}"
|
|
554
|
-
></user-card>
|
|
555
|
-
</template>
|
|
556
|
-
|
|
557
|
-
<template is="swc-when" value="$value?.type === 'product'">
|
|
558
|
-
<product-card
|
|
559
|
-
data-product-name="{{= $value?.title }}"
|
|
560
|
-
data-price="{{= $value?.price }}"
|
|
561
|
-
></product-card>
|
|
562
|
-
</template>
|
|
563
|
-
</template>
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
---
|
|
567
|
-
|
|
568
|
-
#### 8.2 **SwcIf - Binary Conditional**
|
|
569
|
-
|
|
570
|
-
If-then-else rendering with optional `swc-default` fallback.
|
|
571
|
-
|
|
572
|
-
```typescript
|
|
573
|
-
@elementDefine('user-profile')
|
|
574
|
-
class UserProfile extends HTMLElement {
|
|
575
|
-
private isLoggedIn = false;
|
|
576
|
-
private userData = { userName: 'Alice', userId: 123 };
|
|
577
|
-
|
|
578
|
-
@setProperty('#content', 'value')
|
|
579
|
-
checkAuth() {
|
|
580
|
-
return this.isLoggedIn;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
@onConnectedInnerHtml
|
|
584
|
-
render() {
|
|
585
|
-
return `
|
|
586
|
-
<!-- True condition: renders main template using {{ }} -->
|
|
587
|
-
<template id="content" is="swc-if" value="{{ $host.isLoggedIn }}">
|
|
588
|
-
<!-- Attribute substitution with {{= }} expressions -->
|
|
589
|
-
<user-card
|
|
590
|
-
data-username="{{= $host.userData.userName }}"
|
|
591
|
-
data-userid="{{= $host.userData.userId }}"
|
|
592
|
-
></user-card>
|
|
593
|
-
<div>
|
|
594
|
-
<h2>Welcome, {{ $host.userData.userName }}</h2>
|
|
595
|
-
<button on-click="logout">Logout</button>
|
|
596
|
-
</div>
|
|
597
|
-
|
|
598
|
-
<!-- Fallback template: renders if condition is false -->
|
|
599
|
-
<template is="swc-default">
|
|
600
|
-
<div>
|
|
601
|
-
<p>Please log in</p>
|
|
602
|
-
<button on-click="login">Login</button>
|
|
603
|
-
</div>
|
|
604
|
-
</template>
|
|
605
|
-
</template>
|
|
606
|
-
`;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
**Expression Features:**
|
|
612
|
-
- Binary true/false logic with `{{ }} condition`
|
|
613
|
-
- Main template renders when `value` is truthy
|
|
614
|
-
- `swc-default` sub-template renders when value is falsy
|
|
615
|
-
- Dynamic attribute substitution with `{{= }}` supported
|
|
616
|
-
- Access `$host`, `$value`, and other context variables
|
|
617
|
-
- `{{= functionCall() }}` executes functions and returns results for attributes
|
|
618
|
-
|
|
619
|
-
---
|
|
620
|
-
|
|
621
|
-
#### 8.3 **SwcAsync - Promise/Loading State**
|
|
622
|
-
|
|
623
|
-
Automatically handle async operations with loading, success, and error states.
|
|
624
|
-
|
|
625
|
-
```typescript
|
|
626
|
-
@elementDefine('data-loader')
|
|
627
|
-
class DataLoader extends HTMLElement {
|
|
628
|
-
private data: any = null;
|
|
629
|
-
|
|
630
|
-
async fetchData() {
|
|
631
|
-
return new Promise((resolve, reject) => {
|
|
632
|
-
setTimeout(() => {
|
|
633
|
-
resolve({ id: 1, name: 'Sample Data', description: 'API response' });
|
|
634
|
-
}, 2000);
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
@setProperty('#async-content', 'value')
|
|
639
|
-
loadData() {
|
|
640
|
-
return this.fetchData();
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
@onConnectedInnerHtml
|
|
644
|
-
render() {
|
|
645
|
-
return `
|
|
646
|
-
<template id="async-content" is="swc-async" value="{{= $host.fetchData() }}">
|
|
647
|
-
<!-- Loading State: Shows while Promise is pending -->
|
|
648
|
-
<template is="swc-loading">
|
|
649
|
-
<div>🔄 Loading data...</div>
|
|
650
|
-
</template>
|
|
651
|
-
|
|
652
|
-
<!-- Success State: Shows when Promise resolves -->
|
|
653
|
-
<!-- Using {{= }} to extract data from resolved Promise -->
|
|
654
|
-
<template is="swc-success">
|
|
655
|
-
<data-card
|
|
656
|
-
data-id="{{= $value.id }}"
|
|
657
|
-
data-name="{{= $value.name }}"
|
|
658
|
-
></data-card>
|
|
659
|
-
<div>
|
|
660
|
-
<p>Data ID: {{ $value.id }}</p>
|
|
661
|
-
<p>Data Name: {{ $value.name }}</p>
|
|
662
|
-
<p>Description: {{= $value.description }}</p>
|
|
663
|
-
</div>
|
|
664
|
-
</template>
|
|
665
|
-
|
|
666
|
-
<!-- Error State: Shows if Promise rejects -->
|
|
667
|
-
<template is="swc-error">
|
|
668
|
-
<div style="color: red;">❌ Failed to load data</div>
|
|
669
|
-
</template>
|
|
670
|
-
</template>
|
|
671
|
-
`;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
```
|
|
675
|
-
|
|
676
|
-
**Expression Features:**
|
|
677
|
-
- Promise lifecycle management with `{{ }} conditions`
|
|
678
|
-
- `{{= $host.fetchData() }}` - function call expression that executes and returns Promise
|
|
679
|
-
- `{{= $value.propertyName }}` - extract properties from resolved value
|
|
680
|
-
- `swc-loading` - displays during pending state
|
|
681
|
-
- `swc-success` - displays on successful resolution, `$value` contains resolved result
|
|
682
|
-
- `swc-error` - displays on Promise rejection, `$value` contains error
|
|
683
|
-
- Dynamic attribute substitution with `{{= }}` in all states
|
|
684
|
-
- `on-clone-node` and `on-clone-nodes` callbacks for DOM manipulation
|
|
685
|
-
|
|
686
|
-
---
|
|
687
|
-
|
|
688
|
-
#### 8.4 **SwcLoop - List Rendering**
|
|
689
|
-
|
|
690
|
-
Iterate over arrays and render templates for each item.
|
|
691
|
-
|
|
692
|
-
```typescript
|
|
693
|
-
@elementDefine('product-list')
|
|
694
|
-
class ProductList extends HTMLElement {
|
|
695
|
-
private products = [
|
|
696
|
-
{ id: 1, name: 'Laptop', price: 1200, category: 'electronics' },
|
|
697
|
-
{ id: 2, name: 'Phone', price: 800, category: 'electronics' },
|
|
698
|
-
{ id: 3, name: 'Tablet', price: 500, category: 'electronics' }
|
|
699
|
-
];
|
|
700
|
-
|
|
701
|
-
@setProperty('#products-list', 'value')
|
|
702
|
-
getProducts() {
|
|
703
|
-
return this.products;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
@onConnectedInnerHtml
|
|
707
|
-
render() {
|
|
708
|
-
return `
|
|
709
|
-
<template id="products-list" is="swc-loop" value="{{= $host.products }}">
|
|
710
|
-
<!-- Loop iteration with {{ }} for display and {{= }} for attributes -->
|
|
711
|
-
<div style="margin: 10px; padding: 10px; border: 1px solid #ddd;">
|
|
712
|
-
<!-- Using {{ }} for template content rendering -->
|
|
713
|
-
<h3>{{ $item.name }}</h3>
|
|
714
|
-
<p>Price: {{ $item.price }}</p>
|
|
715
|
-
<p>ID: {{ $item.id }} (Index: {{ $index }})</p>
|
|
716
|
-
|
|
717
|
-
<!-- Using {{= }} for dynamic attribute substitution -->
|
|
718
|
-
<product-card
|
|
719
|
-
data-product-id="{{= $item.id }}"
|
|
720
|
-
data-product-name="{{= $item.name }}"
|
|
721
|
-
data-product-price="{{= $item.price }}"
|
|
722
|
-
data-product-category="{{= $item.category }}"
|
|
723
|
-
></product-card>
|
|
724
|
-
|
|
725
|
-
<!-- Computed expressions with {{= }} -->
|
|
726
|
-
<button data-id="{{= $item.id }}" data-price="{{= $item.price }}">
|
|
727
|
-
Add to Cart - {{ $item.price }}
|
|
728
|
-
</button>
|
|
729
|
-
</div>
|
|
730
|
-
|
|
731
|
-
<!-- Empty fallback: renders when array is empty -->
|
|
732
|
-
<template is="swc-default">
|
|
733
|
-
<p>No products available</p>
|
|
734
|
-
</template>
|
|
735
|
-
</template>
|
|
736
|
-
`;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
**Context Variables (Available in loop):**
|
|
742
|
-
- `$item` - current item in iteration
|
|
743
|
-
- `$index` - zero-based index (0, 1, 2, ...)
|
|
744
|
-
- `$value` - entire array being iterated
|
|
745
|
-
- `$nodes` - cloned DOM nodes
|
|
746
|
-
- `$elements` - cloned HTML elements
|
|
747
|
-
- `$firstElement` - first element in cloned nodes
|
|
748
|
-
|
|
749
|
-
**Expression Features:**
|
|
750
|
-
- `{{= $host.products }}` - function call expression that returns the array to iterate
|
|
751
|
-
- `{{ $item.propertyName }}` - display item properties using standard `{{ }}`
|
|
752
|
-
- `{{= $item.propertyName }}` - extract item properties for attributes using `{{= }}`
|
|
753
|
-
- `{{= computedValue }}` - compute values in attributes (e.g., calculations, method calls)
|
|
754
|
-
- `swc-default` fallback renders when array is empty
|
|
755
|
-
- All cloned attributes support `{{ }}` and `{{= }}` substitution
|
|
756
|
-
- `on-clone-node` callback fires for each cloned node
|
|
757
|
-
- `on-clone-nodes` callback fires after all nodes cloned
|
|
758
|
-
|
|
759
|
-
**Common Patterns:**
|
|
760
|
-
```html
|
|
761
|
-
<!-- Template functions with {{= }} -->
|
|
762
|
-
<template is="swc-loop" value="{{= filterProducts($host.products) }}">
|
|
763
|
-
<!-- Dynamic calculations -->
|
|
764
|
-
<span>Total: {{= $item.price * $item.quantity }}</span>
|
|
765
|
-
|
|
766
|
-
<!-- Conditional attributes -->
|
|
767
|
-
<div class="{{= $item.inStock ? 'available' : 'unavailable' }}"></div>
|
|
768
|
-
|
|
769
|
-
<!-- Method calls as attributes -->
|
|
770
|
-
<button data-formatted="{{= formatPrice($item.price) }}">
|
|
771
|
-
Buy - {{ formatPrice($item.price) }}
|
|
772
|
-
</button>
|
|
773
|
-
</template>
|
|
774
|
-
```
|
|
775
|
-
|
|
776
|
-
**Advanced: replace-wrap-start replace-wrap-end for Custom Expression Syntax**
|
|
777
|
-
|
|
778
|
-
Override default `{{ }}` and `{{= }}` syntax with custom wrappers:
|
|
779
|
-
|
|
780
|
-
```typescript
|
|
781
|
-
@elementDefine('custom-loop')
|
|
782
|
-
class CustomLoop extends HTMLElement {
|
|
783
|
-
private items = [{ id: 1, name: 'Item 1' }];
|
|
784
|
-
|
|
785
|
-
@onConnectedInnerHtml
|
|
786
|
-
render() {
|
|
787
|
-
return `
|
|
788
|
-
<!-- Using custom wrapper syntax [(= ... )] instead of {{= ... }} -->
|
|
789
|
-
<template id="items" is="swc-loop" replace-wrap-start="[(" replace-wrap-end=")]" value="{{= $host.items }}">
|
|
790
|
-
<div>
|
|
791
|
-
<p>Name: [( $item.name )]</p>
|
|
792
|
-
<button data-id="[( $item.id )]">Click me</button>
|
|
793
|
-
</div>
|
|
794
|
-
</template>
|
|
795
|
-
`;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
**replace-wrap attribute options:**
|
|
801
|
-
- Change expression wrapper from default `{#{ }#}` to custom pattern
|
|
802
|
-
- Supported on: `SwcLoop`, `SwcChoose`, `SwcIf`, `SwcAsync`, `register()` directives
|
|
803
|
-
|
|
804
|
-
---
|
|
805
|
-
|
|
806
|
-
**Benefits:**
|
|
807
|
-
- ✅ **No imperative route methods** - routes are declarative templates
|
|
808
|
-
- ✅ **Automatic re-rendering** - attributes like `user-id` update automatically
|
|
809
|
-
- ✅ **Performance optimization** - use `skip-if-same` on templates to prevent re-renders
|
|
810
|
-
- ✅ **Dynamic path parameters** - use `{{= $value?.pathData?.id }}` syntax to inject route params with `{{= }}`
|
|
811
|
-
- ✅ **Cleaner DX** - single unified routing pattern
|
|
812
|
-
- ✅ **Full expression support** - use `{{= }}` for complex parameter transformations
|
|
813
|
-
|
|
814
464
|
### 9. **Component Communication - Message Bus**
|
|
815
465
|
|
|
816
466
|
SWC provides a powerful message bus system for inter-component communication through SwcApp. Components can publish and subscribe to typed messages while connected.
|
|
@@ -821,7 +471,7 @@ Subscribe to messages while component is connected to DOM.
|
|
|
821
471
|
```typescript
|
|
822
472
|
@elementDefine('notification-panel')
|
|
823
473
|
class NotificationPanel extends HTMLElement {
|
|
824
|
-
@subscribeSwcAppMessageWhileConnected
|
|
474
|
+
@subscribeSwcAppMessageWhileConnected
|
|
825
475
|
onAnyMessage(message: SwcAppMessage) {
|
|
826
476
|
console.log('Received message:', message);
|
|
827
477
|
}
|
|
@@ -948,7 +598,7 @@ import {
|
|
|
948
598
|
```typescript
|
|
949
599
|
// index.ts
|
|
950
600
|
import 'reflect-metadata';
|
|
951
|
-
import { SwcAppInterface, defineSwcAppAll } from '@dooboostore/simple-web-component';
|
|
601
|
+
import { defineSwcAppBody, SwcAppInterface, defineSwcAppAll } from '@dooboostore/simple-web-component';
|
|
952
602
|
import bootFactory from './bootFactory';
|
|
953
603
|
|
|
954
604
|
const w = window;
|
|
@@ -957,10 +607,8 @@ w.document.addEventListener('DOMContentLoaded', async () => {
|
|
|
957
607
|
const container = Symbol('app');
|
|
958
608
|
|
|
959
609
|
// Initialize services and components
|
|
960
|
-
|
|
610
|
+
await defineSwcAppBody(w);
|
|
961
611
|
|
|
962
|
-
// Register all SwcApp variants
|
|
963
|
-
await defineSwcAppAll(w);
|
|
964
612
|
|
|
965
613
|
// Get app root element
|
|
966
614
|
const appElement = w.document.querySelector('#app') as SwcAppInterface;
|
|
@@ -970,6 +618,7 @@ w.document.addEventListener('DOMContentLoaded', async () => {
|
|
|
970
618
|
path: '/',
|
|
971
619
|
routeType: 'path',
|
|
972
620
|
container: container,
|
|
621
|
+
onStartedLazyDefineComponent: [yourComponentFactory1, yourComponentFactor2],
|
|
973
622
|
window: w,
|
|
974
623
|
onEngineStarted: () => {
|
|
975
624
|
console.log('🚀 Application started successfully');
|
|
@@ -979,32 +628,6 @@ w.document.addEventListener('DOMContentLoaded', async () => {
|
|
|
979
628
|
});
|
|
980
629
|
```
|
|
981
630
|
|
|
982
|
-
#### SwcAppMixin - Reusable Component Logic
|
|
983
|
-
|
|
984
|
-
The `SwcAppMixin` extracts all SwcApp functionality into a reusable mixin that can be applied to any HTMLElement type:
|
|
985
|
-
|
|
986
|
-
```typescript
|
|
987
|
-
import { SwcAppMixin } from '@dooboostore/simple-web-component';
|
|
988
|
-
import { elementDefine } from '@dooboostore/simple-web-component';
|
|
989
|
-
|
|
990
|
-
// Create custom SwcApp variant extending HTMLDivElement
|
|
991
|
-
@elementDefine('custom-app', { window: w, extends: 'div' })
|
|
992
|
-
class CustomApp extends SwcAppMixin(w.HTMLDivElement) implements SwcAppInterface {
|
|
993
|
-
// All SwcApp functionality inherited from mixin
|
|
994
|
-
// Add custom logic here if needed
|
|
995
|
-
}
|
|
996
|
-
```
|
|
997
|
-
|
|
998
|
-
#### SwcAppMixin Features
|
|
999
|
-
|
|
1000
|
-
The mixin provides:
|
|
1001
|
-
- **Lifecycle Management**: `connectedCallback`, `disconnectedCallback` with parent class support
|
|
1002
|
-
- **Routing**: `routing(path)`, `back()`, `forward()`, `reload()`
|
|
1003
|
-
- **Message Bus**: `publishMessage()` for inter-component communication
|
|
1004
|
-
- **Route Subscriptions**: Automatic route change handling for connected components
|
|
1005
|
-
- **SSR Support**: `isSSR()`, `setSSRAttribute()`, `removeSSRAttribute()` utilities
|
|
1006
|
-
- **DI Integration**: Full dependency injection support via `@onInitialize`
|
|
1007
|
-
|
|
1008
631
|
#### Factory Pattern - Component Registration
|
|
1009
632
|
|
|
1010
633
|
```typescript
|
|
@@ -1048,455 +671,30 @@ Factory-based component registration with explicit DI.
|
|
|
1048
671
|
|
|
1049
672
|
```typescript
|
|
1050
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
|
+
};
|
|
1051
682
|
export default (w: Window) => {
|
|
1052
|
-
const tagName = 'my-component';
|
|
1053
683
|
const existing = w.customElements.get(tagName);
|
|
1054
|
-
if (existing) return
|
|
684
|
+
if (existing) return existing;
|
|
1055
685
|
|
|
1056
686
|
@elementDefine(tagName, { window: w })
|
|
1057
|
-
class
|
|
687
|
+
class MyComponentImp extends w.HTMLElement implements MyComponent{
|
|
688
|
+
value: string = 'value';
|
|
1058
689
|
@onInitialize
|
|
1059
690
|
onconstructor(service: MyService) {
|
|
1060
691
|
this.service = service;
|
|
1061
692
|
}
|
|
1062
693
|
}
|
|
1063
|
-
|
|
1064
|
-
return tagName;
|
|
694
|
+
return w.customElements.whenDefined(swcAppTagName);
|
|
1065
695
|
};
|
|
1066
696
|
|
|
1067
|
-
// Boot factory
|
|
1068
|
-
export default (w: Window, container: symbol) => {
|
|
1069
|
-
serviceFactories.forEach(s => s(container));
|
|
1070
|
-
register(w, [...pageFactories, ...componentFactories]);
|
|
1071
|
-
};
|
|
1072
|
-
|
|
1073
|
-
// Entry point
|
|
1074
|
-
const container = Symbol('app');
|
|
1075
|
-
bootFactory(w, container);
|
|
1076
|
-
appElement.connect({
|
|
1077
|
-
container,
|
|
1078
|
-
window: w,
|
|
1079
|
-
onEngineStarted: () => {
|
|
1080
|
-
appElement.innerHTML = '<root-router></root-router>';
|
|
1081
|
-
}
|
|
1082
|
-
});
|
|
1083
|
-
```
|
|
1084
|
-
|
|
1085
|
-
### 12. **Quickstart - Basic SPA Setup**
|
|
1086
|
-
|
|
1087
|
-
Complete example of setting up a production SPA with routing, services, and components.
|
|
1088
|
-
|
|
1089
|
-
#### Step 1. HTML Entry Point
|
|
1090
|
-
```html
|
|
1091
|
-
<!DOCTYPE html>
|
|
1092
|
-
<html>
|
|
1093
|
-
<head>
|
|
1094
|
-
<meta charset="UTF-8">
|
|
1095
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1096
|
-
<title>My SPA</title>
|
|
1097
|
-
<!-- Web Components polyfill for older browsers -->
|
|
1098
|
-
<script src="https://unpkg.com/@ungap/custom-elements"></script>
|
|
1099
|
-
</head>
|
|
1100
|
-
<!-- Use is="swc-app-body" for semantic HTML root -->
|
|
1101
|
-
<body id="app" is="swc-app-body">
|
|
1102
|
-
<!-- Root router will render pages here -->
|
|
1103
|
-
<root-router></root-router>
|
|
1104
|
-
</body>
|
|
1105
|
-
<script src="bundle.js"></script>
|
|
1106
|
-
</html>
|
|
1107
697
|
```
|
|
1108
|
-
|
|
1109
|
-
#### Step 2. Services (Dependency Injection)
|
|
1110
|
-
```typescript
|
|
1111
|
-
// services/UserService.ts
|
|
1112
|
-
import { Sim } from '@dooboostore/simple-boot';
|
|
1113
|
-
import { ConstructorType } from '@dooboostore/core';
|
|
1114
|
-
|
|
1115
|
-
export namespace UserService {
|
|
1116
|
-
export const SYMBOL = Symbol('UserService');
|
|
1117
|
-
|
|
1118
|
-
export interface User {
|
|
1119
|
-
id: number;
|
|
1120
|
-
name: string;
|
|
1121
|
-
email: string;
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
const users: User[] = [
|
|
1125
|
-
{ id: 1, name: 'Alice', email: 'alice@example.com' },
|
|
1126
|
-
{ id: 2, name: 'Bob', email: 'bob@example.com' }
|
|
1127
|
-
];
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
export interface UserService {
|
|
1131
|
-
getUser(id: number): UserService.User | undefined;
|
|
1132
|
-
getAllUsers(): UserService.User[];
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// Factory function: accepts container and returns service class
|
|
1136
|
-
export default (container: symbol): ConstructorType<any> => {
|
|
1137
|
-
@Sim({ symbol: UserService.SYMBOL, container: container })
|
|
1138
|
-
class UserServiceImpl implements UserService {
|
|
1139
|
-
getUser(id: number) {
|
|
1140
|
-
return UserService.users.find(u => u.id === id);
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
getAllUsers() {
|
|
1144
|
-
return [...UserService.users];
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
return UserServiceImpl;
|
|
1149
|
-
};
|
|
1150
|
-
```
|
|
1151
|
-
|
|
1152
|
-
```typescript
|
|
1153
|
-
// services/index.ts
|
|
1154
|
-
import userServiceFactory from './UserService';
|
|
1155
|
-
|
|
1156
|
-
export const serviceFactories = [userServiceFactory];
|
|
1157
|
-
```
|
|
1158
|
-
|
|
1159
|
-
#### Step 3. Router Configuration (Declarative with swc-choose)
|
|
1160
|
-
```typescript
|
|
1161
|
-
// pages/RootRouter.ts
|
|
1162
|
-
import { elementDefine, subscribeSwcAppRouteChangeWhileConnected, setProperty, onConnectedInnerHtml, onInitialize } from '@dooboostore/simple-web-component';
|
|
1163
|
-
import { Router, RouterEventType } from '@dooboostore/core-web';
|
|
1164
|
-
|
|
1165
|
-
const rootRouterFactory = (w: Window) => {
|
|
1166
|
-
const tagName = 'root-router';
|
|
1167
|
-
const existing = w.customElements.get(tagName);
|
|
1168
|
-
if (existing) return tagName;
|
|
1169
|
-
|
|
1170
|
-
@elementDefine(tagName, { window: w })
|
|
1171
|
-
class RootRouter extends w.HTMLElement {
|
|
1172
|
-
private router: Router;
|
|
1173
|
-
|
|
1174
|
-
@onInitialize
|
|
1175
|
-
onconstructor(router: Router) {
|
|
1176
|
-
this.router = router;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
@setProperty('#router', 'value')
|
|
1180
|
-
@subscribeSwcAppRouteChangeWhileConnected(['/', '/users', '/users/{id}'])
|
|
1181
|
-
routeChanged(route: RouterEventType) {
|
|
1182
|
-
return route;
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
navigate(path: string): void {
|
|
1186
|
-
this.router?.go(path);
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
@onConnectedInnerHtml
|
|
1190
|
-
render() {
|
|
1191
|
-
return `
|
|
1192
|
-
<template id="router" is="swc-choose">
|
|
1193
|
-
<!-- Home Page -->
|
|
1194
|
-
<template is="swc-when" test="['', '/'].includes($value?.path)">
|
|
1195
|
-
<home-page/>
|
|
1196
|
-
</template>
|
|
1197
|
-
|
|
1198
|
-
<!-- Users List -->
|
|
1199
|
-
<template is="swc-when" test="$value?.path === '/users'">
|
|
1200
|
-
<users-list-page/>
|
|
1201
|
-
</template>
|
|
1202
|
-
|
|
1203
|
-
<!-- User Detail -->
|
|
1204
|
-
<template is="swc-when" test="$value?.path.startsWith('/users/')">
|
|
1205
|
-
<user-detail-page user-id="{{= $value?.pathData?.id}}"/>
|
|
1206
|
-
</template>
|
|
1207
|
-
|
|
1208
|
-
<!-- Not Found -->
|
|
1209
|
-
<template is="swc-otherwise">
|
|
1210
|
-
<not-found-page/>
|
|
1211
|
-
</template>
|
|
1212
|
-
</template>
|
|
1213
|
-
`;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
return tagName;
|
|
1218
|
-
};
|
|
1219
|
-
|
|
1220
|
-
export default rootRouterFactory;
|
|
1221
|
-
```
|
|
1222
|
-
|
|
1223
|
-
#### Step 4. Page Components
|
|
1224
|
-
```typescript
|
|
1225
|
-
// pages/HomePage.ts
|
|
1226
|
-
import { elementDefine, onConnectedInnerHtml } from '@dooboostore/simple-web-component';
|
|
1227
|
-
|
|
1228
|
-
const HomePage = (w: Window) => {
|
|
1229
|
-
const tagName = 'home-page';
|
|
1230
|
-
const existing = w.customElements.get(tagName);
|
|
1231
|
-
if (existing) return tagName;
|
|
1232
|
-
|
|
1233
|
-
@elementDefine(tagName, { window: w })
|
|
1234
|
-
class HomePage extends w.HTMLElement {
|
|
1235
|
-
@onConnectedInnerHtml({ useShadow: true })
|
|
1236
|
-
render() {
|
|
1237
|
-
return `
|
|
1238
|
-
<style>
|
|
1239
|
-
:host { display: block; padding: 20px; }
|
|
1240
|
-
h1 { color: #333; }
|
|
1241
|
-
a { color: #0066cc; text-decoration: none; margin-right: 20px; }
|
|
1242
|
-
</style>
|
|
1243
|
-
<h1>Welcome to My SPA</h1>
|
|
1244
|
-
<nav>
|
|
1245
|
-
<a href="/users">View All Users</a>
|
|
1246
|
-
</nav>
|
|
1247
|
-
`;
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
return tagName;
|
|
1252
|
-
};
|
|
1253
|
-
|
|
1254
|
-
export default HomePage;
|
|
1255
|
-
```
|
|
1256
|
-
|
|
1257
|
-
```typescript
|
|
1258
|
-
// pages/UsersListPage.ts
|
|
1259
|
-
import { elementDefine, onConnectedInnerHtml, onConnectedBefore } from '@dooboostore/simple-web-component';
|
|
1260
|
-
import { Inject } from '@dooboostore/simple-boot';
|
|
1261
|
-
import { UserService } from '../services/UserService';
|
|
1262
|
-
|
|
1263
|
-
const UsersListPage = (w: Window) => {
|
|
1264
|
-
const tagName = 'users-list-page';
|
|
1265
|
-
const existing = w.customElements.get(tagName);
|
|
1266
|
-
if (existing) return tagName;
|
|
1267
|
-
|
|
1268
|
-
@elementDefine(tagName, { window: w })
|
|
1269
|
-
class UsersListPage extends w.HTMLElement {
|
|
1270
|
-
private users: any[] = [];
|
|
1271
|
-
|
|
1272
|
-
@onConnectedBefore
|
|
1273
|
-
onconstructor(@Inject({ symbol: UserService.SYMBOL }) userService: UserService) {
|
|
1274
|
-
this.users = userService.getAllUsers();
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
@onConnectedInnerHtml({ useShadow: true })
|
|
1278
|
-
render() {
|
|
1279
|
-
const usersList = this.users
|
|
1280
|
-
.map(u => `<li><a href="/users/${u.id}">${u.name}</a></li>`)
|
|
1281
|
-
.join('');
|
|
1282
|
-
|
|
1283
|
-
return `
|
|
1284
|
-
<style>
|
|
1285
|
-
:host { display: block; padding: 20px; }
|
|
1286
|
-
h1 { color: #333; }
|
|
1287
|
-
ul { list-style: none; padding: 0; }
|
|
1288
|
-
li { margin: 10px 0; }
|
|
1289
|
-
a { color: #0066cc; text-decoration: none; }
|
|
1290
|
-
</style>
|
|
1291
|
-
<h1>Users</h1>
|
|
1292
|
-
<ul>${usersList}</ul>
|
|
1293
|
-
<a href="/">Back to Home</a>
|
|
1294
|
-
`;
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
return tagName;
|
|
1299
|
-
};
|
|
1300
|
-
|
|
1301
|
-
export default UsersListPage;
|
|
1302
|
-
```
|
|
1303
|
-
|
|
1304
|
-
#### Step 4-1. Reusable Components
|
|
1305
|
-
```typescript
|
|
1306
|
-
// components/UserCard.ts
|
|
1307
|
-
import { elementDefine, onConnectedInnerHtml, attributeThis } from '@dooboostore/simple-web-component';
|
|
1308
|
-
|
|
1309
|
-
const UserCard = (w: Window) => {
|
|
1310
|
-
const tagName = 'user-card';
|
|
1311
|
-
const existing = w.customElements.get(tagName);
|
|
1312
|
-
if (existing) return tagName;
|
|
1313
|
-
|
|
1314
|
-
@elementDefine(tagName, { window: w })
|
|
1315
|
-
class UserCard extends w.HTMLElement {
|
|
1316
|
-
@attributeThis('data-username')
|
|
1317
|
-
username: string;
|
|
1318
|
-
|
|
1319
|
-
@attributeThis('data-email')
|
|
1320
|
-
email: string;
|
|
1321
|
-
|
|
1322
|
-
@onConnectedInnerHtml({ useShadow: true })
|
|
1323
|
-
render() {
|
|
1324
|
-
return `
|
|
1325
|
-
<style>
|
|
1326
|
-
:host { display: block; }
|
|
1327
|
-
.card { border: 1px solid #ddd; border-radius: 4px; padding: 12px; }
|
|
1328
|
-
.name { font-weight: bold; font-size: 14px; }
|
|
1329
|
-
.email { color: #666; font-size: 12px; }
|
|
1330
|
-
</style>
|
|
1331
|
-
<div class="card">
|
|
1332
|
-
<div class="name">${this.username}</div>
|
|
1333
|
-
<div class="email">${this.email}</div>
|
|
1334
|
-
</div>
|
|
1335
|
-
`;
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
return tagName;
|
|
1340
|
-
};
|
|
1341
|
-
|
|
1342
|
-
export default UserCard;
|
|
1343
|
-
```
|
|
1344
|
-
|
|
1345
|
-
#### Step 5. Export Factories
|
|
1346
|
-
|
|
1347
|
-
**services/index.ts** - Export all service factories
|
|
1348
|
-
```typescript
|
|
1349
|
-
// services/index.ts
|
|
1350
|
-
import userServiceFactory from './UserService';
|
|
1351
|
-
|
|
1352
|
-
export const serviceFactories = [userServiceFactory];
|
|
1353
|
-
```
|
|
1354
|
-
|
|
1355
|
-
**components/index.ts** - Export all component factories
|
|
1356
|
-
```typescript
|
|
1357
|
-
// components/index.ts
|
|
1358
|
-
import UserCard from './UserCard';
|
|
1359
|
-
|
|
1360
|
-
export const componentFactories = [
|
|
1361
|
-
UserCard,
|
|
1362
|
-
];
|
|
1363
|
-
```
|
|
1364
|
-
|
|
1365
|
-
**pages/index.ts** - Export all page factories with router
|
|
1366
|
-
```typescript
|
|
1367
|
-
// pages/index.ts
|
|
1368
|
-
import rootRouterFactory from './RootRouter';
|
|
1369
|
-
import HomePage from './HomePage';
|
|
1370
|
-
import UsersListPage from './UsersListPage';
|
|
1371
|
-
import UserDetailPage from './UserDetailPage';
|
|
1372
|
-
|
|
1373
|
-
export const pageFactories = [
|
|
1374
|
-
rootRouterFactory, // Router first
|
|
1375
|
-
HomePage,
|
|
1376
|
-
UsersListPage,
|
|
1377
|
-
UserDetailPage
|
|
1378
|
-
];
|
|
1379
|
-
```
|
|
1380
|
-
|
|
1381
|
-
#### Step 6. Application Bootstrap
|
|
1382
|
-
```typescript
|
|
1383
|
-
// bootFactory.ts
|
|
1384
|
-
import { register } from '@dooboostore/simple-web-component';
|
|
1385
|
-
import { serviceFactories } from './services';
|
|
1386
|
-
import { componentFactories } from './components';
|
|
1387
|
-
import { pageFactories } from './pages';
|
|
1388
|
-
|
|
1389
|
-
export default (w: Window, container: symbol) => {
|
|
1390
|
-
// Initialize services with container (dependency injection)
|
|
1391
|
-
// Each factory receives the container symbol and registers its service in the DI container
|
|
1392
|
-
serviceFactories.forEach(serviceFactory => serviceFactory(container));
|
|
1393
|
-
|
|
1394
|
-
// Register pages and components
|
|
1395
|
-
register(w, [...pageFactories, ...componentFactories]);
|
|
1396
|
-
};
|
|
1397
|
-
```
|
|
1398
|
-
|
|
1399
|
-
#### Step 7. Application Entry Point
|
|
1400
|
-
```typescript
|
|
1401
|
-
// index.ts
|
|
1402
|
-
import 'reflect-metadata';
|
|
1403
|
-
import { SwcAppInterface, defineSwcAppAll } from '@dooboostore/simple-web-component';
|
|
1404
|
-
import bootFactory from './bootFactory';
|
|
1405
|
-
|
|
1406
|
-
const w = window;
|
|
1407
|
-
|
|
1408
|
-
w.document.addEventListener('DOMContentLoaded', async () => {
|
|
1409
|
-
const container = Symbol('app');
|
|
1410
|
-
|
|
1411
|
-
// Initialize services and components
|
|
1412
|
-
bootFactory(w, container);
|
|
1413
|
-
|
|
1414
|
-
// Register all SwcApp variants (swc-app, swc-app-body, swc-app-div, etc.)
|
|
1415
|
-
await defineSwcAppAll(w);
|
|
1416
|
-
|
|
1417
|
-
// Get app root element
|
|
1418
|
-
const appElement = w.document.querySelector('#app') as SwcAppInterface;
|
|
1419
|
-
|
|
1420
|
-
if (appElement && typeof appElement.connect === 'function') {
|
|
1421
|
-
// Connect SPA with routing and DI
|
|
1422
|
-
appElement.connect({
|
|
1423
|
-
path: '/',
|
|
1424
|
-
routeType: 'path',
|
|
1425
|
-
container: container,
|
|
1426
|
-
window: w,
|
|
1427
|
-
onEngineStarted: () => {
|
|
1428
|
-
console.log('🚀 Application started successfully');
|
|
1429
|
-
// Root router automatically initialized via @elementDefine
|
|
1430
|
-
}
|
|
1431
|
-
});
|
|
1432
|
-
} else {
|
|
1433
|
-
console.error('Failed to initialize SPA');
|
|
1434
|
-
}
|
|
1435
|
-
});
|
|
1436
|
-
```ow;
|
|
1437
|
-
|
|
1438
|
-
w.document.addEventListener('DOMContentLoaded', async () => {
|
|
1439
|
-
const container = Symbol('app');
|
|
1440
|
-
|
|
1441
|
-
// Initialize services and components
|
|
1442
|
-
bootFactory(w, container);
|
|
1443
|
-
|
|
1444
|
-
// Wait for customized built-in element to be defined
|
|
1445
|
-
await w.customElements.whenDefined('swc-app-body');
|
|
1446
|
-
|
|
1447
|
-
// Get app root element
|
|
1448
|
-
const appElement = w.document.querySelector('#app') as SwcAppInterface;
|
|
1449
|
-
|
|
1450
|
-
if (appElement && typeof appElement.connect === 'function') {
|
|
1451
|
-
// Connect SPA with routing and DI
|
|
1452
|
-
appElement.connect({
|
|
1453
|
-
path: '/',
|
|
1454
|
-
routeType: 'path',
|
|
1455
|
-
container: container,
|
|
1456
|
-
window: w,
|
|
1457
|
-
onEngineStarted: () => {
|
|
1458
|
-
console.log('🚀 Application started successfully');
|
|
1459
|
-
// Root router automatically initialized via @elementDefine
|
|
1460
|
-
}
|
|
1461
|
-
});
|
|
1462
|
-
} else {
|
|
1463
|
-
console.error('Failed to initialize SPA');
|
|
1464
|
-
}
|
|
1465
|
-
});
|
|
1466
|
-
```
|
|
1467
|
-
|
|
1468
|
-
#### Resulting Application Structure
|
|
1469
|
-
```
|
|
1470
|
-
src/
|
|
1471
|
-
├── index.ts # Entry point
|
|
1472
|
-
├── index.html # HTML root with <root-router>
|
|
1473
|
-
├── bootFactory.ts # Bootstrap services & components via register()
|
|
1474
|
-
├── services/
|
|
1475
|
-
│ ├── UserService.ts # Service factory (export default)
|
|
1476
|
-
│ └── index.ts # Export serviceFactories
|
|
1477
|
-
├── pages/
|
|
1478
|
-
│ ├── RootRouter.ts # Router factory (export default)
|
|
1479
|
-
│ ├── HomePage.ts # Page factory (export default)
|
|
1480
|
-
│ ├── UsersListPage.ts # Page factory (export default)
|
|
1481
|
-
│ ├── UserDetailPage.ts # Page factory (export default)
|
|
1482
|
-
│ └── index.ts # Export pageFactories
|
|
1483
|
-
└── components/
|
|
1484
|
-
├── UserCard.ts # Component factory (export default)
|
|
1485
|
-
└── index.ts # Export componentFactories
|
|
1486
|
-
```
|
|
1487
|
-
|
|
1488
|
-
**Key Flow:**
|
|
1489
|
-
1. HTML loads → `<root-router>` element already in DOM
|
|
1490
|
-
2. `index.ts` executes → calls `bootFactory`
|
|
1491
|
-
3. `bootFactory` registers services and components (including `rootRouterFactory`)
|
|
1492
|
-
4. App waits for `swc-app-body` to be defined via `defineSwcAppAll()`
|
|
1493
|
-
5. `appElement.connect()` initializes routing & DI
|
|
1494
|
-
6. `<root-router>` becomes active with `@subscribeSwcAppRouteChangeWhileConnected` + `swc-choose` template
|
|
1495
|
-
7. Route changes automatically trigger `swc-choose` re-evaluation and template re-rendering
|
|
1496
|
-
8. Template attributes like `user-id="{{= $value?.pathData?.id}}"` are dynamically populated with route params
|
|
1497
|
-
9. All components have access to injected services via `@onInitialize`
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
698
|
---
|
|
1501
699
|
|
|
1502
700
|
## ⚠️ Critical Rules
|
|
@@ -1518,17 +716,17 @@ class UserWidget extends w.HTMLElement { }
|
|
|
1518
716
|
class UserWidget extends w.HTMLElement { }
|
|
1519
717
|
```
|
|
1520
718
|
|
|
1521
|
-
### Factory Always Returns
|
|
719
|
+
### Factory Always Returns
|
|
1522
720
|
```typescript
|
|
1523
721
|
export default (w: Window) => {
|
|
1524
722
|
const tagName = 'my-element';
|
|
1525
723
|
const existing = w.customElements.get(tagName);
|
|
1526
|
-
if (existing) return
|
|
724
|
+
if (existing) return existing;
|
|
1527
725
|
|
|
1528
726
|
@elementDefine(tagName, { window: w })
|
|
1529
727
|
class MyElement extends w.HTMLElement { }
|
|
1530
728
|
|
|
1531
|
-
return
|
|
729
|
+
return existing;
|
|
1532
730
|
};
|
|
1533
731
|
```
|
|
1534
732
|
|
|
@@ -1536,7 +734,7 @@ export default (w: Window) => {
|
|
|
1536
734
|
```typescript
|
|
1537
735
|
// ✅ CORRECT
|
|
1538
736
|
@onInitialize
|
|
1539
|
-
onconstructor(@Inject(
|
|
737
|
+
onconstructor(@Inject(symbol: Service.SYMBOL) service: Service) {
|
|
1540
738
|
this.service = service;
|
|
1541
739
|
}
|
|
1542
740
|
|