lego-dom 0.0.4 → 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 +344 -0
- package/package.json +3 -4
- package/about.md +0 -523
package/README.md
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# LegoJS
|
|
2
|
+
|
|
3
|
+
LegoJS is a tiny, zero-dependency JavaScript library for building reactive Web Components directly in the browser.
|
|
4
|
+
|
|
5
|
+
The goal of LegoJS is **mental model simplicity**:
|
|
6
|
+
|
|
7
|
+
* No virtual DOM
|
|
8
|
+
* No compilation step required
|
|
9
|
+
* No JSX
|
|
10
|
+
* No framework-specific syntax to learn
|
|
11
|
+
|
|
12
|
+
You write **HTML**, add a few **directives**, and LegoJS takes care of reactivity and updates.
|
|
13
|
+
|
|
14
|
+
This README is intentionally designed so that a developer can understand **everything they need** about LegoJS by reading this file alone.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
The package name on npm is **`lego-dom`** (the name `legojs` was already taken).
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install lego-dom
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or include it directly in the browser:
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<script src="node_modules/lego-dom/main.js"></script>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Once loaded, `Lego` is available globally.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## The Mental Model
|
|
37
|
+
|
|
38
|
+
Think of LegoJS like real Lego blocks:
|
|
39
|
+
|
|
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**
|
|
44
|
+
|
|
45
|
+
There is no mounting, diffing, or reconciliation engine.
|
|
46
|
+
|
|
47
|
+
You change JavaScript objects → LegoJS updates the DOM.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Defining a Component (Block)
|
|
52
|
+
|
|
53
|
+
A component is defined using a standard HTML `<template>` with a `b-id`.
|
|
54
|
+
|
|
55
|
+
```html
|
|
56
|
+
<template b-id="hello-card">
|
|
57
|
+
<style>
|
|
58
|
+
self {
|
|
59
|
+
display: block;
|
|
60
|
+
padding: 1rem;
|
|
61
|
+
border: 1px solid #ccc;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
64
|
+
|
|
65
|
+
<h2>Hello {{ name }}</h2>
|
|
66
|
+
<button @click="count++">Clicked {{ count }} times</button>
|
|
67
|
+
</template>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use the component in HTML:
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<hello-card b-data="{ name: 'Ahmed', count: 0 }"></hello-card>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Reactive State (`studs`)
|
|
79
|
+
|
|
80
|
+
Each component has a reactive state object internally called **studs**.
|
|
81
|
+
|
|
82
|
+
* Defined via `b-data` or component logic
|
|
83
|
+
* Implemented using JavaScript `Proxy`
|
|
84
|
+
* Any mutation automatically schedules a re-render
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<button @click="count++"></button>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
No setters. No actions. No reducers.
|
|
91
|
+
|
|
92
|
+
Just mutate data.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Templating (`{{ }}`)
|
|
97
|
+
|
|
98
|
+
Text interpolation works in:
|
|
99
|
+
|
|
100
|
+
* Text nodes
|
|
101
|
+
* Attributes
|
|
102
|
+
* Class names
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<p>Hello {{ user.name }}</p>
|
|
106
|
+
<img src="/avatars/{{ user.id }}.png">
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Expressions are plain JavaScript.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Event Handling (`@event`)
|
|
114
|
+
|
|
115
|
+
Use `@` followed by any DOM event.
|
|
116
|
+
|
|
117
|
+
```html
|
|
118
|
+
<button @click="submit()">Submit</button>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The expression runs in the component’s state scope.
|
|
122
|
+
|
|
123
|
+
You also have access to:
|
|
124
|
+
|
|
125
|
+
* `event` – the native DOM event
|
|
126
|
+
* `$emit(name, detail)` – dispatch custom events
|
|
127
|
+
* `$element` – the host custom element
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Conditional Rendering (`b-if`)
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<p b-if="isLoggedIn">Welcome back</p>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
When the expression is falsy, the element is hidden via `display: none`.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Lists (`b-for`)
|
|
142
|
+
|
|
143
|
+
Render lists using `b-for`:
|
|
144
|
+
|
|
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
|
+
```
|
|
153
|
+
|
|
154
|
+
* DOM nodes are reused
|
|
155
|
+
* Items are tracked internally
|
|
156
|
+
* Updates are efficient without a virtual DOM
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Two-Way Binding (`b-sync`)
|
|
161
|
+
|
|
162
|
+
`b-sync` keeps inputs and state in sync.
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<input b-sync="username">
|
|
166
|
+
<input type="checkbox" b-sync="settings.enabled">
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Works with:
|
|
170
|
+
|
|
171
|
+
* text inputs
|
|
172
|
+
* checkboxes
|
|
173
|
+
* nested objects
|
|
174
|
+
* items inside `b-for`
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Styling and Shadow DOM
|
|
179
|
+
|
|
180
|
+
Every component uses **Shadow DOM** automatically.
|
|
181
|
+
|
|
182
|
+
Inside `<style>` blocks:
|
|
183
|
+
|
|
184
|
+
* Use `self` to target the component root
|
|
185
|
+
* `self` is converted to `:host`
|
|
186
|
+
|
|
187
|
+
```css
|
|
188
|
+
self {
|
|
189
|
+
display: block;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Styles never leak in or out.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Lifecycle Hooks
|
|
198
|
+
|
|
199
|
+
Define lifecycle methods directly on the component state:
|
|
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
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Custom Events (`$emit`)
|
|
218
|
+
|
|
219
|
+
Child components communicate upward using events.
|
|
220
|
+
|
|
221
|
+
```html
|
|
222
|
+
<button @click="$emit('save', data)">Save</button>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Events:
|
|
226
|
+
|
|
227
|
+
* bubble
|
|
228
|
+
* cross Shadow DOM boundaries
|
|
229
|
+
* are standard `CustomEvent`s
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Accessing Ancestors (`$ancestors`)
|
|
234
|
+
|
|
235
|
+
Read state from the nearest ancestor component:
|
|
236
|
+
|
|
237
|
+
```html
|
|
238
|
+
<p>{{ $ancestors('app-shell').user.name }}</p>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
This is intended for **reading**, not mutation.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Shared State (`$registry`)
|
|
246
|
+
|
|
247
|
+
Components defined via `Lego.define` get a shared singleton state.
|
|
248
|
+
|
|
249
|
+
```js
|
|
250
|
+
$registry('settings').theme
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Useful for global configuration or app-wide state.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Router
|
|
258
|
+
|
|
259
|
+
LegoJS includes a minimal client-side router.
|
|
260
|
+
|
|
261
|
+
Add a router outlet:
|
|
262
|
+
|
|
263
|
+
```html
|
|
264
|
+
<lego-router></lego-router>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Define routes:
|
|
268
|
+
|
|
269
|
+
```js
|
|
270
|
+
Lego.route('/', 'home-page');
|
|
271
|
+
Lego.route('/user/:id', 'user-page');
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Access route params:
|
|
275
|
+
|
|
276
|
+
```html
|
|
277
|
+
<p>User ID: {{ global.params.id }}</p>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Navigation:
|
|
281
|
+
|
|
282
|
+
```html
|
|
283
|
+
<a href="/dashboard" b-link>Dashboard</a>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Programmatic Navigation
|
|
289
|
+
|
|
290
|
+
```js
|
|
291
|
+
history.pushState({}, '', '/success');
|
|
292
|
+
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Defining Components in JavaScript
|
|
298
|
+
|
|
299
|
+
You can also define components programmatically:
|
|
300
|
+
|
|
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
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Initialization
|
|
315
|
+
|
|
316
|
+
LegoJS initializes automatically on `DOMContentLoaded`.
|
|
317
|
+
|
|
318
|
+
You usually do **not** need to call anything manually.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Design Philosophy
|
|
323
|
+
|
|
324
|
+
LegoJS is intentionally small and opinionated:
|
|
325
|
+
|
|
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
|
|
330
|
+
|
|
331
|
+
If you can explain your UI with plain objects and markup, LegoJS will feel natural.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Summary
|
|
336
|
+
|
|
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
|
|
343
|
+
|
|
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
|
}
|
package/about.md
DELETED
|
@@ -1,523 +0,0 @@
|
|
|
1
|
-
# Lego JS Library
|
|
2
|
-
|
|
3
|
-
Lego JS is a surgical, zero-dependency reactive library for building native Web Components. It skips the virtual DOM overhead, leveraging Proxies and Shadow DOM to provide a lightweight (<5KB) alternative to the heavy-duty framework status quo.
|
|
4
|
-
|
|
5
|
-
## 🏗 Core Concepts
|
|
6
|
-
|
|
7
|
-
### 1. Reactive State (`studs`)
|
|
8
|
-
|
|
9
|
-
Lego components use a reactive state object called `studs`. State mutations trigger efficient, batched DOM updates via `requestAnimationFrame`—minimizing reflows without a complex reconciliation engine.
|
|
10
|
-
|
|
11
|
-
### 2. Shadow DOM Encapsulation
|
|
12
|
-
|
|
13
|
-
Styles and logic are scoped by default. Use the `self` selector inside a component's `<style>` tag to target the host element without worrying about CSS leakage or global collisions.
|
|
14
|
-
|
|
15
|
-
### 3. Directives (`b-`)
|
|
16
|
-
|
|
17
|
-
Native-feeling attributes (`b-if`, `b-for`, `b-sync`) bridge your data to the DOM. No JSX, no compilation required for core features—just standard HTML.
|
|
18
|
-
|
|
19
|
-
## 🚀 Quickstart
|
|
20
|
-
|
|
21
|
-
### 📦 Installation
|
|
22
|
-
|
|
23
|
-
Drop the script and start building. No build step, no `npm install` fatigue.
|
|
24
|
-
|
|
25
|
-
```
|
|
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
|
-
|
|
41
|
-
<h2>Hello, {{ name }}!</h2>
|
|
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>
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## 📖 Directives Deep Dive
|
|
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`
|
|
70
|
-
|
|
71
|
-
Initialization
|
|
72
|
-
|
|
73
|
-
Sets the initial reactive state.
|
|
74
|
-
|
|
75
|
-
`<my-tag b-data="{ count: 0 }">`
|
|
76
|
-
|
|
77
|
-
`b-sync`
|
|
78
|
-
|
|
79
|
-
Two-way
|
|
80
|
-
|
|
81
|
-
Bi-directional binding for form inputs.
|
|
82
|
-
|
|
83
|
-
`<input b-sync="query">`
|
|
84
|
-
|
|
85
|
-
`b-for`
|
|
86
|
-
|
|
87
|
-
Structural
|
|
88
|
-
|
|
89
|
-
High-performance array looping.
|
|
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">
|
|
123
|
-
<style>
|
|
124
|
-
.done { text-decoration: line-through; opacity: 0.6; }
|
|
125
|
-
ul { list-style: none; padding: 0; }
|
|
126
|
-
</style>
|
|
127
|
-
|
|
128
|
-
<h3>Tasks ({{ tasks.filter(t => !t.done).length }} left)</h3>
|
|
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>
|
|
139
|
-
</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
|
-
```
|
|
149
|
-
|
|
150
|
-
## 🏗 Tooling & Ecosystem
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
# 📦 Lego Single File Components (.lego)
|
|
154
|
-
|
|
155
|
-
Single File Components (SFCs) are the professional standard for building with Lego JS. They allow you to co-locate your markup, scoped CSS, and reactive logic in a single `.lego` file, providing a clean separation of concerns without the mental overhead of switching between multiple files.
|
|
156
|
-
|
|
157
|
-
## 🏗 Anatomy of a .lego File
|
|
158
|
-
|
|
159
|
-
A `.lego` file is composed of three top-level blocks: `<template>`, `<style>`, and `<script>`.
|
|
160
|
-
|
|
161
|
-
```
|
|
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
|
-
|
|
172
|
-
<style>
|
|
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>
|
|
207
|
-
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
## 🎨 Scoped Styling
|
|
211
|
-
|
|
212
|
-
Styles defined inside a `.lego` file are automatically scoped to that component using the **Shadow DOM**.
|
|
213
|
-
|
|
214
|
-
- **The `self` selector**: Use `self` to target the host element of the component itself (the custom tag). In the final build, this is converted to the `:host` CSS selector.
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
## ⚙️ Component Logic (`export default`)
|
|
220
|
-
|
|
221
|
-
The `<script>` block must `export default` a plain JavaScript object. This object defines the component's state and behavior.
|
|
222
|
-
|
|
223
|
-
### 1. Reactive Data
|
|
224
|
-
|
|
225
|
-
Any property defined in the exported object becomes part of the reactive `data` proxy. When you change `this.username = 'NewName'`, the template updates automatically.
|
|
226
|
-
|
|
227
|
-
### 2. Lifecycle Hooks
|
|
228
|
-
|
|
229
|
-
Lego JS provides specific hooks to manage the component's existence:
|
|
230
|
-
|
|
231
|
-
Hook
|
|
232
|
-
|
|
233
|
-
Description
|
|
234
|
-
|
|
235
|
-
`mounted()`
|
|
236
|
-
|
|
237
|
-
Called after the component is attached to the DOM and its shadow root is initialized.
|
|
238
|
-
|
|
239
|
-
`updated()`
|
|
240
|
-
|
|
241
|
-
Called after the state changes and the DOM has been patched via the batcher.
|
|
242
|
-
|
|
243
|
-
`unmounted()`
|
|
244
|
-
|
|
245
|
-
Called just before the component is removed from the DOM. Use for cleanup (e.g., clearing timers).
|
|
246
|
-
|
|
247
|
-
## ✨ Magic Helpers ($)
|
|
248
|
-
|
|
249
|
-
Lego JS provides several "magic" variables available directly in your templates and event handlers to speed up development.
|
|
250
|
-
|
|
251
|
-
### `$element`
|
|
252
|
-
|
|
253
|
-
Refers to the host custom element itself (the root of the component).
|
|
254
|
-
|
|
255
|
-
### `$refs`
|
|
256
|
-
|
|
257
|
-
Provides quick access to DOM elements within the component's Shadow DOM that have a `ref` attribute.
|
|
258
|
-
|
|
259
|
-
### `$emit(eventName, detail)`
|
|
260
|
-
|
|
261
|
-
A shorthand for dispatching custom events. By default, these events have `bubbles: true` and `composed: true`, allowing them to pass through multiple levels of Shadow DOM.
|
|
262
|
-
|
|
263
|
-
### `$ancestors(id)`
|
|
264
|
-
|
|
265
|
-
Searches up the DOM tree for the nearest component matching the provided tag name or `b-id`.
|
|
266
|
-
|
|
267
|
-
### `$event` and `$self`
|
|
268
|
-
|
|
269
|
-
- `$event`: The native DOM event object.
|
|
270
|
-
|
|
271
|
-
- `$self`: The specific element that triggered the event (alias for `event.target`).
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
## 📡 The "One Way" Pattern (Data Flow)
|
|
275
|
-
|
|
276
|
-
While Lego JS allows deep tree access, the **only** recommended way to handle state updates in large apps is **Data Down, Events Up**. This prevents "mutant state" where you don't know which child changed a value.
|
|
277
|
-
|
|
278
|
-
### ❌ The "Bad" Way (Direct Mutation)
|
|
279
|
-
|
|
280
|
-
Avoid having children directly change an ancestor's data. It makes debugging impossible.
|
|
281
|
-
|
|
282
|
-
```
|
|
283
|
-
<!-- child.lego: DON'T DO THIS -->
|
|
284
|
-
<button @click="$ancestors('app-shell').user.age++">Update Age</button>
|
|
285
|
-
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
### ✅ The "Lego" Way (Generic Update Pattern)
|
|
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`:
|
|
339
|
-
|
|
340
|
-
```
|
|
341
|
-
import { defineConfig } from 'vite';
|
|
342
|
-
import { Lego } from './path/to/lego.js';
|
|
343
|
-
|
|
344
|
-
export default defineConfig({
|
|
345
|
-
plugins: [Lego.vitePlugin()]
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
## 💡 Best Practices
|
|
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).
|
|
363
|
-
|
|
364
|
-
## 🧩 The Router Viewport
|
|
365
|
-
|
|
366
|
-
To use routing, you must place the `<lego-router>` element in your HTML. This acts as the container where the matched component will be rendered.
|
|
367
|
-
|
|
368
|
-
```
|
|
369
|
-
<body>
|
|
370
|
-
<nav>
|
|
371
|
-
<a href="/" b-link>Home</a>
|
|
372
|
-
<a href="/profile/123" b-link>Profile</a>
|
|
373
|
-
</nav>
|
|
374
|
-
|
|
375
|
-
<!-- Routed components appear here -->
|
|
376
|
-
<lego-router></lego-router>
|
|
377
|
-
</body>
|
|
378
|
-
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
## 📍 Basic Route Definition
|
|
382
|
-
|
|
383
|
-
Routes are defined using `Lego.route(path, componentName)`.
|
|
384
|
-
|
|
385
|
-
```
|
|
386
|
-
// Map the root path to 'home-page' block
|
|
387
|
-
Lego.route('/', 'home-page');
|
|
388
|
-
|
|
389
|
-
// Map /about to 'about-page' block
|
|
390
|
-
Lego.route('/about', 'about-page');
|
|
391
|
-
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
## 动态 Dynamic Route Parameters
|
|
395
|
-
|
|
396
|
-
You can define dynamic segments in your paths using the `:` prefix. These parameters are automatically parsed and injected into `Lego.globals.params`.
|
|
397
|
-
|
|
398
|
-
### Definition
|
|
399
|
-
|
|
400
|
-
```
|
|
401
|
-
Lego.route('/user/:id', 'user-profile');
|
|
402
|
-
Lego.route('/post/:category/:slug', 'blog-post');
|
|
403
|
-
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### Usage inside a Component
|
|
407
|
-
|
|
408
|
-
Any component can access these parameters via the `global` object.
|
|
409
|
-
|
|
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
|
-
```
|
|
419
|
-
|
|
420
|
-
## 🛡 Navigation Middleware (Authentication)
|
|
421
|
-
|
|
422
|
-
Middleware allows you to guard routes. The middleware function is executed before the component is rendered. If it returns `false`, the navigation is cancelled.
|
|
423
|
-
|
|
424
|
-
### Example: Auth Guard
|
|
425
|
-
|
|
426
|
-
```
|
|
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
|
-
|
|
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.
|
|
446
|
-
|
|
447
|
-
```
|
|
448
|
-
<!-- This will trigger the router logic -->
|
|
449
|
-
<a href="/dashboard" b-link>Go to Dashboard</a>
|
|
450
|
-
|
|
451
|
-
<!-- This will trigger a standard browser reload -->
|
|
452
|
-
<a href="/external-site.com">External Link</a>
|
|
453
|
-
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
## 🏗 Programmatic Navigation
|
|
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
|
-
}
|
|
468
|
-
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
## 📂 Large Scale Structure
|
|
472
|
-
|
|
473
|
-
For larger projects, define your routes in a dedicated initialization file and ensure your page components are registered before the router starts.
|
|
474
|
-
|
|
475
|
-
```
|
|
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
|
-
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
## 📝 Router State Summary
|
|
494
|
-
|
|
495
|
-
Feature
|
|
496
|
-
|
|
497
|
-
Syntax
|
|
498
|
-
|
|
499
|
-
Description
|
|
500
|
-
|
|
501
|
-
**Viewport**
|
|
502
|
-
|
|
503
|
-
`<lego-router>`
|
|
504
|
-
|
|
505
|
-
Where the content renders.
|
|
506
|
-
|
|
507
|
-
**Directive**
|
|
508
|
-
|
|
509
|
-
`b-link`
|
|
510
|
-
|
|
511
|
-
Intercepts link clicks.
|
|
512
|
-
|
|
513
|
-
**Globals**
|
|
514
|
-
|
|
515
|
-
`global.params`
|
|
516
|
-
|
|
517
|
-
Object containing URL variables.
|
|
518
|
-
|
|
519
|
-
**Middleware**
|
|
520
|
-
|
|
521
|
-
`(params, globals) => boolean`
|
|
522
|
-
|
|
523
|
-
Logic to permit/block access.
|