lego-dom 0.0.8 → 1.0.0

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 (175) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +49 -432
  3. package/cdn.html +124 -0
  4. package/docs/.vitepress/config.js +43 -5
  5. package/docs/api/directives.md +3 -3
  6. package/docs/api/globals.md +1 -1
  7. package/docs/api/index.md +3 -3
  8. package/docs/api/vite-plugin.md +1 -1
  9. package/docs/contributing/01-welcome.md +36 -0
  10. package/docs/contributing/02-registry.md +99 -0
  11. package/docs/contributing/03-batcher.md +110 -0
  12. package/docs/contributing/04-reactivity.md +87 -0
  13. package/docs/contributing/05-caching.md +59 -0
  14. package/docs/contributing/06-init.md +125 -0
  15. package/docs/contributing/07-observer.md +69 -0
  16. package/docs/contributing/08-snap.md +126 -0
  17. package/docs/contributing/09-diffing.md +69 -0
  18. package/docs/contributing/10-studs.md +76 -0
  19. package/docs/contributing/11-scanner.md +104 -0
  20. package/docs/contributing/12-render.md +116 -0
  21. package/docs/contributing/13-directives.md +225 -0
  22. package/docs/contributing/14-events.md +57 -0
  23. package/docs/contributing/15-router.md +9 -0
  24. package/docs/contributing/16-state.md +48 -0
  25. package/docs/contributing/17-legodom.md +55 -0
  26. package/docs/contributing/index.md +5 -0
  27. package/docs/examples/form.md +2 -2
  28. package/docs/examples/index.md +4 -4
  29. package/docs/examples/routing.md +8 -8
  30. package/docs/examples/sfc-showcase.md +4 -4
  31. package/docs/examples/todo-app.md +3 -3
  32. package/docs/guide/cdn-usage.md +16 -8
  33. package/docs/guide/components.md +34 -16
  34. package/docs/guide/contributing.md +2 -2
  35. package/docs/guide/directives.md +23 -23
  36. package/docs/guide/getting-started.md +41 -16
  37. package/docs/guide/index.md +12 -12
  38. package/docs/guide/lifecycle.md +1 -1
  39. package/docs/guide/quick-start.md +8 -5
  40. package/docs/guide/reactivity.md +30 -9
  41. package/docs/guide/routing.md +189 -289
  42. package/docs/guide/sfc.md +40 -40
  43. package/docs/guide/templating.md +4 -4
  44. package/docs/index.md +48 -14
  45. package/docs/public/logo.svg +17 -38
  46. package/docs/router/basic-routing.md +103 -0
  47. package/docs/router/cold-entry.md +91 -0
  48. package/docs/router/history.md +69 -0
  49. package/docs/router/index.md +73 -0
  50. package/docs/router/resolver.md +74 -0
  51. package/docs/router/surgical-swaps.md +134 -0
  52. package/examples/vite-app/README.md +2 -2
  53. package/examples/vite-app/index.html +9 -13
  54. package/examples/vite-app/package.json +4 -2
  55. package/examples/vite-app/src/app.css +3 -0
  56. package/examples/vite-app/src/app.js +29 -0
  57. package/examples/vite-app/src/components/app-navbar.lego +34 -0
  58. package/examples/vite-app/src/components/customers/customer-details.lego +24 -0
  59. package/examples/vite-app/src/components/customers/customer-orders.lego +21 -0
  60. package/examples/vite-app/src/components/customers/order-list.lego +55 -0
  61. package/examples/vite-app/src/components/greeting-card.lego +26 -26
  62. package/examples/vite-app/src/components/sample-component.lego +58 -58
  63. package/examples/vite-app/src/components/shells/customers-shell.lego +21 -0
  64. package/examples/vite-app/src/components/todo-list.lego +239 -0
  65. package/examples/vite-app/src/components/widgets/user-card.lego +27 -0
  66. package/examples/vite-app/vite.config.js +7 -2
  67. package/lego.js +2 -0
  68. package/main.js +280 -83
  69. package/package.json +8 -3
  70. package/parse-lego.js +17 -8
  71. package/parse-lego.test.js +1 -1
  72. package/{main.test.js → tests/main.test.js} +34 -17
  73. package/tests/parse-lego.test.js +65 -0
  74. package/vite-plugin.js +62 -24
  75. package/docs/.vitepress/dist/404.html +0 -22
  76. package/docs/.vitepress/dist/api/define.html +0 -35
  77. package/docs/.vitepress/dist/api/directives.html +0 -32
  78. package/docs/.vitepress/dist/api/globals.html +0 -27
  79. package/docs/.vitepress/dist/api/index.html +0 -25
  80. package/docs/.vitepress/dist/api/lifecycle.html +0 -38
  81. package/docs/.vitepress/dist/api/route.html +0 -34
  82. package/docs/.vitepress/dist/api/vite-plugin.html +0 -37
  83. package/docs/.vitepress/dist/assets/api_define.md.UA-ygUnQ.js +0 -11
  84. package/docs/.vitepress/dist/assets/api_define.md.UA-ygUnQ.lean.js +0 -1
  85. package/docs/.vitepress/dist/assets/api_directives.md.BV-D251p.js +0 -8
  86. package/docs/.vitepress/dist/assets/api_directives.md.BV-D251p.lean.js +0 -1
  87. package/docs/.vitepress/dist/assets/api_globals.md.DOjt7AV0.js +0 -3
  88. package/docs/.vitepress/dist/assets/api_globals.md.DOjt7AV0.lean.js +0 -1
  89. package/docs/.vitepress/dist/assets/api_index.md.OS6h01ct.js +0 -1
  90. package/docs/.vitepress/dist/assets/api_index.md.OS6h01ct.lean.js +0 -1
  91. package/docs/.vitepress/dist/assets/api_lifecycle.md.Ccm5xw6-.js +0 -14
  92. package/docs/.vitepress/dist/assets/api_lifecycle.md.Ccm5xw6-.lean.js +0 -1
  93. package/docs/.vitepress/dist/assets/api_route.md.CAHf_KNp.js +0 -10
  94. package/docs/.vitepress/dist/assets/api_route.md.CAHf_KNp.lean.js +0 -1
  95. package/docs/.vitepress/dist/assets/api_vite-plugin.md.DNn9VhL5.js +0 -13
  96. package/docs/.vitepress/dist/assets/api_vite-plugin.md.DNn9VhL5.lean.js +0 -1
  97. package/docs/.vitepress/dist/assets/app.BG5s3B0P.js +0 -1
  98. package/docs/.vitepress/dist/assets/chunks/@localSearchIndexroot.DQmuWC2Z.js +0 -1
  99. package/docs/.vitepress/dist/assets/chunks/VPLocalSearchBox.BO-PSxt1.js +0 -9
  100. package/docs/.vitepress/dist/assets/chunks/framework.B7OFBR9X.js +0 -19
  101. package/docs/.vitepress/dist/assets/chunks/theme.DA-iSa9B.js +0 -2
  102. package/docs/.vitepress/dist/assets/examples_form.md.B3stGKbu.js +0 -34
  103. package/docs/.vitepress/dist/assets/examples_form.md.B3stGKbu.lean.js +0 -1
  104. package/docs/.vitepress/dist/assets/examples_index.md.BDEG_D4J.js +0 -30
  105. package/docs/.vitepress/dist/assets/examples_index.md.BDEG_D4J.lean.js +0 -1
  106. package/docs/.vitepress/dist/assets/examples_routing.md.bqZ9DjDK.js +0 -338
  107. package/docs/.vitepress/dist/assets/examples_routing.md.bqZ9DjDK.lean.js +0 -1
  108. package/docs/.vitepress/dist/assets/examples_sfc-showcase.md.DLXaUiop.js +0 -13
  109. package/docs/.vitepress/dist/assets/examples_sfc-showcase.md.DLXaUiop.lean.js +0 -1
  110. package/docs/.vitepress/dist/assets/examples_todo-app.md.D5RhZoo5.js +0 -297
  111. package/docs/.vitepress/dist/assets/examples_todo-app.md.D5RhZoo5.lean.js +0 -1
  112. package/docs/.vitepress/dist/assets/guide_cdn-usage.md.CAjf03Lr.js +0 -182
  113. package/docs/.vitepress/dist/assets/guide_cdn-usage.md.CAjf03Lr.lean.js +0 -1
  114. package/docs/.vitepress/dist/assets/guide_components.md.BIFWF1Hc.js +0 -174
  115. package/docs/.vitepress/dist/assets/guide_components.md.BIFWF1Hc.lean.js +0 -1
  116. package/docs/.vitepress/dist/assets/guide_contributing.md.BgbUN-Mr.js +0 -1
  117. package/docs/.vitepress/dist/assets/guide_contributing.md.BgbUN-Mr.lean.js +0 -1
  118. package/docs/.vitepress/dist/assets/guide_directives.md.Bi3ynu1d.js +0 -140
  119. package/docs/.vitepress/dist/assets/guide_directives.md.Bi3ynu1d.lean.js +0 -1
  120. package/docs/.vitepress/dist/assets/guide_getting-started.md.2Nr1lp2z.js +0 -107
  121. package/docs/.vitepress/dist/assets/guide_getting-started.md.2Nr1lp2z.lean.js +0 -1
  122. package/docs/.vitepress/dist/assets/guide_index.md.GvZq_Yf2.js +0 -2
  123. package/docs/.vitepress/dist/assets/guide_index.md.GvZq_Yf2.lean.js +0 -1
  124. package/docs/.vitepress/dist/assets/guide_lifecycle.md.B28j1OzS.js +0 -304
  125. package/docs/.vitepress/dist/assets/guide_lifecycle.md.B28j1OzS.lean.js +0 -1
  126. package/docs/.vitepress/dist/assets/guide_quick-start.md.CNk3VGTF.js +0 -33
  127. package/docs/.vitepress/dist/assets/guide_quick-start.md.CNk3VGTF.lean.js +0 -1
  128. package/docs/.vitepress/dist/assets/guide_reactivity.md.CVsaMaPv.js +0 -135
  129. package/docs/.vitepress/dist/assets/guide_reactivity.md.CVsaMaPv.lean.js +0 -1
  130. package/docs/.vitepress/dist/assets/guide_routing.md.DSpDP25o.js +0 -193
  131. package/docs/.vitepress/dist/assets/guide_routing.md.DSpDP25o.lean.js +0 -1
  132. package/docs/.vitepress/dist/assets/guide_sfc.md.CVUP66tS.js +0 -187
  133. package/docs/.vitepress/dist/assets/guide_sfc.md.CVUP66tS.lean.js +0 -1
  134. package/docs/.vitepress/dist/assets/guide_templating.md.BgCGe4aa.js +0 -119
  135. package/docs/.vitepress/dist/assets/guide_templating.md.BgCGe4aa.lean.js +0 -1
  136. package/docs/.vitepress/dist/assets/index.md.xV1taCED.js +0 -23
  137. package/docs/.vitepress/dist/assets/index.md.xV1taCED.lean.js +0 -1
  138. package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  139. package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  140. package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  141. package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  142. package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  143. package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  144. package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  145. package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  146. package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  147. package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  148. package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  149. package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  150. package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  151. package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  152. package/docs/.vitepress/dist/assets/style.eycE2Jhw.css +0 -1
  153. package/docs/.vitepress/dist/examples/form.html +0 -58
  154. package/docs/.vitepress/dist/examples/index.html +0 -368
  155. package/docs/.vitepress/dist/examples/routing.html +0 -362
  156. package/docs/.vitepress/dist/examples/sfc-showcase.html +0 -37
  157. package/docs/.vitepress/dist/examples/todo-app.html +0 -321
  158. package/docs/.vitepress/dist/guide/cdn-usage.html +0 -206
  159. package/docs/.vitepress/dist/guide/components.html +0 -198
  160. package/docs/.vitepress/dist/guide/contributing.html +0 -25
  161. package/docs/.vitepress/dist/guide/directives.html +0 -164
  162. package/docs/.vitepress/dist/guide/getting-started.html +0 -131
  163. package/docs/.vitepress/dist/guide/index.html +0 -26
  164. package/docs/.vitepress/dist/guide/lifecycle.html +0 -328
  165. package/docs/.vitepress/dist/guide/quick-start.html +0 -57
  166. package/docs/.vitepress/dist/guide/reactivity.html +0 -159
  167. package/docs/.vitepress/dist/guide/routing.html +0 -217
  168. package/docs/.vitepress/dist/guide/sfc.html +0 -211
  169. package/docs/.vitepress/dist/guide/templating.html +0 -143
  170. package/docs/.vitepress/dist/hashmap.json +0 -1
  171. package/docs/.vitepress/dist/index.html +0 -47
  172. package/docs/.vitepress/dist/logo.svg +0 -38
  173. package/docs/.vitepress/dist/vp-icons.css +0 -1
  174. package/examples/vite-app/src/main.js +0 -11
  175. package/examples.js +0 -99
@@ -0,0 +1,69 @@
1
+ # Big Brother is Watching
2
+
3
+ In Topic 6, we saw that Lego.init() sets up a "watchdog." Now, let's look at exactly how that watchdog functions. This is what makes the library feel "automatic"—you can inject HTML into the page using vanilla JavaScript, and Lego will instantly recognize it and bring it to life.
4
+
5
+ ## Mutation Observer
6
+
7
+ The `MutationObserver` is a built-in browser API that provides the ability to watch for changes being made to the DOM tree. In this library, it acts as the "Event Loop" for component lifecycles.
8
+
9
+ ### 1. The Configuration
10
+
11
+ The library observes the `document.body` with specific settings:
12
+
13
+ ```js
14
+ observer.observe(document.body, { childList: true, subtree: true });
15
+
16
+ ```
17
+
18
+ - **`childList: true`**: Tells the observer to watch for elements being added or removed.
19
+
20
+ - **`subtree: true`**: This is vital. Without this, the library would only see things added directly to the `<body>`. With it, the library sees changes happening deep inside nested divs or other components.
21
+
22
+
23
+ ### 2. Processing `addedNodes`
24
+
25
+ Whenever a new piece of HTML is injected (via `innerHTML`, `appendChild`, etc.), the observer receives a list of `addedNodes`.
26
+
27
+ - **Filter**: The library checks `n.nodeType === Node.ELEMENT_NODE` to ensure it is an **Element** (like a `<div>` or `<my-comp>`) and not just a fragment of text.
28
+
29
+ - **The Action**: It calls `snap(n)`. This triggers the entire initialization process: attaching the Shadow DOM, creating reactive state, and rendering the template.
30
+
31
+
32
+ ### 3. Processing `removedNodes`
33
+
34
+ When an element is deleted from the page, the observer catches it in `removedNodes`.
35
+
36
+ - **Cleanup**: It calls `unsnap(n)`.
37
+
38
+ - **Lifecycle Hook**: `unsnap` checks the component's state for an `unmounted` function. This allows you to perform cleanup, like stopping timers or closing WebSocket connections, preventing memory leaks.
39
+
40
+
41
+ ### Why this is superior to manual initialization
42
+ In most frameworks, you have to tell the library when you’ve changed the page (e.g., calling `root.render()`). This library turns that upside down by using the browser's native **MutationObserver** to watch the DOM and react automatically.
43
+ Unlike some libraries where adding a button via `innerHTML` requires a manual "re-scan" or "re-bind" call, this setup makes LegoDOM **reactive to the DOM itself**: the moment a tag like `<user-card>` appears in the document, it is automatically detected and upgraded into a functional, living component.
44
+
45
+ ### 1. The Strategy: "Observe Once, Act Everywhere"
46
+
47
+ The library sets up a single observer on `document.body`.
48
+
49
+ - **`childList: true`**: This tells the observer to watch for the addition or removal of direct children.
50
+
51
+ - **`subtree: true`**: This is the "secret sauce." It extends the observation to the entire DOM tree. If you have a deeply nested `div` and you inject a Lego component into it, the observer will see it.
52
+
53
+
54
+ ### 2. The Logic Loop: Added Nodes
55
+
56
+ When the observer detects changes, it provides a list of `MutationRecord` objects. The library loops through these records specifically looking for `addedNodes`.
57
+
58
+ - **Type Filtering**: It checks `n.nodeType === Node.ELEMENT_NODE`. This ensures the library ignores text changes or comments and only focuses on **Elements** (tags).
59
+
60
+ - **The Upgrade (Snapping)**: For every new element found, it calls `snap(n)`. This is why you can do `document.body.innerHTML += '<my-counter></my-counter>'` in the console, and the counter will immediately start working.
61
+
62
+
63
+ ### 3. The Logic Loop: Removed Nodes
64
+
65
+ This is equally important for "garbage collection." When an element is deleted, it appears in `removedNodes`.
66
+
67
+ - **The `unsnap` Trigger**: The library calls `unsnap(n)`.
68
+
69
+ - **Lifecycle Cleanup**: Inside `unsnap`, the library looks for a developer-defined `unmounted` function. This is where you'd kill `setInterval` timers or close database connections. Without this, your app would suffer from "Memory Leaks"—processes that keep running even though the component is gone.
@@ -0,0 +1,126 @@
1
+ # Let there be, and it was!
2
+
3
+ If LegoDOM were a robot it would probably say - snap() is the most complex function I have because it acts as the "Middleman" between the static DOM, the reactive state, and the Shadow DOM. It is the "constructor" that the ~~stingy~~ browser never gave anyone - LegoDOM.
4
+
5
+
6
+ ## Snap, snap, snap!
7
+
8
+ The `snap(el)` function is responsible for "upgrading" a standard HTML element. It is recursive, meaning if you snap a `<div>`, it will automatically look inside that `<div>` and snap every child as well.
9
+
10
+ ```js
11
+ const snap = (el) => {
12
+ if (!el || el.nodeType !== Node.ELEMENT_NODE) return;
13
+ const data = getPrivateData(el);
14
+ const name = el.tagName.toLowerCase();
15
+ const templateNode = registry[name];
16
+
17
+ if (templateNode && !data.snapped) {
18
+ data.snapped = true;
19
+ const tpl = templateNode.content.cloneNode(true);
20
+ const shadow = el.attachShadow({ mode: 'open' });
21
+
22
+ // TIER 1: Logic from Lego.define (SFC)
23
+ const scriptLogic = sfcLogic.get(name) || {};
24
+
25
+ // TIER 2: Logic from the <template b-data="..."> attribute
26
+ const templateLogic = parseJSObject(templateNode.getAttribute('b-data') || '{}');
27
+
28
+ // TIER 3: Logic from the <my-comp b-data="..."> tag
29
+ const instanceLogic = parseJSObject(el.getAttribute('b-data') || '{}');
30
+
31
+ // Priority: Script < Template < Instance
32
+ el._studs = reactive({
33
+ ...scriptLogic,
34
+ ...templateLogic,
35
+ ...instanceLogic
36
+ }, el);
37
+
38
+ shadow.appendChild(tpl);
39
+
40
+ const style = shadow.querySelector('style');
41
+ if (style) {
42
+ style.textContent = style.textContent.replace(/\bself\b/g, ':host');
43
+ }
44
+
45
+ bind(shadow, el);
46
+ render(el);
47
+
48
+ if (typeof el._studs.mounted === 'function') {
49
+ try { el._studs.mounted.call(el._studs); } catch (e) { console.error(`[Lego] Error in mounted <${name}>:`, e); }
50
+ }
51
+ }
52
+
53
+ let provider = el.parentElement;
54
+ while (provider && !provider._studs) provider = provider.parentElement;
55
+ if (provider && provider._studs) bind(el, provider);
56
+
57
+ [...el.children].forEach(snap);
58
+ };
59
+ ```
60
+
61
+ ### 1. The Blueprint Lookup
62
+
63
+ When `snap(el)` runs, the first thing it does is determine if the element is a Lego component.
64
+
65
+ - It converts the tag name to lowercase (e.g., `<MY-COMP>` becomes `my-comp`).
66
+
67
+ - It checks the **`registry`** (which we filled in [Topic 2](./02-registry.md)) to see if a `<template>` exists for that name.
68
+
69
+ - It uses `getPrivateData(el).snapped` to ensure it never "snaps" the same element twice, preventing infinite loops.
70
+
71
+
72
+ ### 2. Attaching the Shadow DOM
73
+
74
+ If a template is found, Lego creates a **Shadow DOM** for the element:
75
+
76
+ JavaScript
77
+
78
+ ```
79
+ const shadow = el.attachShadow({ mode: 'open' });
80
+
81
+ ```
82
+
83
+ - **Encapsulation**: By using `attachShadow`, the component’s internal styles and HTML are shielded from the rest of the page.
84
+
85
+ - **Template Injection**: It clones the content of the template and appends it to this new Shadow Root.
86
+
87
+
88
+ ### 3. CSS "self" Transformation
89
+
90
+ Lego includes a small but clever utility for styling:
91
+
92
+ JavaScript
93
+
94
+ ```
95
+ style.textContent = style.textContent.replace(/\bself\b/g, ':host');
96
+
97
+ ```
98
+
99
+ - This allows you to write `self { color: red; }` in your template CSS.
100
+
101
+ - During the snap process, Lego converts the word `self` to the official Web Component selector `:host`, which targets the component itself.
102
+
103
+
104
+ ### 4. Data Merging & Reactivity
105
+
106
+ This is where the component's state is born. The library merges data from three different sources (we will dive deeper into this "Tier System" in Topic 9) and wraps the result in the **`reactive()`** proxy.
107
+
108
+ - The resulting proxy is stored in `el._studs`. This is the "brain" of your component.
109
+
110
+
111
+ ### 5. The First Render and Lifecycle
112
+
113
+ Once the data is ready and the Shadow DOM is attached:
114
+
115
+ 1. **`bind(shadow, el)`**: Connects event listeners (like `@click`) inside the Shadow DOM.
116
+
117
+ 2. **`render(el)`**: Performs the initial pass to fill in <code v-pre>{{ variables }}</code> and handle `b-show/b-for` logic.
118
+
119
+ 3. **`mounted()`**: If you defined a `mounted` function in your logic, Lego calls it now. This is your signal that the component is officially "alive" and visible on the page.
120
+
121
+ ---------
122
+
123
+ **Crucial Logic Note:** At the very end of the function, `snap` calls itself on every child of the element: `[...el.children].forEach(snap)`. This ensures that if you have components nested inside components, they all "wake up" in a top-down order.
124
+
125
+
126
+ **Summary**: `snap()` takes a raw tag, gives it a Shadow DOM "soul," injects its HTML blueprint, sets up its reactive "brain" (`_studs`), and triggers its first breath (`mounted`).
@@ -0,0 +1,69 @@
1
+ # He said, She said, They Said, Who Said?
2
+
3
+ This topic is about the "Power Struggle" inside the code. When a component is born, it needs to know what its data is. The library looks at three different places to find that data, and it has a strict hierarchy of who wins in a conflict.
4
+
5
+
6
+ ## Diffing, Merging, and Priorities (The Tier System)
7
+
8
+ Inside the `snap(el)` function, you will see a variable called `logic`. This isn't just a simple object; it is the result of a three-way merge. The code uses the spread operator `{ ... }` to layer these "Tiers."
9
+
10
+ ### The Three Tiers of Authority
11
+
12
+ 1. **Tier 1: The Global Definition (Lowest Priority)**
13
+
14
+ ```js
15
+ const baseLogic = sfcLogic.get(name) || {};
16
+
17
+ ```
18
+
19
+ This is the JavaScript object you provided when you called `Lego.define()`. It contains your default values and methods. It’s the "fallback" data. SFCs and .lego files fall under this tier.
20
+
21
+ 2. **Tier 2: The Template Attributes (Middle Priority)**
22
+
23
+
24
+ ```js
25
+ const templateLogic = parseJSObject(t.getAttribute('b-data')) || {};
26
+
27
+ ```
28
+
29
+ Lego looks at the `<template>` tag itself in your HTML. If you added a `b-data` attribute there, it overrides the global definition. This is useful for creating "variants" of a component template without writing new JavaScript.
30
+
31
+ 3. **Tier 3: The Instance Attributes (Highest Priority)**
32
+
33
+ ```js
34
+ const instanceLogic = parseJSObject(el.getAttribute('b-data')) || {};
35
+
36
+ ```
37
+
38
+ This is the data attached to the specific tag on your page (e.g., `<user-profile b-data="{id: 42}">`). This is the "Final Word." If the instance says the ID is 42, it doesn't matter what the template or the global definition said.
39
+
40
+
41
+ ### The Merge Order
42
+
43
+ The code combines them like this:
44
+
45
+ JavaScript
46
+
47
+ ```
48
+ const mergedLogic = { ...baseLogic, ...templateLogic, ...instanceLogic };
49
+
50
+ ```
51
+
52
+ In JavaScript, when you spread objects, the properties on the right overwrite properties on the left. So, Instance > Template > Definition.
53
+
54
+ ### The `parseJSObject` Utility
55
+
56
+ To make this work, the library includes a helper called `parseJSObject`.
57
+
58
+ - **Why not `JSON.parse`?** JSON is strict (requires double quotes, no functions).
59
+
60
+ - **The Library's Way**: It uses `new Function('return ' + str)()`. This is a powerful (and dangerous) trick that allows you to write actual JavaScript inside your HTML attributes, including functions or arrays, which Lego then evaluates into a real object.
61
+
62
+
63
+ ### The Edge Case: `b-data` as a "Ref"
64
+
65
+ If the `b-data` attribute starts with a `$`, like `b-data="$someGlobal"`, the library treats it as a pointer to a global variable instead of a raw object string. This allows for deep data sharing between the component and the outside world.
66
+
67
+ ----------
68
+
69
+ **Summary**: A component's data is a "Cake" with three layers. The Global Logic is the bottom, the Template Logic is the middle, and the Instance Logic is the frosting. The "Frosting" (Instance) is what the user ultimately sees.
@@ -0,0 +1,76 @@
1
+ # State Management
2
+
3
+ Let's see how the "Cake" of data we merged in Topic 9 is actually brought to life. This is the moment a static object becomes a **Reactive State**, which the library stores in a property called `_studs`.
4
+
5
+
6
+ ## The `reactive` State (`_studs`)
7
+
8
+ Once `snap()` has merged the data from the SFC (Tier 1), the Template (Tier 2), and the Instance (Tier 3), it doesn't just save that object to the element. It transforms it.
9
+
10
+ ```js
11
+ //...rest of code
12
+ const tpl = templateNode.content.cloneNode(true);
13
+ const shadow = el.attachShadow({ mode: 'open' });
14
+
15
+ // TIER 1: Logic from Lego.define (SFC)
16
+ const scriptLogic = sfcLogic.get(name) || {};
17
+
18
+ // TIER 2: Logic from the <template b-data="..."> attribute
19
+ const templateLogic = parseJSObject(templateNode.getAttribute('b-data') || '{}');
20
+
21
+ // TIER 3: Logic from the <my-comp b-data="..."> tag
22
+ const instanceLogic = parseJSObject(el.getAttribute('b-data') || '{}');
23
+
24
+ // Priority: Script < Template < Instance
25
+ el._studs = reactive({
26
+ ...scriptLogic,
27
+ ...templateLogic,
28
+ ...instanceLogic
29
+ }, el);
30
+ //... rest of code
31
+ ```
32
+
33
+ ### 1. Why the name `_studs`?
34
+
35
+ In the physical world, "studs" are the bumps on top of a Lego brick that allow it to connect to others. In this library:
36
+
37
+ - **`el._studs`** represents the connection point between your JavaScript logic and the DOM.
38
+
39
+ - It is the "source of truth" for the component. Every `{{variable}}` you write in your HTML is looking for a matching key inside `_studs`.
40
+
41
+
42
+ ### 2. The Transformation
43
+
44
+ The library executes this line during the snap process:
45
+
46
+ ```js
47
+ el._studs = reactive({ ...mergedLogic }, el);
48
+
49
+ ```
50
+
51
+ - **The `reactive` call**: This wraps the merged object in the **Proxy** we discussed in Topic 4.
52
+
53
+ - **The `el` argument**: Crucially, the proxy is given a reference to the DOM element (`el`). This allows the Proxy's `set` trap to know exactly which component needs to be added to the `globalBatcher` when a property changes.
54
+
55
+
56
+ ### 3. Contextual Binding (The `this` Keyword)
57
+
58
+ After the `_studs` proxy is created, the library ensures that your methods work correctly. When you define a method in your SFC, you expect `this` to point to your data.
59
+
60
+ - Inside `snap()`, lifecycle hooks are called like this: `el._studs.mounted.call(el._studs)`.
61
+
62
+ - By using `.call(el._studs)`, the library forces the execution context of your functions to be the reactive proxy.
63
+
64
+ - **Result**: When you write `this.count++` in your code, you are actually interacting with the **Proxy**, which triggers the `set` trap, which notifies the **Batcher**, which triggers the **Render**.
65
+
66
+
67
+ ### 4. Visibility vs. Privacy
68
+
69
+ - **`_studs`**: This is attached directly to the DOM element. You can actually type `document.querySelector('my-component')._studs` in your browser console to see and even modify the live state of any component.
70
+
71
+ - **`privateData`**: Unlike `_studs`, the internal "housekeeping" (like whether the component has already snapped) is kept in a `WeakMap` called `privateData`, making it inaccessible to the outside world.
72
+
73
+
74
+ ----------
75
+
76
+ **Summary**: `_studs` is the reactive engine of your component. It is created by merging all data tiers into a Proxy that is uniquely linked to that specific DOM element.
@@ -0,0 +1,104 @@
1
+ # Scanner
2
+
3
+ Now let's get into the "eyes" of the rendering engine. For LegoDOM to be able to update the DOM efficiently, it needs to know which specific parts of your HTML are static (never change) and which parts are dynamic (contain {{mustaches}} or b- directives).
4
+
5
+
6
+ ## Scanning for Bindings (The `TreeWalker`)
7
+
8
+ In `main.js`, the `scanForBindings` function is called the very first time a component renders. Instead of re-scanning the DOM every time a variable changes, Lego scans **once**, creates a "map" of all the dynamic spots, and saves that map in the element's `privateData`.
9
+
10
+ ```js
11
+ const scanForBindings = (container) => {
12
+ const bindings = [];
13
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
14
+ let node;
15
+ while (node = walker.nextNode()) {
16
+ const isInsideBFor = (n) => {
17
+ let curr = n.parentNode;
18
+ while (curr && curr !== container) {
19
+ if (curr.hasAttribute && curr.hasAttribute('b-for')) return true;
20
+ if (curr.tagName && curr.tagName.includes('-') && registry[curr.tagName.toLowerCase()]) return true;
21
+ curr = curr.parentNode;
22
+ }
23
+ return false;
24
+ };
25
+ if (isInsideBFor(node)) continue;
26
+
27
+ if (node.nodeType === Node.ELEMENT_NODE) {
28
+ if (node.hasAttribute('b-show')) bindings.push({ type: 'b-show', node, expr: node.getAttribute('b-show') });
29
+ if (node.hasAttribute('b-for')) {
30
+ const match = node.getAttribute('b-for').match(/^\s*(\w+)\s+in\s+(.+)\s*$/);
31
+ if (match) {
32
+ bindings.push({
33
+ type: 'b-for',
34
+ node,
35
+ itemName: match[1],
36
+ listName: match[2].trim(),
37
+ template: node.innerHTML
38
+ });
39
+ node.innerHTML = '';
40
+ }
41
+ }
42
+ if (node.hasAttribute('b-text')) bindings.push({ type: 'b-text', node, path: node.getAttribute('b-text') });
43
+ if (node.hasAttribute('b-sync')) bindings.push({ type: 'b-sync', node });
44
+ [...node.attributes].forEach(attr => {
45
+ if (attr.value.includes('{{')) bindings.push({ type: 'attr', node, attrName: attr.name, template: attr.value });
46
+ });
47
+ } else if (node.nodeType === Node.TEXT_NODE && node.textContent.includes('{{')) {
48
+ bindings.push({ type: 'text', node, template: node.textContent });
49
+ }
50
+ }
51
+ return bindings;
52
+ };
53
+ ```
54
+
55
+ ### 1. The `TreeWalker` Efficiency
56
+
57
+ LegoDOM uses a native browser tool called a `TreeWalker`.
58
+
59
+ ```js
60
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
61
+
62
+ ```
63
+
64
+ - **Why not `querySelectorAll('*')`?** A `TreeWalker` is much faster and more memory-efficient. It allows the library to step through every single node (including text nodes) one by one.
65
+
66
+
67
+ ### 2. The "Nested Component" Shield
68
+
69
+ One of the smartest parts of this function is the `isInsideBFor` check.
70
+
71
+ - If the scanner finds an element that is inside a `b-for` loop or belongs to a **different** custom component (i.e. user-profile, product-card etc.), it stops.
72
+
73
+ - **Reasoning**: You don't want the parent component to try and manage the internal text of a child component. This maintains **Encapsulation**.
74
+
75
+
76
+ ### 3. What it looks for
77
+
78
+ As it walks the tree, it populates a `bindings` array with "Instruction Objects":
79
+
80
+ - **Directives**: It looks for `b-show`, `b-text`, `b-sync`, and `b-for` attributes.
81
+
82
+ - **Mustaches in Text**: It looks for text nodes containing <code v-pre>{{ }}</code>. It saves the original template (e.g., `"Hello {{name}}"`) so it can swap the name later without losing the "Hello".
83
+
84
+ - **Mustaches in Attributes**: It scans every attribute (like <code v-pre>class="btn {{color}}"</code>) to see if it needs to be dynamic.
85
+
86
+
87
+ ### 4. The "Instruction Object" Structure
88
+
89
+ When it finds something, it pushes an object like this into the list:
90
+
91
+ ```js
92
+ {
93
+ type: 'text',
94
+ node: [The actual TextNode],
95
+ template: 'Hello {{user.name}}'
96
+ }
97
+
98
+ ```
99
+
100
+ By storing the **actual Node reference**, the library can update the screen later with surgical precision. It doesn't have to search the DOM again; it just goes straight to that specific memory address and updates the value.
101
+
102
+ ----------
103
+
104
+ **Summary**: `scanForBindings` is a one-time "reconnaissance mission." it maps out every dynamic part of your component so that future updates are lightning-fast.
@@ -0,0 +1,116 @@
1
+ # Paint Me HTML
2
+
3
+ In Topic 11, we mapped out where the dynamic "holes" in your HTML are. Now, we look at the engine that actually fills them with data. The `render()` function is the most frequently called piece of code in LegoDOM, it is the bridge between JavaScript state and the pixels on the screen.
4
+
5
+
6
+ ## Rendering `render()` Engine
7
+
8
+ The `render(el)` function doesn't refresh the whole component. Instead, it iterates through the "Instruction Objects" (bindings) created during the scanning phase and updates only what is necessary.
9
+
10
+ ```js
11
+ const render = (el) => {
12
+ const state = el._studs;
13
+ if (!state) return;
14
+ const data = getPrivateData(el);
15
+ if (data.rendering) return;
16
+ data.rendering = true;
17
+
18
+ try {
19
+ const shadow = el.shadowRoot;
20
+ if (!shadow) return;
21
+ if (!data.bindings) data.bindings = scanForBindings(shadow);
22
+
23
+ data.bindings.forEach(b => {
24
+ if (b.type === 'b-show') b.node.style.display = safeEval(b.expr, { state, self: b.node }) ? '' : 'none';
25
+ if (b.type === 'b-text') b.node.textContent = escapeHTML(resolve(b.path, state));
26
+ if (b.type === 'b-sync') syncModelValue(b.node, resolve(b.node.getAttribute('b-sync'), state));
27
+ if (b.type === 'text') {
28
+ const out = b.template.replace(/{{(.*?)}}/g, (_, k) => escapeHTML(safeEval(k.trim(), { state, self: b.node }) ?? ''));
29
+ if (b.node.textContent !== out) b.node.textContent = out;
30
+ }
31
+ if (b.type === 'attr') {
32
+ const out = b.template.replace(/{{(.*?)}}/g, (_, k) => escapeHTML(safeEval(k.trim(), { state, self: b.node }) ?? ''));
33
+ if (b.node.getAttribute(b.attrName) !== out) {
34
+ b.node.setAttribute(b.attrName, out);
35
+ if (b.attrName === 'class') b.node.className = out;
36
+ }
37
+ }
38
+ if (b.type === 'b-for') {
39
+ const list = safeEval(b.listName, { state, global: Lego.globals, self: el }) || [];
40
+ if (!forPools.has(b.node)) forPools.set(b.node, new Map());
41
+ const pool = forPools.get(b.node);
42
+ const currentKeys = new Set();
43
+ list.forEach((item, i) => {
44
+ const key = (item && typeof item === 'object') ? (item.__id || (item.__id = Math.random())) : `${i}-${item}`;
45
+ currentKeys.add(key);
46
+ let child = pool.get(key);
47
+ if (!child) {
48
+ const temp = document.createElement('div');
49
+ temp.innerHTML = b.template;
50
+ child = temp.firstElementChild;
51
+ pool.set(key, child);
52
+ bind(child, el, { name: b.itemName, listName: b.listName, index: i });
53
+ }
54
+ const localScope = Object.assign(Object.create(state), { [b.itemName]: item });
55
+ updateNodeBindings(child, localScope);
56
+
57
+ child.querySelectorAll('[b-sync]').forEach(input => {
58
+ const path = input.getAttribute('b-sync');
59
+ if (path.startsWith(b.itemName + '.')) {
60
+ const list = safeEval(b.listName, { state, global: Lego.globals, self: el });
61
+ syncModelValue(input, resolve(path.split('.').slice(1).join('.'), list[i]));
62
+ }
63
+ });
64
+ if (b.node.children[i] !== child) b.node.insertBefore(child, b.node.children[i] || null);
65
+ });
66
+ for (const [key, node] of pool.entries()) {
67
+ if (!currentKeys.has(key)) { node.remove(); pool.delete(key); }
68
+ }
69
+ }
70
+ });
71
+ } finally {
72
+ data.rendering = false;
73
+ }
74
+ };
75
+ ```
76
+
77
+ ### 1. The Guard Rails
78
+
79
+ Before doing any work, `render` checks two things:
80
+
81
+ - **The State**: It ensures `el._studs` exists.
82
+
83
+ - **The Recursion Lock**: It sets `data.rendering = true` at the start and `false` at the end. This prevents a "Render Loop" where an update triggers a render, which accidentally triggers another update.
84
+
85
+
86
+ ### 2. Surgical Execution
87
+
88
+ The function loops through `data.bindings` and performs specific actions based on the `type`:
89
+
90
+ - **`b-show`**: It evaluates the expression. If false, it sets `display: none`. This is a "CSS-based" conditional; the element stays in the DOM but becomes invisible. **Might change to `remove()` in the future**
91
+
92
+ - **`b-text`**: It uses the `resolve()` helper to find the value in your state and sets the `textContent`.
93
+
94
+ - **`text` (Mustaches)**: It takes the original template (e.g., `Count: {{count}}`), replaces the mustache with the actual value, and updates the text node.
95
+
96
+ - **`attr`**: It updates attributes like `src`, `href`, or `class`. It even has a special check: if the attribute is `class`, it also updates `node.className` to ensure the browser applies the styles correctly.
97
+
98
+
99
+ ### 3. The `safeEval` Bridge
100
+
101
+ You’ll notice that for things like `b-show` or <code v-pre>{{mustaches}}</code>, the library calls `safeEval(expr, { state, self: b.node })`.
102
+
103
+ - This allows your HTML to handle more than just simple variables.
104
+
105
+ - You can write logic directly in your template, like <code v-pre>{{ count > 10 ? 'Big' : 'Small' }}</code>.
106
+
107
+ - `render` passes the current state as the "context," so those expressions know exactly what `count` refers to.
108
+
109
+
110
+ ### 4. Directives vs. Mustache Priority
111
+
112
+ `render` processes directives (like `b-show` and `b-text`) and mustaches in the same loop. However, because it works with direct DOM references saved in the `bindings` array, it never has to "re-parse" the HTML string. It simply touches the specific property (like `.value` or `.textContent`) of the existing DOM node.
113
+
114
+ ----------
115
+
116
+ **Summary**: `render()` is a "Loop of Truth." It walks through the map created by the scanner, evaluates the current state of your data, and applies those values to the specific DOM nodes that need them.