ladrillosjs 2.0.0-beta.1 → 2.0.0-beta.2
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 +680 -473
- package/dist/core/html/htmlRenderer.d.ts +6 -1
- package/dist/core/html/htmlparser.d.ts +7 -1
- package/dist/{index-CXHidyhO.js → index-63mcJ9Hl.js} +4 -4
- package/dist/index-63mcJ9Hl.js.map +1 -0
- package/dist/{index-VkDZJVOR.mjs → index-CUJmLlUh.mjs} +4 -4
- package/dist/index-CUJmLlUh.mjs.map +1 -0
- package/dist/ladrillosjs.cjs.js +1 -1
- package/dist/ladrillosjs.es.js +1 -1
- package/dist/ladrillosjs.umd.js +26 -17
- package/dist/ladrillosjs.umd.js.map +1 -1
- package/dist/types/LadrilloTypes.d.ts +14 -0
- package/dist/webcomponent-DNeyn3kB.js +79 -0
- package/dist/webcomponent-DNeyn3kB.js.map +1 -0
- package/dist/webcomponent-DzBRHkic.mjs +970 -0
- package/dist/webcomponent-DzBRHkic.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/index-CXHidyhO.js.map +0 -1
- package/dist/index-VkDZJVOR.mjs.map +0 -1
- package/dist/webcomponent-CJ3lZBZb.mjs +0 -703
- package/dist/webcomponent-CJ3lZBZb.mjs.map +0 -1
- package/dist/webcomponent-i9W7LUiv.js +0 -70
- package/dist/webcomponent-i9W7LUiv.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,116 +1,57 @@
|
|
|
1
1
|
# LadrillosJS
|
|
2
2
|
|
|
3
|
-
<img src="https://raw.githubusercontent.com/drubiodev/LadrillosJS/refs/heads/main/LadrillosJS.
|
|
3
|
+
<img src="https://raw.githubusercontent.com/drubiodev/LadrillosJS/refs/heads/main/LadrillosJS.jpg" alt="LadrillosJS" width="400"/>
|
|
4
4
|
|
|
5
5
|
A lightweight, zero-dependency web component framework for building modular web applications.
|
|
6
6
|
|
|
7
|
-
**Version 2.0** -
|
|
7
|
+
**Version 2.0** - Rewritten in TypeScript with enhanced performance, improved developer experience, and powerful new features.
|
|
8
8
|
|
|
9
|
-
"
|
|
9
|
+
> "Empower developers to componentize code efficiently without the complexity of a full-scale framework. Focus on simplicity while leveraging core web fundamentals."
|
|
10
10
|
|
|
11
11
|
## Table of Contents
|
|
12
12
|
|
|
13
13
|
- [Features](#features)
|
|
14
|
-
- [Getting Started](#getting-started)
|
|
15
|
-
- [Prerequisites](#prerequisites)
|
|
16
|
-
- [What's New in v2.0](#whats-new-in-v20)
|
|
17
|
-
- [Example Applications](#example-applications)
|
|
18
14
|
- [Installation](#installation)
|
|
19
|
-
- [
|
|
15
|
+
- [Quick Start](#quick-start)
|
|
20
16
|
- [Core Concepts](#core-concepts)
|
|
21
17
|
- [Component Registration](#component-registration)
|
|
22
18
|
- [State Management](#state-management)
|
|
23
19
|
- [Event Handling](#event-handling)
|
|
24
20
|
- [Data Binding](#data-binding)
|
|
25
21
|
- [Conditional Rendering](#conditional-rendering)
|
|
22
|
+
- [List Rendering](#list-rendering)
|
|
26
23
|
- [Slots](#slots)
|
|
24
|
+
- [Component Props](#component-props)
|
|
27
25
|
- [Advanced Features](#advanced-features)
|
|
28
|
-
- [External Scripts](#external-scripts)
|
|
29
26
|
- [Global Event Bus](#global-event-bus)
|
|
27
|
+
- [External Scripts](#external-scripts)
|
|
30
28
|
- [Shadow DOM](#shadow-dom)
|
|
29
|
+
- [Styling Components](#styling-components)
|
|
31
30
|
- [Performance & Caching](#performance--caching)
|
|
31
|
+
- [Common Patterns](#common-patterns)
|
|
32
|
+
- [Keyboard Events](#keyboard-events)
|
|
33
|
+
- [Form Validation](#form-validation)
|
|
34
|
+
- [Loading States](#loading-states)
|
|
32
35
|
- [API Reference](#api-reference)
|
|
33
|
-
- [
|
|
34
|
-
|
|
35
|
-
- [Dynamic Component Creation](#dynamic-component-creation)
|
|
36
|
-
- [Passing Complex Data](#passing-complex-data)
|
|
37
|
-
- [Migration Guide (v1.x to v2.0)](#migration-guide-v1x-to-v20)
|
|
38
|
-
- [Contributing](#contributing)
|
|
36
|
+
- [Development](#development)
|
|
37
|
+
- [Attribution](#attribution)
|
|
39
38
|
- [License](#license)
|
|
40
39
|
|
|
41
40
|
## Features
|
|
42
41
|
|
|
43
42
|
- 🚀 **Zero Dependencies** - Pure JavaScript, no build tools required
|
|
44
43
|
- 📦 **Single-File Components** - HTML, CSS, and JavaScript in one file
|
|
45
|
-
- ⚡ **Reactive State** - Automatic re-rendering on state changes
|
|
46
|
-
- 🎯 **Event System** -
|
|
47
|
-
- 🔄 **Two-Way Data Binding** - Seamless
|
|
44
|
+
- ⚡ **Reactive State** - Automatic re-rendering on state changes
|
|
45
|
+
- 🎯 **Event System** - Global event bus for component communication
|
|
46
|
+
- 🔄 **Two-Way Data Binding** - Seamless form input binding with `$bind`
|
|
48
47
|
- 🎨 **Scoped Styles** - Component styles with optional Shadow DOM
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
- 🔧 **Framework Utilities** -
|
|
55
|
-
-
|
|
56
|
-
- 🧩 **External Scripts** - Load and bind external JavaScript with your components
|
|
57
|
-
|
|
58
|
-
## Getting Started
|
|
59
|
-
|
|
60
|
-
### Prerequisites
|
|
61
|
-
|
|
62
|
-
- **Node.js**: Version 20.19+ or 22.12+ is required for development
|
|
63
|
-
- **TypeScript**: v5.8+ (included in devDependencies)
|
|
64
|
-
|
|
65
|
-
### What's New in v2.0
|
|
66
|
-
|
|
67
|
-
- **🔄 Complete TypeScript Rewrite** - Full type safety and improved IDE support
|
|
68
|
-
- **⚡ Performance Enhancements** - LRU caching for components and compiled functions
|
|
69
|
-
- **🎯 Global Event Bus** - New `$emit` and `$listen` for cross-component communication
|
|
70
|
-
- **🔧 Framework Utilities** - New `$state`, `$setState`, `$querySelector` helpers
|
|
71
|
-
- **🚀 Automatic Reactivity** - Variable assignments like `count++` automatically trigger re-renders
|
|
72
|
-
- **📦 Better Build System** - Vite-powered builds with sourcemaps and multiple output formats (ESM, UMD, CJS)
|
|
73
|
-
- **🧪 Testing** - Vitest with coverage reporting
|
|
74
|
-
- **🎭 Enhanced Conditionals** - More robust `data-if`, `data-else-if`, `data-else` rendering
|
|
75
|
-
- **🔄 Two-Way Binding** - Simplified with `$bind` prefix for automatic state synchronization
|
|
76
|
-
- **🧹 Memory Management** - Automatic cleanup of event listeners on component disconnect
|
|
77
|
-
|
|
78
|
-
### Example Applications
|
|
79
|
-
|
|
80
|
-
The repository includes several example applications that demonstrate various features:
|
|
81
|
-
|
|
82
|
-
- **[Todo App](samples/apps/todo)** - Classic todo list with component composition
|
|
83
|
-
- **[Notes App](samples/apps/notes)** - Multi-component app with global event bus
|
|
84
|
-
- **[Markdown Editor](samples/apps/markdown)** - Real-time markdown preview
|
|
85
|
-
- **[API Example](samples/apps/api)** - Fetching and displaying external data
|
|
86
|
-
- **[Business Card](samples/apps/biz)** - Editable form with two-way data binding using `$bind`
|
|
87
|
-
- **[Button Game](samples/apps/button-game)** - Interactive game with component events
|
|
88
|
-
- **[Slideshow](samples/apps/slideshow)** - Multi-slide presentation system
|
|
89
|
-
- **[Document Chat](samples/apps/document-chat)** - Chat interface with component communication
|
|
90
|
-
- **[Docs](samples/apps/docs)** - Documentation viewer with syntax highlighting
|
|
91
|
-
|
|
92
|
-
To run the examples:
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
# Clone the repository
|
|
96
|
-
git clone https://github.com/drubiodev/LadrillosJS.git
|
|
97
|
-
cd LadrillosJS
|
|
98
|
-
|
|
99
|
-
# Install dependencies
|
|
100
|
-
npm install
|
|
101
|
-
|
|
102
|
-
# Start the development server (Vite)
|
|
103
|
-
npm run dev
|
|
104
|
-
|
|
105
|
-
# Build the library
|
|
106
|
-
npm run build
|
|
107
|
-
|
|
108
|
-
# Run tests
|
|
109
|
-
npm test
|
|
110
|
-
|
|
111
|
-
# Run tests with coverage
|
|
112
|
-
npm run test:coverage
|
|
113
|
-
```
|
|
48
|
+
- 🔌 **Slots** - Content projection with named and default slots
|
|
49
|
+
- 📝 **TypeScript** - Full type definitions and TypeScript source code
|
|
50
|
+
- 🎭 **Conditional Rendering** - `$if`, `$else-if`, and `$else` directives
|
|
51
|
+
- � **List Rendering** - `$for` directive for rendering arrays
|
|
52
|
+
- �🚄 **Smart Caching** - LRU cache for components and compiled functions
|
|
53
|
+
- 🔧 **Framework Utilities** - Helper functions for common tasks
|
|
54
|
+
- 🧩 **External Scripts** - Load and bind external JavaScript modules
|
|
114
55
|
|
|
115
56
|
## Installation
|
|
116
57
|
|
|
@@ -123,45 +64,42 @@ npm install ladrillosjs
|
|
|
123
64
|
### CDN
|
|
124
65
|
|
|
125
66
|
```html
|
|
126
|
-
<!--
|
|
67
|
+
<!-- ES Module (Recommended) -->
|
|
127
68
|
<script type="module">
|
|
128
69
|
import { registerComponent } from "https://cdn.jsdelivr.net/npm/ladrillosjs/dist/ladrillosjs.es.js";
|
|
129
70
|
registerComponent("my-component", "./my-component.html");
|
|
130
71
|
</script>
|
|
131
72
|
|
|
132
|
-
<!-- UMD (
|
|
73
|
+
<!-- UMD (Browser Global) -->
|
|
133
74
|
<script src="https://cdn.jsdelivr.net/npm/ladrillosjs/dist/ladrillosjs.umd.js"></script>
|
|
134
75
|
<script>
|
|
135
|
-
// Access via global ladrillosjs object
|
|
136
76
|
ladrillosjs.registerComponent("my-component", "./my-component.html");
|
|
137
77
|
</script>
|
|
138
78
|
```
|
|
139
79
|
|
|
140
|
-
##
|
|
80
|
+
## Quick Start
|
|
141
81
|
|
|
142
|
-
|
|
82
|
+
### 1. Create Your First Component
|
|
143
83
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
Create `hello-world.html`:
|
|
84
|
+
Create a file called `hello-world.html`:
|
|
147
85
|
|
|
148
86
|
```html
|
|
149
87
|
<!-- hello-world.html -->
|
|
150
88
|
<div class="greeting">
|
|
151
89
|
<h1>{title}</h1>
|
|
152
90
|
<p>Hello, {name}!</p>
|
|
153
|
-
<button onclick="greet">
|
|
91
|
+
<button onclick="greet()">Greet ({count})</button>
|
|
154
92
|
</div>
|
|
155
93
|
|
|
156
94
|
<script>
|
|
157
|
-
// Component state
|
|
95
|
+
// Component state - automatically reactive
|
|
158
96
|
let title = "Welcome to LadrillosJS";
|
|
159
97
|
let name = "World";
|
|
160
98
|
let count = 0;
|
|
161
99
|
|
|
162
100
|
// Event handler
|
|
163
101
|
const greet = () => {
|
|
164
|
-
count++;
|
|
102
|
+
count++; // Automatically triggers re-render
|
|
165
103
|
name = prompt("What's your name?") || "World";
|
|
166
104
|
};
|
|
167
105
|
</script>
|
|
@@ -170,14 +108,24 @@ Create `hello-world.html`:
|
|
|
170
108
|
.greeting {
|
|
171
109
|
text-align: center;
|
|
172
110
|
padding: 2rem;
|
|
173
|
-
background: #
|
|
111
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
112
|
+
color: white;
|
|
174
113
|
border-radius: 8px;
|
|
175
114
|
}
|
|
176
115
|
|
|
177
116
|
button {
|
|
178
|
-
padding: 0.5rem
|
|
117
|
+
padding: 0.75rem 1.5rem;
|
|
179
118
|
font-size: 1rem;
|
|
119
|
+
background: white;
|
|
120
|
+
color: #667eea;
|
|
121
|
+
border: none;
|
|
122
|
+
border-radius: 4px;
|
|
180
123
|
cursor: pointer;
|
|
124
|
+
transition: transform 0.2s;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
button:hover {
|
|
128
|
+
transform: scale(1.05);
|
|
181
129
|
}
|
|
182
130
|
</style>
|
|
183
131
|
```
|
|
@@ -186,15 +134,17 @@ Create `hello-world.html`:
|
|
|
186
134
|
|
|
187
135
|
```html
|
|
188
136
|
<!DOCTYPE html>
|
|
189
|
-
<html>
|
|
137
|
+
<html lang="en">
|
|
190
138
|
<head>
|
|
191
|
-
<
|
|
139
|
+
<meta charset="UTF-8" />
|
|
140
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
141
|
+
<title>My LadrillosJS App</title>
|
|
192
142
|
</head>
|
|
193
143
|
<body>
|
|
194
144
|
<!-- Use your component -->
|
|
195
145
|
<hello-world></hello-world>
|
|
196
146
|
|
|
197
|
-
<!-- Register component -->
|
|
147
|
+
<!-- Register the component -->
|
|
198
148
|
<script type="module">
|
|
199
149
|
import { registerComponent } from "ladrillosjs";
|
|
200
150
|
registerComponent("hello-world", "./hello-world.html");
|
|
@@ -203,231 +153,349 @@ Create `hello-world.html`:
|
|
|
203
153
|
</html>
|
|
204
154
|
```
|
|
205
155
|
|
|
156
|
+
### 3. Explore Example Apps
|
|
157
|
+
|
|
158
|
+
Check out the `samples/apps/` directory for complete examples:
|
|
159
|
+
|
|
160
|
+
- **[Todo App](samples/apps/todo)** - Classic todo list with component composition
|
|
161
|
+
- **[Notes App](samples/apps/notes)** - Multi-component app with event bus
|
|
162
|
+
- **[Simple Button](samples/apps/simple-button)** - Basic interactive component
|
|
163
|
+
- **[Business Card](samples/apps/biz)** - Form with two-way data binding
|
|
164
|
+
- **[Slideshow](samples/apps/slideshow)** - Multi-slide presentation
|
|
165
|
+
- **[Markdown Editor](samples/apps/markdown)** - Real-time markdown preview
|
|
166
|
+
- **[List Rendering](samples/apps/list-test)** - Dynamic lists with `$for` directive
|
|
167
|
+
|
|
168
|
+
Run the development server to view examples:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm install
|
|
172
|
+
npm run dev
|
|
173
|
+
```
|
|
174
|
+
|
|
206
175
|
## Core Concepts
|
|
207
176
|
|
|
208
177
|
### Component Registration
|
|
209
178
|
|
|
210
|
-
Register
|
|
179
|
+
Register components to make them available as custom HTML elements:
|
|
211
180
|
|
|
212
181
|
```javascript
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
182
|
+
import { registerComponent, registerComponents } from "ladrillosjs";
|
|
183
|
+
|
|
184
|
+
// Register a single component
|
|
185
|
+
await registerComponent("my-button", "./components/my-button.html");
|
|
216
186
|
|
|
217
|
-
//
|
|
218
|
-
import { registerComponents } from "ladrillosjs";
|
|
187
|
+
// Register multiple components
|
|
219
188
|
await registerComponents([
|
|
220
189
|
{ name: "app-header", path: "./components/header.html" },
|
|
221
190
|
{ name: "app-footer", path: "./components/footer.html" },
|
|
222
191
|
{ name: "user-card", path: "./components/user-card.html" },
|
|
223
192
|
]);
|
|
224
193
|
|
|
225
|
-
//
|
|
226
|
-
|
|
194
|
+
// Disable Shadow DOM for a component
|
|
195
|
+
await registerComponent("global-styles", "./components/global.html", false);
|
|
227
196
|
```
|
|
228
197
|
|
|
229
|
-
**Note:** The `registerComponents` function is planned for v2.0 to enable bulk registration with concurrency control.
|
|
230
|
-
|
|
231
198
|
### State Management
|
|
232
199
|
|
|
233
|
-
Components have reactive state that automatically triggers re-renders
|
|
200
|
+
Components have reactive state that automatically triggers re-renders when changed:
|
|
234
201
|
|
|
235
202
|
```html
|
|
236
203
|
<div>
|
|
237
|
-
<h2>
|
|
238
|
-
<p>
|
|
239
|
-
<button onclick="
|
|
240
|
-
<button onclick="
|
|
204
|
+
<h2>Counter: {count}</h2>
|
|
205
|
+
<p>User: {user.name}</p>
|
|
206
|
+
<button onclick="increment()">Add</button>
|
|
207
|
+
<button onclick="updateUser()">Change User</button>
|
|
208
|
+
<button onclick="reset()">Reset</button>
|
|
241
209
|
</div>
|
|
242
210
|
|
|
243
211
|
<script>
|
|
244
|
-
//
|
|
245
|
-
let score = 0;
|
|
212
|
+
// State variables - automatically reactive
|
|
246
213
|
let count = 0;
|
|
247
|
-
let user = {
|
|
248
|
-
name: "Player 1",
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const updateScore = () => {
|
|
252
|
-
// Direct assignment triggers re-render automatically
|
|
253
|
-
score++;
|
|
254
|
-
};
|
|
214
|
+
let user = { name: "John", age: 25 };
|
|
255
215
|
|
|
256
216
|
const increment = () => {
|
|
257
|
-
//
|
|
258
|
-
count++; // Increment
|
|
259
|
-
count += 5; // Compound assignment
|
|
260
|
-
count = count * 2; // Direct assignment
|
|
217
|
+
count++; // Automatically triggers re-render
|
|
261
218
|
};
|
|
262
219
|
|
|
263
|
-
// You can also use the explicit setState method
|
|
264
220
|
const updateUser = () => {
|
|
265
|
-
|
|
221
|
+
user.name = "Jane"; // Direct mutation triggers re-render
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// You can also use $setState for explicit updates
|
|
225
|
+
const reset = () => {
|
|
226
|
+
$setState({ count: 0, user: { name: "Anonymous", age: 0 } });
|
|
266
227
|
};
|
|
267
228
|
</script>
|
|
268
229
|
```
|
|
269
230
|
|
|
270
|
-
**
|
|
231
|
+
**Available State Utilities:**
|
|
271
232
|
|
|
272
|
-
-
|
|
273
|
-
-
|
|
274
|
-
-
|
|
233
|
+
- Direct assignment: `count++`, `name = "New"`
|
|
234
|
+
- `$setState(updates)`: Merge updates into state
|
|
235
|
+
- `$getState()`: Access state in external modules
|
|
275
236
|
|
|
276
237
|
### Event Handling
|
|
277
238
|
|
|
278
|
-
|
|
239
|
+
Attach event handlers directly to elements:
|
|
279
240
|
|
|
280
241
|
```html
|
|
281
242
|
<!-- Method reference -->
|
|
282
|
-
<button onclick="handleClick">Click me
|
|
243
|
+
<button onclick="handleClick(event)">Click me</button>
|
|
283
244
|
|
|
284
245
|
<!-- Function with arguments -->
|
|
285
|
-
<button onclick="addItem('
|
|
246
|
+
<button onclick="addItem('apple', 5)">Add Item</button>
|
|
286
247
|
|
|
287
248
|
<!-- Inline arrow function -->
|
|
288
|
-
<button onclick="
|
|
289
|
-
|
|
290
|
-
<!--
|
|
291
|
-
<
|
|
249
|
+
<button onclick="console.log(event.target)">Log Event</button>
|
|
250
|
+
|
|
251
|
+
<!-- Multiple events -->
|
|
252
|
+
<label $if="isValid">Valid</label>
|
|
253
|
+
<input
|
|
254
|
+
type="text"
|
|
255
|
+
placeholder="Enter text (min 3 characters)"
|
|
256
|
+
onkeyup="validateInput(event)"
|
|
257
|
+
onfocus="highlightField(event)"
|
|
258
|
+
onblur="saveField(event)"
|
|
259
|
+
/>
|
|
292
260
|
|
|
293
261
|
<script>
|
|
294
|
-
let count = 0;
|
|
295
262
|
let items = [];
|
|
263
|
+
let isValid = false;
|
|
296
264
|
|
|
297
265
|
const handleClick = (event) => {
|
|
298
|
-
console.log("
|
|
299
|
-
count++;
|
|
266
|
+
console.log("Button clicked!", event);
|
|
300
267
|
};
|
|
301
268
|
|
|
302
|
-
const addItem = (name,
|
|
303
|
-
items = [...items, { name,
|
|
269
|
+
const addItem = (name, quantity) => {
|
|
270
|
+
items = [...items, { name, quantity }];
|
|
271
|
+
console.log("Items:", items);
|
|
304
272
|
};
|
|
305
273
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
274
|
+
const validateInput = (e) => {
|
|
275
|
+
const value = e.target.value;
|
|
276
|
+
isValid = value.length >= 3;
|
|
309
277
|
};
|
|
310
278
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
279
|
+
const highlightField = (e) => {
|
|
280
|
+
e.target.style.backgroundColor = "lightyellow";
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const saveField = (e) => {
|
|
284
|
+
e.target.style.backgroundColor = "";
|
|
285
|
+
console.log("Field saved:", e.target.value);
|
|
286
|
+
};
|
|
316
287
|
</script>
|
|
317
288
|
```
|
|
318
289
|
|
|
319
|
-
**New in v2.0:**
|
|
320
|
-
|
|
321
|
-
- **`$emit(eventName, data)`**: Send events to other components via global event bus
|
|
322
|
-
- **`$listen(eventName, callback)`**: Listen for events from any component
|
|
323
|
-
- **Automatic Cleanup**: Event listeners are automatically removed when component is disconnected
|
|
324
|
-
|
|
325
290
|
### Data Binding
|
|
326
291
|
|
|
327
|
-
|
|
292
|
+
#### One-Way Binding (Display Data)
|
|
328
293
|
|
|
329
|
-
|
|
294
|
+
Use curly braces `{}` to display state in your template:
|
|
330
295
|
|
|
331
296
|
```html
|
|
332
297
|
<div>
|
|
333
298
|
<h1>{title}</h1>
|
|
334
299
|
<p>{user.name} - {user.email}</p>
|
|
335
|
-
<span>
|
|
300
|
+
<span>Total: {items.length} items</span>
|
|
301
|
+
<p>Formatted: {formatPrice(price)}</p>
|
|
336
302
|
</div>
|
|
337
303
|
|
|
338
304
|
<script>
|
|
339
305
|
let title = "My App";
|
|
340
306
|
let user = { name: "John", email: "john@example.com" };
|
|
341
|
-
let items = [
|
|
307
|
+
let items = ["apple", "banana", "orange"];
|
|
308
|
+
let price = 29.99;
|
|
309
|
+
|
|
310
|
+
const formatPrice = (value) => `$${value.toFixed(2)}`;
|
|
342
311
|
</script>
|
|
343
312
|
```
|
|
344
313
|
|
|
345
|
-
#### Two-Way Binding (
|
|
314
|
+
#### Two-Way Binding (Form Inputs)
|
|
346
315
|
|
|
347
|
-
Use the `$bind`
|
|
316
|
+
Use the `$bind` attribute for automatic synchronization between inputs and state:
|
|
348
317
|
|
|
349
318
|
```html
|
|
350
319
|
<div>
|
|
351
|
-
<h2>Hello, {
|
|
352
|
-
<input type="text" $bind="name" placeholder="
|
|
320
|
+
<h2>Hello, {name}!</h2>
|
|
321
|
+
<input type="text" $bind="name" placeholder="Your name" />
|
|
353
322
|
|
|
354
|
-
<p>Email: {
|
|
323
|
+
<p>Email: {email}</p>
|
|
355
324
|
<input type="email" $bind="email" />
|
|
356
325
|
|
|
357
|
-
<p>Bio: {
|
|
326
|
+
<p>Bio: {bio}</p>
|
|
358
327
|
<textarea $bind="bio"></textarea>
|
|
359
328
|
|
|
360
|
-
<p>Country: {
|
|
329
|
+
<p>Country: {country}</p>
|
|
361
330
|
<select $bind="country">
|
|
362
331
|
<option value="us">United States</option>
|
|
363
332
|
<option value="uk">United Kingdom</option>
|
|
364
333
|
<option value="ca">Canada</option>
|
|
365
334
|
</select>
|
|
335
|
+
|
|
336
|
+
<label>
|
|
337
|
+
<input type="checkbox" $bind="subscribe" />
|
|
338
|
+
Subscribe to newsletter: {subscribe}
|
|
339
|
+
</label>
|
|
366
340
|
</div>
|
|
367
341
|
|
|
368
342
|
<script>
|
|
369
|
-
// Variables
|
|
370
|
-
let
|
|
371
|
-
let
|
|
372
|
-
let
|
|
373
|
-
let
|
|
343
|
+
// Variables are automatically synced with inputs
|
|
344
|
+
let name = "World";
|
|
345
|
+
let email = "";
|
|
346
|
+
let bio = "";
|
|
347
|
+
let country = "us";
|
|
348
|
+
let subscribe = false;
|
|
374
349
|
</script>
|
|
375
350
|
```
|
|
376
351
|
|
|
377
|
-
**New in v2.0:**
|
|
378
|
-
|
|
379
|
-
- **`$bind` attribute**: Simplified two-way binding syntax
|
|
380
|
-
- **Automatic State Sync**: Input changes automatically update component state
|
|
381
|
-
- **All Input Types**: Works with text inputs, textareas, selects, checkboxes, and radio buttons
|
|
382
|
-
- **Nested Paths**: Supports nested object bindings like `$bind="user.email"`
|
|
383
|
-
|
|
384
352
|
### Conditional Rendering
|
|
385
353
|
|
|
386
|
-
|
|
354
|
+
Show or hide elements based on conditions:
|
|
387
355
|
|
|
388
356
|
```html
|
|
389
357
|
<div>
|
|
390
|
-
<h1>Shopping Cart
|
|
358
|
+
<h1>Shopping Cart</h1>
|
|
391
359
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
</div>
|
|
360
|
+
<!-- Simple condition -->
|
|
361
|
+
<p $if="{items.length === 0}">Your cart is empty</p>
|
|
395
362
|
|
|
396
|
-
|
|
397
|
-
|
|
363
|
+
<!-- Multiple conditions -->
|
|
364
|
+
<div $if="{items.length > 0 && items.length < 5}">
|
|
365
|
+
<p>You have {items.length} items</p>
|
|
398
366
|
</div>
|
|
399
367
|
|
|
400
|
-
<div
|
|
401
|
-
<p>
|
|
368
|
+
<div $else-if="{items.length >= 5}">
|
|
369
|
+
<p>Your cart is full! ({items.length} items)</p>
|
|
402
370
|
</div>
|
|
403
371
|
|
|
404
|
-
|
|
405
|
-
<button
|
|
372
|
+
<!-- Login/Logout example -->
|
|
373
|
+
<button $if="{!isLoggedIn}" onclick="login()">Login</button>
|
|
374
|
+
<button $else onclick="logout()">Logout</button>
|
|
375
|
+
|
|
376
|
+
<!-- Complex conditions -->
|
|
377
|
+
<div $if="{user && user.role.toLowerCase() === 'admin'}">
|
|
378
|
+
<p>{user.role} Panel</p>
|
|
379
|
+
Hello {user.name}
|
|
380
|
+
<button onclick="addToCart()">🛒 Add To Cart</button>
|
|
381
|
+
</div>
|
|
406
382
|
</div>
|
|
407
383
|
|
|
408
384
|
<script>
|
|
409
|
-
let items = [
|
|
385
|
+
let items = [];
|
|
410
386
|
let isLoggedIn = false;
|
|
387
|
+
let user = null;
|
|
411
388
|
|
|
412
389
|
const login = () => {
|
|
413
390
|
isLoggedIn = true;
|
|
391
|
+
user = { name: "John", role: "Admin" };
|
|
414
392
|
};
|
|
415
393
|
|
|
416
394
|
const logout = () => {
|
|
417
395
|
isLoggedIn = false;
|
|
396
|
+
user = null;
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const addToCart = () => {
|
|
400
|
+
if (items.length < 5) {
|
|
401
|
+
items.push(`Item ${items.length + 1}`);
|
|
402
|
+
} else {
|
|
403
|
+
alert("Cart is full!");
|
|
404
|
+
}
|
|
418
405
|
};
|
|
419
406
|
</script>
|
|
420
407
|
```
|
|
421
408
|
|
|
422
409
|
**Conditional Directives:**
|
|
423
410
|
|
|
424
|
-
-
|
|
425
|
-
-
|
|
426
|
-
-
|
|
411
|
+
- `$if="{expression}"`: Show if expression is truthy
|
|
412
|
+
- `$else-if="{expression}"`: Chain multiple conditions
|
|
413
|
+
- `$else`: Fallback when previous conditions are false
|
|
414
|
+
|
|
415
|
+
### List Rendering
|
|
416
|
+
|
|
417
|
+
Render lists of items using the `$for` directive:
|
|
418
|
+
|
|
419
|
+
```html
|
|
420
|
+
<div>
|
|
421
|
+
<h2>My Fruits</h2>
|
|
422
|
+
<ul>
|
|
423
|
+
<li $for="fruit in fruits">{fruit}</li>
|
|
424
|
+
</ul>
|
|
425
|
+
<button onclick="addFruit()">Add Fruit</button>
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
<script>
|
|
429
|
+
let fruits = ["Apple", "Banana", "Orange"];
|
|
430
|
+
|
|
431
|
+
const addFruit = () => {
|
|
432
|
+
const newFruit = prompt("Enter a fruit name:");
|
|
433
|
+
if (newFruit) {
|
|
434
|
+
fruits = [...fruits, newFruit]; // Triggers re-render
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
</script>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**List Rendering with Objects:**
|
|
441
|
+
|
|
442
|
+
```html
|
|
443
|
+
<div>
|
|
444
|
+
<h2>User List</h2>
|
|
445
|
+
<div class="user-card" $for="user in users">
|
|
446
|
+
<h3>{user.name}</h3>
|
|
447
|
+
<p>Email: {user.email}</p>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
<script>
|
|
452
|
+
let users = [
|
|
453
|
+
{ name: "John Doe", email: "john@example.com" },
|
|
454
|
+
{ name: "Jane Smith", email: "jane@example.com" },
|
|
455
|
+
];
|
|
456
|
+
</script>
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**With Index:**
|
|
460
|
+
|
|
461
|
+
```html
|
|
462
|
+
<ul>
|
|
463
|
+
<li $for="(item, index) in items">{index + 1}. {item}</li>
|
|
464
|
+
</ul>
|
|
465
|
+
|
|
466
|
+
<script>
|
|
467
|
+
let items = ["First", "Second", "Third"];
|
|
468
|
+
</script>
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Performance Optimization with `$key`:**
|
|
472
|
+
|
|
473
|
+
Use the `$key` attribute to help LadrillosJS track items efficiently:
|
|
474
|
+
|
|
475
|
+
```html
|
|
476
|
+
<div>
|
|
477
|
+
<div class="user-card" $for="user in users" $key="user.id">
|
|
478
|
+
<h3>{user.name}</h3>
|
|
479
|
+
<p>Email: {user.email}</p>
|
|
480
|
+
<button onclick="removeUser(user.id)">Remove</button>
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
|
|
484
|
+
<script>
|
|
485
|
+
let users = [
|
|
486
|
+
{ id: 1, name: "John", email: "john@example.com" },
|
|
487
|
+
{ id: 2, name: "Jane", email: "jane@example.com" },
|
|
488
|
+
];
|
|
489
|
+
|
|
490
|
+
const removeUser = (id) => {
|
|
491
|
+
users = users.filter((u) => u.id !== id);
|
|
492
|
+
};
|
|
493
|
+
</script>
|
|
494
|
+
```
|
|
427
495
|
|
|
428
496
|
### Slots
|
|
429
497
|
|
|
430
|
-
|
|
498
|
+
Project content from parent to child components:
|
|
431
499
|
|
|
432
500
|
```html
|
|
433
501
|
<!-- card.html -->
|
|
@@ -444,450 +512,589 @@ Content projection using slots:
|
|
|
444
512
|
</div>
|
|
445
513
|
</div>
|
|
446
514
|
|
|
447
|
-
|
|
515
|
+
<style>
|
|
516
|
+
.card {
|
|
517
|
+
border: 1px solid #ddd;
|
|
518
|
+
border-radius: 8px;
|
|
519
|
+
padding: 1rem;
|
|
520
|
+
}
|
|
521
|
+
.card-header {
|
|
522
|
+
font-weight: bold;
|
|
523
|
+
margin-bottom: 1rem;
|
|
524
|
+
}
|
|
525
|
+
.card-footer {
|
|
526
|
+
margin-top: 1rem;
|
|
527
|
+
border-top: 1px solid #eee;
|
|
528
|
+
padding-top: 1rem;
|
|
529
|
+
}
|
|
530
|
+
</style>
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Usage:**
|
|
534
|
+
|
|
535
|
+
```html
|
|
448
536
|
<my-card>
|
|
449
537
|
<h2 slot="header">User Profile</h2>
|
|
450
|
-
<p>
|
|
451
|
-
<
|
|
538
|
+
<p>Name: John Doe</p>
|
|
539
|
+
<p>Email: john@example.com</p>
|
|
540
|
+
<button slot="footer">Save Changes</button>
|
|
452
541
|
</my-card>
|
|
453
542
|
```
|
|
454
543
|
|
|
455
|
-
|
|
544
|
+
### Component Props
|
|
456
545
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
Load external JavaScript with components and bind them to the component context:
|
|
546
|
+
Pass data to components using HTML attributes:
|
|
460
547
|
|
|
461
548
|
```html
|
|
462
|
-
<!--
|
|
463
|
-
<
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
549
|
+
<!-- greeting.html -->
|
|
550
|
+
<div>
|
|
551
|
+
<h1>Hello, {name}!</h1>
|
|
552
|
+
<p>Age: {age}</p>
|
|
553
|
+
</div>
|
|
467
554
|
|
|
468
|
-
|
|
469
|
-
|
|
555
|
+
<script>
|
|
556
|
+
// Access attributes passed to the component
|
|
557
|
+
let name = this.getAttribute("name") || "Guest";
|
|
558
|
+
let age = this.getAttribute("age") || "unknown";
|
|
559
|
+
</script>
|
|
470
560
|
```
|
|
471
561
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
```javascript
|
|
475
|
-
// component-logic.js
|
|
476
|
-
export default function () {
|
|
477
|
-
// 'this' refers to the component instance
|
|
478
|
-
// Access component utilities
|
|
479
|
-
const { $state, $setState, $emit, $listen } = this;
|
|
480
|
-
|
|
481
|
-
this.formatDate = (date) => {
|
|
482
|
-
return new Intl.DateTimeFormat("en-US").format(date);
|
|
483
|
-
};
|
|
562
|
+
**Usage:**
|
|
484
563
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
$setState({ data });
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
// Listen for events from other components
|
|
492
|
-
$listen("refresh-data", () => {
|
|
493
|
-
this.loadData();
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
// Called automatically if defined
|
|
497
|
-
if (this.init) {
|
|
498
|
-
this.init();
|
|
499
|
-
}
|
|
500
|
-
}
|
|
564
|
+
```html
|
|
565
|
+
<my-greeting name="John" age="25"></my-greeting>
|
|
566
|
+
<my-greeting name="Jane" age="30"></my-greeting>
|
|
501
567
|
```
|
|
502
568
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
- **Better Context Binding**: External scripts get full access to component utilities
|
|
506
|
-
- **`$emit` and `$listen`**: Available in external scripts for event communication
|
|
507
|
-
- **Automatic Initialization**: Functions are auto-attached to component context
|
|
569
|
+
## Advanced Features
|
|
508
570
|
|
|
509
571
|
### Global Event Bus
|
|
510
572
|
|
|
511
|
-
|
|
573
|
+
The global event bus enables communication between components without prop drilling:
|
|
512
574
|
|
|
513
575
|
```javascript
|
|
514
|
-
//
|
|
515
|
-
// Emit an event
|
|
576
|
+
// Emit events to other components
|
|
516
577
|
$emit("user-logged-in", { userId: 123, username: "john" });
|
|
517
578
|
|
|
518
|
-
// Listen for events
|
|
579
|
+
// Listen for events from any component
|
|
519
580
|
$listen("user-logged-in", (data) => {
|
|
520
581
|
console.log(`User ${data.username} logged in`);
|
|
521
|
-
// Update local state
|
|
522
582
|
isLoggedIn = true;
|
|
523
583
|
currentUser = data;
|
|
524
584
|
});
|
|
525
585
|
```
|
|
526
586
|
|
|
527
|
-
|
|
587
|
+
**Example: Cross-Component Communication**
|
|
528
588
|
|
|
529
589
|
```html
|
|
530
590
|
<!-- header.html -->
|
|
531
591
|
<header>
|
|
532
|
-
<span
|
|
533
|
-
<button
|
|
592
|
+
<span $if="{isLoggedIn}">Welcome, {username}!</span>
|
|
593
|
+
<button $else onclick="requestLogin()">Login</button>
|
|
534
594
|
</header>
|
|
535
595
|
|
|
536
596
|
<script>
|
|
537
597
|
let isLoggedIn = false;
|
|
538
598
|
let username = "";
|
|
539
599
|
|
|
540
|
-
// Listen for login event from other components
|
|
541
600
|
$listen("user-logged-in", (user) => {
|
|
601
|
+
console.log("User logged in:", user);
|
|
542
602
|
isLoggedIn = true;
|
|
543
603
|
username = user.username;
|
|
544
604
|
});
|
|
545
605
|
|
|
546
|
-
const
|
|
547
|
-
$emit("show-login-
|
|
606
|
+
const requestLogin = () => {
|
|
607
|
+
$emit("show-login-dialog");
|
|
548
608
|
};
|
|
549
609
|
</script>
|
|
550
610
|
```
|
|
551
611
|
|
|
552
612
|
```html
|
|
553
613
|
<!-- login-form.html -->
|
|
554
|
-
<form onsubmit="handleLogin">
|
|
614
|
+
<form onsubmit="handleLogin(event)" $if="{showLoginDialog}">
|
|
555
615
|
<input type="text" $bind="username" placeholder="Username" />
|
|
556
616
|
<input type="password" $bind="password" placeholder="Password" />
|
|
557
617
|
<button type="submit">Login</button>
|
|
558
618
|
</form>
|
|
559
619
|
|
|
560
620
|
<script>
|
|
561
|
-
let
|
|
562
|
-
|
|
621
|
+
let showLoginDialog = false;
|
|
622
|
+
|
|
623
|
+
$listen("show-login-dialog", () => {
|
|
624
|
+
showLoginDialog = true;
|
|
625
|
+
});
|
|
563
626
|
|
|
564
627
|
const handleLogin = (e) => {
|
|
565
628
|
e.preventDefault();
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
username: $username,
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
// Clear form
|
|
574
|
-
$username = "";
|
|
575
|
-
$password = "";
|
|
629
|
+
$emit("user-logged-in", { userId: 123, username });
|
|
630
|
+
username = "";
|
|
631
|
+
password = "";
|
|
632
|
+
showLoginDialog = false;
|
|
576
633
|
};
|
|
577
634
|
</script>
|
|
578
635
|
```
|
|
579
636
|
|
|
580
|
-
|
|
637
|
+
### External Scripts
|
|
638
|
+
|
|
639
|
+
LadrillosJS supports three ways to include external JavaScript:
|
|
640
|
+
|
|
641
|
+
#### 1. Component-Scoped Scripts (Default)
|
|
581
642
|
|
|
582
|
-
|
|
583
|
-
- **Decoupled Architecture**: Components don't need to know about each other
|
|
584
|
-
- **Automatic Cleanup**: Listeners are removed when components disconnect
|
|
585
|
-
- **Promise Support**: `$emit` returns a promise when listeners are async
|
|
643
|
+
Regular script tags execute within the component's context and have access to component state and utilities:
|
|
586
644
|
|
|
587
|
-
|
|
645
|
+
```html
|
|
646
|
+
<!-- alert-button.html -->
|
|
647
|
+
<button onclick="increaseCount()">{title}: {count}</button>
|
|
588
648
|
|
|
589
|
-
|
|
649
|
+
<!-- This script runs in the component context -->
|
|
650
|
+
<script src="./alert.js"></script>
|
|
651
|
+
```
|
|
590
652
|
|
|
591
|
-
**
|
|
653
|
+
**alert.js:**
|
|
592
654
|
|
|
593
655
|
```javascript
|
|
594
|
-
|
|
656
|
+
// Variables and functions are available to the component
|
|
657
|
+
let count = 0;
|
|
658
|
+
const title = "Button Count";
|
|
595
659
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
660
|
+
const increaseCount = () => {
|
|
661
|
+
count++; // Updates component state
|
|
662
|
+
};
|
|
663
|
+
```
|
|
600
664
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
665
|
+
#### 2. ES Modules
|
|
666
|
+
|
|
667
|
+
Use `type="module"` for standard ES module imports:
|
|
668
|
+
|
|
669
|
+
```html
|
|
670
|
+
<!-- side-nav.html -->
|
|
671
|
+
<nav>
|
|
672
|
+
<h1><Note App/></h1>
|
|
673
|
+
<button onclick="createNote()">Create Note</button>
|
|
674
|
+
<ul></ul>
|
|
675
|
+
</nav>
|
|
676
|
+
|
|
677
|
+
<!-- Load as ES module -->
|
|
678
|
+
<script type="module" src="../js/side.js"></script>
|
|
604
679
|
```
|
|
605
680
|
|
|
606
|
-
**
|
|
681
|
+
**side.js:**
|
|
607
682
|
|
|
608
683
|
```javascript
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
684
|
+
import { registerComponent, $listen, $querySelector } from "ladrillosjs";
|
|
685
|
+
|
|
686
|
+
const notes = [];
|
|
687
|
+
registerComponent("note-item", "./components/note-item.html");
|
|
688
|
+
|
|
689
|
+
$listen("note_saved", (data) => {
|
|
690
|
+
notes.push({ ...data });
|
|
691
|
+
const ul = $querySelector("ul");
|
|
692
|
+
if (ul) {
|
|
693
|
+
ul.innerHTML = notes
|
|
694
|
+
.map((n) => `<note-item data-note='${JSON.stringify(n)}'></note-item>`)
|
|
695
|
+
.join("");
|
|
696
|
+
}
|
|
617
697
|
});
|
|
618
698
|
```
|
|
619
699
|
|
|
700
|
+
#### 3. External Libraries
|
|
701
|
+
|
|
702
|
+
Use the `external` attribute for third-party libraries that shouldn't be bound to component context:
|
|
703
|
+
|
|
704
|
+
```html
|
|
705
|
+
<!-- codeblock.html -->
|
|
706
|
+
<link
|
|
707
|
+
rel="stylesheet"
|
|
708
|
+
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css"
|
|
709
|
+
/>
|
|
710
|
+
<script
|
|
711
|
+
src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"
|
|
712
|
+
external
|
|
713
|
+
></script>
|
|
714
|
+
|
|
715
|
+
<div class="code-container">
|
|
716
|
+
<pre><code id="code" class="language-html"></code></pre>
|
|
717
|
+
</div>
|
|
718
|
+
|
|
719
|
+
<script>
|
|
720
|
+
// Access the external library (hljs is global)
|
|
721
|
+
const codeElement = $querySelector("pre code");
|
|
722
|
+
codeElement.textContent = "console.log('Hello')";
|
|
723
|
+
hljs.highlightElement(codeElement); // Use external library
|
|
724
|
+
</script>
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
**When to use `external`:**
|
|
728
|
+
|
|
729
|
+
- Third-party CDN libraries (highlight.js, Chart.js, etc.)
|
|
730
|
+
- Libraries that need to be loaded globally
|
|
731
|
+
- Scripts that don't need component context or utilities
|
|
732
|
+
|
|
620
733
|
### Shadow DOM
|
|
621
734
|
|
|
622
|
-
Components use Shadow DOM by default for style encapsulation
|
|
735
|
+
Components use Shadow DOM by default for style encapsulation:
|
|
623
736
|
|
|
624
737
|
```javascript
|
|
625
|
-
//
|
|
626
|
-
await registerComponent("
|
|
627
|
-
|
|
628
|
-
// With Shadow DOM enabled (default)
|
|
738
|
+
// Shadow DOM enabled (default)
|
|
739
|
+
await registerComponent("isolated-widget", "./widget.html");
|
|
629
740
|
await registerComponent("isolated-widget", "./widget.html", true);
|
|
741
|
+
|
|
742
|
+
// Shadow DOM disabled
|
|
743
|
+
await registerComponent("global-styles", "./global.html", false);
|
|
630
744
|
```
|
|
631
745
|
|
|
632
746
|
**Shadow DOM Benefits:**
|
|
633
747
|
|
|
634
748
|
- **Style Isolation**: Component styles don't leak to global scope
|
|
635
|
-
- **Encapsulation**: Internal DOM
|
|
636
|
-
- **
|
|
749
|
+
- **Encapsulation**: Internal DOM is hidden from parent
|
|
750
|
+
- **Clean Separation**: Each component has its own styling context
|
|
637
751
|
|
|
638
752
|
**When to Disable:**
|
|
639
753
|
|
|
640
|
-
- Need global CSS
|
|
641
|
-
- Using third-party
|
|
642
|
-
-
|
|
754
|
+
- Need to apply global CSS frameworks (Bootstrap, Tailwind)
|
|
755
|
+
- Using third-party libraries that expect normal DOM
|
|
756
|
+
- Easier debugging without shadow boundaries
|
|
643
757
|
|
|
644
|
-
###
|
|
758
|
+
### Styling Components
|
|
645
759
|
|
|
646
|
-
|
|
760
|
+
LadrillosJS supports multiple ways to style your components:
|
|
647
761
|
|
|
648
|
-
####
|
|
762
|
+
#### 1. Inline Styles (Scoped)
|
|
649
763
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
- **Faster Re-renders**: Cached components load instantly on re-use
|
|
764
|
+
```html
|
|
765
|
+
<div class="button">{label}</div>
|
|
653
766
|
|
|
654
|
-
|
|
767
|
+
<style>
|
|
768
|
+
.button {
|
|
769
|
+
padding: 10px 20px;
|
|
770
|
+
background: #007bff;
|
|
771
|
+
color: white;
|
|
772
|
+
}
|
|
773
|
+
</style>
|
|
655
774
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
775
|
+
<script>
|
|
776
|
+
let label = "Click me";
|
|
777
|
+
</script>
|
|
778
|
+
```
|
|
659
779
|
|
|
660
|
-
|
|
780
|
+
#### 2. External Stylesheets
|
|
661
781
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
782
|
+
```html
|
|
783
|
+
<link rel="stylesheet" href="./styles.css" />
|
|
784
|
+
<div class="container">Content</div>
|
|
785
|
+
```
|
|
666
786
|
|
|
667
|
-
|
|
787
|
+
#### 3. Import Fonts and External CSS
|
|
668
788
|
|
|
669
|
-
|
|
789
|
+
```html
|
|
790
|
+
<style>
|
|
791
|
+
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
|
|
670
792
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
793
|
+
body {
|
|
794
|
+
font-family: "Roboto", sans-serif;
|
|
795
|
+
}
|
|
796
|
+
</style>
|
|
797
|
+
```
|
|
674
798
|
|
|
675
|
-
|
|
799
|
+
#### 4. Using `:host` Selector (Shadow DOM)
|
|
676
800
|
|
|
677
|
-
|
|
801
|
+
When using Shadow DOM, style the component's container with `:host`:
|
|
678
802
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
| `$querySelectorAll(selector)` | Query all matching elements within component |
|
|
803
|
+
```html
|
|
804
|
+
<style>
|
|
805
|
+
:host {
|
|
806
|
+
display: block;
|
|
807
|
+
padding: 1rem;
|
|
808
|
+
background: white;
|
|
809
|
+
}
|
|
687
810
|
|
|
688
|
-
|
|
811
|
+
:host(.highlighted) {
|
|
812
|
+
border: 2px solid gold;
|
|
813
|
+
}
|
|
814
|
+
</style>
|
|
815
|
+
```
|
|
689
816
|
|
|
690
|
-
|
|
691
|
-
| --------------------------- | ----------------------------------------------------------------------------------- |
|
|
692
|
-
| `$bind="variableName"` | Create two-way data binding with form inputs |
|
|
693
|
-
| `data-if="expression"` | Conditionally render element if expression is truthy |
|
|
694
|
-
| `data-else-if="expression"` | Chain multiple conditional expressions |
|
|
695
|
-
| `data-else` | Render when all previous conditions are false |
|
|
696
|
-
| `onclick="handler"` (etc.) | Attach event handlers (supports method names, inline functions, or arrow functions) |
|
|
817
|
+
**Usage:**
|
|
697
818
|
|
|
698
|
-
|
|
819
|
+
```html
|
|
820
|
+
<my-component class="highlighted"></my-component>
|
|
821
|
+
```
|
|
699
822
|
|
|
700
|
-
|
|
701
|
-
| ---------------------------------------------- | ------------------------------------------------------ |
|
|
702
|
-
| `registerComponent(name, path, useShadowDOM?)` | Register a single component (returns Promise) |
|
|
703
|
-
| `registerComponents(components)` | **Coming soon** - Register multiple components at once |
|
|
823
|
+
### Performance & Caching
|
|
704
824
|
|
|
705
|
-
|
|
825
|
+
LadrillosJS includes built-in performance optimizations:
|
|
706
826
|
|
|
707
|
-
|
|
827
|
+
#### Component Caching
|
|
708
828
|
|
|
709
|
-
**
|
|
829
|
+
- **LRU Cache**: Stores up to 25 most recently used components
|
|
830
|
+
- **Instant Loading**: Cached components load immediately
|
|
831
|
+
- **Reduced Network**: No repeated HTTP requests for components
|
|
710
832
|
|
|
711
|
-
|
|
712
|
-
<!-- parent.html -->
|
|
713
|
-
<div>
|
|
714
|
-
<h2>Parent Component</h2>
|
|
715
|
-
<p>Messages received: {messageCount}</p>
|
|
716
|
-
<child-component></child-component>
|
|
717
|
-
</div>
|
|
833
|
+
#### Function Caching
|
|
718
834
|
|
|
719
|
-
|
|
720
|
-
|
|
835
|
+
- **Template Compilation**: Caches up to 100 compiled expressions
|
|
836
|
+
- **Memory Efficient**: Reuses Function objects for identical expressions
|
|
837
|
+
- **Faster Renders**: Expressions like `{formatName(user)}` compile once
|
|
721
838
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
839
|
+
**Performance Tips:**
|
|
840
|
+
|
|
841
|
+
- Component files are cached after first load
|
|
842
|
+
- State updates trigger minimal DOM changes
|
|
843
|
+
- Event listeners are automatically cleaned up
|
|
844
|
+
- Conditional rendering skips hidden elements
|
|
845
|
+
|
|
846
|
+
## Common Patterns
|
|
847
|
+
|
|
848
|
+
### Keyboard Events
|
|
849
|
+
|
|
850
|
+
Handle keyboard input for interactive features:
|
|
729
851
|
|
|
730
852
|
```html
|
|
731
|
-
<!-- child.html -->
|
|
732
853
|
<div>
|
|
733
|
-
<
|
|
734
|
-
<
|
|
854
|
+
<p>Press arrow keys to navigate. Current: {direction}</p>
|
|
855
|
+
<p>Press Enter to submit</p>
|
|
735
856
|
</div>
|
|
736
857
|
|
|
737
858
|
<script>
|
|
738
|
-
let
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
859
|
+
let direction = "none";
|
|
860
|
+
|
|
861
|
+
document.addEventListener("keyup", (event) => {
|
|
862
|
+
if (event.key === "ArrowRight") {
|
|
863
|
+
direction = "right";
|
|
864
|
+
} else if (event.key === "ArrowLeft") {
|
|
865
|
+
direction = "left";
|
|
866
|
+
} else if (event.key === "ArrowUp") {
|
|
867
|
+
direction = "up";
|
|
868
|
+
} else if (event.key === "ArrowDown") {
|
|
869
|
+
direction = "down";
|
|
870
|
+
} else if (event.key === "Enter") {
|
|
871
|
+
direction = "submitted!";
|
|
872
|
+
}
|
|
873
|
+
});
|
|
748
874
|
</script>
|
|
749
875
|
```
|
|
750
876
|
|
|
751
|
-
###
|
|
877
|
+
### Form Validation
|
|
752
878
|
|
|
753
|
-
|
|
754
|
-
// Create components programmatically
|
|
755
|
-
const createCard = (userData) => {
|
|
756
|
-
const card = document.createElement("user-card");
|
|
757
|
-
card.setAttribute("user-id", userData.id);
|
|
758
|
-
card.setAttribute("name", userData.name);
|
|
759
|
-
card.setAttribute("email", userData.email);
|
|
760
|
-
document.querySelector("#user-list").appendChild(card);
|
|
761
|
-
};
|
|
879
|
+
Real-time form validation with reactive state:
|
|
762
880
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
881
|
+
```html
|
|
882
|
+
<form onsubmit="handleSubmit(event)">
|
|
883
|
+
<input type="email" $bind="email" placeholder="Email" onkeyup="validate()" />
|
|
884
|
+
<p $if="{!isValid}" style="color: red">Please enter a valid email</p>
|
|
885
|
+
<button type="submit" $if="{isValid}">Submit</button>
|
|
886
|
+
</form>
|
|
768
887
|
|
|
769
|
-
|
|
888
|
+
<script>
|
|
889
|
+
let email = "";
|
|
890
|
+
let isValid = false;
|
|
770
891
|
|
|
771
|
-
|
|
892
|
+
const validate = () => {
|
|
893
|
+
isValid = email.includes("@") && email.length > 5;
|
|
894
|
+
};
|
|
772
895
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
// Create HTML with stringified data
|
|
780
|
-
const cardHtml = `
|
|
781
|
-
<user-card data-user='${JSON.stringify(user)}'></user-card>
|
|
782
|
-
<list-component data-items='${JSON.stringify(items)}'></list-component>
|
|
783
|
-
`;
|
|
784
|
-
|
|
785
|
-
// Or use the built-in stringify helper in v2.0
|
|
786
|
-
const cardHtml2 = `
|
|
787
|
-
<user-card data-user="${this.stringify(user)}"></user-card>
|
|
788
|
-
`;
|
|
896
|
+
const handleSubmit = (e) => {
|
|
897
|
+
e.preventDefault();
|
|
898
|
+
if (isValid) {
|
|
899
|
+
alert(`Submitted: ${email}`);
|
|
900
|
+
}
|
|
901
|
+
};
|
|
789
902
|
</script>
|
|
790
903
|
```
|
|
791
904
|
|
|
905
|
+
### Loading States
|
|
906
|
+
|
|
907
|
+
Show loading indicators while fetching data:
|
|
908
|
+
|
|
792
909
|
```html
|
|
793
|
-
<!-- In child component (user-card.html) -->
|
|
794
910
|
<div>
|
|
795
|
-
<
|
|
796
|
-
<
|
|
797
|
-
<
|
|
911
|
+
<div $if="{isLoading}">Loading...</div>
|
|
912
|
+
<div $else-if="{error}">Error: {error}</div>
|
|
913
|
+
<div $else>
|
|
914
|
+
<h2>{data.title}</h2>
|
|
915
|
+
<p>{data.description}</p>
|
|
916
|
+
</div>
|
|
917
|
+
<button onclick="fetchData()">Refresh</button>
|
|
798
918
|
</div>
|
|
799
919
|
|
|
800
920
|
<script>
|
|
801
|
-
|
|
802
|
-
let
|
|
921
|
+
let isLoading = false;
|
|
922
|
+
let error = null;
|
|
923
|
+
let data = { title: "", description: "" };
|
|
924
|
+
|
|
925
|
+
const fetchData = async () => {
|
|
926
|
+
isLoading = true;
|
|
927
|
+
error = null;
|
|
928
|
+
|
|
929
|
+
try {
|
|
930
|
+
const response = await fetch("https://api.example.com/data");
|
|
931
|
+
data = await response.json();
|
|
932
|
+
} catch (e) {
|
|
933
|
+
error = e.message;
|
|
934
|
+
} finally {
|
|
935
|
+
isLoading = false;
|
|
936
|
+
}
|
|
937
|
+
};
|
|
803
938
|
</script>
|
|
804
939
|
```
|
|
805
940
|
|
|
806
|
-
##
|
|
941
|
+
## API Reference
|
|
807
942
|
|
|
808
|
-
###
|
|
943
|
+
### Registration Functions
|
|
809
944
|
|
|
810
|
-
|
|
945
|
+
```typescript
|
|
946
|
+
registerComponent(name: string, path: string, useShadowDOM?: boolean): Promise<void>
|
|
947
|
+
registerComponents(components: Array<{name, path, useShadowDOM?}>): Promise<void>
|
|
948
|
+
```
|
|
811
949
|
|
|
812
|
-
|
|
813
|
-
- **After:** Use global event bus with `$emit` and `$listen`
|
|
950
|
+
### Component Utilities
|
|
814
951
|
|
|
815
|
-
|
|
952
|
+
Available within component `<script>` tags:
|
|
816
953
|
|
|
817
|
-
|
|
818
|
-
|
|
954
|
+
```typescript
|
|
955
|
+
// State management
|
|
956
|
+
$getState(): object // Access component state
|
|
957
|
+
$setState(updates: object): void // Update state and re-render
|
|
819
958
|
|
|
820
|
-
|
|
959
|
+
// Event bus
|
|
960
|
+
$emit(eventName: string, data?: any): void
|
|
961
|
+
$listen(eventName: string, callback: (data?) => void): () => void
|
|
821
962
|
|
|
822
|
-
|
|
823
|
-
|
|
963
|
+
// DOM queries (respects Shadow DOM boundaries)
|
|
964
|
+
$querySelector(selector: string): Element | null
|
|
965
|
+
$querySelectorAll(selector: string): NodeListOf<Element>
|
|
824
966
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
967
|
+
// Reactive variables (for external modules)
|
|
968
|
+
$reactive(name: string, initialValue: any): (value: any) => void
|
|
969
|
+
```
|
|
828
970
|
|
|
829
|
-
###
|
|
971
|
+
### Component Attributes
|
|
830
972
|
|
|
831
|
-
|
|
832
|
-
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
973
|
+
```html
|
|
974
|
+
<!-- Two-way data binding -->
|
|
975
|
+
<input $bind="variableName" />
|
|
976
|
+
|
|
977
|
+
<!-- Conditional rendering -->
|
|
978
|
+
<div $if="{condition}">...</div>
|
|
979
|
+
<div $else-if="{anotherCondition}">...</div>
|
|
980
|
+
<div $else>...</div>
|
|
981
|
+
|
|
982
|
+
<!-- List rendering -->
|
|
983
|
+
<li $for="item in items">{item}</li>
|
|
984
|
+
<li $for="(item, index) in items">{index}: {item}</li>
|
|
985
|
+
<div $for="user in users" $key="user.id">{user.name}</div>
|
|
986
|
+
|
|
987
|
+
<!-- Event handlers -->
|
|
988
|
+
<button onclick="methodName()">Click</button>
|
|
989
|
+
<button onclick="method(arg1, arg2)">Call with args</button>
|
|
990
|
+
<button onclick="console.log(event)">Inline function</button>
|
|
991
|
+
|
|
992
|
+
<!-- Slots -->
|
|
993
|
+
<slot></slot>
|
|
994
|
+
<!-- Default slot -->
|
|
995
|
+
<slot name="header"></slot>
|
|
996
|
+
<!-- Named slot -->
|
|
997
|
+
```
|
|
836
998
|
|
|
837
|
-
|
|
999
|
+
### Template Syntax
|
|
1000
|
+
|
|
1001
|
+
```html
|
|
1002
|
+
<!-- Variable interpolation -->
|
|
1003
|
+
{variableName} {object.property} {array[0]}
|
|
1004
|
+
|
|
1005
|
+
<!-- Function calls -->
|
|
1006
|
+
{functionName(arg1, arg2)} {object.method()}
|
|
1007
|
+
|
|
1008
|
+
<!-- Expressions -->
|
|
1009
|
+
{count + 1} {isActive ? 'Yes' : 'No'} {items.length > 0 ? 'Has items' : 'Empty'}
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
## Development
|
|
1013
|
+
|
|
1014
|
+
### Prerequisites
|
|
838
1015
|
|
|
839
|
-
|
|
1016
|
+
- **Node.js**: v20.19+ or v22.12+
|
|
1017
|
+
- **npm**: v9+ (comes with Node.js)
|
|
840
1018
|
|
|
841
|
-
###
|
|
1019
|
+
### Setup
|
|
842
1020
|
|
|
843
1021
|
```bash
|
|
1022
|
+
# Clone the repository
|
|
1023
|
+
git clone https://github.com/drubiodev/LadrillosJS.git
|
|
1024
|
+
cd LadrillosJS
|
|
1025
|
+
|
|
844
1026
|
# Install dependencies
|
|
845
1027
|
npm install
|
|
846
1028
|
|
|
847
|
-
#
|
|
1029
|
+
# Start development server
|
|
848
1030
|
npm run dev
|
|
849
1031
|
|
|
1032
|
+
# Build the library
|
|
1033
|
+
npm run build
|
|
1034
|
+
|
|
850
1035
|
# Run tests
|
|
851
1036
|
npm test
|
|
852
1037
|
|
|
853
1038
|
# Run tests with coverage
|
|
854
1039
|
npm run test:coverage
|
|
855
|
-
|
|
856
|
-
# Build the library
|
|
857
|
-
npm run build
|
|
858
|
-
|
|
859
|
-
# Build TypeScript types
|
|
860
|
-
npm run build:types
|
|
861
1040
|
```
|
|
862
1041
|
|
|
863
1042
|
### Project Structure
|
|
864
1043
|
|
|
865
1044
|
```
|
|
866
|
-
|
|
867
|
-
├──
|
|
868
|
-
├──
|
|
869
|
-
│ ├──
|
|
870
|
-
│ ├──
|
|
871
|
-
│ ├──
|
|
872
|
-
│ ├──
|
|
873
|
-
│ ├──
|
|
874
|
-
│ ├──
|
|
875
|
-
│ │
|
|
876
|
-
│
|
|
877
|
-
│ │ ├──
|
|
878
|
-
│ │
|
|
879
|
-
│ └──
|
|
880
|
-
│
|
|
881
|
-
|
|
882
|
-
│ ├──
|
|
883
|
-
│
|
|
884
|
-
|
|
885
|
-
│
|
|
886
|
-
└──
|
|
887
|
-
|
|
888
|
-
|
|
1045
|
+
LadrillosJS/
|
|
1046
|
+
├── src/
|
|
1047
|
+
│ ├── index.ts # Main entry point
|
|
1048
|
+
│ ├── core/
|
|
1049
|
+
│ │ ├── main.ts # Core Ladrillos class
|
|
1050
|
+
│ │ ├── webcomponent.ts # Web component wrapper
|
|
1051
|
+
│ │ ├── componentParser.ts # Parse component files
|
|
1052
|
+
│ │ ├── componentSource.ts # Fetch with caching
|
|
1053
|
+
│ │ ├── eventBus.ts # Global event bus
|
|
1054
|
+
│ │ ├── css/
|
|
1055
|
+
│ │ │ └── cssParser.ts
|
|
1056
|
+
│ │ ├── html/
|
|
1057
|
+
│ │ │ ├── htmlparser.ts
|
|
1058
|
+
│ │ │ └── htmlRenderer.ts
|
|
1059
|
+
│ │ └── js/
|
|
1060
|
+
│ │ └── scriptParser.ts
|
|
1061
|
+
│ ├── cache/
|
|
1062
|
+
│ │ ├── index.ts # LRU component cache
|
|
1063
|
+
│ │ └── functionCache.ts # LRU function cache
|
|
1064
|
+
│ ├── types/
|
|
1065
|
+
│ │ └── LadrilloTypes.ts
|
|
1066
|
+
│ └── utils/
|
|
1067
|
+
│ ├── logger.ts
|
|
1068
|
+
│ └── regex.ts
|
|
1069
|
+
├── samples/ # Example applications
|
|
1070
|
+
│ └── apps/
|
|
1071
|
+
│ ├── todo/
|
|
1072
|
+
│ ├── notes/
|
|
1073
|
+
│ ├── simple-button/
|
|
1074
|
+
│ └── ...
|
|
1075
|
+
├── test/ # Test files
|
|
1076
|
+
├── dist/ # Built files
|
|
1077
|
+
├── package.json
|
|
1078
|
+
├── tsconfig.json
|
|
1079
|
+
├── vite.config.js
|
|
1080
|
+
└── vitest.config.js
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
### NPM Scripts
|
|
1084
|
+
|
|
1085
|
+
```bash
|
|
1086
|
+
npm run dev # Start Vite dev server
|
|
1087
|
+
npm run build # Build library (ESM, UMD, CJS)
|
|
1088
|
+
npm run build:types # Generate TypeScript declarations
|
|
1089
|
+
npm test # Run tests with Vitest
|
|
1090
|
+
npm run test:coverage # Run tests with coverage report
|
|
1091
|
+
npm run preview # Preview production build
|
|
889
1092
|
```
|
|
890
1093
|
|
|
1094
|
+
## Attribution
|
|
1095
|
+
|
|
1096
|
+
If you use LadrillosJS in your project, I'd appreciate a mention or link back to this repository, though it's not required. It helps others discover the framework and motivates continued development!
|
|
1097
|
+
|
|
891
1098
|
## License
|
|
892
1099
|
|
|
893
1100
|
MIT License - see [LICENSE](LICENSE) file for details.
|
|
@@ -896,4 +1103,4 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
896
1103
|
|
|
897
1104
|
**LadrillosJS v2.0** - Built with ❤️ by [Daniel Rubio](https://github.com/drubiodev)
|
|
898
1105
|
|
|
899
|
-
|
|
1106
|
+
🌟 [GitHub](https://github.com/drubiodev/LadrillosJS) • 📦 [NPM](https://www.npmjs.com/package/ladrillosjs) • 📖 [Documentation](https://github.com/drubiodev/LadrillosJS/blob/main/README.md)
|