lego-dom 1.3.4 → 1.5.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 (93) hide show
  1. package/CHANGELOG.md +72 -1
  2. package/main.js +48 -17
  3. package/main.min.js +2 -2
  4. package/package.json +1 -1
  5. package/parse-lego.js +2 -2
  6. package/vite-plugin.js +0 -14
  7. package/.github/workflows/deploy-docs.yml +0 -56
  8. package/.legodom +0 -87
  9. package/docs/.vitepress/config.js +0 -161
  10. package/docs/api/config.md +0 -95
  11. package/docs/api/define.md +0 -58
  12. package/docs/api/directives.md +0 -50
  13. package/docs/api/globals.md +0 -29
  14. package/docs/api/index.md +0 -30
  15. package/docs/api/lifecycle.md +0 -40
  16. package/docs/api/route.md +0 -37
  17. package/docs/api/vite-plugin.md +0 -58
  18. package/docs/contributing/01-welcome.md +0 -38
  19. package/docs/contributing/02-registry.md +0 -133
  20. package/docs/contributing/03-batcher.md +0 -110
  21. package/docs/contributing/04-reactivity.md +0 -87
  22. package/docs/contributing/05-caching.md +0 -59
  23. package/docs/contributing/06-init.md +0 -136
  24. package/docs/contributing/07-observer.md +0 -72
  25. package/docs/contributing/08-snap.md +0 -140
  26. package/docs/contributing/09-diffing.md +0 -69
  27. package/docs/contributing/10-studs.md +0 -78
  28. package/docs/contributing/11-scanner.md +0 -117
  29. package/docs/contributing/12-render.md +0 -138
  30. package/docs/contributing/13-directives.md +0 -243
  31. package/docs/contributing/14-events.md +0 -57
  32. package/docs/contributing/15-router.md +0 -57
  33. package/docs/contributing/16-state.md +0 -47
  34. package/docs/contributing/17-legodom.md +0 -48
  35. package/docs/contributing/index.md +0 -24
  36. package/docs/examples/form.md +0 -42
  37. package/docs/examples/index.md +0 -104
  38. package/docs/examples/routing.md +0 -409
  39. package/docs/examples/sfc-showcase.md +0 -34
  40. package/docs/examples/todo-app.md +0 -383
  41. package/docs/guide/cdn-usage.md +0 -354
  42. package/docs/guide/components.md +0 -418
  43. package/docs/guide/directives.md +0 -539
  44. package/docs/guide/directory-structure.md +0 -248
  45. package/docs/guide/faq.md +0 -210
  46. package/docs/guide/getting-started.md +0 -262
  47. package/docs/guide/index.md +0 -88
  48. package/docs/guide/lifecycle.md +0 -525
  49. package/docs/guide/quick-start.md +0 -49
  50. package/docs/guide/reactivity.md +0 -415
  51. package/docs/guide/routing.md +0 -334
  52. package/docs/guide/server-side.md +0 -134
  53. package/docs/guide/sfc.md +0 -464
  54. package/docs/guide/templating.md +0 -388
  55. package/docs/index.md +0 -160
  56. package/docs/public/logo.svg +0 -17
  57. package/docs/router/basic-routing.md +0 -103
  58. package/docs/router/cold-entry.md +0 -91
  59. package/docs/router/history.md +0 -69
  60. package/docs/router/index.md +0 -73
  61. package/docs/router/resolver.md +0 -74
  62. package/docs/router/surgical-swaps.md +0 -134
  63. package/docs/tutorial/01-project-setup.md +0 -152
  64. package/docs/tutorial/02-your-first-component.md +0 -226
  65. package/docs/tutorial/03-adding-routes.md +0 -279
  66. package/docs/tutorial/04-multi-page-app.md +0 -329
  67. package/docs/tutorial/05-state-and-globals.md +0 -285
  68. package/docs/tutorial/index.md +0 -40
  69. package/examples/vite-app/README.md +0 -71
  70. package/examples/vite-app/index.html +0 -42
  71. package/examples/vite-app/package.json +0 -18
  72. package/examples/vite-app/src/app.css +0 -3
  73. package/examples/vite-app/src/app.js +0 -29
  74. package/examples/vite-app/src/components/app-navbar.lego +0 -34
  75. package/examples/vite-app/src/components/customers/customer-details.lego +0 -24
  76. package/examples/vite-app/src/components/customers/customer-orders.lego +0 -21
  77. package/examples/vite-app/src/components/customers/order-list.lego +0 -55
  78. package/examples/vite-app/src/components/greeting-card.lego +0 -41
  79. package/examples/vite-app/src/components/sample-component.lego +0 -75
  80. package/examples/vite-app/src/components/shells/customers-shell.lego +0 -21
  81. package/examples/vite-app/src/components/side-menu.lego +0 -46
  82. package/examples/vite-app/src/components/todo-list.lego +0 -239
  83. package/examples/vite-app/src/components/widgets/user-card.lego +0 -27
  84. package/examples/vite-app/vite.config.js +0 -22
  85. package/tests/error.test.js +0 -74
  86. package/tests/main.test.js +0 -103
  87. package/tests/memory.test.js +0 -68
  88. package/tests/monitoring.test.js +0 -74
  89. package/tests/naming.test.js +0 -74
  90. package/tests/parse-lego.test.js +0 -65
  91. package/tests/security.test.js +0 -67
  92. package/tests/server.test.js +0 -114
  93. package/tests/syntax.test.js +0 -67
@@ -1,334 +0,0 @@
1
- # Surgical Routing
2
-
3
- **Stop rebuilding your entire page just to change one div.**
4
-
5
- LegoDOM's router is different. It doesn't have a single "Root Outlet". Instead, **any element** can be a router target. This allows you to build **Persistent Layouts** (like Sidebars, Music Players, or Chat Windows) that never reload or lose state while the user navigates.
6
-
7
- ::: tip 🚀 Just Want to Navigate Between Pages?
8
- Here's the quick answer:
9
-
10
- **1. Define routes in `app.js`:**
11
- ```javascript
12
- Lego.route('/', 'home-page');
13
- Lego.route('/login', 'login-page');
14
- Lego.route('/dashboard', 'dashboard-page');
15
- ```
16
-
17
- **2. Navigate with links:**
18
- ```html
19
- <a href="/login" b-link>Go to Login</a>
20
- ```
21
-
22
- **3. Or navigate with JavaScript:**
23
- ```javascript
24
- this.$go('/login').get();
25
- ```
26
-
27
- That's it! For the full tutorial, see [Adding Routes](/tutorial/03-adding-routes).
28
- :::
29
-
30
- ## Quick Reference
31
-
32
- | I want to... | Code |
33
- |--------------|------|
34
- | Define a route | `Lego.route('/path', 'component-name')` |
35
- | Link to a page | `<a href="/path" b-link>Click</a>` |
36
- | Navigate via JS | `this.$go('/path').get()` |
37
- | Get URL params | `this.$route.params.id` |
38
- | Update only one div | `<a href="/x" b-target="#myDiv">` |
39
- | Navigate without URL change | `this.$go('/x').get(false)` |
40
-
41
- ---
42
-
43
- ## The Architecture: "The Persistent Shell"
44
-
45
- The best way to use LegoDOM is to define a static "Shell" that holds your persistent tools, and standard outlets for your content.
46
-
47
- ```html
48
- <body>
49
- <!-- 1. The Shell (Sidebar): Never reloads. Keeps scroll pos & draft state. -->
50
- <aside id="sidebar">
51
- <file-tree></file-tree>
52
- </aside>
53
-
54
- <!-- 2. The Stage (Main Content): This changes when URL changes. -->
55
- <lego-router id="stage"></lego-router>
56
-
57
- <!-- 3. The Context (Right Panel): Tools based on selection. -->
58
- <aside id="tools"></aside>
59
- </body>
60
- ```
61
-
62
- Then, you simply tell links *where* to render their content:
63
-
64
- ```html
65
- <!-- Updates component in #stage (Default URL navigation) -->
66
- <a href="/dashboard" b-target="#stage">Dashboard</a>
67
-
68
- <!-- Updates component in #tools (Keeps URL sync, but only touches right panel) -->
69
- <a href="/tools/settings" b-target="#tools">Settings</a>
70
- ```
71
-
72
- This feels like a native app. The Sidebar doesn't flicker. The scroll position isn't lost.
73
-
74
- ---
75
-
76
- ## 1. Declarative Routing
77
-
78
- The most common way to route is using standard `<a>` tags enriched with Lego attributes.
79
-
80
- ### `b-target`
81
- Specifies the CSS selector of the element to replace.
82
-
83
- ```html
84
- <!-- Swaps content into <div id="main-content"> -->
85
- <a href="/profile" b-target="#main-content">Go to Profile</a>
86
-
87
- <!-- Example of using route params in a template -->
88
- <main>
89
- <blog-posts b-show="$route.params.section === 'posts'"></blog-posts>
90
- <blog-authors b-show="$route.params.section === 'authors'"></blog-authors>
91
- </main>
92
- ```
93
-
94
- ### `b-link`
95
- Controls browser history behavior.
96
- - `b-link` (or just `b-target`): Defaults to `true` (updates URL, pushes history).
97
- - `b-link="false"`: Does **not** update the URL. Great for tabs, modals, or side-panels.
98
-
99
- ```html
100
- <!-- Updates URL to /settings, swaps #main -->
101
- <a href="/settings" b-target="#main">Settings</a>
102
-
103
- <!-- Keeps URL same, just swaps the sidebar context -->
104
- <a href="/sidebar/tools" b-target="#sidebar" b-link="false">Open Tools</a>
105
- ```
106
-
107
- ### Deep Linking & Defaults
108
- If a user refreshes the page, surgical targets (like `#sidebar`) usually won't have content because the `b-target` click never happened.
109
-
110
- **The Golden Rule:** Always have a `<lego-router>` as your default "Main" outlet.
111
- When the page loads, Lego looks for `<lego-router>` to render the URL's matching component.
112
-
113
- ```html
114
- <body>
115
- <nav>...</nav>
116
-
117
- <!-- Default Outlet: Renders /home, /about, etc. -->
118
- <lego-router id="main-app"></lego-router>
119
-
120
- <!-- Surgical Outlet: Only updated when specifically targeted -->
121
- <aside id="sidebar"></aside>
122
- </body>
123
- ```
124
-
125
- ## 2. The `$go` API
126
-
127
- For full programmatic control, use the globally available `$go` helper. It allows for surgical updates from your JavaScript logic.
128
-
129
- ### Syntax
130
- `Lego.globals.$go(path, ...targets)`
131
-
132
- - **path**: The URL to navigate to (e.g., `/user/1`).
133
- - **targets**: A list of selectors (e.g., `#main`, `#sidebar`). Passing nothing defaults to `lego-router`.
134
-
135
- ### Methods
136
- The `$go` function returns an object with HTTP verb methods, primarily only `.get()` is relevant for routing, but others exist for consistency.
137
-
138
- ```javascript
139
- // 1. Standard Navigation (pushes to history)
140
- Lego.globals.$go('/profile').get();
141
-
142
- // 2. Surgical Navigation (updates #sidebar, pushes to history)
143
- Lego.globals.$go('/widgets/clock', '#sidebar').get();
144
-
145
- // 3. Silent Update (updates #modal, NO history change)
146
- // Pass `false` as the first argument to .get()
147
- Lego.globals.$go('/modals/login', '#modal').get(false);
148
- ```
149
-
150
- ### Interactive Example: "The Shell"
151
- You can update **multiple** targets at once (future feature) or chain them.
152
- Commonly, you use `$go` inside your component logic:
153
-
154
- ```html
155
- <script>
156
- export default {
157
- methods: {
158
- async loadUser() {
159
- const userId = Lego.globals.$route.params.id; // Access in JS logic
160
- const user = await fetch(`/api/users/${userId}`).then(r => r.json());
161
- this.username = user.name;
162
- },
163
- openSettings() {
164
- // Open settings in the sidebar without losing the main page context
165
- this.global.$go('/settings-panel', '#sidebar').get(false);
166
- }
167
- }
168
- }
169
- </script>
170
- ```
171
-
172
- ---
173
-
174
- ## 3. Advanced Patterns
175
-
176
- ### The "Sidebar" Pattern
177
- Keep a persistent Main Content while swapping sidebars.
178
-
179
- ```html
180
- <nav>
181
- <!-- Main Nav: Updates URL and main view -->
182
- <a href="/dashboard" b-target="#main">Dashboard</a>
183
- <a href="/files" b-target="#main">Files</a>
184
- </nav>
185
-
186
- <main id="main">
187
- <!-- Dashboard or Files render here -->
188
- </main>
189
-
190
- <aside id="context-pane">
191
- <!-- Context specific tools render here -->
192
- <template b-id="user-profile">
193
- <h1>User Profile</h1>
194
- <p>User ID: [[ $route.params.id ]]</p>
195
- <button @click="loadUser()">Load User</button>
196
- </template>
197
- </aside>
198
-
199
- <!-- Inside Dashboard Component -->
200
- <button onclick="Lego.globals.$go('/tools/chart-config', '#context-pane').get(false)">
201
- Configure Chart
202
- </button>
203
- ```
204
-
205
- ### The "Modal" Pattern
206
- Render a route into a modal dialog container.
207
-
208
- ```html
209
- <dialog id="modal-container"></dialog>
210
-
211
- <a href="/login" b-target="#modal-container"
212
- onclick="document.getElementById('modal-container').showModal()">
213
- Login
214
- </a>
215
- ```
216
-
217
- ### The "Persistent Layout" Pattern (The Holy Grail)
218
- This is where LegoDOM outshines traditional routers. You can have static sidebars that **never** reload, while the center content changes dynamically.
219
-
220
- ```html
221
- <body>
222
- <!-- LEFT: Never reloads. Keeps scroll position & expanded folders. -->
223
- <aside id="static-left">
224
- <file-tree></file-tree>
225
- </aside>
226
-
227
- <!-- CENTER: The main router outlet -->
228
- <lego-router id="main-content"></lego-router>
229
-
230
- <!-- RIGHT: Context panel for tools/details -->
231
- <aside id="static-right"></aside>
232
- </body>
233
- ```
234
- * **Main Links:** `<a href="/page" b-target="#main-content">`
235
- * **Tool Links:** `<a href="/tool" b-target="#static-right">`
236
-
237
- ---
238
-
239
- ## 4. Deep Routing Strategies
240
-
241
- When handling deep routes like `/customers/:id/orders/:orderId`, you have two architectural choices.
242
-
243
- ### Option A: The Shell Strategy (Self-Healing)
244
- Map everything to a single "Shell" component. The Shell determines what to show in its sub-outlets based on the URL params.
245
-
246
- * **Pros:** Highly surgical. The Shell never re-renders, only its children do.
247
- * **Cons:** Requires logic in `mounted()` to "heal" the state on page load.
248
-
249
- ```javascript
250
- // Route Configuration
251
- Lego.route('/customers/:id', 'customers-shell');
252
- Lego.route('/customers/:id/orders/:orderId', 'customers-shell');
253
-
254
- // Component Logic (Self-Healing)
255
- mounted() {
256
- if (this.$route.params.orderId) {
257
- this.$go(window.location.pathname, '#details-pane').get();
258
- }
259
- }
260
- ```
261
-
262
- ### Option B: The Page Strategy (Component Nesting)
263
- Map deep routes to specific "Page" components. Each page imports and wraps itself in a shared Layout.
264
-
265
- * **Pros:** Simpler logic. No "healing" code required.
266
- * **Cons:** The Layout is technically re-created on every route change (though diffing makes it cheap).
267
-
268
- ```javascript
269
- // Route Configuration
270
- Lego.route('/customers/:id/orders/:orderId', 'order-details-page');
271
- ```
272
-
273
- ```html
274
- <!-- order-details-page.lego -->
275
- <template>
276
- <customers-layout>
277
- <order-info id="[[ $route.params.orderId ]]"></order-info>
278
- </customers-layout>
279
- </template>
280
- ```
281
-
282
- ---
283
-
284
- ## 5. Middleware & Guards
285
-
286
- Middleware runs **before** the surgical swap happens. It### Accessing Parameters
287
- Route parameters are available directly via `$route.params` in templates.
288
-
289
- > **Note:** `$route` is a global helper available in all templates.
290
-
291
- ```javascript
292
- /*
293
- * Middleware Signature:
294
- * (params: Object, globals: Object) => boolean | Promise<boolean>
295
- * Return `true` to allow navigation, `false` to block.
296
- */
297
-
298
- // Example: Auth Guard
299
- const requireAuth = (params, globals) => {
300
- if (!globals.user) {
301
- // Redirect to login using surgical routing!
302
- globals.$go('/login', '#main').get();
303
- return false; // Stop original navigation
304
- }
305
- return true;
306
- };
307
-
308
- Lego.route('/admin', 'admin-panel', requireAuth);
309
- ```
310
-
311
- ## 5. Smart History
312
-
313
- Lego's router is "History Aware".
314
- When you use `b-target`, Lego stores the target selectors in the browser's History State.
315
-
316
- **What this means:**
317
- 1. You click "Open Sidebar" (Surgical update to `#sidebar`).
318
- 2. You click "Home" (Main update to `#main`).
319
- 3. You click **Back**.
320
- 4. Lego automatically knows to reverse the "Home" navigation.
321
- 5. You click **Back** again.
322
- 6. Lego knows the previous state was a surgical update to `#sidebar` and restores it correctly!
323
-
324
- ## Summary Table
325
-
326
- | Feature | Code | Description |
327
- | :--- | :--- | :--- |
328
- | **Standard Link** | `<a href="/x">` | Standard browser navigation (full reload). |
329
- | **SPA Link** | `<a href="/x" b-target>` | Default SPA nav. Swaps `<lego-router>`. |
330
- | **Surgical Link** | `<a href="/x" b-target="#id">` | Swaps content of `#id`. Updates URL. |
331
- | **Silent Link** | `... b-link="false">` | Swaps content. **No** URL update. |
332
- | **JS Nav** | `$go('/x').get()` | Programmatic navigation. |
333
- | **Silent JS** | `$go('/x').get(false)` | Programmatic silent swap. |
334
-
@@ -1,134 +0,0 @@
1
- # Server-Side Architecture
2
-
3
- LegoDOM is designed to play nicely with backend frameworks like **Heaven**, **Django**, **Rails**, or **Go**.
4
-
5
- Unlike typical SPAs that require a massive build step, LegoDOM can fetch components **on-demand** from your server. This gives you the routing simplicity of a backend with the interactivity of a frontend framework.
6
-
7
- ## The Auto-Loader Pattern
8
-
9
- Instead of bundling every component `users-list`, `chat-widget`, `billing-modal` into one big `app.js` file, you can load them lazily.
10
-
11
- Configure the `loader` hook in your main entry file:
12
-
13
- ```javascript
14
- Lego.init(document.body, {
15
- loader: (tagName) => `/components/${tagName}.lego`
16
- });
17
- ```
18
-
19
- Now, your HTML can just use tags that haven't been defined yet:
20
-
21
- ```html
22
- <!-- index.html (Server Rendered) -->
23
- <h1>Dashboard</h1>
24
-
25
- <!-- LegoDOM sees this, fetches /components/user-feed.lego, and upgrades it -->
26
- <user-feed></user-feed>
27
- ```
28
-
29
- ## Power Mode: Authentication & State
30
-
31
- Often you need to pass **Authentication Tokens** or **Global State** to the server to get a personalized component.
32
-
33
- Return a `Promise` from your loader to take full control of the fetch:
34
-
35
- ```javascript
36
- Lego.init(document.body, {
37
- loader: async (tagName) => {
38
- const token = localStorage.getItem('jwt');
39
-
40
- const response = await fetch(`/components/${tagName}`, {
41
- headers: {
42
- 'Authorization': `Bearer ${token}`,
43
- 'X-Theme': Lego.globals.theme
44
- }
45
- });
46
-
47
- if (!response.ok) return null;
48
- return await response.text(); // Return the SFC content
49
- }
50
- });
51
- ```
52
-
53
- ## Server-Side State Injection
54
-
55
- Since the server generates the `.lego` file, it can inject data before sending it to the browser.
56
-
57
- **Example (Backend Pseudocode):**
58
-
59
- ```python
60
- # GET /components/user-card ("Flask-like" syntax)
61
- @app.route('/components/user-card')
62
- def get_user_card():
63
- user = db.get_current_user()
64
-
65
- # We bake the data right into the template!
66
- return f"""
67
- <template>
68
- <div class="card">
69
- <h2>[[ name ]]</h2>
70
- <p>Balance: $[[ balance ]]</p>
71
- </div>
72
- </template>
73
-
74
- <script>
75
- export default {
76
- name: "{user.name}",
77
- balance: {user.balance}
78
- }
79
- </script>
80
- """
81
- ```
82
-
83
- The browser receives a component that **already has the data**. No second API call needed!
84
-
85
- ## Runtime Components
86
-
87
- If you fetch component code manually (e.g. via WebSockets or a custom pipeline), you can register it using `Lego.defineSFC`:
88
-
89
- ```javascript
90
- socket.on('component_update', (msg) => {
91
- // msg.code contains the <template>... string
92
- Lego.defineSFC(msg.code, msg.name + '.lego');
93
- });
94
- ```
95
- ```
96
-
97
- ## Production Considerations
98
-
99
- ### 1. Error Handling
100
- What if the server returns a 404 or 500? Your `loader` should handle this gracefully so the user isn't stuck with a blank screen.
101
-
102
- ```javascript
103
- loader: async (tagName) => {
104
- try {
105
- const res = await fetch(`/components/${tagName}.lego`);
106
- if (!res.ok) {
107
- console.error(`Component ${tagName} failed: ${res.status}`);
108
- // Fallback to a generic error component
109
- return `<template><div class="error">Failed to load ${tagName}</div></template>`;
110
- }
111
- return await res.text();
112
- } catch (err) {
113
- return `<template><div class="error">Network Error</div></template>`;
114
- }
115
- }
116
- ```
117
-
118
- ### 2. Caching Strategy
119
- LegoDOM caches the *compiled class* of a component once loaded. It does **not** re-fetch the `.lego` file for every instance.
120
- However, if you want browser-level caching for the HTTP requests, ensure your server sends correct headers:
121
-
122
- ```http
123
- Cache-Control: public, max-age=3600, immutable
124
- ```
125
-
126
- ### 3. Preloading
127
- If you know a user is about to visit a page (e.g. hovering a link), you can preload the component SFC:
128
-
129
- ```javascript
130
- const preload = (tagName) => {
131
- // Just calling the loader puts it in the browser's fetch cache
132
- Lego.config.loader(tagName);
133
- };
134
- ```