lego-dom 0.0.3 โ 0.0.5
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/.ignore/auto.html +135 -0
- package/.ignore/test.html +73 -0
- package/README.md +448 -102
- package/index.js +26 -0
- package/main.js +459 -0
- package/main.test.js +86 -0
- package/package.json +14 -8
- package/dist/dom/index.js +0 -1
- package/dist/index.js +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/traverser.js +0 -1
- package/dist/veyors/basket.js +0 -2
- package/dist/veyors/brick.js +0 -1
- package/dist/veyors/index.js +0 -1
- package/dist/veyors/router.js +0 -1
- package/example/blocks/banner.lego +0 -0
- package/example/blocks/card.lego +0 -40
- package/example/blocks/form.lego +0 -31
- package/example/bricks/index.html +0 -50
- package/src/dom/index.ts +0 -0
- package/src/index.ts +0 -0
- package/src/utils/index.ts +0 -0
- package/src/utils/traverser.ts +0 -0
- package/src/veyors/basket.ts +0 -5
- package/src/veyors/brick.ts +0 -0
- package/src/veyors/index.ts +0 -0
- package/src/veyors/router.ts +0 -0
- package/tsconfig.json +0 -69
package/README.md
CHANGED
|
@@ -1,177 +1,523 @@
|
|
|
1
|
-
Lego
|
|
2
|
-
===============
|
|
3
|
-
*Work In Progress and API will change drastically before 1.0.0*
|
|
1
|
+
# Lego JS Library
|
|
4
2
|
|
|
5
|
-
|
|
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.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
## ๐ Core Concepts
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
### 1. Reactive State (`studs`)
|
|
10
8
|
|
|
11
|
-
|
|
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.
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
### 2. Shadow DOM Encapsulation
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
<head>
|
|
17
|
-
<meta charset="UTF-8">
|
|
18
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
-
<title>Lego Example</title>
|
|
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.
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
p {
|
|
23
|
-
color: red;
|
|
24
|
-
}
|
|
25
|
-
</style>
|
|
26
|
-
</head>
|
|
27
|
-
<body>
|
|
28
|
-
<!-- This is a lego block -->
|
|
29
|
-
<SomeFormWizard></SomeFormWizard>
|
|
15
|
+
### 3. Directives (`b-`)
|
|
30
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.
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
<!-- whatever is nested inline in a block will be removed and appended as the last child of the loaded block -->
|
|
34
|
-
</AnotherWizard>
|
|
19
|
+
## ๐ Quickstart
|
|
35
20
|
|
|
21
|
+
### ๐ฆ Installation
|
|
36
22
|
|
|
23
|
+
Drop the script and start building. No build step, no `npm install` fatigue.
|
|
37
24
|
|
|
25
|
+
```
|
|
26
|
+
<script src="path/to/main.js"></script>
|
|
38
27
|
|
|
28
|
+
```
|
|
39
29
|
|
|
30
|
+
### ๐ Creating your first Component
|
|
40
31
|
|
|
32
|
+
Define logic and layout in a standard `<template>`.
|
|
41
33
|
|
|
42
|
-
|
|
34
|
+
```
|
|
35
|
+
<!-- Define -->
|
|
36
|
+
<template b-id="greeting-card">
|
|
37
|
+
<style>
|
|
38
|
+
self { display: block; padding: 20px; border-radius: 8px; background: #f0f4f8; }
|
|
39
|
+
</style>
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
<h2>Hello, {{ name }}!</h2>
|
|
42
|
+
<button @click="showBio = !showBio">Toggle Bio</button>
|
|
43
|
+
<p b-if="showBio">{{ bio }}</p>
|
|
44
|
+
</template>
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
let basket = Lego.use("basket"); //case insensitive so .use("BaSkEt") works also
|
|
50
|
-
let router = Lego.use("router");
|
|
46
|
+
<!-- Execute -->
|
|
47
|
+
<greeting-card b-data="{ name: 'Alex', bio: 'Dev', showBio: false }"></greeting-card>
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
```
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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>
|
|
58
147
|
|
|
59
|
-
//Explicit registration
|
|
60
|
-
Basket.register(AnotherWizard);
|
|
61
|
-
Basket.register(Router.get("./snippet.html")).as("SomeFormWizard");
|
|
62
|
-
</script>
|
|
63
|
-
</body>
|
|
64
|
-
</html>
|
|
65
148
|
```
|
|
66
149
|
|
|
150
|
+
## ๐ Tooling & Ecosystem
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ๐ฆ Lego Single File Components (.lego)
|
|
67
154
|
|
|
68
|
-
|
|
69
|
-
|
|
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>`.
|
|
70
160
|
|
|
71
|
-
- B is for Basket: This is similar to a real basket and is where all blocks `*.lego` pages are kept in memory for reuse.
|
|
72
|
-
```js
|
|
73
|
-
let basket = lego.use("basket");
|
|
74
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>
|
|
75
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>
|
|
76
207
|
|
|
77
|
-
- R is for Router: This is the global router responsible for loading and displaying blocks.
|
|
78
|
-
```js
|
|
79
|
-
let router = lego.use("router");
|
|
80
208
|
```
|
|
81
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.
|
|
82
367
|
|
|
83
|
-
- I is for Interface: This is a proxy to the UI/DOM that helps tie your JS code to what appears on screen.
|
|
84
|
-
```js
|
|
85
|
-
let interface = lego.use("interface");
|
|
86
368
|
```
|
|
369
|
+
<body>
|
|
370
|
+
<nav>
|
|
371
|
+
<a href="/" b-link>Home</a>
|
|
372
|
+
<a href="/profile/123" b-link>Profile</a>
|
|
373
|
+
</nav>
|
|
87
374
|
|
|
375
|
+
<!-- Routed components appear here -->
|
|
376
|
+
<lego-router></lego-router>
|
|
377
|
+
</body>
|
|
88
378
|
|
|
89
|
-
- C is for Connection: The connection holds utilities for fetching data and sending data to an API.
|
|
90
|
-
```js
|
|
91
|
-
let connection = lego.use("connection");
|
|
92
379
|
```
|
|
93
380
|
|
|
381
|
+
## ๐ Basic Route Definition
|
|
94
382
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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');
|
|
98
388
|
|
|
99
|
-
|
|
389
|
+
// Map /about to 'about-page' block
|
|
390
|
+
Lego.route('/about', 'about-page');
|
|
100
391
|
|
|
101
|
-
db.register("User", User);
|
|
102
|
-
db.register("User.Enterprise", User); //scoped
|
|
103
392
|
```
|
|
104
393
|
|
|
105
|
-
|
|
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
|
|
106
399
|
|
|
107
|
-
<script src="unpkg.com/lego-dom@1.0.0"></script>
|
|
108
|
-
<script></script>
|
|
109
400
|
```
|
|
401
|
+
Lego.route('/user/:id', 'user-profile');
|
|
402
|
+
Lego.route('/post/:category/:slug', 'blog-post');
|
|
110
403
|
|
|
404
|
+
```
|
|
111
405
|
|
|
112
|
-
|
|
406
|
+
### Usage inside a Component
|
|
113
407
|
|
|
114
|
-
|
|
408
|
+
Any component can access these parameters via the `global` object.
|
|
115
409
|
|
|
116
|
-
|
|
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>
|
|
117
417
|
|
|
118
|
-
|
|
418
|
+
```
|
|
119
419
|
|
|
120
|
-
|
|
121
|
-
There are two ways to load a file in Lego
|
|
420
|
+
## ๐ก Navigation Middleware (Authentication)
|
|
122
421
|
|
|
123
|
-
|
|
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.
|
|
124
423
|
|
|
424
|
+
### Example: Auth Guard
|
|
125
425
|
|
|
126
|
-
|
|
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
|
+
});
|
|
127
440
|
|
|
441
|
+
```
|
|
128
442
|
|
|
129
|
-
|
|
130
|
-
<hr>
|
|
443
|
+
## ๐ Internal Navigation (`b-link`)
|
|
131
444
|
|
|
132
|
-
|
|
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.
|
|
133
446
|
|
|
134
|
-
|
|
447
|
+
```
|
|
448
|
+
<!-- This will trigger the router logic -->
|
|
449
|
+
<a href="/dashboard" b-link>Go to Dashboard</a>
|
|
135
450
|
|
|
136
|
-
|
|
451
|
+
<!-- This will trigger a standard browser reload -->
|
|
452
|
+
<a href="/external-site.com">External Link</a>
|
|
137
453
|
|
|
454
|
+
```
|
|
138
455
|
|
|
456
|
+
## ๐ Programmatic Navigation
|
|
139
457
|
|
|
140
|
-
|
|
141
|
-
<hr>
|
|
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:
|
|
142
459
|
|
|
143
|
-
|
|
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
|
+
}
|
|
144
468
|
|
|
145
|
-
```
|
|
469
|
+
```
|
|
146
470
|
|
|
147
|
-
|
|
471
|
+
## ๐ Large Scale Structure
|
|
148
472
|
|
|
473
|
+
For larger projects, define your routes in a dedicated initialization file and ensure your page components are registered before the router starts.
|
|
149
474
|
|
|
150
|
-
<script src="/path.to.bundle.js"></script>
|
|
151
|
-
<script>
|
|
152
|
-
let blocks = Basket.get("/approute/:ids/supported");
|
|
153
|
-
blocks.insert("#insertionPoint")
|
|
154
|
-
</script>
|
|
155
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
|
+
};
|
|
156
490
|
|
|
491
|
+
```
|
|
157
492
|
|
|
493
|
+
## ๐ Router State Summary
|
|
158
494
|
|
|
159
|
-
|
|
160
|
-
The keep is where state and objects are stored for reuse i.e. a User class or object that should be shared across multiple bricks and blocks. Like all other parts of Lego the API is pretty straightforward.
|
|
495
|
+
Feature
|
|
161
496
|
|
|
162
|
-
|
|
497
|
+
Syntax
|
|
163
498
|
|
|
164
|
-
|
|
165
|
-
<body>
|
|
166
|
-
</body>
|
|
499
|
+
Description
|
|
167
500
|
|
|
168
|
-
|
|
169
|
-
<script>
|
|
170
|
-
let keep = lego.use("keep");
|
|
501
|
+
**Viewport**
|
|
171
502
|
|
|
172
|
-
|
|
503
|
+
`<lego-router>`
|
|
173
504
|
|
|
174
|
-
|
|
175
|
-
|
|
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`
|
|
176
522
|
|
|
177
|
-
|
|
523
|
+
Logic to permit/block access.
|
package/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
async function init() {
|
|
9
|
+
const targetDir = process.argv[2] || 'my-lego-app';
|
|
10
|
+
const templateDir = path.resolve(__dirname, 'template');
|
|
11
|
+
|
|
12
|
+
console.log(`Building your Lego app in ${targetDir}...`);
|
|
13
|
+
|
|
14
|
+
await fs.copy(templateDir, targetDir);
|
|
15
|
+
|
|
16
|
+
// Customizing package.json name
|
|
17
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
18
|
+
const pkg = await fs.readJson(pkgPath);
|
|
19
|
+
pkg.name = targetDir;
|
|
20
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
21
|
+
|
|
22
|
+
console.log('\nDone! Now run:\n');
|
|
23
|
+
console.log(` cd ${targetDir}\n npm install\n npm run dev`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
init();
|