native-document 1.0.9 → 1.0.11
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/dist/native-document.dev.js +2671 -0
- package/dist/native-document.min.js +1 -0
- package/docs/anchor.md +208 -0
- package/docs/conditional-rendering.md +628 -0
- package/docs/contributing.md +51 -0
- package/docs/core-concepts.md +513 -0
- package/docs/elements.md +383 -0
- package/docs/getting-started.md +403 -0
- package/docs/lifecycle-events.md +106 -0
- package/docs/memory-management.md +90 -0
- package/docs/observables.md +265 -0
- package/docs/routing.md +817 -0
- package/docs/state-management.md +423 -0
- package/docs/validation.md +193 -0
- package/elements.js +3 -1
- package/index.js +2 -0
- package/package.json +1 -1
- package/readme.md +189 -425
- package/router.js +2 -0
- package/src/data/MemoryManager.js +15 -5
- package/src/data/Observable.js +35 -2
- package/src/data/ObservableChecker.js +3 -0
- package/src/data/ObservableItem.js +4 -0
- package/src/data/Store.js +6 -6
- package/src/router/Router.js +13 -13
- package/src/router/link.js +8 -5
- package/src/utils/plugins-manager.js +12 -0
- package/src/utils/prototypes.js +13 -0
- package/src/utils/validator.js +23 -1
- package/src/wrappers/AttributesWrapper.js +11 -1
- package/src/wrappers/HtmlElementWrapper.js +10 -1
package/readme.md
CHANGED
|
@@ -1,505 +1,269 @@
|
|
|
1
|
-
#
|
|
1
|
+
# NativeDocument
|
|
2
2
|
|
|
3
|
-
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](#)
|
|
5
|
+
[](#)
|
|
6
|
+
[](#)
|
|
6
7
|
|
|
7
|
-
> A
|
|
8
|
+
> **A reactive JavaScript framework that preserves native DOM simplicity without sacrificing modern features**
|
|
8
9
|
|
|
9
|
-
NativeDocument
|
|
10
|
+
NativeDocument combines the familiarity of vanilla JavaScript with the power of modern reactivity. No compilation, no virtual DOM, just pure JavaScript with an intuitive API.
|
|
10
11
|
|
|
11
|
-
##
|
|
12
|
+
## Why NativeDocument?
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- 🎯 **Zero Dependencies** - Pure vanilla JavaScript
|
|
16
|
-
- 🛣️ **Built-in Router** - Hash, History, and Memory modes
|
|
17
|
-
- 🏪 **Global State Management** - Share state across components
|
|
18
|
-
- 📦 **Complete HTML Elements** - All HTML elements with extended functionality
|
|
19
|
-
- 🎨 **Conditional Rendering** - ShowIf, ToggleView, ForEach components
|
|
20
|
-
- ✅ **Runtime Validation** - Type checking and argument validation
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## 🚀 Quick Start
|
|
24
|
-
|
|
25
|
-
### Installation
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
# Download the minified version
|
|
29
|
-
curl -o native-document.min.js https://raw.githubusercontent.com/afrocodeur/native-document/refs/heads/main/dist/native-document.min.js
|
|
30
|
-
|
|
31
|
-
# Or include via CDN
|
|
14
|
+
### **Instant Start**
|
|
15
|
+
```html
|
|
32
16
|
<script src="https://cdn.jsdelivr.net/gh/afrocodeur/native-document@latest/dist/native-document.min.js"></script>
|
|
33
17
|
```
|
|
34
18
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
cd my-project
|
|
40
|
-
npm install
|
|
41
|
-
npm start
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
|
|
19
|
+
### **Familiar API**
|
|
20
|
+
```javascript
|
|
21
|
+
import { Div, Button, Observable } from 'native-document/src/elements';
|
|
22
|
+
import { Observable } from 'native-document';
|
|
45
23
|
|
|
46
|
-
|
|
24
|
+
// CDN
|
|
25
|
+
// const { Div, Button } = NativeDocument.elements;
|
|
26
|
+
// const { Observable } = NativeDocument;
|
|
47
27
|
|
|
48
|
-
```javascript
|
|
49
|
-
const { Observable } = NativeDocument;
|
|
50
|
-
const { H1, P, Button } = NativeDocument.elements;
|
|
51
|
-
// Create reactive state
|
|
52
28
|
const count = Observable(0);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
P(['Count: ',count]),
|
|
59
|
-
Button("Increment").nd.on.click(() => count.set(count.val() + 1)),
|
|
60
|
-
Button("Reset").nd.on.click(() => count.set(0))
|
|
29
|
+
|
|
30
|
+
const App = Div({ class: 'app' }, [
|
|
31
|
+
Div([ 'Count ', count]),
|
|
32
|
+
// OR Div(`Count ${count}`),
|
|
33
|
+
Button('Increment').nd.on.click(() => count.set(count.val() + 1))
|
|
61
34
|
]);
|
|
62
35
|
|
|
63
|
-
|
|
64
|
-
document.body.appendChild(app);
|
|
36
|
+
document.body.appendChild(App);
|
|
65
37
|
```
|
|
66
38
|
|
|
67
|
-
|
|
39
|
+
### **Complete Features**
|
|
40
|
+
- **Native reactivity** with observables
|
|
41
|
+
- **Global store** for state management
|
|
42
|
+
- **Built-in conditional rendering**
|
|
43
|
+
- **Full-featured router** (hash, history, memory modes)
|
|
44
|
+
- **Advanced debugging system**
|
|
45
|
+
- **Automatic memory management** via FinalizationRegistry
|
|
68
46
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Observables are the heart of NativeDocument's reactivity system:
|
|
72
|
-
|
|
73
|
-
```javascript
|
|
74
|
-
const { Observable } = NativeDocument;
|
|
75
|
-
// Create observable
|
|
76
|
-
const name = Observable("John");
|
|
47
|
+
## Quick Installation
|
|
77
48
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// Set value
|
|
87
|
-
name.set("Jane");
|
|
88
|
-
|
|
89
|
-
// Cleanup
|
|
90
|
-
unsubscribe();
|
|
49
|
+
### Option 1: CDN (Instant Start)
|
|
50
|
+
```html
|
|
51
|
+
<script src="https://cdn.jsdelivr.net/gh/afrocodeur/native-document@latest/dist/native-document.min.js"></script>
|
|
52
|
+
<script>
|
|
53
|
+
const { Div, Observable } = NativeDocument.elements
|
|
54
|
+
// Your code here
|
|
55
|
+
</script>
|
|
91
56
|
```
|
|
92
57
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const { Observable } = NativeDocument;
|
|
100
|
-
const { Div, H1, H2, P, Link, Strong, Input, Button, Br } = NativeDocument.elements;
|
|
101
|
-
// Basic elements
|
|
102
|
-
const header = H1("My App");
|
|
103
|
-
const paragraph = P("Welcome to NativeDocument!");
|
|
104
|
-
|
|
105
|
-
// With attributes
|
|
106
|
-
const link = Link({href: "https://example.com", target: "_blank" }, "Click me");
|
|
107
|
-
|
|
108
|
-
const name = Observable('');
|
|
109
|
-
|
|
110
|
-
// With reactive attributes
|
|
111
|
-
const input = Input({
|
|
112
|
-
type: "text",
|
|
113
|
-
value: name, // Observable binding
|
|
114
|
-
placeholder: "Enter name"
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Nested elements
|
|
118
|
-
const card = Div({ class: "card" }, [
|
|
119
|
-
H2("Card Title"),
|
|
120
|
-
Strong(name),
|
|
121
|
-
P("Card content here..."),
|
|
122
|
-
Button("Action")
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
document.body.appendChild(Div([
|
|
126
|
-
input,
|
|
127
|
-
card,
|
|
128
|
-
Br(),
|
|
129
|
-
link
|
|
130
|
-
]));
|
|
58
|
+
### Option 2: Vite Template (Complete Project)
|
|
59
|
+
```bash
|
|
60
|
+
npx degit afrocodeur/native-document-vite my-app
|
|
61
|
+
cd my-app
|
|
62
|
+
npm install
|
|
63
|
+
npm run dev
|
|
131
64
|
```
|
|
132
65
|
|
|
66
|
+
### Option 3: NPM/Yarn
|
|
67
|
+
```bash
|
|
68
|
+
npm install native-document
|
|
69
|
+
# or
|
|
70
|
+
yarn add native-document
|
|
71
|
+
```
|
|
133
72
|
|
|
134
|
-
|
|
73
|
+
## Quick Example
|
|
135
74
|
|
|
136
75
|
```javascript
|
|
137
|
-
|
|
138
|
-
|
|
76
|
+
import { Div, Input, Button, ShowIf, ForEach } from 'native-document/src/elements'
|
|
77
|
+
import { Observable } from 'native-document'
|
|
139
78
|
|
|
79
|
+
// CDN
|
|
80
|
+
// const { Div, Input, Button, ShowIf, ForEach } = NativeDocument.elements;
|
|
81
|
+
// const { Observable } = NativeDocument;
|
|
140
82
|
|
|
141
|
-
|
|
142
|
-
const
|
|
83
|
+
// Reactive state
|
|
84
|
+
const todos = Observable.array([])
|
|
85
|
+
const newTodo = Observable('')
|
|
143
86
|
|
|
144
|
-
//
|
|
145
|
-
const
|
|
146
|
-
ShowIf(isVisible, P("This content is visible!")),
|
|
147
|
-
Button('Toggle').nd.on.click(() => isVisible.set((currentValue) => !currentValue))
|
|
148
|
-
]);
|
|
149
|
-
|
|
150
|
-
const whenContent = Div([
|
|
151
|
-
H2('When'),
|
|
152
|
-
When(user.check(u => u.age >= 18))
|
|
153
|
-
.show(() => P("Adult content"))
|
|
154
|
-
.otherwise(() => P("Minor content")),
|
|
155
|
-
]);
|
|
87
|
+
// Todo Component
|
|
88
|
+
const TodoApp = Div({ class: 'todo-app' }, [
|
|
156
89
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
H2('Switch'),
|
|
160
|
-
Switch(
|
|
161
|
-
user.check(u => u.age >= 18),
|
|
162
|
-
() => P("Adult content"), // use function to create element only if requested
|
|
163
|
-
() => P("Minor content")
|
|
164
|
-
),
|
|
165
|
-
Button('Toggle user age').nd.on.click(() => user.set((currentValue) => ({ ...currentValue, age: currentValue.age === 25 ? 15 :25 })))
|
|
166
|
-
]);
|
|
90
|
+
// Input for new todo
|
|
91
|
+
Input({ placeholder: 'Add new task...', value: newTodo }),
|
|
167
92
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
93
|
+
// Add button
|
|
94
|
+
Button('Add Todo').nd.on.click(() => {
|
|
95
|
+
if (newTodo.val().trim()) {
|
|
96
|
+
todos.push({ id: Date.now(), text: newTodo.val(), done: false })
|
|
97
|
+
newTodo.set('')
|
|
98
|
+
}
|
|
99
|
+
}),
|
|
100
|
+
|
|
101
|
+
// Todo list
|
|
102
|
+
ForEach(todos, (todo, index) =>
|
|
103
|
+
Div({ class: 'todo-item' }, [
|
|
104
|
+
Input({ type: 'checkbox', checked: todo.done }),
|
|
105
|
+
`${todo.text}`,
|
|
106
|
+
Button('Delete').nd.on.click(() => todos.splice(index.val(), 1))
|
|
107
|
+
]), /*item key (string | callback) */(item) => item),
|
|
108
|
+
|
|
109
|
+
// Empty state
|
|
110
|
+
ShowIf(
|
|
111
|
+
todos.check(list => list.length === 0),
|
|
112
|
+
Div({ class: 'empty' }, 'No todos yet!')
|
|
113
|
+
)
|
|
187
114
|
]);
|
|
188
115
|
|
|
189
|
-
|
|
190
|
-
Div({ class: "item" }, [
|
|
191
|
-
H4(item.name),
|
|
192
|
-
P(`$${item.price}`),
|
|
193
|
-
Button("Remove").nd.on.click(() => {
|
|
194
|
-
items.set((currentItems) => currentItems.filter(i => i.id !== item.id))
|
|
195
|
-
})
|
|
196
|
-
]), 'id');
|
|
197
|
-
|
|
198
|
-
document.body.appendChild(itemList);
|
|
116
|
+
document.body.appendChild(TodoApp)
|
|
199
117
|
```
|
|
200
118
|
|
|
201
|
-
|
|
119
|
+
## Core Concepts
|
|
202
120
|
|
|
121
|
+
### Observables
|
|
122
|
+
Reactive data that automatically updates the DOM:
|
|
203
123
|
```javascript
|
|
124
|
+
import { Div } from 'native-document/src/elements'
|
|
125
|
+
import { Observable } from 'native-document'
|
|
204
126
|
|
|
205
|
-
|
|
206
|
-
const {
|
|
207
|
-
|
|
208
|
-
const formData = Observable.object({ name: "", email: "d.mamadou@miridoo.net" });
|
|
127
|
+
// CDN
|
|
128
|
+
// const { Div } = NativeDocument.elements;
|
|
129
|
+
// const { Observable } = NativeDocument;
|
|
209
130
|
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
Div(input)
|
|
214
|
-
]);
|
|
215
|
-
}
|
|
131
|
+
const user = Observable({ name: 'John', age: 25 });
|
|
132
|
+
const greeting = Observable.computed(() => `Hello ${user.$value.name}!`, [user])
|
|
133
|
+
// Or const greeting = Observable.computed(() => `Hello ${user.val().name}!`, [user])
|
|
216
134
|
|
|
217
|
-
|
|
218
|
-
FormItem("Name", Input({ value: formData.name })),
|
|
219
|
-
FormItem("Email", Input({ value: formData.email })),
|
|
220
|
-
SubmitButton("Submit")
|
|
221
|
-
]).nd.on.prevent.submit((event) => {
|
|
222
|
-
console.log(Observable.value(formData));
|
|
223
|
-
});
|
|
135
|
+
document.body.appendChild(Div(greeting));
|
|
224
136
|
|
|
225
|
-
|
|
137
|
+
// user.name = 'Fausty'; // will not work
|
|
138
|
+
// user.$value = { ...user.$value, name: ' Hermes!' }; // will work
|
|
139
|
+
// user.set(data => ({ ...data, name: 'Hermes!' })); // will work
|
|
140
|
+
user.set({ ...user.val(), name: 'Hermes!' });
|
|
226
141
|
```
|
|
227
|
-
## 🛣️ Routing
|
|
228
|
-
|
|
229
|
-
NativeDocument includes a powerful routing system:
|
|
230
142
|
|
|
143
|
+
### Elements
|
|
144
|
+
Familiar HTML element creation with reactive bindings:
|
|
231
145
|
```javascript
|
|
146
|
+
import { Div, Button } from 'native-document/src/elements'
|
|
147
|
+
import { Observable } from 'native-document'
|
|
232
148
|
|
|
233
|
-
|
|
234
|
-
const {
|
|
235
|
-
|
|
236
|
-
const CustomMiddleware = (request, next) => {
|
|
237
|
-
console.log('check custom middleware', request);
|
|
238
|
-
// request.params.customValue = true;
|
|
239
|
-
return next(request);
|
|
240
|
-
};
|
|
241
|
-
const AuthMiddleware = (request, next) => {
|
|
242
|
-
console.log('check if user is authenticated');
|
|
243
|
-
return next(request);
|
|
244
|
-
};
|
|
149
|
+
// CDN
|
|
150
|
+
// const { Div, Button } = NativeDocument.elements;
|
|
151
|
+
// const { Observable } = NativeDocument;
|
|
245
152
|
|
|
246
|
-
|
|
247
|
-
const
|
|
153
|
+
const App = function() {
|
|
154
|
+
const isVisible = Observable(true)
|
|
155
|
+
|
|
248
156
|
return Div([
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
Link({ to: { name: 'admin.dashboard' } }, 'Show Admin Dashboard'),
|
|
255
|
-
Link({ to: { name: 'admin.users' } }, 'Show Admin User'),
|
|
256
|
-
Button('Product Page').nd.on.click(() => {
|
|
257
|
-
const router = Router.get();
|
|
258
|
-
// Navigate programmatically
|
|
259
|
-
router.push('/product/123?name=ProductName');
|
|
260
|
-
}),
|
|
261
|
-
]))
|
|
157
|
+
Div({
|
|
158
|
+
class: { 'hidden': isVisible.check(v => !v) },
|
|
159
|
+
style: { opacity: isVisible.check(v => v ? 1 : 0.2) }
|
|
160
|
+
}, 'Content'),
|
|
161
|
+
Button('Toggle').nd.on.click(() => isVisible.set(v => !v)),
|
|
262
162
|
]);
|
|
263
163
|
};
|
|
264
164
|
|
|
265
|
-
const HomePage = () => {
|
|
266
|
-
return DefaultLayout(Div('Home page'));
|
|
267
|
-
};
|
|
268
|
-
const UserPage = ({ params, query }) => {
|
|
269
|
-
return DefaultLayout(Div('User page for '+params.id));
|
|
270
|
-
};
|
|
271
|
-
const ProductPage = ({ params, query }) => {
|
|
272
|
-
return DefaultLayout(Div('Product page '+params.id+' with product name '+query.name));
|
|
273
|
-
};
|
|
274
|
-
const AdminDashboard = () => {
|
|
275
|
-
return DefaultLayout(Div('Admin dashboard'));
|
|
276
|
-
};
|
|
277
|
-
const AdminUsers = () => {
|
|
278
|
-
return DefaultLayout(Div('Admin users'));
|
|
279
|
-
};
|
|
280
|
-
const ProfilePage = () => {
|
|
281
|
-
return DefaultLayout(Div('Profile page'));
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const router = Router.create({ mode: "history" }, (router) => {
|
|
286
|
-
// Basic route
|
|
287
|
-
router.add("/", HomePage, { name: 'home' });
|
|
288
|
-
|
|
289
|
-
// Route with parameters
|
|
290
|
-
router.add("/user/{id}", UserPage, { name: "user.show" });
|
|
291
|
-
|
|
292
|
-
// Route with constraints
|
|
293
|
-
router.add("/product/{id:number}", ProductPage);
|
|
294
|
-
|
|
295
|
-
// Grouped routes with middleware
|
|
296
|
-
router.group("/admin", { middlewares: [AuthMiddleware], name: 'admin' }, () => {
|
|
297
|
-
router.add("/dashboard", AdminDashboard, { name: 'dashboard' }); // name = admin.dashboard
|
|
298
|
-
router.add("/users", AdminUsers, { name: 'users' }); // name = admin.users
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
// Named routes
|
|
302
|
-
router.add("/profile", ProfilePage, { name: "profile" });
|
|
303
|
-
|
|
304
|
-
router.add(".*", () => {
|
|
305
|
-
return DefaultLayout(
|
|
306
|
-
Div([
|
|
307
|
-
'404',
|
|
308
|
-
Div('Route not found')
|
|
309
|
-
])
|
|
310
|
-
);
|
|
311
|
-
})
|
|
312
|
-
}).mount(document.body);
|
|
313
|
-
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
## 🏪 State Management
|
|
317
|
-
|
|
318
|
-
### Global Store
|
|
319
|
-
|
|
320
|
-
```javascript
|
|
321
|
-
const { Div, P, Button, ShowIf, When } = NativeDocument.elements;
|
|
322
|
-
const { Store, Observable } = NativeDocument;
|
|
323
|
-
|
|
324
|
-
const $ = Observable.computed;
|
|
325
|
-
|
|
326
|
-
// Create global store
|
|
327
|
-
Store.create("user", {
|
|
328
|
-
name: "Anonymous",
|
|
329
|
-
loginAt: '...',
|
|
330
|
-
isLoggedIn: false
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
const Footer = () => {
|
|
334
|
-
const user = Store.use("user");
|
|
335
|
-
const dynamicLoginAtText = $(() => 'Login at '+user.val().loginAt, [user]);
|
|
336
|
-
|
|
337
|
-
return Div({ class: 'footer-container', style: { padding: '1rem 0' }}, [
|
|
338
|
-
ShowIf(user.check(u => u.isLoggedIn), P(dynamicLoginAtText))
|
|
339
|
-
]);
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
// Use in components
|
|
343
|
-
const Header = () => {
|
|
344
|
-
const user = Store.use("user");
|
|
345
|
-
|
|
346
|
-
return Div([
|
|
347
|
-
When(user.check(u => u.isLoggedIn))
|
|
348
|
-
.show(() => {
|
|
349
|
-
const dynamicHelloText = $(() => 'Hello '+user.val().name, [user]);
|
|
350
|
-
|
|
351
|
-
return Div([
|
|
352
|
-
P(dynamicHelloText),
|
|
353
|
-
Button("Logout").nd.on.click(() => {
|
|
354
|
-
user.set({ name: null, isLoggedIn: false })
|
|
355
|
-
})
|
|
356
|
-
]);
|
|
357
|
-
})
|
|
358
|
-
.otherwise(() => {
|
|
359
|
-
return Button('Login').nd.on.click(() => {
|
|
360
|
-
user.set({ name: "John", loginAt: (new Date()).toLocaleString(), isLoggedIn: true })
|
|
361
|
-
});
|
|
362
|
-
})
|
|
363
|
-
]);
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
const App = () => {
|
|
367
|
-
return Div([
|
|
368
|
-
Header(),
|
|
369
|
-
Footer()
|
|
370
|
-
]);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
165
|
document.body.appendChild(App());
|
|
374
166
|
```
|
|
375
167
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
### Local State
|
|
379
|
-
|
|
168
|
+
### Conditional Rendering
|
|
169
|
+
Built-in components for dynamic content:
|
|
380
170
|
```javascript
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
const Counter = () => {
|
|
385
|
-
const count = Observable(0);
|
|
171
|
+
ShowIf(user.check(u => u.isLoggedIn),
|
|
172
|
+
Div('Welcome back!')
|
|
173
|
+
)
|
|
386
174
|
|
|
387
|
-
|
|
388
|
-
|
|
175
|
+
Match(theme, {
|
|
176
|
+
'dark': Div({ class: 'dark-mode' }),
|
|
177
|
+
'light': Div({ class: 'light-mode' })
|
|
178
|
+
})
|
|
389
179
|
|
|
390
|
-
|
|
391
|
-
H2(['Count: ', count]),
|
|
392
|
-
Button("-").nd.on.click(Decrement),
|
|
393
|
-
Button("+").nd.on.click(Increment),
|
|
394
|
-
]);
|
|
395
|
-
};
|
|
180
|
+
Switch(condition, onTrue, onFalse)
|
|
396
181
|
|
|
397
|
-
|
|
182
|
+
When(condition)
|
|
183
|
+
.show(onTrue)
|
|
184
|
+
.otherwise(onFalse)
|
|
398
185
|
```
|
|
399
186
|
|
|
187
|
+
## Documentation
|
|
400
188
|
|
|
401
|
-
|
|
189
|
+
- **[Getting Started](docs/getting-started.md)** - Installation and first steps
|
|
190
|
+
- **[Core Concepts](docs/core-concepts.md)** - Understanding the fundamentals
|
|
191
|
+
- **[Observables](docs/observables.md)** - Reactive state management
|
|
192
|
+
- **[Elements](docs/elements.md)** - Creating and composing UI
|
|
193
|
+
- **[Conditional Rendering](docs/conditional-rendering.md)** - Dynamic content
|
|
194
|
+
- **[Routing](docs/routing.md)** - Navigation and URL management
|
|
195
|
+
- **[State Management](docs/state-management.md)** - Global state patterns
|
|
196
|
+
- **[Lifecycle Events](docs/lifecycle-events.md)** - Lifecycle events
|
|
197
|
+
- **[Memory Management](docs/memory-management.md)** - Memory management
|
|
198
|
+
- **[Anchor](docs/anchor.md)** - Anchor
|
|
402
199
|
|
|
403
|
-
|
|
200
|
+
## Examples
|
|
404
201
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
return element
|
|
413
|
-
.nd.mounted(() => {
|
|
414
|
-
console.log("Component mounted");
|
|
415
|
-
})
|
|
416
|
-
.nd.unmounted(() => {
|
|
417
|
-
console.log("Component unmounted");
|
|
418
|
-
});
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
const App = () => {
|
|
422
|
-
const isActive = Observable(true);
|
|
202
|
+
### Todo App
|
|
203
|
+
```bash
|
|
204
|
+
# Complete todo application with local storage
|
|
205
|
+
git clone https://github.com/afrocodeur/native-document-examples
|
|
206
|
+
cd examples/todo-app
|
|
207
|
+
```
|
|
423
208
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
209
|
+
### SPA Router
|
|
210
|
+
```bash
|
|
211
|
+
# Single Page Application with routing
|
|
212
|
+
cd examples/routing-spa
|
|
213
|
+
```
|
|
429
214
|
|
|
430
|
-
|
|
215
|
+
### Reusable Components
|
|
216
|
+
```bash
|
|
217
|
+
# Component library patterns
|
|
218
|
+
cd examples/components
|
|
431
219
|
```
|
|
432
220
|
|
|
221
|
+
## Key Features Deep Dive
|
|
433
222
|
|
|
434
|
-
###
|
|
223
|
+
### Performance Optimized
|
|
224
|
+
- Direct DOM manipulation (no virtual DOM overhead)
|
|
225
|
+
- Automatic batching of updates
|
|
226
|
+
- Lazy evaluation of computed values
|
|
227
|
+
- Efficient list rendering with keyed updates
|
|
435
228
|
|
|
229
|
+
### Developer Experience
|
|
436
230
|
```javascript
|
|
437
|
-
|
|
231
|
+
// Built-in debugging
|
|
232
|
+
Observable.debug.enable()
|
|
438
233
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
234
|
+
// Argument validation
|
|
235
|
+
const createUser = (function (name, age) {
|
|
236
|
+
// Auto-validates argument types
|
|
237
|
+
}).args(ArgTypes.string('name'), ArgTypes.number('age'))
|
|
443
238
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
]);
|
|
239
|
+
// Error boundaries
|
|
240
|
+
const AppWithBoundayError = App.errorBoundary(() => {
|
|
241
|
+
return Div('Error in the Create User component');
|
|
242
|
+
})
|
|
449
243
|
|
|
450
|
-
|
|
451
|
-
const user = createUserWithArgsValidation("John", 25, "john@example.com");
|
|
244
|
+
document.body.appendChild(AppWithBoundayError());
|
|
452
245
|
```
|
|
453
246
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
```javascript
|
|
457
|
-
|
|
458
|
-
const { withValidation, ArgTypes, Observable } = NativeDocument;
|
|
459
|
-
|
|
460
|
-
const createUser = ((name, age, email) => {
|
|
461
|
-
// Function implementation
|
|
462
|
-
console.log(name, age, email);
|
|
463
|
-
return { name, age, email };
|
|
464
|
-
}).args(ArgTypes.string('name'), ArgTypes.number('age'), ArgTypes.string('email'));
|
|
465
|
-
|
|
247
|
+
## Contributing
|
|
466
248
|
|
|
467
|
-
|
|
468
|
-
const user = createUser('John', 25, "john@example.com");
|
|
249
|
+
We welcome contributions! Please see our [Contributing Guide](docs/contributing.md) for details.
|
|
469
250
|
|
|
251
|
+
### Development Setup
|
|
252
|
+
```bash
|
|
253
|
+
git clone https://github.com/afrocodeur/native-document
|
|
254
|
+
cd native-document
|
|
255
|
+
npm install
|
|
256
|
+
npm run dev
|
|
470
257
|
```
|
|
471
258
|
|
|
259
|
+
## License
|
|
472
260
|
|
|
473
|
-
|
|
261
|
+
MIT © [AfroCodeur](https://github.com/afrocodeur)
|
|
474
262
|
|
|
475
|
-
|
|
263
|
+
## Acknowledgments
|
|
476
264
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const StyledComponent = () => {
|
|
482
|
-
const isActive = Observable(false);
|
|
483
|
-
|
|
484
|
-
return Div({
|
|
485
|
-
class: {
|
|
486
|
-
"component": true,
|
|
487
|
-
"active": isActive,
|
|
488
|
-
"inactive": isActive.check(active => !active)
|
|
489
|
-
},
|
|
490
|
-
style: {
|
|
491
|
-
color: isActive.check(active => active ? "white" : "black"),
|
|
492
|
-
backgroundColor: isActive.check(active => active ? "#2980b9" : "#7f8c8d"),
|
|
493
|
-
padding: "20px",
|
|
494
|
-
borderRadius: "4px"
|
|
495
|
-
}
|
|
496
|
-
}, [
|
|
497
|
-
H2("Styled Component"),
|
|
498
|
-
ShowIf(isActive, Div({ style: 'color: white; padding: 1rem 0'}, 'Styled Component is active')),
|
|
499
|
-
Button("Toggle").nd.on.click(() => isActive.set((val) => !val))
|
|
500
|
-
]);
|
|
501
|
-
};
|
|
265
|
+
Thanks to all contributors and the JavaScript community for inspiration.
|
|
266
|
+
|
|
267
|
+
---
|
|
502
268
|
|
|
503
|
-
|
|
504
|
-
document.body.appendChild(StyledComponent());
|
|
505
|
-
```
|
|
269
|
+
**Ready to build with native simplicity?** [Get Started →](docs/getting-started.md)
|