@dooboostore/simple-web-component 1.0.29 → 1.0.31

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 (108) hide show
  1. package/README.md +651 -27
  2. package/dist/cjs/decorators/elementDefine.js +5 -3
  3. package/dist/cjs/decorators/elementDefine.js.map +2 -2
  4. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  5. package/dist/cjs/decorators/index.js +1 -0
  6. package/dist/cjs/decorators/index.js.map +2 -2
  7. package/dist/cjs/decorators/setProperty.js +97 -0
  8. package/dist/cjs/decorators/setProperty.js.map +7 -0
  9. package/dist/cjs/elements/SwcAppEngine.js +3 -3
  10. package/dist/cjs/elements/SwcAppEngine.js.map +2 -2
  11. package/dist/cjs/elements/SwcAppMixin.js +1 -8
  12. package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
  13. package/dist/cjs/elements/SwcAsync.js +37 -53
  14. package/dist/cjs/elements/SwcAsync.js.map +2 -2
  15. package/dist/cjs/elements/SwcChoose.js +55 -60
  16. package/dist/cjs/elements/SwcChoose.js.map +2 -2
  17. package/dist/cjs/elements/SwcIf.js +31 -60
  18. package/dist/cjs/elements/SwcIf.js.map +2 -2
  19. package/dist/cjs/elements/SwcLoop.js +20 -43
  20. package/dist/cjs/elements/SwcLoop.js.map +2 -2
  21. package/dist/cjs/elements/SwcSubTemplates.js +0 -27
  22. package/dist/cjs/elements/SwcSubTemplates.js.map +2 -2
  23. package/dist/cjs/elements/register.js +216 -128
  24. package/dist/cjs/elements/register.js.map +2 -2
  25. package/dist/cjs/index.js +2 -1
  26. package/dist/cjs/index.js.map +2 -2
  27. package/dist/cjs/router/ElementRouter.js +1 -8
  28. package/dist/cjs/router/ElementRouter.js.map +2 -2
  29. package/dist/cjs/types.js.map +1 -1
  30. package/dist/cjs/utils/Utils.js +26 -10
  31. package/dist/cjs/utils/Utils.js.map +2 -2
  32. package/dist/esm/decorators/elementDefine.js +5 -3
  33. package/dist/esm/decorators/elementDefine.js.map +2 -2
  34. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  35. package/dist/esm/decorators/index.js +1 -0
  36. package/dist/esm/decorators/index.js.map +2 -2
  37. package/dist/esm/decorators/setProperty.js +78 -0
  38. package/dist/esm/decorators/setProperty.js.map +7 -0
  39. package/dist/esm/elements/SwcAppEngine.js +3 -3
  40. package/dist/esm/elements/SwcAppEngine.js.map +2 -2
  41. package/dist/esm/elements/SwcAppMixin.js +1 -8
  42. package/dist/esm/elements/SwcAppMixin.js.map +2 -2
  43. package/dist/esm/elements/SwcAsync.js +37 -34
  44. package/dist/esm/elements/SwcAsync.js.map +2 -2
  45. package/dist/esm/elements/SwcChoose.js +55 -41
  46. package/dist/esm/elements/SwcChoose.js.map +2 -2
  47. package/dist/esm/elements/SwcIf.js +31 -41
  48. package/dist/esm/elements/SwcIf.js.map +2 -2
  49. package/dist/esm/elements/SwcLoop.js +20 -24
  50. package/dist/esm/elements/SwcLoop.js.map +2 -2
  51. package/dist/esm/elements/SwcSubTemplates.js +0 -8
  52. package/dist/esm/elements/SwcSubTemplates.js.map +2 -2
  53. package/dist/esm/elements/register.js +216 -128
  54. package/dist/esm/elements/register.js.map +2 -2
  55. package/dist/esm/index.js +2 -1
  56. package/dist/esm/index.js.map +2 -2
  57. package/dist/esm/router/ElementRouter.js +1 -8
  58. package/dist/esm/router/ElementRouter.js.map +2 -2
  59. package/dist/esm/types.js.map +1 -1
  60. package/dist/esm/utils/Utils.js +26 -10
  61. package/dist/esm/utils/Utils.js.map +2 -2
  62. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +105 -13
  63. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  64. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  65. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  66. package/dist/types/decorators/index.d.ts +1 -0
  67. package/dist/types/decorators/index.d.ts.map +1 -1
  68. package/dist/types/decorators/setProperty.d.ts +13 -0
  69. package/dist/types/decorators/setProperty.d.ts.map +1 -0
  70. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  71. package/dist/types/elements/SwcAsync.d.ts +1 -11
  72. package/dist/types/elements/SwcAsync.d.ts.map +1 -1
  73. package/dist/types/elements/SwcChoose.d.ts +1 -13
  74. package/dist/types/elements/SwcChoose.d.ts.map +1 -1
  75. package/dist/types/elements/SwcIf.d.ts +1 -13
  76. package/dist/types/elements/SwcIf.d.ts.map +1 -1
  77. package/dist/types/elements/SwcLoop.d.ts +1 -13
  78. package/dist/types/elements/SwcLoop.d.ts.map +1 -1
  79. package/dist/types/elements/SwcSubTemplates.d.ts +6 -6
  80. package/dist/types/elements/SwcSubTemplates.d.ts.map +1 -1
  81. package/dist/types/elements/register.d.ts +3 -1
  82. package/dist/types/elements/register.d.ts.map +1 -1
  83. package/dist/types/index.d.ts +1 -1
  84. package/dist/types/index.d.ts.map +1 -1
  85. package/dist/types/router/ElementRouter.d.ts.map +1 -1
  86. package/dist/types/types.d.ts +3 -0
  87. package/dist/types/types.d.ts.map +1 -1
  88. package/dist/types/utils/Utils.d.ts +10 -0
  89. package/dist/types/utils/Utils.d.ts.map +1 -1
  90. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +105 -13
  91. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  92. package/package.json +3 -3
  93. package/src/decorators/elementDefine.ts +10 -3
  94. package/src/decorators/emitCustomEvent.ts +1 -0
  95. package/src/decorators/index.ts +1 -0
  96. package/src/decorators/setProperty.ts +97 -0
  97. package/src/elements/SwcAppEngine.ts +3 -3
  98. package/src/elements/SwcAppMixin.ts +7 -28
  99. package/src/elements/SwcAsync.ts +46 -30
  100. package/src/elements/SwcChoose.ts +82 -38
  101. package/src/elements/SwcIf.ts +38 -38
  102. package/src/elements/SwcLoop.ts +24 -19
  103. package/src/elements/SwcSubTemplates.ts +6 -6
  104. package/src/elements/register.ts +261 -121
  105. package/src/index.ts +3 -1
  106. package/src/router/ElementRouter.ts +1 -12
  107. package/src/types.ts +3 -1
  108. package/src/utils/Utils.ts +40 -19
package/README.md CHANGED
@@ -17,8 +17,7 @@ Register Web Components with automatic lifecycle management and DI support.
17
17
  @elementDefine('my-component')
18
18
  class MyComponent extends HTMLElement {
19
19
  @onInitialize
20
- constructor(private service: MyService) {
21
- super();
20
+ onconstructor(service: MyService) {
22
21
  }
23
22
  }
24
23
  ```
@@ -30,10 +29,9 @@ Inject services into Web Components using the `@onInitialize` decorator.
30
29
  @elementDefine('dashboard')
31
30
  class Dashboard extends HTMLElement {
32
31
  @onInitialize
33
- constructor(
32
+ onconstructor(
34
33
  @Inject({ symbol: UserService.SYMBOL }) userService: UserService
35
34
  ) {
36
- super();
37
35
  this.userService = userService;
38
36
  }
39
37
  }
@@ -220,8 +218,7 @@ Bind attributes on host element itself.
220
218
  productId: string = '';
221
219
 
222
220
  @onInitialize
223
- constructor() {
224
- super();
221
+ onconstructor() {
225
222
  if (this.productId) {
226
223
  this.loadProduct(this.productId);
227
224
  }
@@ -241,39 +238,284 @@ onDisconnected() {
241
238
  // Called when element leaves DOM
242
239
  }
243
240
 
244
- @onAttributeChanged
245
- onAttributeChanged(name: string, oldValue: string, newValue: string) {
246
- // Called when attribute changes
247
- }
248
241
 
249
242
  @onConnectedSwcApp
250
- onAppReady(hostSet: HostSet) {
243
+ onAppReady(router: Router) {
251
244
  // Called after SwcApp.connect() completes on all already-connected components
252
245
  // Full DI support available through hostSet
253
246
  console.log('Application initialization complete!');
254
247
  }
255
248
  ```
256
249
 
257
- ### 8. **Routing with @subscribeSwcAppRouteChange**
258
- Declarative route pattern matching with path parameters.
250
+ ### 8. **Structural Directives**
251
+
252
+ Structural Directives allow declarative conditional rendering, list iteration, async handling, and routing with automatic attribute substitution and dynamic expressions.
253
+
254
+ #### 8.1 **SwcChoose - Multi-Condition Rendering**
255
+
256
+ Switch-case style conditional rendering with `swc-when` and `swc-otherwise` sub-templates.
257
+
258
+ ```typescript
259
+ // Component with conditional rendering logic
260
+ @elementDefine('status-display')
261
+ class StatusDisplay extends HTMLElement {
262
+ @attributeHost('status')
263
+ status: string = 'pending';
264
+
265
+ @setProperty('#status-template', 'value')
266
+ getStatus() {
267
+ return this.status;
268
+ }
269
+
270
+ @onConnectedInnerHtml
271
+ render() {
272
+ return `
273
+ <template id="status-template" is="swc-choose">
274
+ <!-- Pending State -->
275
+ <template is="swc-when" value="{{ $value === 'pending' }}">
276
+ <div style="color: orange;">⏳ Processing...</div>
277
+ </template>
278
+
279
+ <!-- Success State -->
280
+ <template is="swc-when" value="{{ $value === 'success' }}">
281
+ <div style="color: green;">✓ Completed</div>
282
+ </template>
283
+
284
+ <!-- Error State -->
285
+ <template is="swc-when" value="{{ $value === 'error' }}">
286
+ <div style="color: red;">✗ Failed</div>
287
+ </template>
288
+
289
+ <!-- Fallback -->
290
+ <template is="swc-otherwise">
291
+ <div style="color: gray;">Unknown status</div>
292
+ </template>
293
+ </template>
294
+ `;
295
+ }
296
+ }
297
+ ```
298
+
299
+ **Features:**
300
+ - `{{ }} braces` for dynamic condition evaluation
301
+ - `$value` contains the value passed to `swc-choose`
302
+ - `skip-if-same` attribute prevents re-render when same template selected
303
+ - Attribute substitution: `attribute-name="{{ expression }}"` automatically evaluated
304
+
305
+ ---
306
+
307
+ #### 8.2 **SwcIf - Binary Conditional**
308
+
309
+ If-then-else rendering with optional `swc-default` fallback.
310
+
311
+ ```typescript
312
+ @elementDefine('user-profile')
313
+ class UserProfile extends HTMLElement {
314
+ private isLoggedIn = false;
315
+
316
+ @setProperty('#content', 'value')
317
+ checkAuth() {
318
+ return this.isLoggedIn;
319
+ }
320
+
321
+ @onConnectedInnerHtml
322
+ render() {
323
+ return `
324
+ <!-- True condition: renders main template -->
325
+ <template id="content" is="swc-if" value="{{ $host.isLoggedIn }}">
326
+ <div>
327
+ <h2>Welcome, {{ $host.userName }}</h2>
328
+ <button on-click="logout">Logout</button>
329
+ </div>
330
+
331
+ <!-- Fallback template: renders if condition is false -->
332
+ <template is="swc-default">
333
+ <div>
334
+ <p>Please log in</p>
335
+ <button on-click="login">Login</button>
336
+ </div>
337
+ </template>
338
+ </template>
339
+ `;
340
+ }
341
+ }
342
+ ```
343
+
344
+ **Features:**
345
+ - Binary true/false logic
346
+ - Main template renders when `value` is truthy
347
+ - `swc-default` sub-template renders when value is falsy
348
+ - Dynamic attribute substitution supported
349
+ - Access `$host`, `$value`, and other context variables
350
+
351
+ ---
352
+
353
+ #### 8.3 **SwcAsync - Promise/Loading State**
354
+
355
+ Automatically handle async operations with loading, success, and error states.
356
+
357
+ ```typescript
358
+ @elementDefine('data-loader')
359
+ class DataLoader extends HTMLElement {
360
+ private data: any = null;
361
+
362
+ async fetchData() {
363
+ return new Promise((resolve, reject) => {
364
+ setTimeout(() => {
365
+ resolve({ id: 1, name: 'Sample Data' });
366
+ }, 2000);
367
+ });
368
+ }
369
+
370
+ @setProperty('#async-content', 'value')
371
+ loadData() {
372
+ return this.fetchData();
373
+ }
374
+
375
+ @onConnectedInnerHtml
376
+ render() {
377
+ return `
378
+ <template id="async-content" is="swc-async" value="{{ $host.fetchData() }}">
379
+ <!-- Loading State: Shows while Promise is pending -->
380
+ <template is="swc-loading">
381
+ <div>🔄 Loading data...</div>
382
+ </template>
383
+
384
+ <!-- Success State: Shows when Promise resolves -->
385
+ <template is="swc-success">
386
+ <div>
387
+ <p>Data ID: {{ $value.id }}</p>
388
+ <p>Data Name: {{ $value.name }}</p>
389
+ </div>
390
+ </template>
391
+
392
+ <!-- Error State: Shows if Promise rejects -->
393
+ <template is="swc-error">
394
+ <div style="color: red;">❌ Failed to load data</div>
395
+ </template>
396
+ </template>
397
+ `;
398
+ }
399
+ }
400
+ ```
401
+
402
+ **Features:**
403
+ - Automatically manages Promise lifecycle
404
+ - `swc-loading` - displays during pending state
405
+ - `swc-success` - displays on successful resolution, `$value` contains result
406
+ - `swc-error` - displays on Promise rejection
407
+ - Dynamic attribute substitution in all states
408
+ - `on-clone-node` and `on-clone-nodes` callbacks for DOM manipulation
409
+
410
+ ---
411
+
412
+ #### 8.4 **SwcLoop - List Rendering**
413
+
414
+ Iterate over arrays and render templates for each item.
415
+
416
+ ```typescript
417
+ @elementDefine('product-list')
418
+ class ProductList extends HTMLElement {
419
+ private products = [
420
+ { id: 1, name: 'Laptop', price: 1200 },
421
+ { id: 2, name: 'Phone', price: 800 },
422
+ { id: 3, name: 'Tablet', price: 500 }
423
+ ];
424
+
425
+ @setProperty('#products-list', 'value')
426
+ getProducts() {
427
+ return this.products;
428
+ }
429
+
430
+ @onConnectedInnerHtml
431
+ render() {
432
+ return `
433
+ <template id="products-list" is="swc-loop" value="{{ $host.products }}">
434
+ <div style="margin: 10px; padding: 10px; border: 1px solid #ddd;">
435
+ <h3>{{ $item.name }}</h3>
436
+ <p>Price: ${{ $item.price }}</p>
437
+ <p>ID: {{ $item.id }} (Index: {{ $index }})</p>
438
+ <button data-id="{{ $item.id }}">Add to Cart</button>
439
+ </div>
440
+
441
+ <!-- Empty fallback: renders when array is empty -->
442
+ <template is="swc-default">
443
+ <p>No products available</p>
444
+ </template>
445
+ </template>
446
+ `;
447
+ }
448
+ }
449
+ ```
450
+
451
+ **Context variables:**
452
+ - `$item` - current item in iteration
453
+ - `$index` - zero-based index
454
+ - `$value` - entire array
455
+ - `$nodes` - cloned DOM nodes
456
+ - `$elements` - cloned HTML elements
457
+ - `$firstElement` - first element in cloned nodes
458
+
459
+ **Features:**
460
+ - Efficient DOM cloning for each item
461
+ - `swc-default` fallback renders when array is empty
462
+ - All cloned attributes support `{{ }}` substitution
463
+ - `on-clone-node` callback fires for each cloned node
464
+ - `on-clone-nodes` callback fires after all nodes cloned
465
+
466
+ ---
467
+
468
+ #### 8.5 **Declarative Routing with swc-choose**
469
+
470
+ Combine `@subscribeSwcAppRouteChange` with `swc-choose` for clean, declarative routing without boilerplate.
259
471
 
260
472
  ```typescript
261
473
  @elementDefine('root-router')
262
474
  class RootRouter extends HTMLElement {
263
- @subscribeSwcAppRouteChange('/')
264
- @applyInnerHtmlNodeHost({ root: 'light' })
265
- homeRoute(router: RouterEventType) {
266
- return `<home-page/>`;
475
+ private router: Router;
476
+
477
+ @onInitialize
478
+ onconstructor(router: Router) {
479
+ this.router = router;
480
+ }
481
+
482
+ @setProperty('#router', 'value')
483
+ @subscribeSwcAppRouteChange(['/', '/product/{id}'])
484
+ routeChanged(router: RouterEventType) {
485
+ return router;
267
486
  }
268
487
 
269
- @subscribeSwcAppRouteChange('/product/{id}')
270
- @applyInnerHtmlNodeHost({ root: 'light' })
271
- productRoute(router: RouterEventType, pathData: any) {
272
- return `<product-page product-id="${pathData.id}"/>`;
488
+ @onConnectedInnerHtml
489
+ render() {
490
+ return `
491
+ <template id="router" is="swc-choose">
492
+ <!-- Home -->
493
+ <template is="swc-when" value="{{ ['', '/'].includes($value?.path) }}">
494
+ <home-page/>
495
+ </template>
496
+
497
+ <!-- Product Detail -->
498
+ <template is="swc-when" value="{{ $value?.path.startsWith('/product/') }}">
499
+ <product-page product-id="{{$value?.pathData?.id}}"/>
500
+ </template>
501
+
502
+ <!-- Not Found -->
503
+ <template is="swc-otherwise">
504
+ <not-found-page/>
505
+ </template>
506
+ </template>
507
+ `;
273
508
  }
274
509
  }
275
510
  ```
276
511
 
512
+ **Benefits:**
513
+ - ✅ **No imperative route methods** - routes are declarative templates
514
+ - ✅ **Automatic re-rendering** - attributes like `product-id` update automatically
515
+ - ✅ **Performance optimization** - use `skip-if-same` on templates to prevent re-renders
516
+ - ✅ **Dynamic path parameters** - use `{{$value?.pathData?.id}}` syntax to inject route params
517
+ - ✅ **Cleaner DX** - single unified routing pattern
518
+
277
519
  ### 9. **Accommodation Pattern**
278
520
  Factory-based component registration with explicit DI.
279
521
 
@@ -287,8 +529,7 @@ export default (w: Window) => {
287
529
  @elementDefine(tagName, { window: w })
288
530
  class MyComponent extends w.HTMLElement {
289
531
  @onInitialize
290
- constructor(service: MyService) {
291
- super();
532
+ onconstructor(service: MyService) {
292
533
  this.service = service;
293
534
  }
294
535
  }
@@ -314,6 +555,390 @@ appElement.connect({
314
555
  });
315
556
  ```
316
557
 
558
+ ### 10. **Quickstart - Basic SPA Setup**
559
+
560
+ Complete example of setting up a production SPA with routing, services, and components.
561
+
562
+ #### Step 1. HTML Entry Point
563
+ ```html
564
+ <!DOCTYPE html>
565
+ <html>
566
+ <head>
567
+ <meta charset="UTF-8">
568
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
569
+ <title>My SPA</title>
570
+ <!-- Web Components polyfill for older browsers -->
571
+ <script src="https://unpkg.com/@ungap/custom-elements"></script>
572
+ </head>
573
+ <body id="app" is="swc-app-body">
574
+ <!-- Root router will render pages here -->
575
+ <root-router></root-router>
576
+ </body>
577
+ <script src="bundle.js"></script>
578
+ </html>
579
+ ```
580
+
581
+ #### Step 2. Services (Dependency Injection)
582
+ ```typescript
583
+ // services/UserService.ts
584
+ import { Sim } from '@dooboostore/simple-boot';
585
+ import { ConstructorType } from '@dooboostore/core';
586
+
587
+ export namespace UserService {
588
+ export const SYMBOL = Symbol('UserService');
589
+
590
+ export interface User {
591
+ id: number;
592
+ name: string;
593
+ email: string;
594
+ }
595
+
596
+ const users: User[] = [
597
+ { id: 1, name: 'Alice', email: 'alice@example.com' },
598
+ { id: 2, name: 'Bob', email: 'bob@example.com' }
599
+ ];
600
+ }
601
+
602
+ export interface UserService {
603
+ getUser(id: number): UserService.User | undefined;
604
+ getAllUsers(): UserService.User[];
605
+ }
606
+
607
+ // Factory function: accepts container and returns service class
608
+ export default (container: symbol): ConstructorType<any> => {
609
+ @Sim({ symbol: UserService.SYMBOL, container: container })
610
+ class UserServiceImpl implements UserService {
611
+ getUser(id: number) {
612
+ return UserService.users.find(u => u.id === id);
613
+ }
614
+
615
+ getAllUsers() {
616
+ return [...UserService.users];
617
+ }
618
+ }
619
+
620
+ return UserServiceImpl;
621
+ };
622
+ ```
623
+
624
+ ```typescript
625
+ // services/index.ts
626
+ import userServiceFactory from './UserService';
627
+
628
+ export const serviceFactories = [userServiceFactory];
629
+ ```
630
+
631
+ #### Step 3. Router Configuration (Declarative with swc-choose)
632
+ ```typescript
633
+ // pages/RootRouter.ts
634
+ import { elementDefine, subscribeSwcAppRouteChange, setProperty, onConnectedInnerHtml, onInitialize } from '@dooboostore/simple-web-component';
635
+ import { Router, RouterEventType } from '@dooboostore/core-web';
636
+
637
+ const rootRouterFactory = (w: Window) => {
638
+ const tagName = 'root-router';
639
+ const existing = w.customElements.get(tagName);
640
+ if (existing) return tagName;
641
+
642
+ @elementDefine(tagName, { window: w })
643
+ class RootRouter extends w.HTMLElement {
644
+ private router: Router;
645
+
646
+ @onInitialize
647
+ onconstructor(router: Router) {
648
+ this.router = router;
649
+ }
650
+
651
+ @setProperty('#router', 'value')
652
+ @subscribeSwcAppRouteChange(['/', '/users', '/users/{id}'])
653
+ routeChanged(route: RouterEventType) {
654
+ return route;
655
+ }
656
+
657
+ navigate(path: string): void {
658
+ this.router?.go(path);
659
+ }
660
+
661
+ @onConnectedInnerHtml
662
+ render() {
663
+ return `
664
+ <template id="router" is="swc-choose">
665
+ <!-- Home Page -->
666
+ <template is="swc-when" value="{{ ['', '/'].includes($value?.path) }}">
667
+ <home-page/>
668
+ </template>
669
+
670
+ <!-- Users List -->
671
+ <template is="swc-when" value="{{ $value?.path === '/users' }}">
672
+ <users-list-page/>
673
+ </template>
674
+
675
+ <!-- User Detail -->
676
+ <template is="swc-when" value="{{ $value?.path.startsWith('/users/') }}">
677
+ <user-detail-page user-id="{{$value?.pathData?.id}}"/>
678
+ </template>
679
+
680
+ <!-- Not Found -->
681
+ <template is="swc-otherwise">
682
+ <not-found-page/>
683
+ </template>
684
+ </template>
685
+ `;
686
+ }
687
+ }
688
+
689
+ return tagName;
690
+ };
691
+
692
+ export default rootRouterFactory;
693
+ ```
694
+
695
+ #### Step 4. Page Components
696
+ ```typescript
697
+ // pages/HomePage.ts
698
+ import { elementDefine, onConnectedInnerHtml } from '@dooboostore/simple-web-component';
699
+
700
+ const HomePage = (w: Window) => {
701
+ const tagName = 'home-page';
702
+ const existing = w.customElements.get(tagName);
703
+ if (existing) return tagName;
704
+
705
+ @elementDefine(tagName, { window: w })
706
+ class HomePage extends w.HTMLElement {
707
+ @onConnectedInnerHtml({ useShadow: true })
708
+ render() {
709
+ return `
710
+ <style>
711
+ :host { display: block; padding: 20px; }
712
+ h1 { color: #333; }
713
+ a { color: #0066cc; text-decoration: none; margin-right: 20px; }
714
+ </style>
715
+ <h1>Welcome to My SPA</h1>
716
+ <nav>
717
+ <a href="/users">View All Users</a>
718
+ </nav>
719
+ `;
720
+ }
721
+ }
722
+
723
+ return tagName;
724
+ };
725
+
726
+ export default HomePage;
727
+ ```
728
+
729
+ ```typescript
730
+ // pages/UsersListPage.ts
731
+ import { elementDefine, onConnectedInnerHtml, onConnectedSwcApp } from '@dooboostore/simple-web-component';
732
+ import { Inject } from '@dooboostore/simple-boot';
733
+ import { UserService } from '../services/UserService';
734
+
735
+ const UsersListPage = (w: Window) => {
736
+ const tagName = 'users-list-page';
737
+ const existing = w.customElements.get(tagName);
738
+ if (existing) return tagName;
739
+
740
+ @elementDefine(tagName, { window: w })
741
+ class UsersListPage extends w.HTMLElement {
742
+ private users: any[] = [];
743
+
744
+ @onConnectedSwcApp
745
+ onconstructor(@Inject({ symbol: UserService.SYMBOL }) userService: UserService) {
746
+ this.users = userService.getAllUsers();
747
+ }
748
+
749
+ @onConnectedInnerHtml({ useShadow: true })
750
+ render() {
751
+ const usersList = this.users
752
+ .map(u => `<li><a href="/users/${u.id}">${u.name}</a></li>`)
753
+ .join('');
754
+
755
+ return `
756
+ <style>
757
+ :host { display: block; padding: 20px; }
758
+ h1 { color: #333; }
759
+ ul { list-style: none; padding: 0; }
760
+ li { margin: 10px 0; }
761
+ a { color: #0066cc; text-decoration: none; }
762
+ </style>
763
+ <h1>Users</h1>
764
+ <ul>${usersList}</ul>
765
+ <a href="/">Back to Home</a>
766
+ `;
767
+ }
768
+ }
769
+
770
+ return tagName;
771
+ };
772
+
773
+ export default UsersListPage;
774
+ ```
775
+
776
+ #### Step 4-1. Reusable Components
777
+ ```typescript
778
+ // components/UserCard.ts
779
+ import { elementDefine, onConnectedInnerHtml, attributeHost } from '@dooboostore/simple-web-component';
780
+
781
+ const UserCard = (w: Window) => {
782
+ const tagName = 'user-card';
783
+ const existing = w.customElements.get(tagName);
784
+ if (existing) return tagName;
785
+
786
+ @elementDefine(tagName, { window: w })
787
+ class UserCard extends w.HTMLElement {
788
+ @attributeHost('data-username')
789
+ username: string;
790
+
791
+ @attributeHost('data-email')
792
+ email: string;
793
+
794
+ @onConnectedInnerHtml({ useShadow: true })
795
+ render() {
796
+ return `
797
+ <style>
798
+ :host { display: block; }
799
+ .card { border: 1px solid #ddd; border-radius: 4px; padding: 12px; }
800
+ .name { font-weight: bold; font-size: 14px; }
801
+ .email { color: #666; font-size: 12px; }
802
+ </style>
803
+ <div class="card">
804
+ <div class="name">${this.username}</div>
805
+ <div class="email">${this.email}</div>
806
+ </div>
807
+ `;
808
+ }
809
+ }
810
+
811
+ return tagName;
812
+ };
813
+
814
+ export default UserCard;
815
+ ```
816
+
817
+ #### Step 5. Export Factories
818
+
819
+ **services/index.ts** - Export all service factories
820
+ ```typescript
821
+ // services/index.ts
822
+ import userServiceFactory from './UserService';
823
+
824
+ export const serviceFactories = [userServiceFactory];
825
+ ```
826
+
827
+ **components/index.ts** - Export all component factories
828
+ ```typescript
829
+ // components/index.ts
830
+ import UserCard from './UserCard';
831
+
832
+ export const componentFactories = [
833
+ UserCard,
834
+ ];
835
+ ```
836
+
837
+ **pages/index.ts** - Export all page factories with router
838
+ ```typescript
839
+ // pages/index.ts
840
+ import rootRouterFactory from './RootRouter';
841
+ import HomePage from './HomePage';
842
+ import UsersListPage from './UsersListPage';
843
+ import UserDetailPage from './UserDetailPage';
844
+
845
+ export const pageFactories = [
846
+ rootRouterFactory, // Router first
847
+ HomePage,
848
+ UsersListPage,
849
+ UserDetailPage
850
+ ];
851
+ ```
852
+
853
+ #### Step 6. Application Bootstrap
854
+ ```typescript
855
+ // bootFactory.ts
856
+ import { register } from '@dooboostore/simple-web-component';
857
+ import { serviceFactories } from './services';
858
+ import { componentFactories } from './components';
859
+ import { pageFactories } from './pages';
860
+
861
+ export default (w: Window, container: symbol) => {
862
+ // Initialize services with container (dependency injection)
863
+ // Each factory receives the container symbol and registers its service in the DI container
864
+ serviceFactories.forEach(serviceFactory => serviceFactory(container));
865
+
866
+ // Register pages and components
867
+ register(w, [...pageFactories, ...componentFactories]);
868
+ };
869
+ ```
870
+
871
+ #### Step 7. Application Entry Point
872
+ ```typescript
873
+ // index.ts
874
+ import 'reflect-metadata';
875
+ import { SwcAppInterface } from '@dooboostore/simple-web-component';
876
+ import bootFactory from './bootFactory';
877
+
878
+ const w = window;
879
+
880
+ w.document.addEventListener('DOMContentLoaded', async () => {
881
+ const container = Symbol('app');
882
+
883
+ // Initialize services and components
884
+ bootFactory(w, container);
885
+
886
+ // Wait for customized built-in element to be defined
887
+ await w.customElements.whenDefined('swc-app-body');
888
+
889
+ // Get app root element
890
+ const appElement = w.document.querySelector('#app') as SwcAppInterface;
891
+
892
+ if (appElement && typeof appElement.connect === 'function') {
893
+ // Connect SPA with routing and DI
894
+ appElement.connect({
895
+ path: '/',
896
+ routeType: 'path',
897
+ container: container,
898
+ window: w,
899
+ onEngineStarted: () => {
900
+ console.log('🚀 Application started successfully');
901
+ // Root router automatically initialized via @elementDefine
902
+ }
903
+ });
904
+ } else {
905
+ console.error('Failed to initialize SPA');
906
+ }
907
+ });
908
+ ```
909
+
910
+ #### Resulting Application Structure
911
+ ```
912
+ src/
913
+ ├── index.ts # Entry point
914
+ ├── index.html # HTML root with <root-router>
915
+ ├── bootFactory.ts # Bootstrap services & components via register()
916
+ ├── services/
917
+ │ ├── UserService.ts # Service factory (export default)
918
+ │ └── index.ts # Export serviceFactories
919
+ ├── pages/
920
+ │ ├── RootRouter.ts # Router factory (export default)
921
+ │ ├── HomePage.ts # Page factory (export default)
922
+ │ ├── UsersListPage.ts # Page factory (export default)
923
+ │ ├── UserDetailPage.ts # Page factory (export default)
924
+ │ └── index.ts # Export pageFactories
925
+ └── components/
926
+ ├── UserCard.ts # Component factory (export default)
927
+ └── index.ts # Export componentFactories
928
+ ```
929
+
930
+ **Key Flow:**
931
+ 1. HTML loads → `<root-router>` element already in DOM
932
+ 2. `index.ts` executes → calls `bootFactory`
933
+ 3. `bootFactory` registers services and components (including `rootRouterFactory`)
934
+ 4. App waits for `swc-app-body` to be defined
935
+ 5. `appElement.connect()` initializes routing & DI
936
+ 6. `<root-router>` becomes active with `@subscribeSwcAppRouteChange` + `swc-choose` template
937
+ 7. Route changes automatically trigger `swc-choose` re-evaluation and template re-rendering
938
+ 8. Template attributes like `user-id="{{$value?.pathData?.id}}"` are dynamically populated with route params
939
+ 9. All components have access to injected services via `@onInitialize` or `@onConnectedSwcApp`
940
+
941
+
317
942
  ---
318
943
 
319
944
  ## ⚠️ Critical Rules
@@ -323,14 +948,14 @@ appElement.connect({
323
948
 
324
949
  ```typescript
325
950
  // ✅ CORRECT
326
- @Sim()
951
+ @Sim
327
952
  export class UserService { }
328
953
 
329
954
  @elementDefine(tagName, { window: w })
330
955
  class UserWidget extends w.HTMLElement { }
331
956
 
332
957
  // ❌ WRONG
333
- @Sim()
958
+ @Sim
334
959
  @elementDefine(tagName, { window: w })
335
960
  class UserWidget extends w.HTMLElement { }
336
961
  ```
@@ -353,8 +978,7 @@ export default (w: Window) => {
353
978
  ```typescript
354
979
  // ✅ CORRECT
355
980
  @onInitialize
356
- constructor(@Inject({ symbol: Service.SYMBOL }) service: Service) {
357
- super();
981
+ onconstructor(@Inject({ symbol: Service.SYMBOL }) service: Service) {
358
982
  this.service = service;
359
983
  }
360
984