@dooboostore/dom-render 1.0.100 → 1.0.103

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 (57) hide show
  1. package/DomRender.d.ts +21 -6
  2. package/DomRender.js +54 -23
  3. package/DomRenderProxy.d.ts +7 -3
  4. package/DomRenderProxy.js +120 -49
  5. package/README.MD +469 -112
  6. package/components/ComponentBase.d.ts +12 -10
  7. package/components/ComponentBase.js +92 -46
  8. package/components/ComponentSet.d.ts +2 -2
  9. package/components/appender/Appender.d.ts +2 -2
  10. package/components/checkbox/CheckBox.d.ts +4 -4
  11. package/components/choose/Choose.d.ts +4 -4
  12. package/components/choose/Choose.js +0 -16
  13. package/components/details/Details.d.ts +5 -5
  14. package/components/forOf/ForOf.d.ts +2 -2
  15. package/components/if/If.d.ts +2 -2
  16. package/components/index.d.ts +26 -24
  17. package/components/index.js +2 -1
  18. package/components/input/Input.d.ts +2 -2
  19. package/components/input/Input.js +27 -24
  20. package/components/promise/PromiseSwitch.d.ts +6 -6
  21. package/components/promise/PromiseSwitch.js +15 -0
  22. package/components/radio/Radio.d.ts +4 -4
  23. package/components/router/Route.d.ts +32 -0
  24. package/components/router/Route.js +116 -0
  25. package/components/select/Select.d.ts +3 -3
  26. package/components/select/Select.js +2 -2
  27. package/components/this/This.d.ts +9 -2
  28. package/components/this/This.js +24 -2
  29. package/components/timer/Timer.d.ts +2 -2
  30. package/configs/Config.d.ts +6 -1
  31. package/decorators/DomRenderNoProxy.d.ts +2 -0
  32. package/decorators/DomRenderNoProxy.js +15 -0
  33. package/dist/bundle.js +2 -0
  34. package/events/EventManager.d.ts +16 -4
  35. package/events/EventManager.js +320 -140
  36. package/index.d.ts +48 -1
  37. package/index.js +40 -0
  38. package/lifecycle/OnDestroyRender.d.ts +1 -1
  39. package/lifecycle/OnProxyDomRender.d.ts +1 -0
  40. package/lifecycle/OnProxyDomRender.js +1 -1
  41. package/operators/DrForOf.js +2 -1
  42. package/operators/DrIf.js +3 -2
  43. package/operators/DrTargetElement.js +2 -1
  44. package/operators/DrTargetElementIsElement.d.ts +0 -5
  45. package/operators/DrTargetElementIsElement.js +2 -1
  46. package/operators/DrThis.js +40 -11
  47. package/operators/OperatorExecuter.js +3 -1
  48. package/package.json +5 -4
  49. package/rawsets/Attrs.d.ts +2 -0
  50. package/rawsets/RawSet.d.ts +2 -2
  51. package/rawsets/RawSet.js +48 -33
  52. package/routers/HashRouter.d.ts +5 -4
  53. package/routers/HashRouter.js +8 -5
  54. package/routers/PathRouter.d.ts +5 -4
  55. package/routers/PathRouter.js +6 -4
  56. package/routers/Router.d.ts +22 -8
  57. package/routers/Router.js +44 -24
package/README.MD CHANGED
@@ -1,30 +1,30 @@
1
1
  DOM-RENDER
2
2
  ===
3
- [![typescript](https://img.shields.io/badge/-npm-black?logo=npm)](https://www.npmjs.com/package/dom-render) [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE.md)
3
+ [![typescript](https://img.shields.io/badge/-npm-black?logo=npm)](https://www.npmjs.com/package/@dooboostore/dom-render) [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE.md)
4
4
  * view template engine
5
5
  * Dom control and reorder and render
6
6
  * all internal variables are managed by proxy. (DomRenderProxy)
7
7
 
8
8
  # 🚀 Quick start
9
- ```html
10
- <script src="https://cdn.jsdelivr.net/npm/dom-render@1.0.91/dist/bundle.js"></script>
9
+ ```typescript
10
+ import { DomRender } from '@dooboostore/dom-render';
11
+
12
+ class AppData {
13
+ name: string = 'my name is dom-render';
14
+ }
15
+
16
+ const data = new AppData();
17
+ DomRender.run(data, document.querySelector('#app')!);
11
18
  ```
12
19
  ```html
13
20
  <!DOCTYPE html>
14
21
  <html lang="en">
15
- <head``>
22
+ <head>
16
23
  <meta charset="UTF-8">
17
24
  <title>Title</title>
18
25
  </head>
19
26
  <body id="app">
20
27
  ${this.name}$
21
- <script src="https://cdn.jsdelivr.net/npm/dom-render@1.0.86/dist/bundle.js"></script>
22
- <script>
23
- let data = {
24
- name: 'my name is dom-render'
25
- };
26
- data = DomRender.run(data , document.querySelector('#app'));
27
- </script>
28
28
  </body>
29
29
  </html>
30
30
  ```
@@ -42,14 +42,18 @@ ${this.name}$
42
42
  <body id="app">
43
43
  ${this.name}$ <!-- outout: <i>my name is dom-render</i> -->
44
44
  #{this.name}# <!-- outout text is italic: my name is dom-render -->
45
- <script>
46
- let data = {
47
- name: '<i>my name is dom-render</i>'
48
- };
49
- data = DomRender.run(data , document.querySelector('#app'));
50
- </script>
51
45
  </body>
52
46
  ```
47
+ ```typescript
48
+ import { DomRender } from '@dooboostore/dom-render';
49
+
50
+ class AppData {
51
+ name: string = '<i>my name is dom-render</i>';
52
+ }
53
+
54
+ const data = new AppData();
55
+ DomRender.run(data, document.querySelector('#app')!);
56
+ ```
53
57
  </details>
54
58
 
55
59
  # dom-render attributes
@@ -107,7 +111,7 @@ export class IndexComponent implements OnInitRender, OnDestroyRender, OnInit, On
107
111
  ```
108
112
  ```css
109
113
  .#uuid# {
110
- color:${@this@.color}$;
114
+ color:${@this@.color}$ ;
111
115
  }
112
116
  ```
113
117
  ```css
@@ -141,14 +145,18 @@ uuid: rawSetUUID
141
145
  <body id="app">
142
146
  <div dr-if="true">true</div> <!-- render -->
143
147
  <div dr-if="this.gender === 'M'">gender: M</div> <!-- No Render -->
144
- <script>
145
- let data = {
146
- gender: 'F',
147
- };
148
- data = DomRender.run(data , document.querySelector('#app'));
149
- </script>
150
148
  </body>
151
149
  ```
150
+ ```typescript
151
+ import { DomRender } from '@dooboostore/dom-render';
152
+
153
+ class AppData {
154
+ gender: string = 'F';
155
+ }
156
+
157
+ const data = new AppData();
158
+ DomRender.run(data, document.querySelector('#app')!);
159
+ ```
152
160
  </details>
153
161
 
154
162
 
@@ -386,6 +394,40 @@ export class MyComponent extends ComponentBase<{ 'title': string, 'show-icon': s
386
394
  ```
387
395
  When attributes change, the `converter` function will be executed before the value is assigned to the property. For `showIcon`, the string `"true"` or `"false"` will be converted to a boolean. For `createdAt`, the date string will be converted to a `Date` object.
388
396
 
397
+ ### Binding to Methods
398
+
399
+ The `@attribute` decorator can also be applied to methods. When the corresponding HTML attribute changes, the decorated method will be invoked with the new attribute value as its argument. This allows you to execute custom logic directly in response to attribute changes.
400
+
401
+ **Component (`MyComponent.ts`) - Method Example**
402
+ ```typescript
403
+ import { ComponentBase, attribute } from '@dooboostore/dom-render';
404
+
405
+ export class MyComponent extends ComponentBase<{ 'data-value': string }> {
406
+ public receivedData: string = '';
407
+
408
+ @attribute({ name: 'data-value' })
409
+ onDataValueChange(newValue: string): void {
410
+ this.receivedData = `Method received: ${newValue}`;
411
+ console.log(this.receivedData);
412
+ }
413
+ }
414
+ ```
415
+
416
+ **Template (`index.html`) - Method Example**
417
+ ```html
418
+ <my-component data-value="initial"></my-component>
419
+
420
+ <p>${@this@.receivedData}$</p>
421
+
422
+ <script>
423
+ setTimeout(() => {
424
+ const comp = document.querySelector('my-component');
425
+ comp.setAttribute('data-value', 'updated value');
426
+ }, 1000);
427
+ </script>
428
+ ```
429
+ When `data-value` attribute changes, the `onDataValueChange` method will be called with the new value.
430
+
389
431
  </details>
390
432
 
391
433
  ## Element Query with `@query` decorator
@@ -396,6 +438,7 @@ You can get a reference to DOM elements within your component's template using t
396
438
 
397
439
  - If the decorated property is **not an array type**, it returns the **first** matching element (like `querySelector`).
398
440
  - If the decorated property **is an array type** (e.g., `HTMLElement[]`), it returns **all** matching elements as an array (like `querySelectorAll`).
441
+ - When applied to a **method**, the method will be called with the queried element(s) as arguments after the component has been rendered. If the method expects a single element, the first match is passed. If it expects an array, all matches are passed.
399
442
 
400
443
  This is useful for directly accessing and manipulating elements you need to interact with from your component's logic.
401
444
 
@@ -428,6 +471,22 @@ export class MyComponent extends ComponentBase<{}> {
428
471
  });
429
472
  }
430
473
  }
474
+
475
+ // Method query: This method will be called with the first element matching '.my-button'
476
+ @query('.my-button')
477
+ private setupButton(button: HTMLButtonElement) {
478
+ if (button) {
479
+ button.addEventListener('click', () => alert('Button clicked from method query!'));
480
+ }
481
+ }
482
+
483
+ // Method query: This method will be called with an array of all elements matching 'span.item'
484
+ @query('span.item')
485
+ private processItems(spans: HTMLSpanElement[]) {
486
+ spans.forEach(span => {
487
+ span.style.fontWeight = 'bold';
488
+ });
489
+ }
431
490
  }
432
491
  ```
433
492
 
@@ -441,9 +500,11 @@ export class MyComponent extends ComponentBase<{}> {
441
500
  <li>Item 2</li>
442
501
  <li>Item 3</li>
443
502
  </ul>
503
+ <button class="my-button">Click Me</button>
504
+ <p><span class="item">First Span</span> <span class="item">Second Span</span></p>
444
505
  </div>
445
506
  ```
446
- The `@query` decorator will assign the corresponding element(s) to the decorated property. The property will be `null`/`undefined` (for single query) or an empty array (for multiple query) if no elements are found or before the component is rendered.
507
+ The `@query` decorator will assign the corresponding element(s) to the decorated property or pass them to the decorated method. The property will be `null`/`undefined` (for single query) or an empty array (for multiple query) if no elements are found or before the component is rendered. For methods, they will only be called if matching elements are found.
447
508
 
448
509
  </details>
449
510
 
@@ -531,6 +592,49 @@ dr-value-link: <input type="text" dr-value-link="@this@.office.addr.street"> <br
531
592
  ```
532
593
  </details>
533
594
 
595
+ ## Element State Attributes
596
+ <details>
597
+ <summary>dr-checked, dr-selected, dr-readonly, dr-disabled<strong>🔻(click)</strong></summary>
598
+
599
+ These attributes allow you to bind boolean values directly to the `checked`, `selected`, `readonly`, and `disabled` properties of HTML elements.
600
+
601
+ * `dr-checked`: Binds the `checked` attribute of an input (checkbox/radio) to a boolean value.
602
+ * `dr-selected`: Binds the `selected` attribute of an option to a boolean value.
603
+ * `dr-readonly`: Binds the `readonly` attribute of an input to a boolean value.
604
+ * `dr-disabled`: Binds the `disabled` attribute of an input/button/etc. to a boolean value.
605
+
606
+ ### Example
607
+
608
+ ```html
609
+ <!-- Example for dr-checked -->
610
+ <input type="checkbox" dr-checked="@this@.isChecked">
611
+ <p>Checkbox is: ${@this@.isChecked}$</p>
612
+
613
+ <!-- Example for dr-selected -->
614
+ <select>
615
+ <option value="apple" dr-selected="@this@.isAppleSelected">Apple</option>
616
+ <option value="banana" dr-selected="@this@.isBananaSelected">Banana</option>
617
+ </select>
618
+
619
+ <!-- Example for dr-readonly -->
620
+ <input type="text" dr-readonly="@this@.isReadOnly" value="Some text">
621
+
622
+ <!-- Example for dr-disabled -->
623
+ <button dr-disabled="@this@.isDisabled">Click Me</button>
624
+ <input type="text" dr-disabled="@this@.isDisabled" value="Disabled input">
625
+ ```
626
+ ```typescript
627
+ class Data {
628
+ isChecked = true;
629
+ isAppleSelected = false;
630
+ isBananaSelected = true;
631
+ isReadOnly = false;
632
+ isDisabled = true;
633
+ }
634
+ // ... DomRender.run(new Data(), ...)
635
+ ```
636
+ </details>
637
+
534
638
  ## dr-on-rendered-init
535
639
  - rendered call
536
640
 
@@ -560,7 +664,7 @@ dr-value-link: <input type="text" dr-value-link="@this@.office.addr.street"> <br
560
664
  <summary>dr-style<strong>🔻(click)</strong></summary>
561
665
 
562
666
  ```html
563
- <div dr-style="{'background-image': 'url(\''+@this@.my?.image+'\')'}" >aa</div>
667
+ <div dr-style="{'background-image': 'url(\' '+@this@.my?.image+'\')'}" >aa</div>
564
668
  <div dr-style="{fontSize: @this@.age + 'px'}"> style </div>
565
669
  <div dr-style="{'font-size': '20px'}"> style</div>
566
670
  <div dr-style="'font-size: ' + @this@.age +'px; margin: ' + @this@.age + 'px'"> style </div>
@@ -637,19 +741,24 @@ dr-value-link: <input type="text" dr-value-link="@this@.office.addr.street"> <br
637
741
  age: <input name="age">
638
742
  <button type="submit">submit</button>
639
743
  </form>
640
- <script>
641
- let data = {
642
- form: {},
643
- submit() {
644
- console.log(this.form);
645
- }
646
- };
647
- data = DomRender.run(data , document.querySelector('#app'));
648
- </script>
649
744
  </body>
745
+ ```
746
+ ```typescript
747
+ import { DomRender } from '@dooboostore/dom-render';
748
+
749
+ class FormData {
750
+ form: any = {};
751
+
752
+ submit() {
753
+ console.log(this.form);
754
+ }
755
+ }
756
+
757
+ const data = new FormData();
758
+ DomRender.run(data, document.querySelector('#app')!);
759
+ ```
650
760
  <!-- 💥 submit call -->
651
761
  <!-- console: {name: 'name data', age: 'age data'} -->
652
- ```
653
762
  </details>
654
763
 
655
764
  <details>
@@ -662,24 +771,32 @@ dr-value-link: <input type="text" dr-value-link="@this@.office.addr.street"> <br
662
771
  age: <input name="age">
663
772
  <button type="submit">submit</button>
664
773
  </form>
665
- <script>
666
- const form = new FormValidator();
667
- form.name = new NotEmptyValidator();
668
- form.age = new NotEmptyValidator();
669
- let data = {
670
- form,
671
- submit() {
672
- if (this.form.valid()){
673
- console.log('valid');
674
- } else {
675
- console.log('inValid');
676
- }
677
- }
678
- };
679
- data = DomRender.run(data , document.querySelector('#app'));
680
- </script>
681
774
  </body>
682
775
  ```
776
+ ```typescript
777
+ import { DomRender } from '@dooboostore/dom-render';
778
+ import { FormValidator, NotEmptyValidator } from '@dooboostore/dom-render/validator';
779
+
780
+ class FormDataWithValidator {
781
+ form: FormValidator = new FormValidator();
782
+
783
+ constructor() {
784
+ this.form.name = new NotEmptyValidator();
785
+ this.form.age = new NotEmptyValidator();
786
+ }
787
+
788
+ submit() {
789
+ if (this.form.valid()){
790
+ console.log('valid');
791
+ } else {
792
+ console.log('inValid');
793
+ }
794
+ }
795
+ }
796
+
797
+ const data = new FormDataWithValidator();
798
+ DomRender.run(data, document.querySelector('#app')!);
799
+ ```
683
800
  validator
684
801
  - Validator (abstract)
685
802
  - ValidatorArray (abstract)
@@ -720,88 +837,328 @@ validator
720
837
 
721
838
  ## Route
722
839
  <details>
723
- <summary>Route<strong>🔻(click)</strong></summary>
840
+ <summary><strong>Route (click)</strong></summary>
841
+
842
+ Declarative routing is supported using the `dr-route-match` and `dr-route-regexp` components. This allows you to conditionally render parts of your UI based on the current URL path.
843
+
844
+ - [Example](./examples/route)
724
845
 
725
- - [examples](./examples/router)
846
+ ### 1. Configuration
847
+
848
+ To enable routing, you must set the `routerType` in your `DomRender` configuration.
849
+
850
+ - `routerType`: Can be `'hash'`, `'path'`, or `'none'` (default).
726
851
 
727
- * config routerType: 'hash' | 'path' | 'none' (default: 'none')
728
852
  ```typescript
729
- // Config
730
- const config: Config = {
731
- window
853
+ // In your main setup file (e.g., index.ts)
854
+ import { DomRender, DomRenderRunConfig } from '@dooboostore/dom-render';
855
+
856
+ // ... your root object class (e.g., Index)
857
+
858
+ const config: DomRenderRunConfig<Index> = {
859
+ window: window,
860
+ routerType: 'path' // or 'hash'
732
861
  };
733
- config.targetElements = [
734
- DomRender.createComponent({type: Main, tagName: 'page-main', template: MainTemplate}),
735
- DomRender.createComponent({type: Second, tagName: 'page-second', template: SecondTemplate}),
736
- DomRender.createComponent({type: Detail, tagName: 'page-detail', template: DetailTemplate})
737
- ]
738
- config.routerType = 'hash'; // 'hash' | 'path' | 'none';
739
- const data = DomRender.run(new Data(), document.querySelector('#app')!, config);
862
+
863
+ const domRender = new DomRender({
864
+ rootObject: new Index(),
865
+ target: document.querySelector('#app')!,
866
+ config: config
867
+ }, {autoGo: '/'}); // autoGo navigates to the initial path
740
868
  ```
869
+
870
+ ### 2. Template Usage
871
+
872
+ Use the `<dr-route-match>` and `<dr-route-regexp>` components in your HTML template to define which content appears for which route.
873
+
874
+ - **`<dr-route-match>`**: Renders its content when the path matches the `value` attribute. It supports simple paths and path parameters (e.g., `/{id}`).
875
+ - **`<dr-route-regexp>`**: Renders its content when the path matches the regular expression in the `value` attribute.
876
+
741
877
  ```html
878
+ <!-- index.html -->
742
879
  <header>
743
- <h1>examples header</h1>
744
- <h2>${@this@.name}$</h2>
745
- <div>
746
- <div><button dr-event-click="$router.go('/')">main</button></div>
747
- <div>
748
- <button dr-event-click="$router.go('/second', {secondata: 555})">second</button>
749
- <button dr-event-click="$router.go('/second/5')">second/1</button>
750
- <button dr-event-click="$router.go('/second/wow')">second/2</button>
751
- </div>
752
- <div><button dr-event-click="$router.go('/detail/25?name=zzz')">detail</button></div>
753
- </div>
880
+ <!-- Navigation buttons -->
881
+ <button dr-event-click="$router?.go({path:'/'})">Main</button>
882
+ <button dr-event-click="$router?.go({path:'/second'})">Second</button>
883
+ <button dr-event-click="$router?.go({path:'/detail/42'})">Detail 42</button>
754
884
  </header>
755
- <hr>
885
+
756
886
  <main>
757
- <page-main dr-if="$router.test('/')"></page-main>
758
- <page-second dr-if="$router.test('/second')">1</page-second>
759
- <page-second dr-if="$router.testRegexp('/second/[0-9]?$')">2</page-second>
760
- <page-second dr-if="$router.testRegexp('/second/wow$')">wow</page-second>
761
- <page-detail url='/detail/{id:[0-9]+}' dr-if="$router.test($attribute.url)" dr-on-create:callback="$component.routerData($router.getRouteData($attribute.url))" ></page-detail>
762
- <div>
763
- <button dr-event-click="@this@.plusCount()">${@this@.count}$ count pluse++</button>
764
- </div>
887
+ <!-- Content for the root path -->
888
+ <dr-route-match value="/">
889
+ <h1>Main Page</h1>
890
+ </dr-route-match>
891
+
892
+ <!-- Content for the /second path -->
893
+ <dr-route-match value="/second">
894
+ <h1>Second Page</h1>
895
+ </dr-route-match>
896
+
897
+ <!-- Content for a path with a numeric parameter -->
898
+ <dr-route-match value="/detail/{id:[0-9]+}">
899
+ <h1>Detail Page</h1>
900
+ <p>The router's pathData will contain the matched 'id'.</p>
901
+ </dr-route-match>
902
+
903
+ <!-- Content using a regular expression -->
904
+ <dr-route-regexp value="/second/[0-9]?$">
905
+ <p>This content appears for paths like /second/1, /second/2, etc.</p>
906
+ </dr-route-regexp>
765
907
  </main>
766
- <hr>
767
- <footer>footer</footer>
768
908
  ```
909
+
910
+ The `$router` object is automatically available in your templates for navigation (`$router.go(...)`) and testing (`$router.test(...)`).
911
+
912
+ </details>
913
+
914
+ ## Accessing the Root Object Proxy
915
+
916
+ After initializing `DomRender`, you can get a proxied reference to your `rootObject`. Any changes made to this proxy will be automatically detected and reflected in the DOM. This is useful for updating state from outside your component's methods, such as in `setTimeout` or in response to external events.
917
+
769
918
  ```typescript
770
- export class Second implements OnCreateRender {
771
- name = 'Second'
919
+ import { DomRender } from '@dooboostore/dom-render';
920
+
921
+ class App {
922
+ message = 'Hello, World!';
923
+ }
924
+
925
+ // 1. Initialize DomRender
926
+ const domRender = new DomRender({
927
+ rootObject: new App(),
928
+ target: document.querySelector('#app')!
929
+ });
930
+
931
+ // 2. Get the proxy to the rootObject
932
+ const data = domRender.rootObject;
933
+
934
+ // 3. Now you can modify the state from anywhere
935
+ setTimeout(() => {
936
+ // This change will automatically update the UI
937
+ data.message = 'Hello, DomRender!';
938
+ }, 2000);
939
+ ```
940
+
941
+ ```html
942
+ <!-- index.html -->
943
+ <div id="app">
944
+ ${@this@.message}$
945
+ </div>
946
+ ```
947
+
948
+ ## Core Components
949
+ This section describes the built-in components provided by `dom-render`.
950
+
951
+ <details>
952
+ <summary><strong>ComponentBase & Decorators (<code>@attribute</code>, <code>@query</code>, <code>@event</code>)</strong>🔻(click)</summary>
953
+
954
+ `ComponentBase` is the foundation for creating custom components that can interact with the `dom-render` lifecycle and attributes. When you extend `ComponentBase`, you gain access to powerful decorators for linking your class to the template.
955
+
956
+ - **`@attribute`**: Binds a class property to an HTML attribute. It can automatically convert values and even bind to methods.
957
+ - **`@query`**: Injects DOM elements from your component's template into class properties or method arguments.
958
+ - **`@event`**: Binds a class method to a DOM event on an element within the template, handling listener cleanup automatically.
772
959
 
773
- onCreateRender(data: CreatorMetaData): void {
774
- console.log('----->', data.router)
960
+ *For detailed examples of these decorators, please see their dedicated sections above in the documentation.*
961
+
962
+ </details>
963
+
964
+ <details>
965
+ <summary><strong>ComponentSet</strong>🔻(click)</summary>
966
+
967
+ `ComponentSet` is a wrapper class that pairs a component instance with its own template and styles. It's the primary mechanism for dynamically rendering and swapping components within a `dr-this` block.
968
+
969
+ ### Usage
970
+ ```typescript
971
+ import { ComponentSet } from '@dooboostore/dom-render';
972
+
973
+ // Define two simple components
974
+ class ComponentA { message = 'This is Component A'; }
975
+ class ComponentB { message = 'Now you see Component B'; }
976
+
977
+ class App {
978
+ // Create ComponentSet instances for each component
979
+ componentA = new ComponentSet(new ComponentA(), { template: '<h1>${@this@.message}$</h1>' });
980
+ componentB = new ComponentSet(new ComponentB(), { template: '<h2>${@this@.message}$</h2>' });
981
+
982
+ // This property will hold the currently active component
983
+ activeComponent: ComponentSet;
984
+
985
+ constructor() {
986
+ this.activeComponent = this.componentA;
775
987
  }
988
+
989
+ showA() { this.activeComponent = this.componentA; }
990
+ showB() { this.activeComponent = this.componentB; }
991
+ }
992
+ ```
993
+
994
+ ```html
995
+ <!-- This div will dynamically render the component held by 'activeComponent' -->
996
+ <div dr-this="@this@.activeComponent"></div>
997
+
998
+ <button dr-event-click="@this@.showA()">Show Component A</button>
999
+ <button dr-event-click="@this@.showB()">Show Component B</button>
1000
+ ```
1001
+ </details>
1002
+
1003
+ <details>
1004
+ <summary><strong><code>dr-this</code></strong>🔻(click)</summary>
1005
+
1006
+ The `dr-this` component is the target for rendering a `ComponentSet`. It dynamically renders the component instance and template defined in the `ComponentSet` object it's bound to.
1007
+
1008
+ See the `ComponentSet` example above for a practical demonstration.
1009
+
1010
+ </details>
1011
+
1012
+ <details>
1013
+ <summary><strong><code>dr-choose</code> / <code>dr-choose-when</code> / <code>dr-choose-other-wise</code></strong>🔻(click)</summary>
1014
+
1015
+ Provides a "switch-case" control flow for rendering blocks of HTML. It evaluates a data object and renders the first `dr-choose-when` block whose `test` condition returns true. If no `when` blocks match, the `dr-choose-other-wise` block is rendered.
1016
+
1017
+ ### Usage
1018
+ ```typescript
1019
+ class App {
1020
+ status: 'loading' | 'success' | 'error' = 'loading';
776
1021
  }
777
1022
  ```
1023
+ ```html
1024
+ <dr-choose data="${@this@.status}$">
1025
+ <dr-choose-when test="(status) => status === 'loading'">
1026
+ <p>Loading...</p>
1027
+ </dr-choose-when>
1028
+ <dr-choose-when test="(status) => status === 'success'">
1029
+ <p>Data loaded successfully!</p>
1030
+ </dr-choose-when>
1031
+ <dr-choose-other-wise>
1032
+ <p>An unknown error occurred.</p>
1033
+ </dr-choose-other-wise>
1034
+ </dr-choose>
1035
+ ```
1036
+ </details>
1037
+
1038
+ <details>
1039
+ <summary><strong><code>dr-promise-switch</code></strong>🔻(click)</summary>
1040
+
1041
+ Declaratively handles the different states of a JavaScript `Promise`. It provides child components to render content for `pending`, `fulfilled`, and `rejected` states.
1042
+
1043
+ ### Child Components
1044
+ - `<dr-promise-switch-pending>`: Shown while the promise is in flight.
1045
+ - `<dr-promise-switch-fulfilled>`: Shown when the promise resolves successfully. The resolved data is available inside via `#component#.data`.
1046
+ - `<dr-promise-switch-rejected>`: Shown when the promise is rejected. The error is available inside via `#component#.data`.
1047
+ - `<dr-promise-switch-default>`: Shown before the promise is triggered.
1048
+
1049
+ ### Usage
778
1050
  ```typescript
779
- import {RouteData} from 'dom-render/routers/Router';
780
- import {OnCreateRender} from 'dom-render/lifecycle/OnCreateRender';
1051
+ class App {
1052
+ userPromise = null;
781
1053
 
782
- export class Detail implements OnCreateRender {
783
- name = 'Detail';
1054
+ fetchUser() {
1055
+ this.userPromise = fetch('/api/user').then(res => res.json());
1056
+ }
1057
+ }
1058
+ ```
1059
+ ```html
1060
+ <button dr-event-click="@this@.fetchUser()">Fetch User</button>
1061
+
1062
+ <dr-promise-switch data="${@this@.userPromise}$">
1063
+ <dr-promise-switch-pending>
1064
+ <p>Fetching user data...</p>
1065
+ </dr-promise-switch-pending>
1066
+ <dr-promise-switch-fulfilled>
1067
+ <p>Welcome, ${#component#.data.name}$!</p>
1068
+ </dr-promise-switch-fulfilled>
1069
+ <dr-promise-switch-rejected>
1070
+ <p>Failed to fetch user: ${#component#.data.message}$</p>
1071
+ </dr-promise-switch-rejected>
1072
+ </dr-promise-switch>
1073
+ ```
1074
+ </details>
784
1075
 
785
- onCreateRender(data: CreatorMetaData) {
786
- console.log('routeData->', data);
787
- }
1076
+ <details>
1077
+ <summary><strong><code>dr-checkbox</code></strong>🔻(click)</summary>
788
1078
 
789
- routerData(routeData: RouteData) {
790
- console.log('--------', routeData);
791
- }
1079
+ A wrapper for creating a custom, stylable checkbox. It uses child components to define the appearance for checked and unchecked states.
1080
+
1081
+ ### Child Components
1082
+ - `<dr-checkbox-checked>`: Content to show when the box is checked.
1083
+ - `<dr-checkbox-unchecked>`: Content to show when the box is unchecked.
1084
+
1085
+ ### Usage
1086
+ ```typescript
1087
+ class App {
1088
+ agreed = false;
792
1089
  }
793
1090
  ```
1091
+ ```html
1092
+ <dr-checkbox name="terms" checked="${@this@.agreed}$" change="${(checked) => @this@.agreed = checked}$">
1093
+ <dr-checkbox-checked>
1094
+ <!-- Using FontAwesome icons as an example -->
1095
+ <i class="fas fa-check-square"></i> I agree to the terms.
1096
+ </dr-checkbox-checked>
1097
+ <dr-checkbox-unchecked>
1098
+ <i class="far fa-square"></i> I agree to the terms.
1099
+ </dr-checkbox-unchecked>
1100
+ </dr-checkbox>
1101
+
1102
+ <p>Agreed: ${@this@.agreed}$</p>
1103
+ ```
1104
+ </details>
794
1105
 
1106
+ <details>
1107
+ <summary><strong><code>dr-details</code></strong>🔻(click)</summary>
1108
+
1109
+ An enhanced version of the native `<details>` element. It provides child components for the summary and body, and includes extra functionality like closing when the user clicks outside of it.
1110
+
1111
+ ### Child Components
1112
+ - `<dr-details-summary>`: The clickable header of the details element.
1113
+ - `<dr-details-body>`: The collapsible content.
1114
+ - `<dr-details-form>`: A special body that is a form, with features like automatic reset on toggle.
1115
+
1116
+ ### Usage
1117
+ ```html
1118
+ <dr-details>
1119
+ <dr-details-summary>
1120
+ <h3>More Information</h3>
1121
+ </dr-details-summary>
1122
+ <dr-details-body>
1123
+ <p>Here is the hidden content that appears on click.</p>
1124
+ </dr-details-body>
1125
+ </dr-details>
1126
+ ```
1127
+ </details>
1128
+
1129
+ <details>
1130
+ <summary><strong><code>dr-input</code></strong>🔻(click)</summary>
1131
+
1132
+ A custom input component that enhances a standard `<input>` with powerful features like debouncing and distinct value change detection. This is useful for performance-intensive tasks like live search.
1133
+
1134
+ ### Attributes
1135
+ - `debounceTime`: (Optional) The time in milliseconds to wait after the user stops typing before firing the `input` event.
1136
+ - `distinct`: (Optional) If set to `true`, the `input` event will only fire if the new value is different from the previous one.
1137
+ - `input`: A callback function that receives the debounced/distinct value.
1138
+
1139
+ ### Usage
795
1140
  ```typescript
796
- // RouteData type
797
- type RouteData = {
798
- path: string;
799
- url: string;
800
- data?: any;
801
- searchParams: URLSearchParams;
802
- pathData?: any;
1141
+ class App {
1142
+ searchQuery = '';
1143
+
1144
+ handleSearch(query: string) {
1145
+ console.log('Searching for:', query);
1146
+ this.searchQuery = query;
1147
+ }
803
1148
  }
804
1149
  ```
1150
+ ```html
1151
+ <label>Search:</label>
1152
+ <dr-input
1153
+ type="text"
1154
+ class="search-input"
1155
+ debounceTime="300"
1156
+ distinct="true"
1157
+ input="${(value) => @this@.handleSearch(value)}$"
1158
+ ></dr-input>
1159
+
1160
+ <p>Current query: ${@this@.searchQuery}$</p>
1161
+ ```
805
1162
  </details>
806
1163
 
807
1164
  ## Messenger (Data transmission)
@@ -866,7 +1223,7 @@ const rangeArray = new Range(100,55, 10).toArray();
866
1223
  const appender = new Appender<number>([1, 2]);
867
1224
  appender.push(3, 4)
868
1225
  for (const data of appender) {
869
- console.log('----appender item--->', data);
1226
+ console.log('----appender item---', data);
870
1227
  }
871
1228
  ```
872
1229
  </details>
@@ -880,10 +1237,10 @@ for (const data of appender) {
880
1237
  {
881
1238
  name: 'dom-render'
882
1239
  onBeforeReturnSet: (name: string, value: any, fullpath: string[]) => {
883
- console.log('set name-->', name, value, fullpath);
1240
+ console.log('set name--', name, value, fullpath);
884
1241
  }
885
1242
  onBeforeReturnGet: (name: string, value: any, fullpath: string[]) => {
886
- console.log('get name-->', name, value, fullpath);
1243
+ console.log('get name--', name, value, fullpath);
887
1244
  }
888
1245
  }
889
1246
  ```
@@ -1107,4 +1464,4 @@ lazy loading rollup config
1107
1464
 
1108
1465
  # License
1109
1466
  * MIT
1110
- * visualkhh@gmail.com
1467
+ * visualkhh@gmail.com