ladrillosjs 2.0.0-beta.4.4 → 2.0.0-beta.5.1
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 +405 -1723
- package/dist/index.d.ts +26 -58
- package/dist/index.js +1 -0
- package/package.json +49 -55
- package/LICENSE +0 -21
- package/dist/cache/functionCache.d.ts +0 -15
- package/dist/cache/index.d.ts +0 -16
- package/dist/core/componentParser.d.ts +0 -36
- package/dist/core/componentSource.d.ts +0 -12
- package/dist/core/css/cssParser.d.ts +0 -3
- package/dist/core/eventBus.d.ts +0 -41
- package/dist/core/html/htmlRenderer.d.ts +0 -23
- package/dist/core/html/htmlparser.d.ts +0 -22
- package/dist/core/js/scriptParser.d.ts +0 -3
- package/dist/core/main.d.ts +0 -13
- package/dist/core/webcomponent.d.ts +0 -2
- package/dist/index-6X7blelu.js +0 -74
- package/dist/index-6X7blelu.js.map +0 -1
- package/dist/index-DE-ur4y8.mjs +0 -758
- package/dist/index-DE-ur4y8.mjs.map +0 -1
- package/dist/ladrillosjs.cjs.js +0 -2
- package/dist/ladrillosjs.cjs.js.map +0 -1
- package/dist/ladrillosjs.es.js +0 -15
- package/dist/ladrillosjs.es.js.map +0 -1
- package/dist/ladrillosjs.umd.js +0 -183
- package/dist/ladrillosjs.umd.js.map +0 -1
- package/dist/types/LadrilloTypes.d.ts +0 -157
- package/dist/utils/devErrors.d.ts +0 -60
- package/dist/utils/logger.d.ts +0 -23
- package/dist/utils/regex.d.ts +0 -20
- package/dist/vite/copyComponentsPlugin.d.ts +0 -45
- package/dist/vite/index.d.ts +0 -4
- package/dist/vite.cjs +0 -51
- package/dist/vite.cjs.map +0 -7
- package/dist/vite.js +0 -51
- package/dist/vite.js.map +0 -7
- package/dist/webcomponent-BvS1JL3O.js +0 -111
- package/dist/webcomponent-BvS1JL3O.js.map +0 -1
- package/dist/webcomponent-DawPaCV3.mjs +0 -1299
- package/dist/webcomponent-DawPaCV3.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,1902 +1,584 @@
|
|
|
1
1
|
# LadrillosJS
|
|
2
2
|
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
**Version 2.0** - Rewritten in TypeScript with enhanced performance, improved developer experience, and powerful new features.
|
|
8
|
-
|
|
9
|
-
> "Empower developers to componentize code efficiently without the complexity of a full-scale framework. Focus on simplicity while leveraging core web fundamentals."
|
|
10
|
-
|
|
11
|
-
## Table of Contents
|
|
12
|
-
|
|
13
|
-
- [Features](#features)
|
|
14
|
-
- [Installation](#installation)
|
|
15
|
-
- [Using with Vite](#using-with-vite)
|
|
16
|
-
- [Quick Start](#quick-start)
|
|
17
|
-
- [Core Concepts](#core-concepts)
|
|
18
|
-
- [Component Registration](#component-registration)
|
|
19
|
-
- [State Management](#state-management)
|
|
20
|
-
- [Event Handling](#event-handling)
|
|
21
|
-
- [Data Binding](#data-binding)
|
|
22
|
-
- [Conditional Rendering](#conditional-rendering)
|
|
23
|
-
- [List Rendering](#list-rendering)
|
|
24
|
-
- [Slots](#slots)
|
|
25
|
-
- [Component Props](#component-props)
|
|
26
|
-
- [Lifecycle Hooks](#lifecycle-hooks)
|
|
27
|
-
- [Advanced Features](#advanced-features)
|
|
28
|
-
- [Lazy Loading](#lazy-loading)
|
|
29
|
-
- [Global Event Bus](#global-event-bus)
|
|
30
|
-
- [External Scripts](#external-scripts)
|
|
31
|
-
- [Shadow DOM](#shadow-dom)
|
|
32
|
-
- [Styling Components](#styling-components)
|
|
33
|
-
- [Performance & Caching](#performance--caching)
|
|
34
|
-
- [Common Patterns](#common-patterns)
|
|
35
|
-
- [Keyboard Events](#keyboard-events)
|
|
36
|
-
- [Form Validation](#form-validation)
|
|
37
|
-
- [Loading States](#loading-states)
|
|
38
|
-
- [API Reference](#api-reference)
|
|
39
|
-
- [Development](#development)
|
|
40
|
-
- [Attribution](#attribution)
|
|
41
|
-
- [License](#license)
|
|
42
|
-
|
|
43
|
-
## Features
|
|
44
|
-
|
|
45
|
-
- 🚀 **Zero Dependencies** - Pure JavaScript, no build tools required
|
|
46
|
-
- 📦 **Single-File Components** - HTML, CSS, and JavaScript in one file
|
|
47
|
-
- ⚡ **Reactive State** - Automatic re-rendering on state changes
|
|
48
|
-
- 🎯 **Event System** - Global event bus for component communication
|
|
49
|
-
- 🔄 **Two-Way Data Binding** - Seamless form input binding with `$bind`
|
|
50
|
-
- 🎨 **Scoped Styles** - Component styles with optional Shadow DOM
|
|
51
|
-
- 🔌 **Slots** - Content projection with named and default slots
|
|
52
|
-
- 📝 **TypeScript** - Full type definitions and TypeScript source code
|
|
53
|
-
- 🎭 **Conditional Rendering** - `$if`, `$else-if`, and `$else` directives
|
|
54
|
-
- 🔁 **List Rendering** - `$for` directive for rendering arrays
|
|
55
|
-
- ⚡ **Lazy Loading** - Load components on-demand with Intersection Observer
|
|
56
|
-
- 🚄 **Smart Caching** - LRU cache for components and compiled functions
|
|
57
|
-
- 🔧 **Framework Utilities** - Helper functions for common tasks
|
|
58
|
-
- 🧩 **External Scripts** - Load and bind external JavaScript modules
|
|
59
|
-
|
|
60
|
-
## Installation
|
|
61
|
-
|
|
62
|
-
### NPM
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/drubiodev/LadrillosJS/refs/heads/main/LadrillosJS.jpg" alt="LadrillosJS" width="300"/>
|
|
5
|
+
</p>
|
|
63
6
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
### CDN
|
|
69
|
-
|
|
70
|
-
```html
|
|
71
|
-
<!-- ES Module (Recommended) -->
|
|
72
|
-
<script type="module">
|
|
73
|
-
import { registerComponent } from "https://cdn.jsdelivr.net/npm/ladrillosjs/dist/ladrillosjs.es.js";
|
|
74
|
-
registerComponent("my-component", "./my-component.html");
|
|
75
|
-
</script>
|
|
76
|
-
|
|
77
|
-
<!-- UMD (Browser Global) -->
|
|
78
|
-
<script src="https://cdn.jsdelivr.net/npm/ladrillosjs/dist/ladrillosjs.umd.js"></script>
|
|
79
|
-
<script>
|
|
80
|
-
ladrillosjs.registerComponent("my-component", "./my-component.html");
|
|
81
|
-
</script>
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Using with Vite
|
|
85
|
-
|
|
86
|
-
LadrillosJS includes a Vite plugin to automatically copy your component files to the distribution folder during build. This is useful when you have static component files that need to be served with your application.
|
|
87
|
-
|
|
88
|
-
### Installation
|
|
89
|
-
|
|
90
|
-
If you haven't already:
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
npm install --save-dev vite
|
|
94
|
-
npm install ladrillosjs
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Setup
|
|
98
|
-
|
|
99
|
-
Create or update your `vite.config.js`:
|
|
100
|
-
|
|
101
|
-
```javascript
|
|
102
|
-
import { defineConfig } from "vite";
|
|
103
|
-
import { copyComponentsPlugin } from "ladrillosjs/vite";
|
|
104
|
-
|
|
105
|
-
export default defineConfig({
|
|
106
|
-
plugins: [
|
|
107
|
-
copyComponentsPlugin({
|
|
108
|
-
src: "components", // Source directory with your components
|
|
109
|
-
dest: "components", // Destination in dist/ folder
|
|
110
|
-
copyOnDev: false, // Only copy during build (not dev server)
|
|
111
|
-
}),
|
|
112
|
-
],
|
|
113
|
-
});
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Basic Usage
|
|
117
|
-
|
|
118
|
-
```javascript
|
|
119
|
-
// vite.config.js with default settings
|
|
120
|
-
import { defineConfig } from "vite";
|
|
121
|
-
import { copyComponentsPlugin } from "ladrillosjs/vite";
|
|
122
|
-
|
|
123
|
-
export default defineConfig({
|
|
124
|
-
plugins: [copyComponentsPlugin()],
|
|
125
|
-
});
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
The plugin will:
|
|
129
|
-
|
|
130
|
-
- 📁 Copy your `components/` folder to `dist/components/` during build
|
|
131
|
-
- 🛡️ Handle errors gracefully with helpful logging
|
|
132
|
-
- ⚙️ Use sensible defaults (src: 'components', dest: 'components')
|
|
133
|
-
|
|
134
|
-
### Plugin Options
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
interface CopyComponentsOptions {
|
|
138
|
-
/** Source directory containing components (default: 'components') */
|
|
139
|
-
src?: string;
|
|
140
|
-
|
|
141
|
-
/** Destination directory in dist/ (default: 'components') */
|
|
142
|
-
dest?: string;
|
|
143
|
-
|
|
144
|
-
/** Copy during development server (default: false) */
|
|
145
|
-
copyOnDev?: boolean;
|
|
146
|
-
|
|
147
|
-
/** Process component module scripts (default: true) */
|
|
148
|
-
processScripts?: boolean;
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Processing Component Module Scripts
|
|
153
|
-
|
|
154
|
-
When `processScripts` is enabled (default), the plugin automatically transforms component scripts using `type="module"` to work with the window scope. This is essential when building with Vite and using ES modules in your components.
|
|
155
|
-
|
|
156
|
-
**What It Does:**
|
|
157
|
-
|
|
158
|
-
- Converts ES module imports to window references
|
|
159
|
-
- Wraps scripts in an async IIFE that waits for the library to load
|
|
160
|
-
- Handles multiple components efficiently with a shared loading promise
|
|
161
|
-
|
|
162
|
-
**Example:**
|
|
163
|
-
|
|
164
|
-
Before build (in your component):
|
|
165
|
-
|
|
166
|
-
```html
|
|
167
|
-
<script type="module">
|
|
168
|
-
import { registerComponent } from "ladrillosjs";
|
|
169
|
-
|
|
170
|
-
let count = 0;
|
|
171
|
-
|
|
172
|
-
export const increment = () => {
|
|
173
|
-
count++;
|
|
174
|
-
};
|
|
175
|
-
</script>
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
After build (automatically transformed):
|
|
179
|
-
|
|
180
|
-
```html
|
|
181
|
-
<script>
|
|
182
|
-
(async () => {
|
|
183
|
-
try {
|
|
184
|
-
if (!window.__ladrillosPromise__) {
|
|
185
|
-
window.__ladrillosPromise__ = new Promise((resolve) => {
|
|
186
|
-
// Wait for LadrillosJS to load...
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
await window.__ladrillosPromise__;
|
|
191
|
-
|
|
192
|
-
(async () => {
|
|
193
|
-
const { registerComponent } = window.ladrillosjs;
|
|
194
|
-
|
|
195
|
-
let count = 0;
|
|
196
|
-
|
|
197
|
-
// Component code...
|
|
198
|
-
})();
|
|
199
|
-
} catch (error) {
|
|
200
|
-
console.error("Failed to execute component module:", error);
|
|
201
|
-
}
|
|
202
|
-
})();
|
|
203
|
-
</script>
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
**Disable Processing (if needed):**
|
|
207
|
-
|
|
208
|
-
```javascript
|
|
209
|
-
import { defineConfig } from "vite";
|
|
210
|
-
import { copyComponentsPlugin } from "ladrillosjs/vite";
|
|
211
|
-
|
|
212
|
-
export default defineConfig({
|
|
213
|
-
plugins: [
|
|
214
|
-
copyComponentsPlugin({
|
|
215
|
-
src: "components",
|
|
216
|
-
dest: "components",
|
|
217
|
-
processScripts: false, // Disable script processing
|
|
218
|
-
}),
|
|
219
|
-
],
|
|
220
|
-
});
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### Complete Example
|
|
224
|
-
|
|
225
|
-
Project structure:
|
|
226
|
-
|
|
227
|
-
```
|
|
228
|
-
my-app/
|
|
229
|
-
├── components/
|
|
230
|
-
│ ├── header.html
|
|
231
|
-
│ ├── footer.html
|
|
232
|
-
│ └── card.html
|
|
233
|
-
├── src/
|
|
234
|
-
│ └── main.js
|
|
235
|
-
├── index.html
|
|
236
|
-
└── vite.config.js
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Configuration:
|
|
240
|
-
|
|
241
|
-
```javascript
|
|
242
|
-
import { defineConfig } from "vite";
|
|
243
|
-
import { copyComponentsPlugin } from "ladrillosjs/vite";
|
|
244
|
-
|
|
245
|
-
export default defineConfig({
|
|
246
|
-
plugins: [
|
|
247
|
-
copyComponentsPlugin({
|
|
248
|
-
src: "components",
|
|
249
|
-
dest: "components",
|
|
250
|
-
}),
|
|
251
|
-
],
|
|
252
|
-
});
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
Usage in your app:
|
|
256
|
-
|
|
257
|
-
```javascript
|
|
258
|
-
// src/main.js
|
|
259
|
-
import { registerComponents } from "ladrillosjs";
|
|
260
|
-
|
|
261
|
-
await registerComponents([
|
|
262
|
-
{ name: "app-header", path: "./components/header.html" },
|
|
263
|
-
{ name: "app-footer", path: "./components/footer.html" },
|
|
264
|
-
{ name: "app-card", path: "./components/card.html" },
|
|
265
|
-
]);
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
After running `npm run build`, your components will be copied to `dist/components/` and be ready for production deployment.
|
|
269
|
-
|
|
270
|
-
### Development Workflow
|
|
271
|
-
|
|
272
|
-
```bash
|
|
273
|
-
# Start dev server (components served from source)
|
|
274
|
-
npm run dev
|
|
275
|
-
|
|
276
|
-
# Build for production (components copied to dist)
|
|
277
|
-
npm run build
|
|
278
|
-
|
|
279
|
-
# Preview production build
|
|
280
|
-
npm run preview
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## Quick Start
|
|
284
|
-
|
|
285
|
-
### 1. Create Your First Component
|
|
286
|
-
|
|
287
|
-
Create a file called `hello-world.html`:
|
|
288
|
-
|
|
289
|
-
```html
|
|
290
|
-
<!-- hello-world.html -->
|
|
291
|
-
<div class="greeting">
|
|
292
|
-
<h1>{title}</h1>
|
|
293
|
-
<p>Hello, {name}!</p>
|
|
294
|
-
<button onclick="greet()">Greet ({count})</button>
|
|
295
|
-
</div>
|
|
296
|
-
|
|
297
|
-
<script>
|
|
298
|
-
// Component state - automatically reactive
|
|
299
|
-
let title = "Welcome to LadrillosJS";
|
|
300
|
-
let name = "World";
|
|
301
|
-
let count = 0;
|
|
302
|
-
|
|
303
|
-
// Event handler
|
|
304
|
-
const greet = () => {
|
|
305
|
-
count++; // Automatically triggers re-render
|
|
306
|
-
name = prompt("What's your name?") || "World";
|
|
307
|
-
};
|
|
308
|
-
</script>
|
|
309
|
-
|
|
310
|
-
<style>
|
|
311
|
-
.greeting {
|
|
312
|
-
text-align: center;
|
|
313
|
-
padding: 2rem;
|
|
314
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
315
|
-
color: white;
|
|
316
|
-
border-radius: 8px;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
button {
|
|
320
|
-
padding: 0.75rem 1.5rem;
|
|
321
|
-
font-size: 1rem;
|
|
322
|
-
background: white;
|
|
323
|
-
color: #667eea;
|
|
324
|
-
border: none;
|
|
325
|
-
border-radius: 4px;
|
|
326
|
-
cursor: pointer;
|
|
327
|
-
transition: transform 0.2s;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
button:hover {
|
|
331
|
-
transform: scale(1.05);
|
|
332
|
-
}
|
|
333
|
-
</style>
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### 2. Register and Use the Component
|
|
337
|
-
|
|
338
|
-
```html
|
|
339
|
-
<!DOCTYPE html>
|
|
340
|
-
<html lang="en">
|
|
341
|
-
<head>
|
|
342
|
-
<meta charset="UTF-8" />
|
|
343
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
344
|
-
<title>My LadrillosJS App</title>
|
|
345
|
-
</head>
|
|
346
|
-
<body>
|
|
347
|
-
<!-- Use your component -->
|
|
348
|
-
<hello-world></hello-world>
|
|
349
|
-
|
|
350
|
-
<!-- Register the component -->
|
|
351
|
-
<script type="module">
|
|
352
|
-
import { registerComponent } from "ladrillosjs";
|
|
353
|
-
registerComponent("hello-world", "./hello-world.html");
|
|
354
|
-
</script>
|
|
355
|
-
</body>
|
|
356
|
-
</html>
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
### 3. Explore Example Apps
|
|
360
|
-
|
|
361
|
-
Check out the `samples/apps/` directory for complete examples:
|
|
362
|
-
|
|
363
|
-
- **[Todo App](samples/apps/todo)** - Classic todo list with component composition
|
|
364
|
-
- **[Notes App](samples/apps/notes)** - Multi-component app with event bus
|
|
365
|
-
- **[Simple Button](samples/apps/simple-button)** - Basic interactive component
|
|
366
|
-
- **[Business Card](samples/apps/biz)** - Form with two-way data binding
|
|
367
|
-
- **[Slideshow](samples/apps/slideshow)** - Multi-slide presentation
|
|
368
|
-
- **[Markdown Editor](samples/apps/markdown)** - Real-time markdown preview
|
|
369
|
-
- **[List Rendering](samples/apps/list-test)** - Dynamic lists with `$for` directive
|
|
370
|
-
|
|
371
|
-
Run the development server to view examples:
|
|
372
|
-
|
|
373
|
-
```bash
|
|
374
|
-
npm install
|
|
375
|
-
npm run dev
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
## Core Concepts
|
|
379
|
-
|
|
380
|
-
### Component Registration
|
|
381
|
-
|
|
382
|
-
Register components to make them available as custom HTML elements:
|
|
383
|
-
|
|
384
|
-
```javascript
|
|
385
|
-
import { registerComponent, registerComponents } from "ladrillosjs";
|
|
386
|
-
|
|
387
|
-
// Register a single component
|
|
388
|
-
await registerComponent("my-button", "./components/my-button.html");
|
|
389
|
-
|
|
390
|
-
// Register multiple components
|
|
391
|
-
await registerComponents([
|
|
392
|
-
{ name: "app-header", path: "./components/header.html" },
|
|
393
|
-
{ name: "app-footer", path: "./components/footer.html" },
|
|
394
|
-
{ name: "user-card", path: "./components/user-card.html" },
|
|
395
|
-
]);
|
|
396
|
-
|
|
397
|
-
// Disable Shadow DOM for a component
|
|
398
|
-
await registerComponent("global-styles", "./components/global.html", false);
|
|
399
|
-
|
|
400
|
-
// Enable lazy loading for a component
|
|
401
|
-
await registerComponent(
|
|
402
|
-
"footer-section",
|
|
403
|
-
"./components/footer.html",
|
|
404
|
-
true,
|
|
405
|
-
true
|
|
406
|
-
);
|
|
407
|
-
|
|
408
|
-
// Register multiple components with lazy loading
|
|
409
|
-
await registerComponents([
|
|
410
|
-
{ name: "hero-section", path: "./components/hero.html" },
|
|
411
|
-
{ name: "feature-section", path: "./components/features.html", lazy: true },
|
|
412
|
-
{ name: "footer-section", path: "./components/footer.html", lazy: true },
|
|
413
|
-
]);
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
### State Management
|
|
417
|
-
|
|
418
|
-
Components have reactive state that automatically triggers re-renders when changed:
|
|
419
|
-
|
|
420
|
-
```html
|
|
421
|
-
<div>
|
|
422
|
-
<h2>Counter: {count}</h2>
|
|
423
|
-
<p>User: {user.name}</p>
|
|
424
|
-
<button onclick="increment()">Add</button>
|
|
425
|
-
<button onclick="updateUser()">Change User</button>
|
|
426
|
-
<button onclick="reset()">Reset</button>
|
|
427
|
-
</div>
|
|
428
|
-
|
|
429
|
-
<script>
|
|
430
|
-
// State variables - automatically reactive
|
|
431
|
-
let count = 0;
|
|
432
|
-
let user = { name: "John", age: 25 };
|
|
433
|
-
|
|
434
|
-
const increment = () => {
|
|
435
|
-
count++; // Automatically triggers re-render
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
const updateUser = () => {
|
|
439
|
-
user.name = "Jane"; // Direct mutation triggers re-render
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
// You can also use $setState for explicit updates
|
|
443
|
-
const reset = () => {
|
|
444
|
-
$setState({ count: 0, user: { name: "Anonymous", age: 0 } });
|
|
445
|
-
};
|
|
446
|
-
</script>
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
**Available State Utilities:**
|
|
450
|
-
|
|
451
|
-
- Direct assignment: `count++`, `name = "New"`
|
|
452
|
-
- `$setState(updates)`: Merge updates into state
|
|
453
|
-
- `$getState()`: Access state in external modules
|
|
454
|
-
|
|
455
|
-
### Event Handling
|
|
456
|
-
|
|
457
|
-
Attach event handlers directly to elements:
|
|
458
|
-
|
|
459
|
-
```html
|
|
460
|
-
<!-- Method reference -->
|
|
461
|
-
<button onclick="handleClick(event)">Click me</button>
|
|
462
|
-
|
|
463
|
-
<!-- Function with arguments -->
|
|
464
|
-
<button onclick="addItem('apple', 5)">Add Item</button>
|
|
465
|
-
|
|
466
|
-
<!-- Inline arrow function -->
|
|
467
|
-
<button onclick="console.log(event.target)">Log Event</button>
|
|
468
|
-
|
|
469
|
-
<!-- Multiple events -->
|
|
470
|
-
<label $if="isValid">Valid</label>
|
|
471
|
-
<input
|
|
472
|
-
type="text"
|
|
473
|
-
placeholder="Enter text (min 3 characters)"
|
|
474
|
-
onkeyup="validateInput(event)"
|
|
475
|
-
onfocus="highlightField(event)"
|
|
476
|
-
onblur="saveField(event)"
|
|
477
|
-
/>
|
|
478
|
-
|
|
479
|
-
<script>
|
|
480
|
-
let items = [];
|
|
481
|
-
let isValid = false;
|
|
482
|
-
|
|
483
|
-
const handleClick = (event) => {
|
|
484
|
-
console.log("Button clicked!", event);
|
|
485
|
-
};
|
|
486
|
-
|
|
487
|
-
const addItem = (name, quantity) => {
|
|
488
|
-
items = [...items, { name, quantity }];
|
|
489
|
-
console.log("Items:", items);
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
const validateInput = (e) => {
|
|
493
|
-
const value = e.target.value;
|
|
494
|
-
isValid = value.length >= 3;
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
const highlightField = (e) => {
|
|
498
|
-
e.target.style.backgroundColor = "lightyellow";
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
const saveField = (e) => {
|
|
502
|
-
e.target.style.backgroundColor = "";
|
|
503
|
-
console.log("Field saved:", e.target.value);
|
|
504
|
-
};
|
|
505
|
-
</script>
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
### Data Binding
|
|
509
|
-
|
|
510
|
-
#### One-Way Binding (Display Data)
|
|
511
|
-
|
|
512
|
-
Use curly braces `{}` to display state in your template:
|
|
513
|
-
|
|
514
|
-
```html
|
|
515
|
-
<div>
|
|
516
|
-
<h1>{title}</h1>
|
|
517
|
-
<p>{user.name} - {user.email}</p>
|
|
518
|
-
<span>Total: {items.length} items</span>
|
|
519
|
-
<p>Formatted: {formatPrice(price)}</p>
|
|
520
|
-
</div>
|
|
521
|
-
|
|
522
|
-
<script>
|
|
523
|
-
let title = "My App";
|
|
524
|
-
let user = { name: "John", email: "john@example.com" };
|
|
525
|
-
let items = ["apple", "banana", "orange"];
|
|
526
|
-
let price = 29.99;
|
|
527
|
-
|
|
528
|
-
const formatPrice = (value) => `$${value.toFixed(2)}`;
|
|
529
|
-
</script>
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
#### Two-Way Binding (Form Inputs)
|
|
533
|
-
|
|
534
|
-
Use the `$bind` attribute for automatic synchronization between inputs and state:
|
|
535
|
-
|
|
536
|
-
```html
|
|
537
|
-
<div>
|
|
538
|
-
<h2>Hello, {name}!</h2>
|
|
539
|
-
<input type="text" $bind="name" placeholder="Your name" />
|
|
540
|
-
|
|
541
|
-
<p>Email: {email}</p>
|
|
542
|
-
<input type="email" $bind="email" />
|
|
543
|
-
|
|
544
|
-
<p>Bio: {bio}</p>
|
|
545
|
-
<textarea $bind="bio"></textarea>
|
|
546
|
-
|
|
547
|
-
<p>Country: {country}</p>
|
|
548
|
-
<select $bind="country">
|
|
549
|
-
<option value="us">United States</option>
|
|
550
|
-
<option value="uk">United Kingdom</option>
|
|
551
|
-
<option value="ca">Canada</option>
|
|
552
|
-
</select>
|
|
553
|
-
|
|
554
|
-
<label>
|
|
555
|
-
<input type="checkbox" $bind="subscribe" />
|
|
556
|
-
Subscribe to newsletter: {subscribe}
|
|
557
|
-
</label>
|
|
558
|
-
</div>
|
|
559
|
-
|
|
560
|
-
<script>
|
|
561
|
-
// Variables are automatically synced with inputs
|
|
562
|
-
let name = "World";
|
|
563
|
-
let email = "";
|
|
564
|
-
let bio = "";
|
|
565
|
-
let country = "us";
|
|
566
|
-
let subscribe = false;
|
|
567
|
-
</script>
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
### Conditional Rendering
|
|
571
|
-
|
|
572
|
-
Show or hide elements based on conditions:
|
|
573
|
-
|
|
574
|
-
```html
|
|
575
|
-
<div>
|
|
576
|
-
<h1>Shopping Cart</h1>
|
|
577
|
-
|
|
578
|
-
<!-- Simple condition -->
|
|
579
|
-
<p $if="{items.length === 0}">Your cart is empty</p>
|
|
580
|
-
|
|
581
|
-
<!-- Multiple conditions -->
|
|
582
|
-
<div $if="{items.length > 0 && items.length < 5}">
|
|
583
|
-
<p>You have {items.length} items</p>
|
|
584
|
-
</div>
|
|
585
|
-
|
|
586
|
-
<div $else-if="{items.length >= 5}">
|
|
587
|
-
<p>Your cart is full! ({items.length} items)</p>
|
|
588
|
-
</div>
|
|
589
|
-
|
|
590
|
-
<!-- Login/Logout example -->
|
|
591
|
-
<button $if="{!isLoggedIn}" onclick="login()">Login</button>
|
|
592
|
-
<button $else onclick="logout()">Logout</button>
|
|
593
|
-
|
|
594
|
-
<!-- Complex conditions -->
|
|
595
|
-
<div $if="{user && user.role.toLowerCase() === 'admin'}">
|
|
596
|
-
<p>{user.role} Panel</p>
|
|
597
|
-
Hello {user.name}
|
|
598
|
-
<button onclick="addToCart()">🛒 Add To Cart</button>
|
|
599
|
-
</div>
|
|
600
|
-
</div>
|
|
601
|
-
|
|
602
|
-
<script>
|
|
603
|
-
let items = [];
|
|
604
|
-
let isLoggedIn = false;
|
|
605
|
-
let user = null;
|
|
606
|
-
|
|
607
|
-
const login = () => {
|
|
608
|
-
isLoggedIn = true;
|
|
609
|
-
user = { name: "John", role: "Admin" };
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
const logout = () => {
|
|
613
|
-
isLoggedIn = false;
|
|
614
|
-
user = null;
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
const addToCart = () => {
|
|
618
|
-
if (items.length < 5) {
|
|
619
|
-
items.push(`Item ${items.length + 1}`);
|
|
620
|
-
} else {
|
|
621
|
-
alert("Cart is full!");
|
|
622
|
-
}
|
|
623
|
-
};
|
|
624
|
-
</script>
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
**Conditional Directives:**
|
|
628
|
-
|
|
629
|
-
- `$if="{expression}"`: Show if expression is truthy
|
|
630
|
-
- `$else-if="{expression}"`: Chain multiple conditions
|
|
631
|
-
- `$else`: Fallback when previous conditions are false
|
|
632
|
-
|
|
633
|
-
### List Rendering
|
|
634
|
-
|
|
635
|
-
Render lists of items using the `$for` directive:
|
|
636
|
-
|
|
637
|
-
```html
|
|
638
|
-
<div>
|
|
639
|
-
<h2>My Fruits</h2>
|
|
640
|
-
<ul>
|
|
641
|
-
<li $for="fruit in fruits">{fruit}</li>
|
|
642
|
-
</ul>
|
|
643
|
-
<button onclick="addFruit()">Add Fruit</button>
|
|
644
|
-
</div>
|
|
645
|
-
|
|
646
|
-
<script>
|
|
647
|
-
let fruits = ["Apple", "Banana", "Orange"];
|
|
648
|
-
|
|
649
|
-
const addFruit = () => {
|
|
650
|
-
const newFruit = prompt("Enter a fruit name:");
|
|
651
|
-
if (newFruit) {
|
|
652
|
-
fruits = [...fruits, newFruit]; // Triggers re-render
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
</script>
|
|
656
|
-
```
|
|
657
|
-
|
|
658
|
-
**List Rendering with Objects:**
|
|
659
|
-
|
|
660
|
-
```html
|
|
661
|
-
<div>
|
|
662
|
-
<h2>User List</h2>
|
|
663
|
-
<div class="user-card" $for="user in users">
|
|
664
|
-
<h3>{user.name}</h3>
|
|
665
|
-
<p>Email: {user.email}</p>
|
|
666
|
-
</div>
|
|
667
|
-
</div>
|
|
668
|
-
|
|
669
|
-
<script>
|
|
670
|
-
let users = [
|
|
671
|
-
{ name: "John Doe", email: "john@example.com" },
|
|
672
|
-
{ name: "Jane Smith", email: "jane@example.com" },
|
|
673
|
-
];
|
|
674
|
-
</script>
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
**With Index:**
|
|
678
|
-
|
|
679
|
-
```html
|
|
680
|
-
<ul>
|
|
681
|
-
<li $for="(item, index) in items">{index + 1}. {item}</li>
|
|
682
|
-
</ul>
|
|
683
|
-
|
|
684
|
-
<script>
|
|
685
|
-
let items = ["First", "Second", "Third"];
|
|
686
|
-
</script>
|
|
687
|
-
```
|
|
688
|
-
|
|
689
|
-
**Performance Optimization with `$key`:**
|
|
690
|
-
|
|
691
|
-
Use the `$key` attribute to help LadrillosJS track items efficiently:
|
|
692
|
-
|
|
693
|
-
```html
|
|
694
|
-
<div>
|
|
695
|
-
<div class="user-card" $for="user in users" $key="user.id">
|
|
696
|
-
<h3>{user.name}</h3>
|
|
697
|
-
<p>Email: {user.email}</p>
|
|
698
|
-
<button onclick="removeUser(user.id)">Remove</button>
|
|
699
|
-
</div>
|
|
700
|
-
</div>
|
|
701
|
-
|
|
702
|
-
<script>
|
|
703
|
-
let users = [
|
|
704
|
-
{ id: 1, name: "John", email: "john@example.com" },
|
|
705
|
-
{ id: 2, name: "Jane", email: "jane@example.com" },
|
|
706
|
-
];
|
|
707
|
-
|
|
708
|
-
const removeUser = (id) => {
|
|
709
|
-
users = users.filter((u) => u.id !== id);
|
|
710
|
-
};
|
|
711
|
-
</script>
|
|
712
|
-
```
|
|
713
|
-
|
|
714
|
-
### Slots
|
|
715
|
-
|
|
716
|
-
Project content from parent to child components:
|
|
717
|
-
|
|
718
|
-
```html
|
|
719
|
-
<!-- card.html -->
|
|
720
|
-
<div class="card">
|
|
721
|
-
<div class="card-header">
|
|
722
|
-
<slot name="header">Default Header</slot>
|
|
723
|
-
</div>
|
|
724
|
-
<div class="card-body">
|
|
725
|
-
<slot></slot>
|
|
726
|
-
<!-- Default slot -->
|
|
727
|
-
</div>
|
|
728
|
-
<div class="card-footer">
|
|
729
|
-
<slot name="footer"></slot>
|
|
730
|
-
</div>
|
|
731
|
-
</div>
|
|
732
|
-
|
|
733
|
-
<style>
|
|
734
|
-
.card {
|
|
735
|
-
border: 1px solid #ddd;
|
|
736
|
-
border-radius: 8px;
|
|
737
|
-
padding: 1rem;
|
|
738
|
-
}
|
|
739
|
-
.card-header {
|
|
740
|
-
font-weight: bold;
|
|
741
|
-
margin-bottom: 1rem;
|
|
742
|
-
}
|
|
743
|
-
.card-footer {
|
|
744
|
-
margin-top: 1rem;
|
|
745
|
-
border-top: 1px solid #eee;
|
|
746
|
-
padding-top: 1rem;
|
|
747
|
-
}
|
|
748
|
-
</style>
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
**Usage:**
|
|
752
|
-
|
|
753
|
-
```html
|
|
754
|
-
<my-card>
|
|
755
|
-
<h2 slot="header">User Profile</h2>
|
|
756
|
-
<p>Name: John Doe</p>
|
|
757
|
-
<p>Email: john@example.com</p>
|
|
758
|
-
<button slot="footer">Save Changes</button>
|
|
759
|
-
</my-card>
|
|
760
|
-
```
|
|
761
|
-
|
|
762
|
-
### Component Props
|
|
763
|
-
|
|
764
|
-
Pass data to components using HTML attributes:
|
|
765
|
-
|
|
766
|
-
```html
|
|
767
|
-
<!-- greeting.html -->
|
|
768
|
-
<div>
|
|
769
|
-
<h1>Hello, {name}!</h1>
|
|
770
|
-
<p>Age: {age}</p>
|
|
771
|
-
</div>
|
|
772
|
-
|
|
773
|
-
<script>
|
|
774
|
-
// Access attributes passed to the component
|
|
775
|
-
let name = this.getAttribute("name") || "Guest";
|
|
776
|
-
let age = this.getAttribute("age") || "unknown";
|
|
777
|
-
</script>
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
**Usage:**
|
|
781
|
-
|
|
782
|
-
```html
|
|
783
|
-
<my-greeting name="John" age="25"></my-greeting>
|
|
784
|
-
<my-greeting name="Jane" age="30"></my-greeting>
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
### Lifecycle Hooks
|
|
788
|
-
|
|
789
|
-
LadrillosJS provides Vue.js-style lifecycle hooks so you can run code at specific times in a component's lifecycle. These hooks are tied directly to Web Component lifecycle methods for maximum control.
|
|
790
|
-
|
|
791
|
-
#### Available Hooks
|
|
792
|
-
|
|
793
|
-
| Hook | Called | Purpose |
|
|
794
|
-
| ------------ | ------------------------------------------------ | ---------------------------------- |
|
|
795
|
-
| `$onMount` | After component is mounted and fully initialized | Setup, initialization, API calls |
|
|
796
|
-
| `$onUpdate` | After component state changes and re-renders | React to state changes |
|
|
797
|
-
| `$onUnmount` | Before component is removed from DOM | Cleanup, unsubscribe, clear timers |
|
|
798
|
-
|
|
799
|
-
#### `$onMount(callback)`
|
|
800
|
-
|
|
801
|
-
Called once when the component is fully mounted and ready. Perfect for initialization, API calls, and setup logic.
|
|
802
|
-
|
|
803
|
-
```html
|
|
804
|
-
<div>
|
|
805
|
-
<h2>User Profile</h2>
|
|
806
|
-
<p $if="{loading}">Loading...</p>
|
|
807
|
-
<div $else>
|
|
808
|
-
<h3>{user.name}</h3>
|
|
809
|
-
<p>{user.email}</p>
|
|
810
|
-
</div>
|
|
811
|
-
</div>
|
|
812
|
-
|
|
813
|
-
<script>
|
|
814
|
-
let user = { name: "", email: "" };
|
|
815
|
-
let loading = true;
|
|
816
|
-
|
|
817
|
-
// Called after component mounts with state ready
|
|
818
|
-
$onMount(() => {
|
|
819
|
-
console.log("✅ Component mounted!");
|
|
820
|
-
|
|
821
|
-
// Perfect for API calls, initialization, etc
|
|
822
|
-
fetch("/api/user/123")
|
|
823
|
-
.then((res) => res.json())
|
|
824
|
-
.then((data) => {
|
|
825
|
-
user = data;
|
|
826
|
-
loading = false;
|
|
827
|
-
});
|
|
828
|
-
});
|
|
829
|
-
</script>
|
|
830
|
-
```
|
|
831
|
-
|
|
832
|
-
**When to use `$onMount`:**
|
|
833
|
-
|
|
834
|
-
- ✅ Fetch data from APIs
|
|
835
|
-
- ✅ Initialize timers or subscriptions
|
|
836
|
-
- ✅ Set up event listeners
|
|
837
|
-
- ✅ Run component setup logic
|
|
838
|
-
- ✅ Validate initial state
|
|
839
|
-
|
|
840
|
-
#### `$onUpdate(callback)`
|
|
841
|
-
|
|
842
|
-
Called after component state changes and the component re-renders. Perfect for reacting to state changes.
|
|
843
|
-
|
|
844
|
-
```html
|
|
845
|
-
<div>
|
|
846
|
-
<p>Count: {count}</p>
|
|
847
|
-
<button onclick="count++">Increment</button>
|
|
848
|
-
</div>
|
|
849
|
-
|
|
850
|
-
<script>
|
|
851
|
-
let count = 0;
|
|
852
|
-
|
|
853
|
-
$onUpdate(() => {
|
|
854
|
-
console.log("🔄 Component updated! New count:", count);
|
|
855
|
-
|
|
856
|
-
// React to state changes
|
|
857
|
-
if (count === 10) {
|
|
858
|
-
console.log("Count reached 10!");
|
|
859
|
-
}
|
|
860
|
-
});
|
|
861
|
-
</script>
|
|
862
|
-
```
|
|
863
|
-
|
|
864
|
-
**When to use `$onUpdate`:**
|
|
865
|
-
|
|
866
|
-
- ✅ Log state changes for debugging
|
|
867
|
-
- ✅ Trigger side effects when specific properties change
|
|
868
|
-
- ✅ Sync with external systems
|
|
869
|
-
- ✅ Perform validations on updated state
|
|
870
|
-
|
|
871
|
-
#### `$onUnmount(callback)`
|
|
872
|
-
|
|
873
|
-
Called before the component is removed from the DOM. Perfect for cleanup operations.
|
|
874
|
-
|
|
875
|
-
```html
|
|
876
|
-
<div>
|
|
877
|
-
<p>Timer: {seconds}</p>
|
|
878
|
-
</div>
|
|
879
|
-
|
|
880
|
-
<script>
|
|
881
|
-
let seconds = 0;
|
|
882
|
-
let interval = null;
|
|
883
|
-
|
|
884
|
-
$onMount(() => {
|
|
885
|
-
// Start timer
|
|
886
|
-
interval = setInterval(() => {
|
|
887
|
-
seconds++;
|
|
888
|
-
}, 1000);
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
$onUnmount(() => {
|
|
892
|
-
console.log("❌ Component unmounting, cleaning up...");
|
|
893
|
-
|
|
894
|
-
// Clear timer to prevent memory leaks
|
|
895
|
-
if (interval) {
|
|
896
|
-
clearInterval(interval);
|
|
897
|
-
}
|
|
898
|
-
});
|
|
899
|
-
</script>
|
|
900
|
-
```
|
|
901
|
-
|
|
902
|
-
**When to use `$onUnmount`:**
|
|
903
|
-
|
|
904
|
-
- ✅ Clear timers and intervals
|
|
905
|
-
- ✅ Remove event listeners
|
|
906
|
-
- ✅ Unsubscribe from observables
|
|
907
|
-
- ✅ Cleanup resources
|
|
908
|
-
- ✅ Cancel pending API requests
|
|
909
|
-
|
|
910
|
-
#### Complete Lifecycle Example
|
|
911
|
-
|
|
912
|
-
```html
|
|
913
|
-
<div>
|
|
914
|
-
<h3>Lifecycle Demo</h3>
|
|
915
|
-
<p>Status: {status}</p>
|
|
916
|
-
<button onclick="status = 'active'">Activate</button>
|
|
917
|
-
<button onclick="status = 'inactive'">Deactivate</button>
|
|
918
|
-
</div>
|
|
919
|
-
|
|
920
|
-
<script>
|
|
921
|
-
let status = "initializing";
|
|
922
|
-
|
|
923
|
-
$onMount(() => {
|
|
924
|
-
console.log("✅ $onMount - Component ready to go!");
|
|
925
|
-
status = "ready";
|
|
926
|
-
});
|
|
927
|
-
|
|
928
|
-
$onUpdate(() => {
|
|
929
|
-
console.log("🔄 $onUpdate - Status changed to:", status);
|
|
930
|
-
});
|
|
931
|
-
|
|
932
|
-
$onUnmount(() => {
|
|
933
|
-
console.log("❌ $onUnmount - Cleaning up before removal");
|
|
934
|
-
});
|
|
935
|
-
</script>
|
|
936
|
-
```
|
|
937
|
-
|
|
938
|
-
#### Lifecycle Timing Diagram
|
|
939
|
-
|
|
940
|
-
```
|
|
941
|
-
┌─────────────────────────────────────────────────────────┐
|
|
942
|
-
│ Element added to DOM │
|
|
943
|
-
│ ↓ │
|
|
944
|
-
│ connectedCallback() starts │
|
|
945
|
-
│ ├─ Parse template │
|
|
946
|
-
│ ├─ Load styles │
|
|
947
|
-
│ ├─ Execute scripts & initialize state │
|
|
948
|
-
│ ├─ Render bindings, conditionals, loops │
|
|
949
|
-
│ │ │
|
|
950
|
-
│ ├─→ $onMount() called ← Component ready! │
|
|
951
|
-
│ │ │
|
|
952
|
-
│ State changes (click, setState, etc) │
|
|
953
|
-
│ ├─ Proxy detects change │
|
|
954
|
-
│ ├─ Schedule update (requestAnimationFrame) │
|
|
955
|
-
│ ├─ Re-render bindings, conditionals, loops │
|
|
956
|
-
│ │ │
|
|
957
|
-
│ ├─→ $onUpdate() called ← After each update │
|
|
958
|
-
│ │ ... (can happen many times) │
|
|
959
|
-
│ │ │
|
|
960
|
-
│ Element removed from DOM │
|
|
961
|
-
│ ├─→ $onUnmount() called ← Before cleanup │
|
|
962
|
-
│ │ │
|
|
963
|
-
│ disconnectedCallback() cleanup │
|
|
964
|
-
│ ├─ Remove event listeners │
|
|
965
|
-
│ ├─ Clear subscriptions │
|
|
966
|
-
│ └─ Complete │
|
|
967
|
-
└─────────────────────────────────────────────────────────┘
|
|
968
|
-
```
|
|
969
|
-
|
|
970
|
-
## Advanced Features
|
|
971
|
-
|
|
972
|
-
### Lazy Loading
|
|
973
|
-
|
|
974
|
-
LadrillosJS supports lazy loading components using the Intersection Observer API. Components are loaded only when they enter or are about to enter the viewport, improving initial page load performance.
|
|
975
|
-
|
|
976
|
-
#### Basic Usage
|
|
977
|
-
|
|
978
|
-
```javascript
|
|
979
|
-
import { registerComponents } from "ladrillosjs";
|
|
980
|
-
|
|
981
|
-
await registerComponents([
|
|
982
|
-
// Eager loading (default) - loads immediately
|
|
983
|
-
{ name: "hero-section", path: "./components/hero.html" },
|
|
984
|
-
{ name: "navbar", path: "./components/navbar.html" },
|
|
985
|
-
|
|
986
|
-
// Lazy loading - loads when scrolled into view
|
|
987
|
-
{ name: "feature-section", path: "./components/features.html", lazy: true },
|
|
988
|
-
{ name: "testimonials", path: "./components/testimonials.html", lazy: true },
|
|
989
|
-
{ name: "footer-section", path: "./components/footer.html", lazy: true },
|
|
990
|
-
]);
|
|
991
|
-
```
|
|
992
|
-
|
|
993
|
-
```html
|
|
994
|
-
<body>
|
|
995
|
-
<!-- Loads immediately -->
|
|
996
|
-
<navbar></navbar>
|
|
997
|
-
<hero-section></hero-section>
|
|
998
|
-
|
|
999
|
-
<!-- Loads when user scrolls near these components -->
|
|
1000
|
-
<feature-section></feature-section>
|
|
1001
|
-
<testimonials></testimonials>
|
|
1002
|
-
<footer-section></footer-section>
|
|
1003
|
-
</body>
|
|
1004
|
-
```
|
|
1005
|
-
|
|
1006
|
-
#### Override Lazy Loading with `eager` Attribute
|
|
1007
|
-
|
|
1008
|
-
You can force a lazy component to load immediately using the `eager` attribute:
|
|
1009
|
-
|
|
1010
|
-
```html
|
|
1011
|
-
<!-- This lazy component loads immediately despite lazy registration -->
|
|
1012
|
-
<footer-section eager></footer-section>
|
|
1013
|
-
```
|
|
1014
|
-
|
|
1015
|
-
#### Best Practices for Lazy Loading
|
|
1016
|
-
|
|
1017
|
-
**✅ Good candidates for lazy loading:**
|
|
1018
|
-
|
|
1019
|
-
- Below-the-fold content (footers, testimonials)
|
|
1020
|
-
- Large components with heavy resources
|
|
1021
|
-
- Components that may not be viewed by all users
|
|
1022
|
-
- Third-party widgets or embeds
|
|
1023
|
-
|
|
1024
|
-
**❌ Avoid lazy loading for:**
|
|
1025
|
-
|
|
1026
|
-
- Above-the-fold content (heroes, navigation)
|
|
1027
|
-
- Critical interactive elements
|
|
1028
|
-
- Small, lightweight components
|
|
1029
|
-
- Content needed for SEO
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>A lightweight, zero-dependency web component framework.</strong>
|
|
9
|
+
</p>
|
|
1030
10
|
|
|
1031
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/ladrillosjs"><img src="https://img.shields.io/npm/v/ladrillosjs.svg" alt="npm version"></a>
|
|
13
|
+
<a href="https://github.com/drubiodev/LadrillosJS/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/ladrillosjs.svg" alt="license"></a>
|
|
14
|
+
<a href="https://bundlephobia.com/package/ladrillosjs"><img src="https://img.shields.io/bundlephobia/minzip/ladrillosjs" alt="bundle size"></a>
|
|
15
|
+
<a href="https://github.com/drubiodev/LadrillosJS"><img src="https://img.shields.io/github/stars/drubiodev/LadrillosJS?style=social" alt="GitHub stars"></a>
|
|
16
|
+
</p>
|
|
1032
17
|
|
|
1033
|
-
|
|
18
|
+
<p align="center">
|
|
19
|
+
Build modular web apps with simple HTML components. No virtual DOM, no complex tooling required.
|
|
20
|
+
</p>
|
|
1034
21
|
|
|
1035
|
-
|
|
1036
|
-
/* In your global CSS */
|
|
1037
|
-
feature-section,
|
|
1038
|
-
testimonials,
|
|
1039
|
-
footer-section {
|
|
1040
|
-
display: block;
|
|
1041
|
-
min-height: 400px; /* Approximate height */
|
|
1042
|
-
}
|
|
1043
|
-
```
|
|
1044
|
-
|
|
1045
|
-
2. **Load strategy**: Lazy components load **100px before** entering the viewport for smooth user experience and to account for network latency.
|
|
1046
|
-
|
|
1047
|
-
3. **Multiple instances**: If you use the same lazy component multiple times on a page, only one network request is made. All instances share the loaded component definition.
|
|
1048
|
-
|
|
1049
|
-
4. **Caching**: Once loaded, lazy components are cached and reused across page navigation.
|
|
1050
|
-
|
|
1051
|
-
#### How It Works
|
|
1052
|
-
|
|
1053
|
-
- **Placeholder**: Lazy components initially render as minimal placeholders
|
|
1054
|
-
- **Intersection Observer**: Monitors when placeholders approach the viewport
|
|
1055
|
-
- **Automatic Loading**: Component fetches and upgrades when visible
|
|
1056
|
-
- **Seamless Swap**: Placeholder is replaced with the real component
|
|
1057
|
-
- **Shared Loading**: Multiple instances coordinate to load only once
|
|
1058
|
-
|
|
1059
|
-
### Global Event Bus
|
|
1060
|
-
|
|
1061
|
-
The global event bus enables communication between components without prop drilling:
|
|
1062
|
-
|
|
1063
|
-
```javascript
|
|
1064
|
-
// Emit events to other components
|
|
1065
|
-
$emit("user-logged-in", { userId: 123, username: "john" });
|
|
1066
|
-
|
|
1067
|
-
// Listen for events from any component
|
|
1068
|
-
$listen("user-logged-in", (data) => {
|
|
1069
|
-
console.log(`User ${data.username} logged in`);
|
|
1070
|
-
isLoggedIn = true;
|
|
1071
|
-
currentUser = data;
|
|
1072
|
-
});
|
|
1073
|
-
```
|
|
1074
|
-
|
|
1075
|
-
**Example: Cross-Component Communication**
|
|
1076
|
-
|
|
1077
|
-
```html
|
|
1078
|
-
<!-- header.html -->
|
|
1079
|
-
<header>
|
|
1080
|
-
<span $if="{isLoggedIn}">Welcome, {username}!</span>
|
|
1081
|
-
<button $else onclick="requestLogin()">Login</button>
|
|
1082
|
-
</header>
|
|
1083
|
-
|
|
1084
|
-
<script>
|
|
1085
|
-
let isLoggedIn = false;
|
|
1086
|
-
let username = "";
|
|
1087
|
-
|
|
1088
|
-
$listen("user-logged-in", (user) => {
|
|
1089
|
-
console.log("User logged in:", user);
|
|
1090
|
-
isLoggedIn = true;
|
|
1091
|
-
username = user.username;
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
const requestLogin = () => {
|
|
1095
|
-
$emit("show-login-dialog");
|
|
1096
|
-
};
|
|
1097
|
-
</script>
|
|
1098
|
-
```
|
|
1099
|
-
|
|
1100
|
-
```html
|
|
1101
|
-
<!-- login-form.html -->
|
|
1102
|
-
<form onsubmit="handleLogin(event)" $if="{showLoginDialog}">
|
|
1103
|
-
<input type="text" $bind="username" placeholder="Username" />
|
|
1104
|
-
<input type="password" $bind="password" placeholder="Password" />
|
|
1105
|
-
<button type="submit">Login</button>
|
|
1106
|
-
</form>
|
|
1107
|
-
|
|
1108
|
-
<script>
|
|
1109
|
-
let showLoginDialog = false;
|
|
1110
|
-
|
|
1111
|
-
$listen("show-login-dialog", () => {
|
|
1112
|
-
showLoginDialog = true;
|
|
1113
|
-
});
|
|
1114
|
-
|
|
1115
|
-
const handleLogin = (e) => {
|
|
1116
|
-
e.preventDefault();
|
|
1117
|
-
$emit("user-logged-in", { userId: 123, username });
|
|
1118
|
-
username = "";
|
|
1119
|
-
password = "";
|
|
1120
|
-
showLoginDialog = false;
|
|
1121
|
-
};
|
|
1122
|
-
</script>
|
|
1123
|
-
```
|
|
22
|
+
---
|
|
1124
23
|
|
|
1125
|
-
|
|
24
|
+
## 📑 Table of Contents
|
|
25
|
+
|
|
26
|
+
- [Quick Start](#-quick-start)
|
|
27
|
+
- [Installation](#-installation)
|
|
28
|
+
- [Core Concepts](#-core-concepts)
|
|
29
|
+
- [Directives](#-directives)
|
|
30
|
+
- [Event Bus](#-event-bus)
|
|
31
|
+
- [Element References](#-element-references)
|
|
32
|
+
- [Lazy Loading](#-lazy-loading)
|
|
33
|
+
- [API Reference](#-api-reference)
|
|
34
|
+
- [Using with Vite](#-using-with-vite)
|
|
35
|
+
- [Browser Support](#-browser-support)
|
|
36
|
+
- [Examples](#-examples)
|
|
37
|
+
- [Contributing](#-contributing)
|
|
38
|
+
- [License](#-license)
|
|
1126
39
|
|
|
1127
|
-
|
|
40
|
+
---
|
|
1128
41
|
|
|
1129
|
-
|
|
42
|
+
## 🚀 Quick Start
|
|
1130
43
|
|
|
1131
|
-
|
|
44
|
+
### 1. Add the Script
|
|
1132
45
|
|
|
1133
46
|
```html
|
|
1134
|
-
|
|
1135
|
-
<nav>
|
|
1136
|
-
<h1><Note App/></h1>
|
|
1137
|
-
<button onclick="createNote()">Create Note</button>
|
|
1138
|
-
<ul></ul>
|
|
1139
|
-
</nav>
|
|
1140
|
-
|
|
1141
|
-
<!-- Load as ES module - variables and exports are auto-synced to state -->
|
|
1142
|
-
<script type="module" src="../js/side.js"></script>
|
|
1143
|
-
```
|
|
1144
|
-
|
|
1145
|
-
**side.js:**
|
|
1146
|
-
|
|
1147
|
-
```javascript
|
|
1148
|
-
import { registerComponent, $listen, $querySelector } from "ladrillosjs";
|
|
1149
|
-
|
|
1150
|
-
// Declare variables that are used in template bindings
|
|
1151
|
-
// They are automatically initialized in component state
|
|
1152
|
-
const notes = [];
|
|
1153
|
-
|
|
1154
|
-
registerComponent("note-item", "./components/note-item.html");
|
|
1155
|
-
|
|
1156
|
-
// Export functions so they're available to event handlers
|
|
1157
|
-
export const createNote = () => {
|
|
1158
|
-
const title = prompt("Note title:");
|
|
1159
|
-
if (title) {
|
|
1160
|
-
notes.push({ title, content: "" });
|
|
1161
|
-
}
|
|
1162
|
-
};
|
|
1163
|
-
|
|
1164
|
-
// Listen to global events
|
|
1165
|
-
$listen("note_saved", (data) => {
|
|
1166
|
-
notes.push({ ...data });
|
|
1167
|
-
const ul = $querySelector("ul");
|
|
1168
|
-
if (ul) {
|
|
1169
|
-
ul.innerHTML = notes
|
|
1170
|
-
.map((n) => `<note-item data-note='${JSON.stringify(n)}'></note-item>`)
|
|
1171
|
-
.join("");
|
|
1172
|
-
}
|
|
1173
|
-
});
|
|
1174
|
-
```
|
|
1175
|
-
|
|
1176
|
-
**How Module State Sync Works:**
|
|
1177
|
-
|
|
1178
|
-
When a module script is loaded, LadrillosJS:
|
|
1179
|
-
|
|
1180
|
-
1. **Detects variables used in template bindings** (e.g., variables in `{}` expressions)
|
|
1181
|
-
2. **Initializes them in component state** automatically when declared
|
|
1182
|
-
3. **Syncs mutations to state** automatically when assigned
|
|
1183
|
-
4. **Auto-attaches exports** to the component so event handlers can access them
|
|
1184
|
-
|
|
1185
|
-
This means you write natural JavaScript without boilerplate:
|
|
1186
|
-
|
|
1187
|
-
```javascript
|
|
1188
|
-
// ✨ This is all you need!
|
|
1189
|
-
let count = 0; // Auto-initialized in state
|
|
1190
|
-
let user = { name: "" }; // Works with objects too
|
|
1191
|
-
|
|
1192
|
-
export const increment = () => {
|
|
1193
|
-
count++; // Auto-synced to state!
|
|
1194
|
-
};
|
|
1195
|
-
|
|
1196
|
-
export const updateUser = (name) => {
|
|
1197
|
-
user.name = name; // Auto-synced to state!
|
|
1198
|
-
};
|
|
47
|
+
<script src="https://cdn.jsdelivr.net/npm/ladrillosjs/dist/ladrillosjs.umd.js"></script>
|
|
1199
48
|
```
|
|
1200
49
|
|
|
1201
|
-
|
|
50
|
+
### 2. Create a Component
|
|
1202
51
|
|
|
1203
|
-
|
|
1204
|
-
- ✅ Organize code across multiple files
|
|
1205
|
-
- ✅ Automatic state initialization for binding variables
|
|
1206
|
-
- ✅ Automatic state sync on variable mutations
|
|
1207
|
-
- ✅ Clean separation of concerns
|
|
1208
|
-
- ✅ Standard JavaScript module syntax
|
|
1209
|
-
|
|
1210
|
-
**Example: Counter Component**
|
|
52
|
+
Save this as `counter.html`:
|
|
1211
53
|
|
|
1212
54
|
```html
|
|
1213
|
-
|
|
1214
|
-
<div>
|
|
1215
|
-
<
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
55
|
+
<div class="counter">
|
|
56
|
+
<div class="count-display">{count}</div>
|
|
57
|
+
<div class="buttons">
|
|
58
|
+
<button onclick="count--">−</button>
|
|
59
|
+
<button onclick="count = 0">Reset</button>
|
|
60
|
+
<button onclick="count++">+</button>
|
|
61
|
+
</div>
|
|
62
|
+
<p>Double: {count * 2} | Squared: {count * count}</p>
|
|
1219
63
|
</div>
|
|
1220
64
|
|
|
1221
|
-
<script
|
|
65
|
+
<script>
|
|
66
|
+
let count = 0;
|
|
67
|
+
</script>
|
|
1222
68
|
|
|
1223
69
|
<style>
|
|
1224
|
-
|
|
70
|
+
.counter {
|
|
1225
71
|
text-align: center;
|
|
1226
72
|
padding: 2rem;
|
|
1227
73
|
}
|
|
74
|
+
.count-display {
|
|
75
|
+
font-size: 4rem;
|
|
76
|
+
font-weight: bold;
|
|
77
|
+
}
|
|
1228
78
|
button {
|
|
1229
79
|
padding: 0.5rem 1rem;
|
|
1230
|
-
margin: 0.
|
|
80
|
+
margin: 0.25rem;
|
|
1231
81
|
cursor: pointer;
|
|
1232
82
|
}
|
|
1233
83
|
</style>
|
|
1234
84
|
```
|
|
1235
85
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
```javascript
|
|
1239
|
-
// Variables used in template are auto-initialized
|
|
1240
|
-
let count = 0;
|
|
1241
|
-
|
|
1242
|
-
// Exports are auto-attached to component for event handlers
|
|
1243
|
-
export const increment = () => {
|
|
1244
|
-
count++;
|
|
1245
|
-
};
|
|
1246
|
-
|
|
1247
|
-
export const decrement = () => {
|
|
1248
|
-
count--;
|
|
1249
|
-
};
|
|
1250
|
-
|
|
1251
|
-
export const reset = () => {
|
|
1252
|
-
count = 0;
|
|
1253
|
-
};
|
|
1254
|
-
```
|
|
1255
|
-
|
|
1256
|
-
#### 2. Component-Scoped Scripts
|
|
1257
|
-
|
|
1258
|
-
Regular script tags execute within the component's context and have access to component state and utilities:
|
|
86
|
+
### 3. Use It
|
|
1259
87
|
|
|
1260
88
|
```html
|
|
1261
|
-
|
|
1262
|
-
<
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
<script
|
|
89
|
+
<!DOCTYPE html>
|
|
90
|
+
<html>
|
|
91
|
+
<head>
|
|
92
|
+
<script src="https://cdn.jsdelivr.net/npm/ladrillosjs/dist/ladrillosjs.umd.js"></script>
|
|
93
|
+
<script type="module">
|
|
94
|
+
ladrillosjs.registerComponent("my-counter", "./counter.html");
|
|
95
|
+
</script>
|
|
96
|
+
</head>
|
|
97
|
+
<body>
|
|
98
|
+
<my-counter></my-counter>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
1266
101
|
```
|
|
1267
102
|
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
```javascript
|
|
1271
|
-
// Variables and functions are available to the component
|
|
1272
|
-
let count = 0;
|
|
1273
|
-
const title = "Button Count";
|
|
103
|
+
That's it! Your reactive component is ready. 🎉
|
|
1274
104
|
|
|
1275
|
-
|
|
1276
|
-
count++; // Updates component state
|
|
1277
|
-
};
|
|
1278
|
-
```
|
|
105
|
+
---
|
|
1279
106
|
|
|
1280
|
-
|
|
107
|
+
## 📦 Installation
|
|
1281
108
|
|
|
1282
|
-
|
|
109
|
+
### CDN (No Build Step)
|
|
1283
110
|
|
|
1284
111
|
```html
|
|
1285
|
-
<!--
|
|
1286
|
-
<
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
<script
|
|
1291
|
-
src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"
|
|
1292
|
-
external
|
|
1293
|
-
></script>
|
|
1294
|
-
|
|
1295
|
-
<div class="code-container">
|
|
1296
|
-
<pre><code id="code" class="language-html"></code></pre>
|
|
1297
|
-
</div>
|
|
112
|
+
<!-- Global (UMD) -->
|
|
113
|
+
<script src="https://cdn.jsdelivr.net/npm/ladrillosjs/dist/ladrillosjs.umd.js"></script>
|
|
114
|
+
<script type="module">
|
|
115
|
+
ladrillosjs.registerComponent("my-component", "./component.html");
|
|
116
|
+
</script>
|
|
1298
117
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
hljs.highlightElement(codeElement); // Use external library
|
|
118
|
+
<!-- ES Module -->
|
|
119
|
+
<script type="module">
|
|
120
|
+
import { registerComponent } from "https://cdn.jsdelivr.net/npm/ladrillosjs/dist/ladrillosjs.es.js";
|
|
121
|
+
registerComponent("my-component", "./component.html");
|
|
1304
122
|
</script>
|
|
1305
123
|
```
|
|
1306
124
|
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
- Third-party CDN libraries (highlight.js, Chart.js, etc.)
|
|
1310
|
-
- Libraries that need to be loaded globally
|
|
1311
|
-
- Scripts that don't need component context or utilities
|
|
1312
|
-
|
|
1313
|
-
#### Module Script Best Practices
|
|
1314
|
-
|
|
1315
|
-
**✅ Use modules for:**
|
|
1316
|
-
|
|
1317
|
-
- Component-specific logic and functions
|
|
1318
|
-
- Local state management
|
|
1319
|
-
- Importing utilities and helpers
|
|
1320
|
-
- Organizing complex components
|
|
1321
|
-
|
|
1322
|
-
**❌ Don't use modules for:**
|
|
1323
|
-
|
|
1324
|
-
- Simple inline event handlers (use inline scripts)
|
|
1325
|
-
- Global libraries (use `external` attribute)
|
|
1326
|
-
- Scripts that need global scope
|
|
125
|
+
### NPM (With Build Tools)
|
|
1327
126
|
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
Modules automatically resolve relative imports to their proper URLs:
|
|
1331
|
-
|
|
1332
|
-
```javascript
|
|
1333
|
-
// These work automatically with modules
|
|
1334
|
-
import { helper } from "./utils.js";
|
|
1335
|
-
import { Component } from "../components/component.js";
|
|
1336
|
-
import { api } from "../../api/client.js";
|
|
127
|
+
```bash
|
|
128
|
+
npm install ladrillosjs
|
|
1337
129
|
```
|
|
1338
130
|
|
|
1339
|
-
#### Legacy: Using `$reactive` (Optional)
|
|
1340
|
-
|
|
1341
|
-
The `$reactive` function is still available for explicit state initialization in edge cases:
|
|
1342
|
-
|
|
1343
131
|
```javascript
|
|
1344
|
-
import {
|
|
132
|
+
import { registerComponent, registerComponents } from "ladrillosjs";
|
|
1345
133
|
|
|
1346
|
-
|
|
1347
|
-
|
|
134
|
+
// Single component
|
|
135
|
+
registerComponent("my-counter", "./components/counter.html");
|
|
1348
136
|
|
|
1349
|
-
//
|
|
1350
|
-
|
|
137
|
+
// Multiple components
|
|
138
|
+
await registerComponents([
|
|
139
|
+
{ name: "app-header", path: "./components/header.html" },
|
|
140
|
+
{ name: "app-footer", path: "./components/footer.html" },
|
|
141
|
+
]);
|
|
1351
142
|
```
|
|
1352
143
|
|
|
1353
|
-
|
|
144
|
+
---
|
|
1354
145
|
|
|
1355
|
-
|
|
1356
|
-
- Manual setter functions
|
|
1357
|
-
- Backward compatibility with older code
|
|
146
|
+
## 📖 Core Concepts
|
|
1358
147
|
|
|
1359
|
-
|
|
148
|
+
### Template Bindings
|
|
1360
149
|
|
|
1361
|
-
|
|
150
|
+
Use `{expression}` to display reactive data. Any JavaScript expression works:
|
|
1362
151
|
|
|
1363
152
|
```html
|
|
1364
|
-
|
|
1365
|
-
<
|
|
1366
|
-
<
|
|
1367
|
-
<
|
|
1368
|
-
<h1>{user.name}</h1>
|
|
1369
|
-
<p>Email: {user.email}</p>
|
|
1370
|
-
<p>Bio: {user.bio}</p>
|
|
1371
|
-
</div>
|
|
1372
|
-
|
|
1373
|
-
<script type="module" src="./user-profile.js"></script>
|
|
153
|
+
<h1>{title}</h1>
|
|
154
|
+
<p>Hello, {user.name}!</p>
|
|
155
|
+
<span>Total: {items.length} items</span>
|
|
156
|
+
<p>Is adult: {age >= 18 ? 'Yes' : 'No'}</p>
|
|
1374
157
|
```
|
|
1375
158
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
```javascript
|
|
1379
|
-
// Variables auto-initialized in component state
|
|
1380
|
-
let user = { name: "", email: "", bio: "" };
|
|
1381
|
-
let loading = true;
|
|
1382
|
-
let error = null;
|
|
1383
|
-
|
|
1384
|
-
// Auto-exported so it's available as event handler
|
|
1385
|
-
export const loadUser = async (userId) => {
|
|
1386
|
-
loading = true;
|
|
1387
|
-
error = null;
|
|
1388
|
-
|
|
1389
|
-
try {
|
|
1390
|
-
const response = await fetch(`/api/users/${userId}`);
|
|
1391
|
-
user = await response.json();
|
|
1392
|
-
} catch (e) {
|
|
1393
|
-
error = e.message;
|
|
1394
|
-
} finally {
|
|
1395
|
-
loading = false;
|
|
1396
|
-
}
|
|
1397
|
-
};
|
|
1398
|
-
|
|
1399
|
-
// Load user on component mount
|
|
1400
|
-
loadUser(1);
|
|
1401
|
-
```
|
|
159
|
+
### Reactive State
|
|
1402
160
|
|
|
1403
|
-
|
|
161
|
+
Just declare variables with `let` — changes automatically update the DOM:
|
|
1404
162
|
|
|
1405
|
-
```
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
let
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
increment: () => count++,
|
|
1412
|
-
decrement: () => count--,
|
|
1413
|
-
reset: () => (count = initial),
|
|
1414
|
-
get value() {
|
|
1415
|
-
return count;
|
|
1416
|
-
},
|
|
1417
|
-
};
|
|
1418
|
-
};
|
|
1419
|
-
|
|
1420
|
-
// counter.js (module script)
|
|
1421
|
-
import { createCounter } from "./utils/counter.js";
|
|
1422
|
-
|
|
1423
|
-
const counter = createCounter(0);
|
|
1424
|
-
|
|
1425
|
-
export const increment = () => counter.increment();
|
|
1426
|
-
export const decrement = () => counter.decrement();
|
|
1427
|
-
export const reset = () => counter.reset();
|
|
163
|
+
```html
|
|
164
|
+
<script>
|
|
165
|
+
let count = 0;
|
|
166
|
+
let user = { name: "Alice", role: "Developer" };
|
|
167
|
+
let items = ["Apple", "Banana", "Cherry"];
|
|
168
|
+
</script>
|
|
1428
169
|
```
|
|
1429
170
|
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
```javascript
|
|
1433
|
-
// store.js - shared module
|
|
1434
|
-
export let globalState = {
|
|
1435
|
-
user: null,
|
|
1436
|
-
notifications: [],
|
|
1437
|
-
theme: "light",
|
|
1438
|
-
};
|
|
1439
|
-
|
|
1440
|
-
export const updateUser = (user) => {
|
|
1441
|
-
globalState.user = user;
|
|
1442
|
-
};
|
|
171
|
+
### Event Handlers
|
|
1443
172
|
|
|
1444
|
-
|
|
1445
|
-
globalState.notifications.push(msg);
|
|
1446
|
-
};
|
|
173
|
+
Attach events directly in HTML with inline expressions or function calls:
|
|
1447
174
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
export const switchTheme = () => {
|
|
1454
|
-
globalState.theme = globalState.theme === "light" ? "dark" : "light";
|
|
1455
|
-
};
|
|
175
|
+
```html
|
|
176
|
+
<button onclick="count++">Increment</button>
|
|
177
|
+
<button onclick="handleClick()">Click me</button>
|
|
178
|
+
<input onkeyup="search(event.target.value)" />
|
|
179
|
+
<form onsubmit="handleSubmit(event)">...</form>
|
|
1456
180
|
```
|
|
1457
181
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
```javascript
|
|
1461
|
-
import { $listen, $emit } from "ladrillosjs";
|
|
1462
|
-
|
|
1463
|
-
let items = [];
|
|
1464
|
-
let selectedId = null;
|
|
1465
|
-
|
|
1466
|
-
$listen("item:created", (item) => {
|
|
1467
|
-
items = [...items, item];
|
|
1468
|
-
});
|
|
182
|
+
### Two-Way Binding
|
|
1469
183
|
|
|
1470
|
-
|
|
1471
|
-
items = items.filter((i) => i.id !== itemId);
|
|
1472
|
-
});
|
|
1473
|
-
|
|
1474
|
-
export const selectItem = (id) => {
|
|
1475
|
-
selectedId = id;
|
|
1476
|
-
$emit("item:selected", { id, item: items.find((i) => i.id === id) });
|
|
1477
|
-
};
|
|
1478
|
-
|
|
1479
|
-
export const deleteItem = (id) => {
|
|
1480
|
-
$emit("item:delete-request", { id });
|
|
1481
|
-
};
|
|
1482
|
-
```
|
|
184
|
+
Use `$bind` to sync form inputs with state:
|
|
1483
185
|
|
|
1484
|
-
|
|
186
|
+
```html
|
|
187
|
+
<input type="text" $bind="username" placeholder="Enter name" />
|
|
188
|
+
<p>Hello, {username}!</p>
|
|
1485
189
|
|
|
1486
|
-
|
|
190
|
+
<textarea $bind="bio"></textarea>
|
|
1487
191
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
192
|
+
<select $bind="country">
|
|
193
|
+
<option value="us">United States</option>
|
|
194
|
+
<option value="uk">United Kingdom</option>
|
|
195
|
+
</select>
|
|
1492
196
|
|
|
1493
|
-
|
|
1494
|
-
|
|
197
|
+
<script>
|
|
198
|
+
let username = "";
|
|
199
|
+
let bio = "";
|
|
200
|
+
let country = "us";
|
|
201
|
+
</script>
|
|
1495
202
|
```
|
|
1496
203
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
- **Style Isolation**: Component styles don't leak to global scope
|
|
1500
|
-
- **Encapsulation**: Internal DOM is hidden from parent
|
|
1501
|
-
- **Clean Separation**: Each component has its own styling context
|
|
1502
|
-
|
|
1503
|
-
**When to Disable:**
|
|
1504
|
-
|
|
1505
|
-
- Need to apply global CSS frameworks (Bootstrap, Tailwind)
|
|
1506
|
-
- Using third-party libraries that expect normal DOM
|
|
1507
|
-
- Easier debugging without shadow boundaries
|
|
204
|
+
---
|
|
1508
205
|
|
|
1509
|
-
|
|
206
|
+
## 🧩 Directives
|
|
1510
207
|
|
|
1511
|
-
|
|
208
|
+
### Conditional Rendering
|
|
1512
209
|
|
|
1513
|
-
|
|
210
|
+
Use `$if`, `$else-if`, and `$else` to conditionally render elements:
|
|
1514
211
|
|
|
1515
212
|
```html
|
|
1516
|
-
<div
|
|
1517
|
-
|
|
1518
|
-
<
|
|
1519
|
-
.button {
|
|
1520
|
-
padding: 10px 20px;
|
|
1521
|
-
background: #007bff;
|
|
1522
|
-
color: white;
|
|
1523
|
-
}
|
|
1524
|
-
</style>
|
|
213
|
+
<div $if="{status === 'loading'}">Loading...</div>
|
|
214
|
+
<div $else-if="{status === 'error'}">Something went wrong!</div>
|
|
215
|
+
<div $else>Content loaded successfully!</div>
|
|
1525
216
|
|
|
1526
217
|
<script>
|
|
1527
|
-
let
|
|
218
|
+
let status = "loading";
|
|
1528
219
|
</script>
|
|
1529
220
|
```
|
|
1530
221
|
|
|
1531
|
-
|
|
222
|
+
### Show/Hide (CSS Toggle)
|
|
1532
223
|
|
|
1533
|
-
|
|
1534
|
-
<link rel="stylesheet" href="./styles.css" />
|
|
1535
|
-
<div class="container">Content</div>
|
|
1536
|
-
```
|
|
1537
|
-
|
|
1538
|
-
#### 3. Import Fonts and External CSS
|
|
224
|
+
Use `$show` to toggle visibility without removing from DOM (uses `display: none`):
|
|
1539
225
|
|
|
1540
226
|
```html
|
|
1541
|
-
<
|
|
1542
|
-
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
|
|
227
|
+
<div $show="{isVisible}">I can be shown or hidden</div>
|
|
1543
228
|
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
229
|
+
<button onclick="isVisible = !isVisible">Toggle</button>
|
|
230
|
+
|
|
231
|
+
<script>
|
|
232
|
+
let isVisible = true;
|
|
233
|
+
</script>
|
|
1548
234
|
```
|
|
1549
235
|
|
|
1550
|
-
|
|
236
|
+
> **`$show` vs `$if`:** `$show` toggles CSS display (element stays in DOM), `$if` adds/removes from DOM entirely.
|
|
237
|
+
|
|
238
|
+
### List Rendering
|
|
1551
239
|
|
|
1552
|
-
|
|
240
|
+
Use `$for` to render lists with optional index and key:
|
|
1553
241
|
|
|
1554
242
|
```html
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
background: white;
|
|
1560
|
-
}
|
|
243
|
+
<!-- Simple list -->
|
|
244
|
+
<ul>
|
|
245
|
+
<li $for="fruit in fruits">🍎 {fruit}</li>
|
|
246
|
+
</ul>
|
|
1561
247
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
}
|
|
1565
|
-
</style>
|
|
1566
|
-
```
|
|
248
|
+
<!-- With index -->
|
|
249
|
+
<div $for="(item, index) in items">#{index + 1}: {item}</div>
|
|
1567
250
|
|
|
1568
|
-
|
|
251
|
+
<!-- Object array with key -->
|
|
252
|
+
<div $for="user in users" $key="user.id">
|
|
253
|
+
<span>{user.avatar}</span>
|
|
254
|
+
<span>{user.name}</span>
|
|
255
|
+
<span>{user.role}</span>
|
|
256
|
+
</div>
|
|
1569
257
|
|
|
1570
|
-
|
|
1571
|
-
|
|
258
|
+
<script>
|
|
259
|
+
let fruits = ["Apple", "Banana", "Cherry"];
|
|
260
|
+
let items = ["First", "Second", "Third"];
|
|
261
|
+
let users = [
|
|
262
|
+
{ id: 1, name: "Alice", role: "Developer", avatar: "👩💻" },
|
|
263
|
+
{ id: 2, name: "Bob", role: "Designer", avatar: "👨🎨" },
|
|
264
|
+
];
|
|
265
|
+
</script>
|
|
1572
266
|
```
|
|
1573
267
|
|
|
1574
|
-
###
|
|
1575
|
-
|
|
1576
|
-
LadrillosJS includes built-in performance optimizations:
|
|
268
|
+
### Directives Cheat Sheet
|
|
1577
269
|
|
|
1578
|
-
|
|
270
|
+
| Directive | Purpose | Example |
|
|
271
|
+
| ---------------- | --------------------- | ------------------------------------------------ |
|
|
272
|
+
| `$if` | Conditional render | `<div $if="{isLoggedIn}">Welcome!</div>` |
|
|
273
|
+
| `$else-if` | Chained condition | `<div $else-if="{isGuest}">Hello Guest</div>` |
|
|
274
|
+
| `$else` | Fallback | `<div $else>Please log in</div>` |
|
|
275
|
+
| `$show` | CSS visibility toggle | `<div $show="{isOpen}">Menu</div>` |
|
|
276
|
+
| `$for` | Loop rendering | `<li $for="item in items">{item}</li>` |
|
|
277
|
+
| `$for` (indexed) | Loop with index | `<li $for="(item, i) in items">{i}: {item}</li>` |
|
|
278
|
+
| `$bind` | Two-way binding | `<input $bind="email" />` |
|
|
279
|
+
| `$key` | List optimization | `<div $for="user in users" $key="user.id">` |
|
|
280
|
+
| `$ref` | Element reference | `<input $ref="inputEl" />` |
|
|
1579
281
|
|
|
1580
|
-
|
|
1581
|
-
- **Instant Loading**: Cached components load immediately
|
|
1582
|
-
- **Reduced Network**: No repeated HTTP requests for components
|
|
282
|
+
---
|
|
1583
283
|
|
|
1584
|
-
|
|
284
|
+
## 📡 Event Bus
|
|
1585
285
|
|
|
1586
|
-
|
|
1587
|
-
- **Memory Efficient**: Reuses Function objects for identical expressions
|
|
1588
|
-
- **Faster Renders**: Expressions like `{formatName(user)}` compile once
|
|
286
|
+
Communicate between components using `$emit` and `$listen`:
|
|
1589
287
|
|
|
1590
|
-
|
|
288
|
+
### Sender Component
|
|
1591
289
|
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
- Event listeners are automatically cleaned up
|
|
1595
|
-
- Conditional rendering skips hidden elements
|
|
290
|
+
```html
|
|
291
|
+
<button onclick="sendMessage()">Send Message</button>
|
|
1596
292
|
|
|
1597
|
-
|
|
293
|
+
<script>
|
|
294
|
+
let message = "Hello from sender!";
|
|
1598
295
|
|
|
1599
|
-
|
|
296
|
+
function sendMessage() {
|
|
297
|
+
$emit("my-event", { text: message, time: new Date().toLocaleTimeString() });
|
|
298
|
+
}
|
|
299
|
+
</script>
|
|
300
|
+
```
|
|
1600
301
|
|
|
1601
|
-
|
|
302
|
+
### Receiver Component
|
|
1602
303
|
|
|
1603
304
|
```html
|
|
1604
305
|
<div>
|
|
1605
|
-
<p>
|
|
1606
|
-
<p>Press Enter to submit</p>
|
|
306
|
+
<p>Received: {receivedMessage}</p>
|
|
1607
307
|
</div>
|
|
1608
308
|
|
|
1609
309
|
<script>
|
|
1610
|
-
let
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
direction = "right";
|
|
1615
|
-
} else if (event.key === "ArrowLeft") {
|
|
1616
|
-
direction = "left";
|
|
1617
|
-
} else if (event.key === "ArrowUp") {
|
|
1618
|
-
direction = "up";
|
|
1619
|
-
} else if (event.key === "ArrowDown") {
|
|
1620
|
-
direction = "down";
|
|
1621
|
-
} else if (event.key === "Enter") {
|
|
1622
|
-
direction = "submitted!";
|
|
1623
|
-
}
|
|
310
|
+
let receivedMessage = "Waiting...";
|
|
311
|
+
|
|
312
|
+
$listen("my-event", (data) => {
|
|
313
|
+
receivedMessage = data.text;
|
|
1624
314
|
});
|
|
1625
315
|
</script>
|
|
1626
316
|
```
|
|
1627
317
|
|
|
1628
|
-
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 🏷️ Element References
|
|
1629
321
|
|
|
1630
|
-
|
|
322
|
+
Use `$ref` to get direct DOM access for advanced manipulation:
|
|
1631
323
|
|
|
1632
324
|
```html
|
|
1633
|
-
<
|
|
1634
|
-
|
|
1635
|
-
<p $if="{!isValid}" style="color: red">Please enter a valid email</p>
|
|
1636
|
-
<button type="submit" $if="{isValid}">Submit</button>
|
|
1637
|
-
</form>
|
|
325
|
+
<input type="text" $ref="inputEl" placeholder="Click button to focus" />
|
|
326
|
+
<button onclick="focusInput()">Focus Input</button>
|
|
1638
327
|
|
|
1639
|
-
<
|
|
1640
|
-
|
|
1641
|
-
let isValid = false;
|
|
328
|
+
<canvas $ref="canvas" width="200" height="100"></canvas>
|
|
329
|
+
<button onclick="draw()">Draw on Canvas</button>
|
|
1642
330
|
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
331
|
+
<script>
|
|
332
|
+
function focusInput() {
|
|
333
|
+
$refs.inputEl.focus();
|
|
334
|
+
$refs.inputEl.select();
|
|
335
|
+
}
|
|
1646
336
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
};
|
|
337
|
+
function draw() {
|
|
338
|
+
const ctx = $refs.canvas.getContext("2d");
|
|
339
|
+
ctx.fillStyle = "blue";
|
|
340
|
+
ctx.fillRect(10, 10, 100, 50);
|
|
341
|
+
}
|
|
1653
342
|
</script>
|
|
1654
343
|
```
|
|
1655
344
|
|
|
1656
|
-
|
|
345
|
+
---
|
|
1657
346
|
|
|
1658
|
-
|
|
347
|
+
## ⏳ Lazy Loading
|
|
1659
348
|
|
|
1660
|
-
|
|
1661
|
-
<div>
|
|
1662
|
-
<div $if="{isLoading}">Loading...</div>
|
|
1663
|
-
<div $else-if="{error}">Error: {error}</div>
|
|
1664
|
-
<div $else>
|
|
1665
|
-
<h2>{data.title}</h2>
|
|
1666
|
-
<p>{data.description}</p>
|
|
1667
|
-
</div>
|
|
1668
|
-
<button onclick="fetchData()">Refresh</button>
|
|
1669
|
-
</div>
|
|
349
|
+
Load components only when needed to improve initial page load:
|
|
1670
350
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
data = await response.json();
|
|
1683
|
-
} catch (e) {
|
|
1684
|
-
error = e.message;
|
|
1685
|
-
} finally {
|
|
1686
|
-
isLoading = false;
|
|
1687
|
-
}
|
|
1688
|
-
};
|
|
1689
|
-
</script>
|
|
1690
|
-
```
|
|
351
|
+
### Lazy Loading Strategies
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
import {
|
|
355
|
+
registerComponents,
|
|
356
|
+
lazyOnVisible,
|
|
357
|
+
lazyOnIdle,
|
|
358
|
+
lazyOnInteraction,
|
|
359
|
+
lazyOnMedia,
|
|
360
|
+
lazyOnDelay,
|
|
361
|
+
} from "ladrillosjs";
|
|
1691
362
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
363
|
+
await registerComponents([
|
|
364
|
+
// Load when visible in viewport
|
|
365
|
+
{
|
|
366
|
+
name: "lazy-footer",
|
|
367
|
+
path: "./footer.html",
|
|
368
|
+
lazy: lazyOnVisible({ rootMargin: "100px" }),
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
// Load when browser is idle
|
|
372
|
+
{
|
|
373
|
+
name: "analytics-widget",
|
|
374
|
+
path: "./analytics.html",
|
|
375
|
+
lazy: lazyOnIdle(5000), // timeout: 5s max wait
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
// Load on user interaction
|
|
379
|
+
{
|
|
380
|
+
name: "modal-dialog",
|
|
381
|
+
path: "./modal.html",
|
|
382
|
+
lazy: lazyOnInteraction(["click", "focusin"]),
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
// Load based on media query
|
|
386
|
+
{
|
|
387
|
+
name: "mobile-nav",
|
|
388
|
+
path: "./mobile-nav.html",
|
|
389
|
+
lazy: lazyOnMedia("(max-width: 768px)"),
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
// Load after delay
|
|
393
|
+
{
|
|
394
|
+
name: "chat-widget",
|
|
395
|
+
path: "./chat.html",
|
|
396
|
+
lazy: lazyOnDelay(3000), // 3 second delay
|
|
397
|
+
},
|
|
398
|
+
]);
|
|
1712
399
|
```
|
|
1713
400
|
|
|
1714
|
-
|
|
401
|
+
| Strategy | Use Case |
|
|
402
|
+
| ------------------- | -------------------------------------------- |
|
|
403
|
+
| `lazyOnVisible` | Below-fold content, footers, image galleries |
|
|
404
|
+
| `lazyOnIdle` | Non-critical features, analytics |
|
|
405
|
+
| `lazyOnInteraction` | Modals, dropdowns, tooltips |
|
|
406
|
+
| `lazyOnMedia` | Mobile/desktop specific components |
|
|
407
|
+
| `lazyOnDelay` | Chat widgets, notifications |
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## 📋 API Reference
|
|
412
|
+
|
|
413
|
+
### registerComponent
|
|
1715
414
|
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
- `lazy`: Enable lazy loading with Intersection Observer (default: `false`)
|
|
415
|
+
```javascript
|
|
416
|
+
registerComponent(name, path, useShadowDOM?, lazy?)
|
|
417
|
+
```
|
|
1720
418
|
|
|
1721
|
-
|
|
419
|
+
| Parameter | Type | Default | Description |
|
|
420
|
+
| -------------- | ----------------------- | -------- | ------------------------------- |
|
|
421
|
+
| `name` | string | required | Tag name (must include hyphen) |
|
|
422
|
+
| `path` | string | required | Path to `.html` component file |
|
|
423
|
+
| `useShadowDOM` | boolean | `true` | Enable Shadow DOM encapsulation |
|
|
424
|
+
| `lazy` | boolean \| LazyStrategy | `false` | Lazy loading configuration |
|
|
1722
425
|
|
|
1723
426
|
```javascript
|
|
1724
|
-
// Basic
|
|
1725
|
-
|
|
427
|
+
// Basic usage
|
|
428
|
+
registerComponent("my-button", "./button.html");
|
|
1726
429
|
|
|
1727
|
-
//
|
|
1728
|
-
|
|
430
|
+
// Without Shadow DOM (for global CSS)
|
|
431
|
+
registerComponent("my-nav", "./nav.html", false);
|
|
1729
432
|
|
|
1730
|
-
//
|
|
1731
|
-
|
|
433
|
+
// With lazy loading
|
|
434
|
+
registerComponent("my-footer", "./footer.html", true, lazyOnVisible());
|
|
435
|
+
```
|
|
1732
436
|
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
437
|
+
### registerComponents
|
|
438
|
+
|
|
439
|
+
Register multiple components with parallel fetching:
|
|
440
|
+
|
|
441
|
+
```javascript
|
|
442
|
+
const result = await registerComponents([
|
|
443
|
+
{ name: "app-header", path: "./header.html" },
|
|
444
|
+
{ name: "app-footer", path: "./footer.html", lazy: lazyOnVisible() },
|
|
445
|
+
{ name: "user-card", path: "./user-card.html", useShadowDOM: false },
|
|
1738
446
|
]);
|
|
447
|
+
|
|
448
|
+
// Returns: { success: [...], failed: [...], skipped: [...] }
|
|
1739
449
|
```
|
|
1740
450
|
|
|
1741
|
-
###
|
|
451
|
+
### Event Bus
|
|
1742
452
|
|
|
1743
|
-
|
|
453
|
+
| Function | Description |
|
|
454
|
+
| -------------------------- | ----------------------------------- |
|
|
455
|
+
| `$emit(event, data)` | Broadcast an event to all listeners |
|
|
456
|
+
| `$listen(event, callback)` | Subscribe to an event |
|
|
1744
457
|
|
|
1745
|
-
|
|
1746
|
-
// State management
|
|
1747
|
-
$getState(): object // Access component state
|
|
1748
|
-
$setState(updates: object): void // Update state and re-render
|
|
458
|
+
---
|
|
1749
459
|
|
|
1750
|
-
|
|
1751
|
-
$emit(eventName: string, data?: any): void
|
|
1752
|
-
$listen(eventName: string, callback: (data?) => void): () => void
|
|
460
|
+
## 🛠️ Using with Vite
|
|
1753
461
|
|
|
1754
|
-
|
|
1755
|
-
$querySelector(selector: string): Element | null
|
|
1756
|
-
$querySelectorAll(selector: string): NodeListOf<Element>
|
|
462
|
+
LadrillosJS works seamlessly with Vite for production builds:
|
|
1757
463
|
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
$reactive(name: string, initialValue: any): (value: any) => void
|
|
464
|
+
```bash
|
|
465
|
+
npm install ladrillosjs vite
|
|
1761
466
|
```
|
|
1762
467
|
|
|
1763
|
-
|
|
468
|
+
```javascript
|
|
469
|
+
// vite.config.js
|
|
470
|
+
import { defineConfig } from "vite";
|
|
1764
471
|
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
<!-- Conditional rendering -->
|
|
1770
|
-
<div $if="{condition}">...</div>
|
|
1771
|
-
<div $else-if="{anotherCondition}">...</div>
|
|
1772
|
-
<div $else>...</div>
|
|
1773
|
-
|
|
1774
|
-
<!-- List rendering -->
|
|
1775
|
-
<li $for="item in items">{item}</li>
|
|
1776
|
-
<li $for="(item, index) in items">{index}: {item}</li>
|
|
1777
|
-
<div $for="user in users" $key="user.id">{user.name}</div>
|
|
1778
|
-
|
|
1779
|
-
<!-- Lazy loading override -->
|
|
1780
|
-
<footer-section eager></footer-section>
|
|
1781
|
-
<!-- Force immediate load -->
|
|
1782
|
-
|
|
1783
|
-
<!-- Event handlers -->
|
|
1784
|
-
<button onclick="methodName()">Click</button>
|
|
1785
|
-
<button onclick="method(arg1, arg2)">Call with args</button>
|
|
1786
|
-
<button onclick="console.log(event)">Inline function</button>
|
|
1787
|
-
|
|
1788
|
-
<!-- Slots -->
|
|
1789
|
-
<slot></slot>
|
|
1790
|
-
<!-- Default slot -->
|
|
1791
|
-
<slot name="header"></slot>
|
|
1792
|
-
<!-- Named slot -->
|
|
472
|
+
export default defineConfig({
|
|
473
|
+
// LadrillosJS components work out of the box!
|
|
474
|
+
});
|
|
1793
475
|
```
|
|
1794
476
|
|
|
1795
|
-
|
|
477
|
+
```javascript
|
|
478
|
+
// main.js
|
|
479
|
+
import { registerComponent } from "ladrillosjs";
|
|
1796
480
|
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
{variableName} {object.property} {array[0]}
|
|
481
|
+
registerComponent("my-counter", "./components/counter.html", false);
|
|
482
|
+
```
|
|
1800
483
|
|
|
1801
|
-
|
|
1802
|
-
{functionName(arg1, arg2)} {object.method()}
|
|
484
|
+
See the [samples/](samples/) directory for complete examples:
|
|
1803
485
|
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
486
|
+
- `samples/vite-sample/` — Basic Vite setup
|
|
487
|
+
- `samples/vite-basic-site/` — Multi-component site
|
|
488
|
+
- `samples/ladrillos-demo/` — Full feature showcase
|
|
1807
489
|
|
|
1808
|
-
|
|
490
|
+
---
|
|
1809
491
|
|
|
1810
|
-
|
|
492
|
+
## 📚 Examples
|
|
1811
493
|
|
|
1812
|
-
|
|
1813
|
-
- **npm**: v9+ (comes with Node.js)
|
|
494
|
+
### Todo List
|
|
1814
495
|
|
|
1815
|
-
|
|
496
|
+
A complete CRUD example combining all directives:
|
|
1816
497
|
|
|
1817
|
-
```
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
498
|
+
```html
|
|
499
|
+
<div class="todo-app">
|
|
500
|
+
<form onsubmit="addTodo(event)">
|
|
501
|
+
<input type="text" $bind="newTodo" placeholder="What needs to be done?" />
|
|
502
|
+
<button type="submit">Add</button>
|
|
503
|
+
</form>
|
|
1821
504
|
|
|
1822
|
-
|
|
1823
|
-
|
|
505
|
+
<ul>
|
|
506
|
+
<li $for="todo in todos" $key="todo.id">
|
|
507
|
+
<input type="checkbox" onclick="toggleTodo({todo.id})" />
|
|
508
|
+
<span class="{todo.completed ? 'done' : ''}">{todo.text}</span>
|
|
509
|
+
<button onclick="removeTodo({todo.id})">🗑️</button>
|
|
510
|
+
</li>
|
|
511
|
+
</ul>
|
|
1824
512
|
|
|
1825
|
-
|
|
1826
|
-
|
|
513
|
+
<p $if="{todos.length === 0}">No todos yet!</p>
|
|
514
|
+
</div>
|
|
1827
515
|
|
|
1828
|
-
|
|
1829
|
-
|
|
516
|
+
<script>
|
|
517
|
+
let todos = [
|
|
518
|
+
{ id: 1, text: "Learn LadrillosJS", completed: false },
|
|
519
|
+
{ id: 2, text: "Build something awesome", completed: false },
|
|
520
|
+
];
|
|
521
|
+
let newTodo = "";
|
|
522
|
+
let nextId = 3;
|
|
523
|
+
|
|
524
|
+
function addTodo(event) {
|
|
525
|
+
event.preventDefault();
|
|
526
|
+
if (newTodo.trim()) {
|
|
527
|
+
todos = [...todos, { id: nextId++, text: newTodo, completed: false }];
|
|
528
|
+
newTodo = "";
|
|
529
|
+
}
|
|
530
|
+
}
|
|
1830
531
|
|
|
1831
|
-
|
|
1832
|
-
|
|
532
|
+
function toggleTodo(id) {
|
|
533
|
+
todos = todos.map((t) =>
|
|
534
|
+
t.id === id ? { ...t, completed: !t.completed } : t
|
|
535
|
+
);
|
|
536
|
+
}
|
|
1833
537
|
|
|
1834
|
-
|
|
1835
|
-
|
|
538
|
+
function removeTodo(id) {
|
|
539
|
+
todos = todos.filter((t) => t.id !== id);
|
|
540
|
+
}
|
|
541
|
+
</script>
|
|
1836
542
|
```
|
|
1837
543
|
|
|
1838
|
-
|
|
544
|
+
> 💡 Check the [samples/](samples/) folder for more examples including lazy loading, event bus communication, and real-world patterns.
|
|
1839
545
|
|
|
1840
|
-
|
|
1841
|
-
LadrillosJS/
|
|
1842
|
-
├── src/
|
|
1843
|
-
│ ├── index.ts # Main entry point
|
|
1844
|
-
│ ├── core/
|
|
1845
|
-
│ │ ├── main.ts # Core Ladrillos class
|
|
1846
|
-
│ │ ├── webcomponent.ts # Web component wrapper
|
|
1847
|
-
│ │ ├── componentParser.ts # Parse component files
|
|
1848
|
-
│ │ ├── componentSource.ts # Fetch with caching
|
|
1849
|
-
│ │ ├── eventBus.ts # Global event bus
|
|
1850
|
-
│ │ ├── css/
|
|
1851
|
-
│ │ │ └── cssParser.ts
|
|
1852
|
-
│ │ ├── html/
|
|
1853
|
-
│ │ │ ├── htmlparser.ts
|
|
1854
|
-
│ │ │ └── htmlRenderer.ts
|
|
1855
|
-
│ │ └── js/
|
|
1856
|
-
│ │ └── scriptParser.ts
|
|
1857
|
-
│ ├── cache/
|
|
1858
|
-
│ │ ├── index.ts # LRU component cache
|
|
1859
|
-
│ │ └── functionCache.ts # LRU function cache
|
|
1860
|
-
│ ├── types/
|
|
1861
|
-
│ │ └── LadrilloTypes.ts
|
|
1862
|
-
│ └── utils/
|
|
1863
|
-
│ ├── logger.ts
|
|
1864
|
-
│ └── regex.ts
|
|
1865
|
-
├── samples/ # Example applications
|
|
1866
|
-
│ └── apps/
|
|
1867
|
-
│ ├── todo/
|
|
1868
|
-
│ ├── notes/
|
|
1869
|
-
│ ├── simple-button/
|
|
1870
|
-
│ └── ...
|
|
1871
|
-
├── test/ # Test files
|
|
1872
|
-
├── dist/ # Built files
|
|
1873
|
-
├── package.json
|
|
1874
|
-
├── tsconfig.json
|
|
1875
|
-
├── vite.config.js
|
|
1876
|
-
└── vitest.config.js
|
|
1877
|
-
```
|
|
546
|
+
---
|
|
1878
547
|
|
|
1879
|
-
|
|
548
|
+
## 🤝 Contributing
|
|
549
|
+
|
|
550
|
+
Contributions are welcome! Here's how you can help:
|
|
551
|
+
|
|
552
|
+
1. **Fork** the repository
|
|
553
|
+
2. **Create** a feature branch: `git checkout -b feature/amazing-feature`
|
|
554
|
+
3. **Commit** your changes: `git commit -m 'Add amazing feature'`
|
|
555
|
+
4. **Push** to the branch: `git push origin feature/amazing-feature`
|
|
556
|
+
5. **Open** a Pull Request
|
|
557
|
+
|
|
558
|
+
### Development Setup
|
|
1880
559
|
|
|
1881
560
|
```bash
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
npm
|
|
1885
|
-
npm
|
|
1886
|
-
npm run
|
|
1887
|
-
npm run preview # Preview production build
|
|
561
|
+
git clone https://github.com/drubiodev/LadrillosJS.git
|
|
562
|
+
cd LadrillosJS
|
|
563
|
+
npm install
|
|
564
|
+
npm run dev # Watch mode for development
|
|
565
|
+
npm run build # Build for production
|
|
1888
566
|
```
|
|
1889
567
|
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
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!
|
|
568
|
+
---
|
|
1893
569
|
|
|
1894
|
-
## License
|
|
570
|
+
## 📄 License
|
|
1895
571
|
|
|
1896
|
-
MIT License
|
|
572
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
1897
573
|
|
|
1898
574
|
---
|
|
1899
575
|
|
|
1900
|
-
|
|
576
|
+
<p align="center">
|
|
577
|
+
<strong>Built with ❤️ by <a href="https://github.com/drubiodev">Daniel Rubio</a></strong>
|
|
578
|
+
</p>
|
|
1901
579
|
|
|
1902
|
-
|
|
580
|
+
<p align="center">
|
|
581
|
+
<a href="https://github.com/drubiodev/LadrillosJS">GitHub</a> •
|
|
582
|
+
<a href="https://www.npmjs.com/package/ladrillosjs">NPM</a> •
|
|
583
|
+
<a href="https://github.com/drubiodev/LadrillosJS/issues">Issues</a>
|
|
584
|
+
</p>
|