lego-dom 0.0.5 โ 0.0.7
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.
- package/README.md +209 -388
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -1,523 +1,344 @@
|
|
|
1
|
-
#
|
|
1
|
+
# LegoJS
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
LegoJS is a tiny, zero-dependency JavaScript library for building reactive Web Components directly in the browser.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The goal of LegoJS is **mental model simplicity**:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
* No virtual DOM
|
|
8
|
+
* No compilation step required
|
|
9
|
+
* No JSX
|
|
10
|
+
* No framework-specific syntax to learn
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
You write **HTML**, add a few **directives**, and LegoJS takes care of reactivity and updates.
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
This README is intentionally designed so that a developer can understand **everything they need** about LegoJS by reading this file alone.
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
---
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
## Installation
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
## ๐ Quickstart
|
|
20
|
-
|
|
21
|
-
### ๐ฆ Installation
|
|
22
|
-
|
|
23
|
-
Drop the script and start building. No build step, no `npm install` fatigue.
|
|
20
|
+
The package name on npm is **`lego-dom`** (the name `legojs` was already taken).
|
|
24
21
|
|
|
22
|
+
```bash
|
|
23
|
+
npm install lego-dom
|
|
25
24
|
```
|
|
26
|
-
<script src="path/to/main.js"></script>
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### ๐ Creating your first Component
|
|
31
|
-
|
|
32
|
-
Define logic and layout in a standard `<template>`.
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
<!-- Define -->
|
|
36
|
-
<template b-id="greeting-card">
|
|
37
|
-
<style>
|
|
38
|
-
self { display: block; padding: 20px; border-radius: 8px; background: #f0f4f8; }
|
|
39
|
-
</style>
|
|
40
25
|
|
|
41
|
-
|
|
42
|
-
<button @click="showBio = !showBio">Toggle Bio</button>
|
|
43
|
-
<p b-if="showBio">{{ bio }}</p>
|
|
44
|
-
</template>
|
|
45
|
-
|
|
46
|
-
<!-- Execute -->
|
|
47
|
-
<greeting-card b-data="{ name: 'Alex', bio: 'Dev', showBio: false }"></greeting-card>
|
|
26
|
+
Or include it directly in the browser:
|
|
48
27
|
|
|
28
|
+
```html
|
|
29
|
+
<script src="node_modules/lego-dom/main.js"></script>
|
|
49
30
|
```
|
|
50
31
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Directive
|
|
54
|
-
|
|
55
|
-
Type
|
|
56
|
-
|
|
57
|
-
Description
|
|
58
|
-
|
|
59
|
-
Example
|
|
60
|
-
|
|
61
|
-
`b-id`
|
|
62
|
-
|
|
63
|
-
Definition
|
|
64
|
-
|
|
65
|
-
Registers the custom element tag name.
|
|
66
|
-
|
|
67
|
-
`<template b-id="my-tag">`
|
|
68
|
-
|
|
69
|
-
`b-data`
|
|
32
|
+
Once loaded, `Lego` is available globally.
|
|
70
33
|
|
|
71
|
-
|
|
34
|
+
---
|
|
72
35
|
|
|
73
|
-
|
|
36
|
+
## The Mental Model
|
|
74
37
|
|
|
75
|
-
|
|
38
|
+
Think of LegoJS like real Lego blocks:
|
|
76
39
|
|
|
77
|
-
|
|
40
|
+
* **Templates** define how a block looks
|
|
41
|
+
* **Studs** define the data attached to a block
|
|
42
|
+
* **Directives** snap data to the DOM
|
|
43
|
+
* **Changes to data automatically update the DOM**
|
|
78
44
|
|
|
79
|
-
|
|
45
|
+
There is no mounting, diffing, or reconciliation engine.
|
|
80
46
|
|
|
81
|
-
|
|
47
|
+
You change JavaScript objects โ LegoJS updates the DOM.
|
|
82
48
|
|
|
83
|
-
|
|
49
|
+
---
|
|
84
50
|
|
|
85
|
-
|
|
51
|
+
## Defining a Component (Block)
|
|
86
52
|
|
|
87
|
-
|
|
53
|
+
A component is defined using a standard HTML `<template>` with a `b-id`.
|
|
88
54
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
`<li b-for="u in users">{{ u.name }}</li>`
|
|
92
|
-
|
|
93
|
-
`b-if`
|
|
94
|
-
|
|
95
|
-
Structural
|
|
96
|
-
|
|
97
|
-
Toggles presence based on truthiness.
|
|
98
|
-
|
|
99
|
-
`<div b-if="isAdmin">Admin</div>`
|
|
100
|
-
|
|
101
|
-
`b-text`
|
|
102
|
-
|
|
103
|
-
Binding
|
|
104
|
-
|
|
105
|
-
Escaped text binding (one-way).
|
|
106
|
-
|
|
107
|
-
`<span b-text="title"></span>`
|
|
108
|
-
|
|
109
|
-
`@event`
|
|
110
|
-
|
|
111
|
-
Event
|
|
112
|
-
|
|
113
|
-
Standard DOM event listeners.
|
|
114
|
-
|
|
115
|
-
`<button @click="run()">`
|
|
116
|
-
|
|
117
|
-
## ๐ Advanced Example: Todo List
|
|
118
|
-
|
|
119
|
-
Demonstrating `b-for`, event handling, and two-way binding in a single block.
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
<template b-id="todo-app">
|
|
55
|
+
```html
|
|
56
|
+
<template b-id="hello-card">
|
|
123
57
|
<style>
|
|
124
|
-
|
|
125
|
-
|
|
58
|
+
self {
|
|
59
|
+
display: block;
|
|
60
|
+
padding: 1rem;
|
|
61
|
+
border: 1px solid #ccc;
|
|
62
|
+
}
|
|
126
63
|
</style>
|
|
127
64
|
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
<input b-sync="newTask" placeholder="Add task...">
|
|
131
|
-
<button @click="tasks.push({text: newTask, done: false}); newTask=''">Add</button>
|
|
132
|
-
|
|
133
|
-
<ul>
|
|
134
|
-
<li b-for="task in tasks">
|
|
135
|
-
<input type="checkbox" b-sync="task.done">
|
|
136
|
-
<span class="{{ task.done ? 'done' : '' }}">{{ task.text }}</span>
|
|
137
|
-
</li>
|
|
138
|
-
</ul>
|
|
65
|
+
<h2>Hello {{ name }}</h2>
|
|
66
|
+
<button @click="count++">Clicked {{ count }} times</button>
|
|
139
67
|
</template>
|
|
140
|
-
|
|
141
|
-
<script>
|
|
142
|
-
export default {
|
|
143
|
-
tasks: [{ text: 'Native Components', done: true }, { text: 'Profit', done: false }],
|
|
144
|
-
newTask: ''
|
|
145
|
-
}
|
|
146
|
-
</script>
|
|
147
|
-
|
|
148
68
|
```
|
|
149
69
|
|
|
150
|
-
|
|
70
|
+
Use the component in HTML:
|
|
151
71
|
|
|
72
|
+
```html
|
|
73
|
+
<hello-card b-data="{ name: 'Ahmed', count: 0 }"></hello-card>
|
|
74
|
+
```
|
|
152
75
|
|
|
153
|
-
|
|
76
|
+
---
|
|
154
77
|
|
|
155
|
-
|
|
78
|
+
## Reactive State (`studs`)
|
|
156
79
|
|
|
157
|
-
|
|
80
|
+
Each component has a reactive state object internally called **studs**.
|
|
158
81
|
|
|
159
|
-
|
|
82
|
+
* Defined via `b-data` or component logic
|
|
83
|
+
* Implemented using JavaScript `Proxy`
|
|
84
|
+
* Any mutation automatically schedules a re-render
|
|
160
85
|
|
|
86
|
+
```html
|
|
87
|
+
<button @click="count++"></button>
|
|
161
88
|
```
|
|
162
|
-
<!-- user-profile.lego -->
|
|
163
|
-
<template>
|
|
164
|
-
<div class="profile-card">
|
|
165
|
-
<img src="{{ avatar }}" alt="{{ name }}">
|
|
166
|
-
<h2>{{ name }}</h2>
|
|
167
|
-
<p>{{ bio }}</p>
|
|
168
|
-
<button @click="poke">Poke {{ name }}</button>
|
|
169
|
-
</div>
|
|
170
|
-
</template>
|
|
171
89
|
|
|
172
|
-
|
|
173
|
-
self {
|
|
174
|
-
display: block;
|
|
175
|
-
border: 1px solid #ddd;
|
|
176
|
-
border-radius: 8px;
|
|
177
|
-
padding: 1rem;
|
|
178
|
-
text-align: center;
|
|
179
|
-
}
|
|
180
|
-
img {
|
|
181
|
-
width: 80px;
|
|
182
|
-
height: 80px;
|
|
183
|
-
border-radius: 50%;
|
|
184
|
-
}
|
|
185
|
-
h2 { color: #333; }
|
|
186
|
-
</style>
|
|
187
|
-
|
|
188
|
-
<script>
|
|
189
|
-
export default {
|
|
190
|
-
// Initial data state
|
|
191
|
-
avatar: 'default.png',
|
|
192
|
-
name: 'Anonymous',
|
|
193
|
-
bio: 'No bio provided.',
|
|
194
|
-
|
|
195
|
-
// Logic methods
|
|
196
|
-
poke() {
|
|
197
|
-
console.log(`${this.name} was poked!`);
|
|
198
|
-
this.$emit('poked', { name: this.name });
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
// Lifecycle hooks
|
|
202
|
-
mounted() {
|
|
203
|
-
console.log('Profile component is now in the DOM');
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
</script>
|
|
90
|
+
No setters. No actions. No reducers.
|
|
207
91
|
|
|
208
|
-
|
|
92
|
+
Just mutate data.
|
|
209
93
|
|
|
210
|
-
|
|
94
|
+
---
|
|
211
95
|
|
|
212
|
-
|
|
96
|
+
## Templating (`{{ }}`)
|
|
213
97
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
- **No Leakage**: Styles defined here will not leak out to the parent page, and global styles (except CSS variables) will not leak into the component.
|
|
217
|
-
|
|
98
|
+
Text interpolation works in:
|
|
218
99
|
|
|
219
|
-
|
|
100
|
+
* Text nodes
|
|
101
|
+
* Attributes
|
|
102
|
+
* Class names
|
|
220
103
|
|
|
221
|
-
|
|
104
|
+
```html
|
|
105
|
+
<p>Hello {{ user.name }}</p>
|
|
106
|
+
<img src="/avatars/{{ user.id }}.png">
|
|
107
|
+
```
|
|
222
108
|
|
|
223
|
-
|
|
109
|
+
Expressions are plain JavaScript.
|
|
224
110
|
|
|
225
|
-
|
|
111
|
+
---
|
|
226
112
|
|
|
227
|
-
|
|
113
|
+
## Event Handling (`@event`)
|
|
228
114
|
|
|
229
|
-
|
|
115
|
+
Use `@` followed by any DOM event.
|
|
230
116
|
|
|
231
|
-
|
|
117
|
+
```html
|
|
118
|
+
<button @click="submit()">Submit</button>
|
|
119
|
+
```
|
|
232
120
|
|
|
233
|
-
|
|
121
|
+
The expression runs in the componentโs state scope.
|
|
234
122
|
|
|
235
|
-
|
|
123
|
+
You also have access to:
|
|
236
124
|
|
|
237
|
-
|
|
125
|
+
* `event` โ the native DOM event
|
|
126
|
+
* `$emit(name, detail)` โ dispatch custom events
|
|
127
|
+
* `$element` โ the host custom element
|
|
238
128
|
|
|
239
|
-
|
|
129
|
+
---
|
|
240
130
|
|
|
241
|
-
|
|
131
|
+
## Conditional Rendering (`b-if`)
|
|
242
132
|
|
|
243
|
-
|
|
133
|
+
```html
|
|
134
|
+
<p b-if="isLoggedIn">Welcome back</p>
|
|
135
|
+
```
|
|
244
136
|
|
|
245
|
-
|
|
137
|
+
When the expression is falsy, the element is hidden via `display: none`.
|
|
246
138
|
|
|
247
|
-
|
|
139
|
+
---
|
|
248
140
|
|
|
249
|
-
|
|
141
|
+
## Lists (`b-for`)
|
|
250
142
|
|
|
251
|
-
|
|
143
|
+
Render lists using `b-for`:
|
|
252
144
|
|
|
253
|
-
|
|
145
|
+
```html
|
|
146
|
+
<ul>
|
|
147
|
+
<li b-for="todo in todos">
|
|
148
|
+
<input type="checkbox" b-sync="todo.done">
|
|
149
|
+
<span class="{{ todo.done ? 'done' : '' }}">{{ todo.text }}</span>
|
|
150
|
+
</li>
|
|
151
|
+
</ul>
|
|
152
|
+
```
|
|
254
153
|
|
|
255
|
-
|
|
154
|
+
* DOM nodes are reused
|
|
155
|
+
* Items are tracked internally
|
|
156
|
+
* Updates are efficient without a virtual DOM
|
|
256
157
|
|
|
257
|
-
|
|
158
|
+
---
|
|
258
159
|
|
|
259
|
-
|
|
160
|
+
## Two-Way Binding (`b-sync`)
|
|
260
161
|
|
|
261
|
-
|
|
162
|
+
`b-sync` keeps inputs and state in sync.
|
|
262
163
|
|
|
263
|
-
|
|
164
|
+
```html
|
|
165
|
+
<input b-sync="username">
|
|
166
|
+
<input type="checkbox" b-sync="settings.enabled">
|
|
167
|
+
```
|
|
264
168
|
|
|
265
|
-
|
|
169
|
+
Works with:
|
|
266
170
|
|
|
267
|
-
|
|
171
|
+
* text inputs
|
|
172
|
+
* checkboxes
|
|
173
|
+
* nested objects
|
|
174
|
+
* items inside `b-for`
|
|
268
175
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
- `$self`: The specific element that triggered the event (alias for `event.target`).
|
|
272
|
-
|
|
176
|
+
---
|
|
273
177
|
|
|
274
|
-
##
|
|
178
|
+
## Styling and Shadow DOM
|
|
275
179
|
|
|
276
|
-
|
|
180
|
+
Every component uses **Shadow DOM** automatically.
|
|
277
181
|
|
|
278
|
-
|
|
182
|
+
Inside `<style>` blocks:
|
|
279
183
|
|
|
280
|
-
|
|
184
|
+
* Use `self` to target the component root
|
|
185
|
+
* `self` is converted to `:host`
|
|
281
186
|
|
|
187
|
+
```css
|
|
188
|
+
self {
|
|
189
|
+
display: block;
|
|
190
|
+
}
|
|
282
191
|
```
|
|
283
|
-
<!-- child.lego: DON'T DO THIS -->
|
|
284
|
-
<button @click="$ancestors('app-shell').user.age++">Update Age</button>
|
|
285
192
|
|
|
286
|
-
|
|
193
|
+
Styles never leak in or out.
|
|
287
194
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
If you have many fields, you don't need unique event handlers for each. Use a single generic update event.
|
|
291
|
-
|
|
292
|
-
1. **Child** sends the field name and the new value:
|
|
293
|
-
|
|
294
|
-
```
|
|
295
|
-
<!-- settings-child.lego -->
|
|
296
|
-
<input type="text"
|
|
297
|
-
value="{{ $ancestors('app-shell').user.name }}"
|
|
298
|
-
@input="$emit('update-field', { key: 'name', value: $self.value })">
|
|
299
|
-
|
|
300
|
-
<input type="number"
|
|
301
|
-
value="{{ $ancestors('app-shell').user.age }}"
|
|
302
|
-
@input="$emit('update-field', { key: 'age', value: $self.value })">
|
|
303
|
-
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
2. **Ancestor** handles all updates in one function:
|
|
307
|
-
|
|
308
|
-
```
|
|
309
|
-
<!-- grand-parent.lego -->
|
|
310
|
-
<template>
|
|
311
|
-
<div @update-field="handleFieldUpdate">
|
|
312
|
-
<settings-child></settings-child>
|
|
313
|
-
</div>
|
|
314
|
-
</template>
|
|
315
|
-
|
|
316
|
-
<script>
|
|
317
|
-
export default {
|
|
318
|
-
user: { name: 'John', age: 30 },
|
|
319
|
-
handleFieldUpdate(event) {
|
|
320
|
-
const { key, value } = event.detail;
|
|
321
|
-
// One place to validate everything
|
|
322
|
-
if (this.user.hasOwnProperty(key)) {
|
|
323
|
-
this.user[key] = value;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
</script>
|
|
328
|
-
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
## ๐ The Build Process (Vite)
|
|
333
|
-
|
|
334
|
-
To use `.lego` files, you must use the `Lego.vitePlugin()`. This plugin transforms your HTML-like file into a standard JavaScript module that registers the component with the `Lego.define` API.
|
|
335
|
-
|
|
336
|
-
### Configuration
|
|
337
|
-
|
|
338
|
-
In your `vite.config.js`:
|
|
195
|
+
---
|
|
339
196
|
|
|
340
|
-
|
|
341
|
-
import { defineConfig } from 'vite';
|
|
342
|
-
import { Lego } from './path/to/lego.js';
|
|
197
|
+
## Lifecycle Hooks
|
|
343
198
|
|
|
344
|
-
|
|
345
|
-
plugins: [Lego.vitePlugin()]
|
|
346
|
-
});
|
|
199
|
+
Define lifecycle methods directly on the component state:
|
|
347
200
|
|
|
201
|
+
```js
|
|
202
|
+
{
|
|
203
|
+
mounted() {
|
|
204
|
+
console.log('Component attached');
|
|
205
|
+
},
|
|
206
|
+
updated() {
|
|
207
|
+
console.log('State changed');
|
|
208
|
+
},
|
|
209
|
+
unmounted() {
|
|
210
|
+
console.log('Component removed');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
348
213
|
```
|
|
349
214
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
1. **Naming**: Use multi-word names for components to avoid collisions.
|
|
353
|
-
|
|
354
|
-
2. **Read-Only Ancestors**: Use `$ancestors()` primarily for **reading** values.
|
|
355
|
-
|
|
356
|
-
3. **Intent-based Events**: For complex logic (e.g., `submit-order`), use a specific event. For simple data syncing, use a generic `update-field` event.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
# ๐ฃ Lego JS Routing Guide
|
|
361
|
-
|
|
362
|
-
The Lego JS router is a lightweight client-side routing solution that leverages the browser's History API. It allows you to build Single Page Applications (SPAs) by mapping URL paths to specific Web Components (Blocks).
|
|
215
|
+
---
|
|
363
216
|
|
|
364
|
-
##
|
|
217
|
+
## Custom Events (`$emit`)
|
|
365
218
|
|
|
366
|
-
|
|
219
|
+
Child components communicate upward using events.
|
|
367
220
|
|
|
221
|
+
```html
|
|
222
|
+
<button @click="$emit('save', data)">Save</button>
|
|
368
223
|
```
|
|
369
|
-
<body>
|
|
370
|
-
<nav>
|
|
371
|
-
<a href="/" b-link>Home</a>
|
|
372
|
-
<a href="/profile/123" b-link>Profile</a>
|
|
373
|
-
</nav>
|
|
374
224
|
|
|
375
|
-
|
|
376
|
-
<lego-router></lego-router>
|
|
377
|
-
</body>
|
|
225
|
+
Events:
|
|
378
226
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
227
|
+
* bubble
|
|
228
|
+
* cross Shadow DOM boundaries
|
|
229
|
+
* are standard `CustomEvent`s
|
|
382
230
|
|
|
383
|
-
|
|
231
|
+
---
|
|
384
232
|
|
|
385
|
-
|
|
386
|
-
// Map the root path to 'home-page' block
|
|
387
|
-
Lego.route('/', 'home-page');
|
|
233
|
+
## Accessing Ancestors (`$ancestors`)
|
|
388
234
|
|
|
389
|
-
|
|
390
|
-
Lego.route('/about', 'about-page');
|
|
235
|
+
Read state from the nearest ancestor component:
|
|
391
236
|
|
|
237
|
+
```html
|
|
238
|
+
<p>{{ $ancestors('app-shell').user.name }}</p>
|
|
392
239
|
```
|
|
393
240
|
|
|
394
|
-
|
|
241
|
+
This is intended for **reading**, not mutation.
|
|
395
242
|
|
|
396
|
-
|
|
243
|
+
---
|
|
397
244
|
|
|
398
|
-
|
|
245
|
+
## Shared State (`$registry`)
|
|
399
246
|
|
|
400
|
-
|
|
401
|
-
Lego.route('/user/:id', 'user-profile');
|
|
402
|
-
Lego.route('/post/:category/:slug', 'blog-post');
|
|
247
|
+
Components defined via `Lego.define` get a shared singleton state.
|
|
403
248
|
|
|
249
|
+
```js
|
|
250
|
+
$registry('settings').theme
|
|
404
251
|
```
|
|
405
252
|
|
|
406
|
-
|
|
253
|
+
Useful for global configuration or app-wide state.
|
|
407
254
|
|
|
408
|
-
|
|
255
|
+
---
|
|
409
256
|
|
|
410
|
-
|
|
411
|
-
<template b-id="user-profile">
|
|
412
|
-
<div>
|
|
413
|
-
<h2>User Profile</h2>
|
|
414
|
-
<p>Viewing ID: {{ global.params.id }}</p>
|
|
415
|
-
</div>
|
|
416
|
-
</template>
|
|
417
|
-
|
|
418
|
-
```
|
|
257
|
+
## Router
|
|
419
258
|
|
|
420
|
-
|
|
259
|
+
LegoJS includes a minimal client-side router.
|
|
421
260
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
### Example: Auth Guard
|
|
261
|
+
Add a router outlet:
|
|
425
262
|
|
|
263
|
+
```html
|
|
264
|
+
<lego-router></lego-router>
|
|
426
265
|
```
|
|
427
|
-
Lego.route('/admin', 'admin-dashboard', async (params, globals) => {
|
|
428
|
-
const isAuthorized = globals.user && globals.user.role === 'admin';
|
|
429
|
-
|
|
430
|
-
if (!isAuthorized) {
|
|
431
|
-
// Redirect to login or home
|
|
432
|
-
history.pushState({}, '', '/login');
|
|
433
|
-
// We must manually trigger the router check after pushState
|
|
434
|
-
Lego.init();
|
|
435
|
-
return false; // Prevent 'admin-dashboard' from loading
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
return true; // Proceed to load the component
|
|
439
|
-
});
|
|
440
266
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
## ๐ Internal Navigation (`b-link`)
|
|
444
|
-
|
|
445
|
-
To navigate without a full page refresh, use the `b-link` directive on anchor tags. This intercepts the click, updates the URL via `pushState`, and tells the Lego router to swap the view.
|
|
267
|
+
Define routes:
|
|
446
268
|
|
|
269
|
+
```js
|
|
270
|
+
Lego.route('/', 'home-page');
|
|
271
|
+
Lego.route('/user/:id', 'user-page');
|
|
447
272
|
```
|
|
448
|
-
<!-- This will trigger the router logic -->
|
|
449
|
-
<a href="/dashboard" b-link>Go to Dashboard</a>
|
|
450
273
|
|
|
451
|
-
|
|
452
|
-
<a href="/external-site.com">External Link</a>
|
|
274
|
+
Access route params:
|
|
453
275
|
|
|
276
|
+
```html
|
|
277
|
+
<p>User ID: {{ global.params.id }}</p>
|
|
454
278
|
```
|
|
455
279
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
Sometimes you need to navigate via JavaScript (e.g., after a successful form submission). Use the standard `history.pushState` but remember that the library listens for the `popstate` event. To trigger a render manually, you can call the internal route matcher or simply use a helper:
|
|
459
|
-
|
|
460
|
-
```
|
|
461
|
-
// In your component logic
|
|
462
|
-
submitForm() {
|
|
463
|
-
// Save data...
|
|
464
|
-
history.pushState({}, '', '/success');
|
|
465
|
-
// Dispatch popstate so the router hears it
|
|
466
|
-
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
467
|
-
}
|
|
280
|
+
Navigation:
|
|
468
281
|
|
|
282
|
+
```html
|
|
283
|
+
<a href="/dashboard" b-link>Dashboard</a>
|
|
469
284
|
```
|
|
470
285
|
|
|
471
|
-
|
|
286
|
+
---
|
|
472
287
|
|
|
473
|
-
|
|
288
|
+
## Programmatic Navigation
|
|
474
289
|
|
|
290
|
+
```js
|
|
291
|
+
history.pushState({}, '', '/success');
|
|
292
|
+
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
475
293
|
```
|
|
476
|
-
// router-config.js
|
|
477
|
-
import './views/home.lego';
|
|
478
|
-
import './views/login.lego';
|
|
479
|
-
import './views/profile.lego';
|
|
480
|
-
|
|
481
|
-
export const setupRouter = () => {
|
|
482
|
-
Lego.route('/', 'home-view');
|
|
483
|
-
Lego.route('/login', 'login-view');
|
|
484
|
-
|
|
485
|
-
// Nested logic for clean organization
|
|
486
|
-
Lego.route('/profile/:username', 'profile-view', (params) => {
|
|
487
|
-
return !!params.username; // Basic validation
|
|
488
|
-
});
|
|
489
|
-
};
|
|
490
294
|
|
|
491
|
-
|
|
295
|
+
---
|
|
492
296
|
|
|
493
|
-
##
|
|
297
|
+
## Defining Components in JavaScript
|
|
494
298
|
|
|
495
|
-
|
|
299
|
+
You can also define components programmatically:
|
|
496
300
|
|
|
497
|
-
|
|
301
|
+
```js
|
|
302
|
+
Lego.define(
|
|
303
|
+
'counter-box',
|
|
304
|
+
`
|
|
305
|
+
<style>self { display:block }</style>
|
|
306
|
+
<button @click="count++">{{ count }}</button>
|
|
307
|
+
`,
|
|
308
|
+
{ count: 0 }
|
|
309
|
+
);
|
|
310
|
+
```
|
|
498
311
|
|
|
499
|
-
|
|
312
|
+
---
|
|
500
313
|
|
|
501
|
-
|
|
314
|
+
## Initialization
|
|
502
315
|
|
|
503
|
-
|
|
316
|
+
LegoJS initializes automatically on `DOMContentLoaded`.
|
|
504
317
|
|
|
505
|
-
|
|
318
|
+
You usually do **not** need to call anything manually.
|
|
506
319
|
|
|
507
|
-
|
|
320
|
+
---
|
|
508
321
|
|
|
509
|
-
|
|
322
|
+
## Design Philosophy
|
|
510
323
|
|
|
511
|
-
|
|
324
|
+
LegoJS is intentionally small and opinionated:
|
|
512
325
|
|
|
513
|
-
|
|
326
|
+
* The DOM is the source of truth
|
|
327
|
+
* JavaScript objects are the state
|
|
328
|
+
* HTML stays HTML
|
|
329
|
+
* Complexity is avoided unless absolutely necessary
|
|
514
330
|
|
|
515
|
-
|
|
331
|
+
If you can explain your UI with plain objects and markup, LegoJS will feel natural.
|
|
516
332
|
|
|
517
|
-
|
|
333
|
+
---
|
|
518
334
|
|
|
519
|
-
|
|
335
|
+
## Summary
|
|
520
336
|
|
|
521
|
-
|
|
337
|
+
* Install with `npm install lego-dom`
|
|
338
|
+
* Define components with `<template b-id>`
|
|
339
|
+
* Use `b-data` for state
|
|
340
|
+
* Use `{{ }}` for binding
|
|
341
|
+
* Use `@event` for logic
|
|
342
|
+
* Use `b-if`, `b-for`, and `b-sync` for structure
|
|
522
343
|
|
|
523
|
-
|
|
344
|
+
Thatโs it.
|
package/package.json
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lego-dom",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "A feature-rich web components + SFC frontend framework",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"keywords": ["framework", "sfc", "components", "lego", "legokit"],
|
|
8
|
-
"author": "
|
|
8
|
+
"author": "",
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "vitest run"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"vitest": "^1.0.0",
|
|
14
14
|
"jsdom": "^22.0.0"
|
|
15
|
-
}
|
|
16
|
-
"license": "MIT"
|
|
15
|
+
}
|
|
17
16
|
}
|