@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.
Files changed (31) hide show
  1. package/README.md +31 -833
  2. package/dist/cjs/decorators/applyNode.js +26 -39
  3. package/dist/cjs/decorators/applyNode.js.map +2 -2
  4. package/dist/cjs/decorators/applySlot.js +69 -64
  5. package/dist/cjs/decorators/applySlot.js.map +3 -3
  6. package/dist/cjs/decorators/elementDefine.js +0 -7
  7. package/dist/cjs/decorators/elementDefine.js.map +2 -2
  8. package/dist/cjs/decorators/state.js +0 -1
  9. package/dist/cjs/decorators/state.js.map +2 -2
  10. package/dist/esm/decorators/applyNode.js +26 -39
  11. package/dist/esm/decorators/applyNode.js.map +2 -2
  12. package/dist/esm/decorators/applySlot.js +69 -64
  13. package/dist/esm/decorators/applySlot.js.map +3 -3
  14. package/dist/esm/decorators/elementDefine.js +0 -7
  15. package/dist/esm/decorators/elementDefine.js.map +2 -2
  16. package/dist/esm/decorators/state.js +0 -1
  17. package/dist/esm/decorators/state.js.map +2 -2
  18. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +124 -139
  19. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  20. package/dist/types/decorators/applyNode.d.ts +2 -2
  21. package/dist/types/decorators/applyNode.d.ts.map +1 -1
  22. package/dist/types/decorators/applySlot.d.ts +14 -12
  23. package/dist/types/decorators/applySlot.d.ts.map +1 -1
  24. package/dist/types/decorators/state.d.ts.map +1 -1
  25. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +124 -139
  26. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  27. package/package.json +3 -3
  28. package/src/decorators/applyNode.ts +43 -51
  29. package/src/decorators/applySlot.ts +97 -86
  30. package/src/decorators/elementDefine.ts +6 -6
  31. 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
- @Inject({ symbol: UserService.SYMBOL }) userService: UserService
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
- @onConnectedInnerHtml({ useShadow: true })
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
- @applyNodeThisInnerHtml({ root: 'light' })
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
- @applyNodeThisReplaceChildren({ root: 'light' })
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
- // Property: Get NodeSlot instance for manual slot manipulation
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
- // Lifecycle: Manual slot manipulation
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
- @onConnectedInnerHtml
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
- bootFactory(w, container);
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 tagName;
684
+ if (existing) return existing;
1055
685
 
1056
686
  @elementDefine(tagName, { window: w })
1057
- class MyComponent extends w.HTMLElement {
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 tagName (String)
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 tagName; // ← Return string!
724
+ if (existing) return existing;
1527
725
 
1528
726
  @elementDefine(tagName, { window: w })
1529
727
  class MyElement extends w.HTMLElement { }
1530
728
 
1531
- return tagName; // ← Always return tagName, never class
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({ symbol: Service.SYMBOL }) service: Service) {
737
+ onconstructor(@Inject(symbol: Service.SYMBOL) service: Service) {
1540
738
  this.service = service;
1541
739
  }
1542
740