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/README.md CHANGED
@@ -1,177 +1,523 @@
1
- Lego UI Library
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
- Lego is a UI Library that borrows heavily from the children toy company's product of the same name `Lego` &copy; Lego.
5
+ ## ๐Ÿ— Core Concepts
8
6
 
9
- It allows for Reusable interlocking User Interfaces (Lego Blocks) to be composed into bricks that are managed in isolation for assembly at runtime.
7
+ ### 1. Reactive State (`studs`)
10
8
 
11
- __Most importantly Lego aims to be the simplest UI Lib a developer can learn...__
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
- ```html
11
+ ### 2. Shadow DOM Encapsulation
14
12
 
15
- <html lang="en">
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
- <style>
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
- <AnotherWizard>
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
- <script src="unpkg.com/lego/1.0"></script>
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
- <!-- After loading Lego you can either link to where your basket is defined i.e. external js bundle -->
45
- <script src="application.bundle.js"></script>
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
- <!-- Or declare your lego blocks and bricks Inline -->
48
- <script>
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
- let AnotherWizard = router.get("./wizard.html");
49
+ ```
53
50
 
54
- let ProtectedBrick = router.get("./wizard.html", () => {
55
- //This can be a global authentication function call to auth0 etc...
56
- return router.get("./unathenticated.html");
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
- There are 5 things to understand all in all to get a complete mastery of Lego JS. These are stylized under the acronym B.R.I.C.K to signify
69
- how lego acts as a building platform with the meanings provided below.
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
- - K is for Keep: The Keep is the global store for your application to register and reuse state and services.
96
- ```js
97
- const db = lego.use("keep");
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
- class User = { /** Methods, Fields, Constructor, Localized state etc. */ }
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
- ```html
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
- &nbsp;
406
+ ### Usage inside a Component
113
407
 
114
- &nbsp;
408
+ Any component can access these parameters via the `global` object.
115
409
 
116
- <hr>
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
- ## Loading Files
418
+ ```
119
419
 
120
- <hr>
121
- There are two ways to load a file in Lego
420
+ ## ๐Ÿ›ก Navigation Middleware (Authentication)
122
421
 
123
- - Server Loaded
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
- - Client Loaded
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
- ### Server Loaded
130
- <hr>
443
+ ## ๐Ÿ”— Internal Navigation (`b-link`)
131
444
 
132
- Java/PHP/Python or XYZ code on the server is responsible for checking if the request is a Lego request (X-Lego) header present or if it is a plain old HTTP Request.
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
- - The server can send back a `Lego Brick` or `full HTML` Page depending on if the request is a Lego Request or plain old HTTP request.
447
+ ```
448
+ <!-- This will trigger the router logic -->
449
+ <a href="/dashboard" b-link>Go to Dashboard</a>
135
450
 
136
- &nbsp;
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
- ### Basket Loaded
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
- This strategy depends on you calling into the `Lego Basket` to get the page where the Naive Brick returned from the server should sit.
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
- ```html
469
+ ```
146
470
 
147
- <Card></Card>
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
- # KEEP
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
- ```html
497
+ Syntax
163
498
 
164
- <html>
165
- <body>
166
- </body>
499
+ Description
167
500
 
168
- <script src="unpkg.com/lego/1.0"></script>
169
- <script>
170
- let keep = lego.use("keep");
501
+ **Viewport**
171
502
 
172
- class User = { /* custom methods and properties etc */ }
503
+ `<lego-router>`
173
504
 
174
- keep.set("User", User);
175
- </script>
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();