lego-dom 0.0.5 → 0.0.8
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/.github/workflows/deploy-docs.yml +56 -0
- package/LICENSE +21 -0
- package/README.md +298 -355
- package/docs/.vitepress/config.js +107 -0
- package/docs/.vitepress/dist/404.html +22 -0
- package/docs/.vitepress/dist/api/define.html +35 -0
- package/docs/.vitepress/dist/api/directives.html +32 -0
- package/docs/.vitepress/dist/api/globals.html +27 -0
- package/docs/.vitepress/dist/api/index.html +25 -0
- package/docs/.vitepress/dist/api/lifecycle.html +38 -0
- package/docs/.vitepress/dist/api/route.html +34 -0
- package/docs/.vitepress/dist/api/vite-plugin.html +37 -0
- package/docs/.vitepress/dist/assets/api_define.md.UA-ygUnQ.js +11 -0
- package/docs/.vitepress/dist/assets/api_define.md.UA-ygUnQ.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_directives.md.BV-D251p.js +8 -0
- package/docs/.vitepress/dist/assets/api_directives.md.BV-D251p.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_globals.md.DOjt7AV0.js +3 -0
- package/docs/.vitepress/dist/assets/api_globals.md.DOjt7AV0.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_index.md.OS6h01ct.js +1 -0
- package/docs/.vitepress/dist/assets/api_index.md.OS6h01ct.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_lifecycle.md.Ccm5xw6-.js +14 -0
- package/docs/.vitepress/dist/assets/api_lifecycle.md.Ccm5xw6-.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_route.md.CAHf_KNp.js +10 -0
- package/docs/.vitepress/dist/assets/api_route.md.CAHf_KNp.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_vite-plugin.md.DNn9VhL5.js +13 -0
- package/docs/.vitepress/dist/assets/api_vite-plugin.md.DNn9VhL5.lean.js +1 -0
- package/docs/.vitepress/dist/assets/app.BG5s3B0P.js +1 -0
- package/docs/.vitepress/dist/assets/chunks/@localSearchIndexroot.DQmuWC2Z.js +1 -0
- package/docs/.vitepress/dist/assets/chunks/VPLocalSearchBox.BO-PSxt1.js +9 -0
- package/docs/.vitepress/dist/assets/chunks/framework.B7OFBR9X.js +19 -0
- package/docs/.vitepress/dist/assets/chunks/theme.DA-iSa9B.js +2 -0
- package/docs/.vitepress/dist/assets/examples_form.md.B3stGKbu.js +34 -0
- package/docs/.vitepress/dist/assets/examples_form.md.B3stGKbu.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_index.md.BDEG_D4J.js +30 -0
- package/docs/.vitepress/dist/assets/examples_index.md.BDEG_D4J.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_routing.md.bqZ9DjDK.js +338 -0
- package/docs/.vitepress/dist/assets/examples_routing.md.bqZ9DjDK.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_sfc-showcase.md.DLXaUiop.js +13 -0
- package/docs/.vitepress/dist/assets/examples_sfc-showcase.md.DLXaUiop.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_todo-app.md.D5RhZoo5.js +297 -0
- package/docs/.vitepress/dist/assets/examples_todo-app.md.D5RhZoo5.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_cdn-usage.md.CAjf03Lr.js +182 -0
- package/docs/.vitepress/dist/assets/guide_cdn-usage.md.CAjf03Lr.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_components.md.BIFWF1Hc.js +174 -0
- package/docs/.vitepress/dist/assets/guide_components.md.BIFWF1Hc.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_contributing.md.BgbUN-Mr.js +1 -0
- package/docs/.vitepress/dist/assets/guide_contributing.md.BgbUN-Mr.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_directives.md.Bi3ynu1d.js +140 -0
- package/docs/.vitepress/dist/assets/guide_directives.md.Bi3ynu1d.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_getting-started.md.2Nr1lp2z.js +107 -0
- package/docs/.vitepress/dist/assets/guide_getting-started.md.2Nr1lp2z.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_index.md.GvZq_Yf2.js +2 -0
- package/docs/.vitepress/dist/assets/guide_index.md.GvZq_Yf2.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_lifecycle.md.B28j1OzS.js +304 -0
- package/docs/.vitepress/dist/assets/guide_lifecycle.md.B28j1OzS.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_quick-start.md.CNk3VGTF.js +33 -0
- package/docs/.vitepress/dist/assets/guide_quick-start.md.CNk3VGTF.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_reactivity.md.CVsaMaPv.js +135 -0
- package/docs/.vitepress/dist/assets/guide_reactivity.md.CVsaMaPv.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_routing.md.DSpDP25o.js +193 -0
- package/docs/.vitepress/dist/assets/guide_routing.md.DSpDP25o.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_sfc.md.CVUP66tS.js +187 -0
- package/docs/.vitepress/dist/assets/guide_sfc.md.CVUP66tS.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_templating.md.BgCGe4aa.js +119 -0
- package/docs/.vitepress/dist/assets/guide_templating.md.BgCGe4aa.lean.js +1 -0
- package/docs/.vitepress/dist/assets/index.md.xV1taCED.js +23 -0
- package/docs/.vitepress/dist/assets/index.md.xV1taCED.lean.js +1 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/docs/.vitepress/dist/assets/style.eycE2Jhw.css +1 -0
- package/docs/.vitepress/dist/examples/form.html +58 -0
- package/docs/.vitepress/dist/examples/index.html +368 -0
- package/docs/.vitepress/dist/examples/routing.html +362 -0
- package/docs/.vitepress/dist/examples/sfc-showcase.html +37 -0
- package/docs/.vitepress/dist/examples/todo-app.html +321 -0
- package/docs/.vitepress/dist/guide/cdn-usage.html +206 -0
- package/docs/.vitepress/dist/guide/components.html +198 -0
- package/docs/.vitepress/dist/guide/contributing.html +25 -0
- package/docs/.vitepress/dist/guide/directives.html +164 -0
- package/docs/.vitepress/dist/guide/getting-started.html +131 -0
- package/docs/.vitepress/dist/guide/index.html +26 -0
- package/docs/.vitepress/dist/guide/lifecycle.html +328 -0
- package/docs/.vitepress/dist/guide/quick-start.html +57 -0
- package/docs/.vitepress/dist/guide/reactivity.html +159 -0
- package/docs/.vitepress/dist/guide/routing.html +217 -0
- package/docs/.vitepress/dist/guide/sfc.html +211 -0
- package/docs/.vitepress/dist/guide/templating.html +143 -0
- package/docs/.vitepress/dist/hashmap.json +1 -0
- package/docs/.vitepress/dist/index.html +47 -0
- package/docs/.vitepress/dist/logo.svg +38 -0
- package/docs/.vitepress/dist/vp-icons.css +1 -0
- package/docs/api/define.md +31 -0
- package/docs/api/directives.md +42 -0
- package/docs/api/globals.md +29 -0
- package/docs/api/index.md +29 -0
- package/docs/api/lifecycle.md +40 -0
- package/docs/api/route.md +37 -0
- package/docs/api/vite-plugin.md +58 -0
- package/docs/examples/form.md +42 -0
- package/docs/examples/index.md +104 -0
- package/docs/examples/routing.md +409 -0
- package/docs/examples/sfc-showcase.md +34 -0
- package/docs/examples/todo-app.md +383 -0
- package/docs/guide/cdn-usage.md +320 -0
- package/docs/guide/components.md +394 -0
- package/docs/guide/contributing.md +32 -0
- package/docs/guide/directives.md +430 -0
- package/docs/guide/getting-started.md +233 -0
- package/docs/guide/index.md +88 -0
- package/docs/guide/lifecycle.md +493 -0
- package/docs/guide/quick-start.md +46 -0
- package/docs/guide/reactivity.md +394 -0
- package/docs/guide/routing.md +373 -0
- package/docs/guide/sfc.md +381 -0
- package/docs/guide/templating.md +383 -0
- package/docs/index.md +126 -0
- package/docs/public/logo.svg +38 -0
- package/examples/vite-app/README.md +71 -0
- package/examples/vite-app/index.html +45 -0
- package/examples/vite-app/package.json +16 -0
- package/examples/vite-app/src/components/greeting-card.lego +41 -0
- package/examples/vite-app/src/components/sample-component.lego +75 -0
- package/examples/vite-app/src/main.js +11 -0
- package/examples/vite-app/vite.config.js +16 -0
- package/examples.js +99 -0
- package/package.json +34 -7
- package/parse-lego.js +119 -0
- package/parse-lego.test.js +107 -0
- package/vite-plugin.js +133 -0
- package/.ignore/auto.html +0 -135
- package/.ignore/test.html +0 -73
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# Templating
|
|
2
|
+
|
|
3
|
+
Learn about LegoJS templating features and syntax.
|
|
4
|
+
|
|
5
|
+
## Interpolation
|
|
6
|
+
|
|
7
|
+
Use `{{ }}` to insert dynamic content:
|
|
8
|
+
|
|
9
|
+
### Simple Values
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<p>{{ message }}</p>
|
|
13
|
+
<h1>{{ title }}</h1>
|
|
14
|
+
<span>{{ count }}</span>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Expressions
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<p>{{ count * 2 }}</p>
|
|
21
|
+
<span>{{ price.toFixed(2) }}</span>
|
|
22
|
+
<div>{{ firstName + ' ' + lastName }}</div>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Method Calls
|
|
26
|
+
|
|
27
|
+
```html
|
|
28
|
+
<p>{{ formatDate(timestamp) }}</p>
|
|
29
|
+
<span>{{ calculateTotal() }}</span>
|
|
30
|
+
<div>{{ getUsername() }}</div>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Conditional (Ternary)
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<p>{{ age >= 18 ? 'Adult' : 'Minor' }}</p>
|
|
37
|
+
<span>{{ isOnline ? '🟢 Online' : '🔴 Offline' }}</span>
|
|
38
|
+
<div>{{ items.length > 0 ? items.length + ' items' : 'Empty' }}</div>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Attribute Binding
|
|
42
|
+
|
|
43
|
+
Interpolate in any attribute:
|
|
44
|
+
|
|
45
|
+
### Simple Attributes
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<img src="/avatars/{{ userId }}.png" alt="{{ username }}">
|
|
49
|
+
<a href="/user/{{ userId }}">{{ username }}</a>
|
|
50
|
+
<input placeholder="{{ defaultText }}">
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Class Names
|
|
54
|
+
|
|
55
|
+
```html
|
|
56
|
+
<div class="card {{ isActive ? 'active' : '' }}">...</div>
|
|
57
|
+
<button class="{{ isDisabled ? 'disabled' : 'enabled' }}">...</button>
|
|
58
|
+
<li class="item status-{{ status }}">...</li>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Data Attributes
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<div data-id="{{ itemId }}" data-type="{{ itemType }}">...</div>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Style (Inline)
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<div style="color: {{ textColor }}; background: {{ bgColor }}">...</div>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Escaping
|
|
74
|
+
|
|
75
|
+
LegoJS automatically escapes HTML to prevent XSS:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
{
|
|
79
|
+
userInput: '<script>alert("XSS")</script>'
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<p>{{ userInput }}</p>
|
|
85
|
+
<!-- Renders as: <script>alert("XSS")</script> -->
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**There is no way to render raw HTML.** This is by design—for security.
|
|
89
|
+
|
|
90
|
+
## Whitespace
|
|
91
|
+
|
|
92
|
+
Templates preserve whitespace:
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<p>
|
|
96
|
+
{{ message }}
|
|
97
|
+
</p>
|
|
98
|
+
<!-- Renders with newlines and indentation -->
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Trim manually if needed:
|
|
102
|
+
|
|
103
|
+
```html
|
|
104
|
+
<p>{{ message.trim() }}</p>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Context
|
|
108
|
+
|
|
109
|
+
Inside `{{ }}`, you have access to:
|
|
110
|
+
|
|
111
|
+
### Component State (`this`)
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<p>{{ count }}</p> <!-- this.count -->
|
|
115
|
+
<span>{{ user.name }}</span> <!-- this.user.name -->
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Methods
|
|
119
|
+
|
|
120
|
+
```html
|
|
121
|
+
<p>{{ formatDate(timestamp) }}</p>
|
|
122
|
+
<div>{{ calculateTotal() }}</div>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Special Keywords
|
|
126
|
+
|
|
127
|
+
- `global` - Access `Lego.globals`
|
|
128
|
+
- `event` - In event handlers
|
|
129
|
+
- `self` - Reference to component element (rare)
|
|
130
|
+
|
|
131
|
+
```html
|
|
132
|
+
<p>{{ global.user.name }}</p>
|
|
133
|
+
<button @click="console.log(event)">Click</button>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Complex Examples
|
|
137
|
+
|
|
138
|
+
### Formatting Currency
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
{
|
|
142
|
+
price: 29.99,
|
|
143
|
+
formatCurrency(value) {
|
|
144
|
+
return '$' + value.toFixed(2);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```html
|
|
150
|
+
<p>Price: {{ formatCurrency(price) }}</p>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Date Formatting
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
{
|
|
157
|
+
timestamp: Date.now(),
|
|
158
|
+
formatDate(ts) {
|
|
159
|
+
return new Date(ts).toLocaleDateString();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<time>{{ formatDate(timestamp) }}</time>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Pluralization
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
{
|
|
172
|
+
items: ['apple', 'banana'],
|
|
173
|
+
plural(count, singular, plural) {
|
|
174
|
+
return count === 1 ? singular : plural;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```html
|
|
180
|
+
<p>{{ items.length }} {{ plural(items.length, 'item', 'items') }}</p>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Truncation
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
{
|
|
187
|
+
description: 'Very long text...',
|
|
188
|
+
truncate(text, length) {
|
|
189
|
+
return text.length > length
|
|
190
|
+
? text.slice(0, length) + '...'
|
|
191
|
+
: text;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
```html
|
|
197
|
+
<p>{{ truncate(description, 100) }}</p>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Limitations
|
|
201
|
+
|
|
202
|
+
### No Statements
|
|
203
|
+
|
|
204
|
+
Can't use statements—only expressions:
|
|
205
|
+
|
|
206
|
+
```html
|
|
207
|
+
<!-- ❌ Doesn't work -->
|
|
208
|
+
<p>{{ if (condition) { return 'yes'; } }}</p>
|
|
209
|
+
<p>{{ for (let i = 0; i < 10; i++) { } }}</p>
|
|
210
|
+
|
|
211
|
+
<!-- ✅ Use ternary or methods -->
|
|
212
|
+
<p>{{ condition ? 'yes' : 'no' }}</p>
|
|
213
|
+
<p>{{ renderList() }}</p>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### No Declarations
|
|
217
|
+
|
|
218
|
+
Can't declare variables:
|
|
219
|
+
|
|
220
|
+
```html
|
|
221
|
+
<!-- ❌ Doesn't work -->
|
|
222
|
+
<p>{{ const total = price * qty; total }}</p>
|
|
223
|
+
|
|
224
|
+
<!-- ✅ Use methods -->
|
|
225
|
+
<p>{{ getTotal() }}</p>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
{
|
|
230
|
+
getTotal() {
|
|
231
|
+
const total = this.price * this.qty;
|
|
232
|
+
return total;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Best Practices
|
|
238
|
+
|
|
239
|
+
### 1. Keep Templates Simple
|
|
240
|
+
|
|
241
|
+
If logic is complex, use methods:
|
|
242
|
+
|
|
243
|
+
```html
|
|
244
|
+
<!-- ❌ Too complex -->
|
|
245
|
+
<p>{{ items.filter(x => x.active).map(x => x.name).join(', ') }}</p>
|
|
246
|
+
|
|
247
|
+
<!-- ✅ Better -->
|
|
248
|
+
<p>{{ getActiveNames() }}</p>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
{
|
|
253
|
+
getActiveNames() {
|
|
254
|
+
return this.items
|
|
255
|
+
.filter(x => x.active)
|
|
256
|
+
.map(x => x.name)
|
|
257
|
+
.join(', ');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 2. Format in Methods
|
|
263
|
+
|
|
264
|
+
Don't put formatting logic in templates:
|
|
265
|
+
|
|
266
|
+
```html
|
|
267
|
+
<!-- ❌ Messy -->
|
|
268
|
+
<p>${{ (price * 1.2).toFixed(2) }}</p>
|
|
269
|
+
|
|
270
|
+
<!-- ✅ Clean -->
|
|
271
|
+
<p>{{ formatPrice(price) }}</p>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 3. Avoid Side Effects
|
|
275
|
+
|
|
276
|
+
Don't mutate state in templates:
|
|
277
|
+
|
|
278
|
+
```html
|
|
279
|
+
<!-- ❌ Bad -->
|
|
280
|
+
<p>{{ count++ }}</p>
|
|
281
|
+
|
|
282
|
+
<!-- ✅ Good -->
|
|
283
|
+
<p>{{ count }}</p>
|
|
284
|
+
<button @click="count++">Increment</button>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### 4. Use Descriptive Method Names
|
|
288
|
+
|
|
289
|
+
```js
|
|
290
|
+
{
|
|
291
|
+
// ✅ Clear purpose
|
|
292
|
+
formatCurrency(value) { ... },
|
|
293
|
+
calculateTax(amount) { ... },
|
|
294
|
+
isValidEmail(email) { ... }
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Performance Tips
|
|
299
|
+
|
|
300
|
+
### Cache Computed Values
|
|
301
|
+
|
|
302
|
+
If a calculation is expensive, cache it:
|
|
303
|
+
|
|
304
|
+
```js
|
|
305
|
+
{
|
|
306
|
+
items: [],
|
|
307
|
+
_cachedTotal: null,
|
|
308
|
+
|
|
309
|
+
total() {
|
|
310
|
+
if (this._cachedTotal === null) {
|
|
311
|
+
this._cachedTotal = this.items.reduce((sum, x) => sum + x.price, 0);
|
|
312
|
+
}
|
|
313
|
+
return this._cachedTotal;
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
updated() {
|
|
317
|
+
this._cachedTotal = null; // Invalidate cache
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Avoid Heavy Calculations
|
|
323
|
+
|
|
324
|
+
```html
|
|
325
|
+
<!-- ❌ Runs on every render -->
|
|
326
|
+
<p>{{ expensiveCalculation() }}</p>
|
|
327
|
+
|
|
328
|
+
<!-- ✅ Calculate once, store result -->
|
|
329
|
+
<p>{{ cachedResult }}</p>
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```js
|
|
333
|
+
{
|
|
334
|
+
cachedResult: null,
|
|
335
|
+
mounted() {
|
|
336
|
+
this.cachedResult = this.expensiveCalculation();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Common Patterns
|
|
342
|
+
|
|
343
|
+
### Show/Hide Based on Condition
|
|
344
|
+
|
|
345
|
+
```html
|
|
346
|
+
<p b-if="user">Welcome, {{ user.name }}!</p>
|
|
347
|
+
<p b-if="!user">Please log in</p>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### List with Index
|
|
351
|
+
|
|
352
|
+
```html
|
|
353
|
+
<ul>
|
|
354
|
+
<li b-for="item in items">
|
|
355
|
+
#{{ $index + 1 }}: {{ item.name }}
|
|
356
|
+
</li>
|
|
357
|
+
</ul>
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Conditional Classes
|
|
361
|
+
|
|
362
|
+
```html
|
|
363
|
+
<div class="item {{ item.active ? 'active' : '' }} {{ item.featured ? 'featured' : '' }}">
|
|
364
|
+
{{ item.name }}
|
|
365
|
+
</div>
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Dynamic Attributes
|
|
369
|
+
|
|
370
|
+
```html
|
|
371
|
+
<a
|
|
372
|
+
href="/product/{{ product.id }}"
|
|
373
|
+
class="product-link {{ product.inStock ? '' : 'out-of-stock' }}"
|
|
374
|
+
title="{{ product.name }} - ${{ product.price }}">
|
|
375
|
+
{{ product.name }}
|
|
376
|
+
</a>
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Next Steps
|
|
380
|
+
|
|
381
|
+
- Learn about [Directives](/guide/directives)
|
|
382
|
+
- See [templating examples](/examples/)
|
|
383
|
+
- Explore [component patterns](/guide/components)
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: home
|
|
3
|
+
|
|
4
|
+
hero:
|
|
5
|
+
name: LegoJS
|
|
6
|
+
text: Build Reactive Web Components
|
|
7
|
+
tagline: A tiny, zero-dependency JavaScript library for creating reactive Web Components directly in the browser
|
|
8
|
+
image:
|
|
9
|
+
src: /logo.svg
|
|
10
|
+
alt: LegoJS
|
|
11
|
+
actions:
|
|
12
|
+
- theme: brand
|
|
13
|
+
text: Get Started
|
|
14
|
+
link: /guide/getting-started
|
|
15
|
+
- theme: alt
|
|
16
|
+
text: View on GitHub
|
|
17
|
+
link: https://github.com/rayattack/LegoJS
|
|
18
|
+
- theme: alt
|
|
19
|
+
text: Try Examples
|
|
20
|
+
link: /examples/
|
|
21
|
+
|
|
22
|
+
features:
|
|
23
|
+
- icon: 🎯
|
|
24
|
+
title: Mental Model Simplicity
|
|
25
|
+
details: No virtual DOM, no compilation step, no JSX. Just HTML with a few directives and reactive objects.
|
|
26
|
+
|
|
27
|
+
- icon: ⚡
|
|
28
|
+
title: Zero Dependencies
|
|
29
|
+
details: Under 500 lines of code with no external dependencies. The entire library is smaller than most framework router plugins.
|
|
30
|
+
|
|
31
|
+
- icon: 🔄
|
|
32
|
+
title: True Reactivity
|
|
33
|
+
details: Direct object mutation triggers updates. No setters, no actions, no reducers. Just change the data and the DOM updates.
|
|
34
|
+
|
|
35
|
+
- icon: 🧩
|
|
36
|
+
title: Web Components Native
|
|
37
|
+
details: Built on standard Web Components with Shadow DOM. Works anywhere, plays well with existing code.
|
|
38
|
+
|
|
39
|
+
- icon: 📦
|
|
40
|
+
title: Single File Components
|
|
41
|
+
details: Use .lego files with Vite for a modern development experience, or load directly in the browser.
|
|
42
|
+
|
|
43
|
+
- icon: 🎨
|
|
44
|
+
title: Scoped Styles
|
|
45
|
+
details: Shadow DOM encapsulation means your styles never leak. Use the 'self' keyword to target component root.
|
|
46
|
+
|
|
47
|
+
- icon: 🛣️
|
|
48
|
+
title: Built-in Router
|
|
49
|
+
details: Client-side routing included. Define routes, handle parameters, and add middleware without extra packages.
|
|
50
|
+
|
|
51
|
+
- icon: 💪
|
|
52
|
+
title: TypeScript Ready
|
|
53
|
+
details: Full JSDoc annotations for excellent IDE support and optional TypeScript integration.
|
|
54
|
+
|
|
55
|
+
- icon: 🚀
|
|
56
|
+
title: Production Ready
|
|
57
|
+
details: Battle-tested patterns from Vue and React, adapted for pure Web Components. No framework lock-in.
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Quick Example
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<!-- Define a component -->
|
|
64
|
+
<template b-id="counter-button">
|
|
65
|
+
<style>
|
|
66
|
+
self {
|
|
67
|
+
display: block;
|
|
68
|
+
padding: 1rem;
|
|
69
|
+
}
|
|
70
|
+
button {
|
|
71
|
+
font-size: 1.2rem;
|
|
72
|
+
padding: 0.5rem 1rem;
|
|
73
|
+
}
|
|
74
|
+
</style>
|
|
75
|
+
|
|
76
|
+
<h2>{{ title }}</h2>
|
|
77
|
+
<button @click="count++">
|
|
78
|
+
Clicked {{ count }} times
|
|
79
|
+
</button>
|
|
80
|
+
</template>
|
|
81
|
+
|
|
82
|
+
<!-- Use it -->
|
|
83
|
+
<counter-button b-data="{ title: 'My Counter', count: 0 }"></counter-button>
|
|
84
|
+
|
|
85
|
+
<script src="https://unpkg.com/lego-dom/main.js"></script>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
That's it. No build step, no npm, no configuration.
|
|
89
|
+
|
|
90
|
+
## Why LegoJS?
|
|
91
|
+
|
|
92
|
+
**For small projects**, you get reactive components without the overhead of a full framework.
|
|
93
|
+
|
|
94
|
+
**For large projects**, you get a clear mental model and Web Standards compliance.
|
|
95
|
+
|
|
96
|
+
**For learning**, you can read the entire source code in an afternoon and understand exactly how it works.
|
|
97
|
+
|
|
98
|
+
## Comparison
|
|
99
|
+
|
|
100
|
+
| Feature | LegoJS | Vue | React |
|
|
101
|
+
|---------|--------|-----|-------|
|
|
102
|
+
| Size | < 17KB | ~33KB | ~40KB |
|
|
103
|
+
| Dependencies | 0 | Many | Many |
|
|
104
|
+
| Build Required | No* | Yes | Yes |
|
|
105
|
+
| Virtual DOM | No | Yes | Yes |
|
|
106
|
+
| Learning Curve | Minimal | Moderate | Moderate |
|
|
107
|
+
| Web Components | Native | Optional | No |
|
|
108
|
+
|
|
109
|
+
\* *Optional with Vite for .lego files*
|
|
110
|
+
|
|
111
|
+
## Browser Support
|
|
112
|
+
|
|
113
|
+
LegoJS works in all modern browsers that support:
|
|
114
|
+
- Web Components
|
|
115
|
+
- Shadow DOM
|
|
116
|
+
- ES6 Proxy
|
|
117
|
+
- Template literals
|
|
118
|
+
|
|
119
|
+
This includes Chrome 63+, Firefox 63+, Safari 11.1+, and Edge 79+.
|
|
120
|
+
|
|
121
|
+
## Community
|
|
122
|
+
|
|
123
|
+
- 📖 [Documentation](https://rayattack.github.io/LegoJS/)
|
|
124
|
+
- 💬 [Discussions](https://github.com/rayattack/LegoJS/discussions)
|
|
125
|
+
- 🐛 [Issue Tracker](https://github.com/rayattack/LegoJS/issues)
|
|
126
|
+
- 📦 [npm Package](https://www.npmjs.com/package/lego-dom)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="blockGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#FF4B2B" />
|
|
5
|
+
<stop offset="100%" style="stop-color:#FF416C" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
|
8
|
+
<feGaussianBlur in="SourceAlpha" stdDeviation="3" />
|
|
9
|
+
<feOffset dx="2" dy="4" result="offsetblur" />
|
|
10
|
+
<feComponentTransfer>
|
|
11
|
+
<feFuncA type="linear" slope="0.2" />
|
|
12
|
+
</feComponentTransfer>
|
|
13
|
+
<feMerge>
|
|
14
|
+
<feMergeNode />
|
|
15
|
+
<feMergeNode in="SourceGraphic" />
|
|
16
|
+
</feMerge>
|
|
17
|
+
</filter>
|
|
18
|
+
</defs>
|
|
19
|
+
|
|
20
|
+
<g filter="url(#shadow)" transform="translate(40, 40)">
|
|
21
|
+
<path d="M60 80 L90 65 L90 95 L60 110 Z" fill="#D32F2F" />
|
|
22
|
+
<path d="M90 65 L120 80 L120 110 L90 95 Z" fill="#B71C1C" />
|
|
23
|
+
<path d="M60 80 L90 65 L120 80 L90 95 Z" fill="url(#blockGrad)" />
|
|
24
|
+
<circle cx="90" cy="80" r="4" fill="white" fill-opacity="0.3" />
|
|
25
|
+
|
|
26
|
+
<path d="M30 65 L60 50 L60 80 L30 95 Z" fill="#D32F2F" />
|
|
27
|
+
<path d="M60 50 L90 65 L90 95 L60 80 Z" fill="#B71C1C" />
|
|
28
|
+
<path d="M30 65 L60 50 L90 65 L60 80 Z" fill="url(#blockGrad)" />
|
|
29
|
+
<circle cx="60" cy="65" r="4" fill="white" fill-opacity="0.3" />
|
|
30
|
+
|
|
31
|
+
<path d="M30 35 L60 20 L60 50 L30 65 Z" fill="#D32F2F" />
|
|
32
|
+
<path d="M60 20 L90 35 L90 65 L60 50 Z" fill="#B71C1C" />
|
|
33
|
+
<path d="M30 35 L60 20 L90 35 L60 50 Z" fill="url(#blockGrad)" />
|
|
34
|
+
<circle cx="60" cy="35" r="4" fill="white" fill-opacity="0.3" />
|
|
35
|
+
</g>
|
|
36
|
+
|
|
37
|
+
<text x="100" y="180" font-family="Arial, sans-serif" font-weight="900" font-size="24" fill="#333" text-anchor="middle" letter-spacing="1">LEGO.JS</text>
|
|
38
|
+
</svg>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# LegoJS SFC and Vite Plugin Example
|
|
2
|
+
|
|
3
|
+
This example demonstrates how to use LegoJS with Single File Components (.lego files) and the Vite plugin.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
1. Install dependencies:
|
|
8
|
+
```bash
|
|
9
|
+
npm install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
2. Run the dev server:
|
|
13
|
+
```bash
|
|
14
|
+
npm run dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
3. Open your browser to the URL shown (usually `http://localhost:5173`)
|
|
18
|
+
|
|
19
|
+
## Structure
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
examples/vite-app/
|
|
23
|
+
├── src/
|
|
24
|
+
│ ├── components/ # .lego files auto-discovered here
|
|
25
|
+
│ │ ├── sample-component.lego
|
|
26
|
+
│ │ └── greeting-card.lego
|
|
27
|
+
│ └── main.js # Entry point
|
|
28
|
+
├── index.html # HTML shell
|
|
29
|
+
├── vite.config.js # Vite configuration with lego plugin
|
|
30
|
+
└── package.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## How It Works
|
|
34
|
+
|
|
35
|
+
1. The Vite plugin scans `src/components/` for `.lego` files
|
|
36
|
+
2. Each `.lego` file is parsed and transformed into a `Lego.define()` call
|
|
37
|
+
3. The virtual module `virtual:lego-components` imports all discovered components
|
|
38
|
+
4. Components are automatically registered and available in your HTML
|
|
39
|
+
|
|
40
|
+
## Creating Components
|
|
41
|
+
|
|
42
|
+
Create a new `.lego` file in `src/components/`:
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<template>
|
|
46
|
+
<h1>{{ message }}</h1>
|
|
47
|
+
<button @click="count++">{{ count }}</button>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<script>
|
|
51
|
+
export default {
|
|
52
|
+
message: 'Hello!',
|
|
53
|
+
count: 0
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<style>
|
|
58
|
+
self {
|
|
59
|
+
display: block;
|
|
60
|
+
padding: 1rem;
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then use it in your HTML:
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<your-component-name></your-component-name>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The component name is automatically derived from the filename (kebab-case required).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>LegoJS Vite Example</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
10
|
+
max-width: 800px;
|
|
11
|
+
margin: 2rem auto;
|
|
12
|
+
padding: 0 1rem;
|
|
13
|
+
background: #f9fafb;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
h1 {
|
|
17
|
+
color: #1f2937;
|
|
18
|
+
border-bottom: 3px solid #4f46e5;
|
|
19
|
+
padding-bottom: 0.5rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.info {
|
|
23
|
+
background: white;
|
|
24
|
+
padding: 1rem;
|
|
25
|
+
border-radius: 0.5rem;
|
|
26
|
+
margin: 1rem 0;
|
|
27
|
+
border-left: 4px solid #10b981;
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<h1>LegoJS + Vite Example</h1>
|
|
33
|
+
|
|
34
|
+
<div class="info">
|
|
35
|
+
<p><strong>✨ These components are auto-discovered from .lego files!</strong></p>
|
|
36
|
+
<p>The Vite plugin automatically finds all .lego files in <code>src/components/</code> and registers them.</p>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<!-- These components are automatically discovered and hydrated -->
|
|
40
|
+
<sample-component></sample-component>
|
|
41
|
+
<greeting-card></greeting-card>
|
|
42
|
+
|
|
43
|
+
<script type="module" src="/src/main.js"></script>
|
|
44
|
+
</body>
|
|
45
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lego-vite-example",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"preview": "vite preview"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"lego-dom": "file:../.."
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"vite": "^5.0.0"
|
|
15
|
+
}
|
|
16
|
+
}
|