native-document 1.0.13 → 1.0.15

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.
Files changed (37) hide show
  1. package/dist/native-document.dev.js +1297 -804
  2. package/dist/native-document.min.js +1 -1
  3. package/docs/anchor.md +216 -53
  4. package/docs/conditional-rendering.md +25 -24
  5. package/docs/core-concepts.md +20 -19
  6. package/docs/elements.md +21 -20
  7. package/docs/getting-started.md +28 -27
  8. package/docs/lifecycle-events.md +2 -2
  9. package/docs/list-rendering.md +607 -0
  10. package/docs/memory-management.md +1 -1
  11. package/docs/observables.md +15 -14
  12. package/docs/routing.md +22 -22
  13. package/docs/state-management.md +8 -8
  14. package/docs/validation.md +0 -2
  15. package/index.js +6 -1
  16. package/package.json +1 -1
  17. package/readme.md +5 -4
  18. package/src/data/MemoryManager.js +8 -20
  19. package/src/data/Observable.js +2 -180
  20. package/src/data/ObservableChecker.js +26 -21
  21. package/src/data/ObservableItem.js +158 -79
  22. package/src/data/observable-helpers/array.js +74 -0
  23. package/src/data/observable-helpers/batch.js +22 -0
  24. package/src/data/observable-helpers/computed.js +28 -0
  25. package/src/data/observable-helpers/object.js +111 -0
  26. package/src/elements/anchor.js +54 -9
  27. package/src/elements/control/for-each-array.js +280 -0
  28. package/src/elements/control/for-each.js +100 -56
  29. package/src/elements/index.js +1 -0
  30. package/src/elements/list.js +4 -0
  31. package/src/utils/helpers.js +44 -21
  32. package/src/wrappers/AttributesWrapper.js +5 -18
  33. package/src/wrappers/DocumentObserver.js +58 -29
  34. package/src/wrappers/ElementCreator.js +114 -0
  35. package/src/wrappers/HtmlElementEventsWrapper.js +52 -65
  36. package/src/wrappers/HtmlElementWrapper.js +11 -167
  37. package/src/wrappers/NdPrototype.js +109 -0
@@ -0,0 +1,607 @@
1
+ # List Rendering
2
+
3
+ List rendering in NativeDocument provides powerful utilities for efficiently displaying dynamic collections of data. The framework offers two specialized functions: `ForEach` for generic iteration over objects and arrays, and `ForEachArray` for high-performance array-specific operations with advanced optimization features.
4
+
5
+ ## Understanding List Rendering
6
+
7
+ List rendering automatically manages DOM updates when your data changes. Instead of manually manipulating the DOM, you define how each item should be rendered, and NativeDocument handles creation, updates, reordering, and cleanup efficiently.
8
+
9
+ ```javascript
10
+ import { ForEach, Observable, Li, Ul } from 'native-document';
11
+
12
+ const items = Observable.array(['Apple', 'Banana', 'Cherry']);
13
+
14
+ // Automatically updates when items change
15
+ const itemList = Ul([
16
+ ForEach(items, item => Li(item))
17
+ ]);
18
+
19
+ // Add items - DOM updates automatically
20
+ items.push('Orange', 'Grape');
21
+ ```
22
+
23
+ ## ForEach - Generic Collection Rendering
24
+
25
+ `ForEach` is the versatile option that works with both arrays and objects. It's perfect when you need flexibility or are working with mixed data types.
26
+
27
+ ### Basic Array Iteration
28
+
29
+ ```javascript
30
+ const fruits = Observable.array(['Apple', 'Banana', 'Cherry']);
31
+
32
+ const FruitList = Ul([
33
+ ForEach(fruits, fruit =>
34
+ Li({ class: 'fruit-item' }, fruit)
35
+ )
36
+ ]);
37
+
38
+ // All array operations trigger updates
39
+ fruits.push('Orange'); // Adds new item
40
+ fruits.splice(1, 1); // Removes 'Banana'
41
+ fruits.sort(); // Reorders items
42
+ ```
43
+
44
+ ### Object Iteration
45
+
46
+ ```javascript
47
+ const userRoles = Observable({
48
+ admin: 'Administrator',
49
+ editor: 'Content Editor',
50
+ viewer: 'Read Only'
51
+ });
52
+
53
+ const RolesList = Ul([
54
+ ForEach(userRoles, (roleName, roleKey) =>
55
+ Li([
56
+ Strong(roleKey), ': ', roleName
57
+ ])
58
+ )
59
+ ]);
60
+
61
+ // Update object - DOM reflects changes
62
+ userRoles.set({
63
+ ...userRoles.val(),
64
+ moderator: 'Community Moderator'
65
+ });
66
+ ```
67
+
68
+ ### Using Index Parameter
69
+
70
+ ```javascript
71
+ const tasks = Observable.array([
72
+ 'Review pull requests',
73
+ 'Update documentation',
74
+ 'Fix bug reports'
75
+ ]);
76
+
77
+ const TaskList = Ol([
78
+ ForEach(tasks, (task, indexObservable) =>
79
+ Li([
80
+ Strong(indexObservable.get(val => val + 1)),
81
+ ' ',
82
+ task,
83
+ Button('Remove').nd.onClick(() =>
84
+ tasks.remove(indexObservable.val())
85
+ )
86
+ ])
87
+ )
88
+ ]);
89
+ ```
90
+
91
+ ### Custom Key Functions
92
+
93
+ Use custom key functions
94
+
95
+ ```javascript
96
+ const users = Observable.array([
97
+ { id: 1, name: 'Alice', role: 'admin' },
98
+ { id: 2, name: 'Bob', role: 'user' },
99
+ { id: 3, name: 'Carol', role: 'editor' }
100
+ ]);
101
+
102
+ // Use 'id' field as the key for efficient updates
103
+ const UserList = Div([
104
+ ForEach(users,
105
+ user => Div({ class: 'user-card' }, [
106
+ H3(user.name),
107
+ Span({ class: 'role' }, user.role)
108
+ ]),
109
+ 'id' // Key function - uses user.id
110
+ // Or (item) => item.id
111
+ )
112
+ ]);
113
+
114
+ // When users reorder, DOM nodes are moved, not recreated
115
+ users.set((items) => items.sort((a, b) => a.name.localeCompare(b.name)));
116
+ ```
117
+
118
+ ## ForEachArray - High-Performance Array Rendering
119
+
120
+ `ForEachArray` is specifically optimized for **arrays of complex objects** and provides superior performance. **Use ForEachArray for object arrays** - it's designed for array-specific operations.
121
+
122
+ ### Why ForEachArray for Complex Arrays?
123
+
124
+ `ForEachArray` includes optimizations that generic `ForEach` not provide:
125
+
126
+ - **Specialized diffing algorithm** optimized for array operations
127
+ - **Batch DOM updates** for better performance
128
+ - **Memory-efficient caching** with WeakMap references
129
+ - **Array method detection** for targeted updates (push, splice, sort, etc.)
130
+
131
+ ### Basic Usage
132
+
133
+ ```javascript
134
+ const messages = Observable.array([
135
+ { id: 1, text: 'Hello world!', timestamp: Date.now() },
136
+ { id: 2, text: 'How are you?', timestamp: Date.now() + 1000 }
137
+ ]);
138
+
139
+ const ChatMessages = Div({ class: 'chat-container' }, [
140
+ ForEachArray(messages, message =>
141
+ Div({ class: 'message' }, [
142
+ Div({ class: 'message-text' }, message.text),
143
+ Div({ class: 'timestamp' }, new Date(message.timestamp).toLocaleTimeString())
144
+ ])
145
+ )
146
+ ]);
147
+
148
+ // Optimized array operations
149
+ messages.push({ id: 3, text: 'New message!', timestamp: Date.now() });
150
+ ```
151
+
152
+ ### Advanced Array Operations
153
+
154
+ `ForEachArray` efficiently handles all array mutations:
155
+
156
+ ```javascript
157
+ const playlist = Observable.array([
158
+ { id: 1, title: 'Song One', artist: 'Artist A' },
159
+ { id: 2, title: 'Song Two', artist: 'Artist B' },
160
+ { id: 3, title: 'Song Three', artist: 'Artist C' }
161
+ ]);
162
+
163
+ const PlaylistView = Div({ class: 'playlist' }, [
164
+ ForEachArray(playlist, (song, indexObservable) => {
165
+
166
+ return Div({ class: 'song-item', style: 'display: flex; align-items: center; column-gap: 10px;' }, [
167
+ Div({ class: 'song-info' }, [
168
+ Strong(indexObservable.get((value) => value + 1)),
169
+ ' - ',
170
+ Strong(song.title),
171
+ Span({ class: 'artist' }, ` by ${song.artist}`)
172
+ ]),
173
+ Div({ class: 'song-controls' }, [
174
+ Button('↑').nd.onClick(() => {
175
+ const index = indexObservable.$value;
176
+ if(index > 0) {
177
+ playlist.swap(index, index-1);
178
+ }
179
+ }),
180
+ Button('↓').nd.onClick(() => {
181
+ const index = indexObservable.$value;
182
+ if(index < playlist.length()-1) {
183
+ playlist.swap(index, index+1);
184
+ }
185
+ }),
186
+ Button('Remove').nd.onClick(() =>{
187
+ playlist.remove(indexObservable.$value);
188
+ })
189
+ ])
190
+ ])
191
+ }, 'id'),
192
+ Br,
193
+ Div([
194
+ Button('Push ').nd.onClick(() => {
195
+ playlist.push({ id: 4, title: 'New Song', artist: 'New Artist' });
196
+ }),
197
+ Button('Unshift').nd.onClick(() => {
198
+ playlist.unshift({ id: 0, title: 'First Song', artist: 'First' })
199
+ }),
200
+ Button('Reverse').nd.onClick(() => {
201
+ playlist.reverse()
202
+ }),
203
+ Button('Sort').nd.onClick(() => {
204
+ playlist.sort((a, b) => a.title.localeCompare(b.title))
205
+ })
206
+ ])
207
+ ]);
208
+ ```
209
+
210
+ ### Custom Key Functions with ForEachArray
211
+
212
+ Key functions are crucial for optimal performance with complex objects:
213
+
214
+ ```javascript
215
+ const products = Observable.array([
216
+ { sku: 'PHONE-001', name: 'Smartphone', price: 599 },
217
+ { sku: 'LAPTOP-001', name: 'Laptop', price: 999 },
218
+ { sku: 'TABLET-001', name: 'Tablet', price: 399 }
219
+ ]);
220
+
221
+ const ProductCatalog = Div({ class: 'catalog' }, [
222
+ ForEachArray(products,
223
+ product => Div({ class: 'product-card' }, [
224
+ H3(product.name),
225
+ Div({ class: 'price' }, `$${product.price}`),
226
+ Div({ class: 'sku' }, `SKU: ${product.sku}`)
227
+ ]),
228
+ 'sku' // Use SKU as key for efficient tracking
229
+ )
230
+ ]);
231
+
232
+ ```
233
+
234
+ ### Performance Configuration
235
+
236
+ `ForEachArray` supports performance tuning for large datasets:
237
+
238
+ ```javascript
239
+ const bigDataset = Observable.array([...Array(10000)].map((_, i) => ({
240
+ id: i,
241
+ value: `Item ${i}`,
242
+ category: Math.floor(i / 100)
243
+ })));
244
+
245
+ const BigList = Div([
246
+ ForEachArray(bigDataset,
247
+ item => Div({ class: 'list-item' }, [
248
+ Strong(`#${item.id}`), ' - ', item.value
249
+ ]),
250
+ 'id', // Key function
251
+ {
252
+ pushDelay: (items) => items.length > 100 ? 50 : 0 // Delay for large additions
253
+ }
254
+ )
255
+ ]);
256
+
257
+ // Large additions are automatically throttled
258
+ bigDataset.push(...[...Array(500)].map((_, i) => ({
259
+ id: 10000 + i,
260
+ value: `New Item ${i}`,
261
+ category: 999
262
+ })));
263
+ ```
264
+
265
+ ## Choosing Between ForEach and ForEachArray
266
+
267
+ ### Use ForEachArray When:
268
+
269
+ ✅ **Working with arrays of complex objects**
270
+ ✅ **Performance is critical** - Large lists, frequent updates
271
+ ✅ **Using array methods** - push, pop, splice, sort, reverse, etc.
272
+
273
+ ```javascript
274
+ // Perfect for ForEachArray
275
+ const comments = Observable.array([...]);
276
+ const CommentList = ForEachArray(comments, comment => CommentComponent(comment));
277
+
278
+ // Array operations work optimally
279
+ comments.push(newComment);
280
+
281
+ comments.splice(index, 1);
282
+
283
+ comments.sort((a, b) => b.timestamp - a.timestamp);
284
+ ```
285
+
286
+ ### Use ForEach When:
287
+
288
+ ✅ **Working with objects** - ForEach is required for object iteration
289
+ ✅ **Mixed data types** - When data might be array or object
290
+ ✅ **Arrays of primitive values** - string, number, boolean
291
+ ✅ **Simple use cases** - Small lists with infrequent updates
292
+
293
+ ## Real-World Examples
294
+
295
+ ### Nested Lists with Mixed Rendering
296
+
297
+ ```javascript
298
+ const categories = Observable.array([
299
+ {
300
+ id: 1,
301
+ name: 'Electronics',
302
+ items: Observable.array([
303
+ { id: 101, name: 'Smartphone', price: 599 },
304
+ { id: 102, name: 'Laptop', price: 999 }
305
+ ])
306
+ },
307
+ {
308
+ id: 2,
309
+ name: 'Books',
310
+ items: Observable.array([
311
+ { id: 201, name: 'JavaScript Guide', price: 29.99 },
312
+ { id: 202, name: 'Design Patterns', price: 39.99 }
313
+ ])
314
+ }
315
+ ]);
316
+
317
+ const CategorizedProducts = Div({ class: 'product-categories' }, [
318
+ // Categories use ForEachArray (it's an array)
319
+ ForEachArray(categories, category =>
320
+ Div({ class: 'category' }, [
321
+ H3({ class: 'category-title' }, category.name),
322
+
323
+ // Items within each category also use ForEachArray
324
+ ForEachArray(category.items,
325
+ item => Div({ class: 'product-item' }, [
326
+ Span({ class: 'product-name' }, item.name),
327
+ Span({ class: 'product-price' }, `$${item.price}`),
328
+ Button('Add to Cart').nd.onClick(() => addToCart(item))
329
+ ]),
330
+ 'id'
331
+ ),
332
+
333
+ Button('Add Item').nd.onClick(() => {
334
+ const newItem = {
335
+ id: Date.now(),
336
+ name: `New ${category.name} Item`,
337
+ price: Math.floor(Math.random() * 100) + 10
338
+ };
339
+ category.items.push(newItem);
340
+ })
341
+ ]),
342
+ 'id'
343
+ )
344
+ ]);
345
+ ```
346
+
347
+ ## Performance Best Practices
348
+
349
+ ### 1. Always Use Keys for Complex Objects
350
+
351
+ ```javascript
352
+ // ✅ Good: Efficient updates and reordering
353
+ ForEachArray(users, user => UserCard(user), 'id')
354
+ ForEach(tags, tag => TagComponent(tag), 'index')
355
+
356
+ // ❌ Poor: Inefficient, may cause unnecessary re-renders
357
+ ForEachArray(users, user => UserCard(user))
358
+ ```
359
+
360
+ ### 2. Choose the Right Function
361
+
362
+ ```javascript
363
+ // ✅ Perfect: ForEachArray for complex objects
364
+ const users = Observable.array([{id: 1, name: 'Alice'}]);
365
+ ForEachArray(users, renderUser, 'id')
366
+
367
+ // ✅ Correct: ForEach for primitives
368
+ const tags = Observable.array(['js', 'css']);
369
+ ForEach(tags, renderTag)
370
+
371
+ // ✅ Correct: ForEach for objects
372
+ const config = Observable({theme: 'dark'});
373
+ ForEach(config, renderSetting, (item, key) => key)
374
+ ```
375
+
376
+ ### 3. Use Computed Values for Derived Lists
377
+
378
+ ```javascript
379
+ // ✅ Efficient: Computed filtered list
380
+ const searchTerm = Observable('');
381
+ const filteredItems = Observable.computed(() =>
382
+ allItems.val().filter(item =>
383
+ item.name.toLowerCase().includes(searchTerm.val().toLowerCase())
384
+ ),
385
+ [allItems, searchTerm]
386
+ );
387
+
388
+ ForEachArray(filteredItems, renderItem, 'id')
389
+
390
+ // ❌ Inefficient: Filtering in render
391
+ ForEachArray(allItems, item => {
392
+ if (item.name.includes(searchTerm.val())) {
393
+ return renderItem(item);
394
+ }
395
+ return null;
396
+ })
397
+ ```
398
+
399
+ ## Memory Management
400
+
401
+ Both `ForEach` and `ForEachArray` automatically manage memory:
402
+
403
+ ```javascript
404
+ // Cleanup is automatic when observables are garbage collected
405
+ let myList = Observable.array([1, 2, 3]);
406
+ let listComponent = ForEachArray(myList, item => Div(item));
407
+
408
+ // When references are lost, cleanup happens automatically
409
+ myList = null;
410
+ listComponent = null; // Memory will be freed
411
+ ```
412
+
413
+ For explicit cleanup:
414
+
415
+ ```javascript
416
+ const items = Observable.array([...]);
417
+ const listComponent = ForEachArray(items, renderItem);
418
+
419
+ // Manual cleanup when needed
420
+ items.cleanup();
421
+ ```
422
+
423
+ ## Common Pitfalls and Solutions
424
+
425
+ ### 1. Missing Keys with Complex Objects
426
+
427
+ ```javascript
428
+ // ❌ Problem: No key, inefficient updates
429
+ ForEachArray(users, user => UserProfile(user))
430
+
431
+ // ✅ Solution: Use unique key
432
+ ForEachArray(users, user => UserProfile(user), 'id')
433
+ ```
434
+
435
+ ### 2. Using ForEach for Complex Arrays
436
+
437
+ ```javascript
438
+ // ❌ Suboptimal: Generic ForEach for arrays
439
+ ForEach(genericObject, renderItem)
440
+
441
+ // ✅ Optimal: Specialized ForEachArray for arrays
442
+ ForEachArray(arrayData, renderItem)
443
+ ```
444
+
445
+ ### 3. Modifying Arrays Directly
446
+
447
+ ```javascript
448
+ // ❌ Wrong: Direct mutation doesn't trigger updates
449
+ items.val().push(newItem);
450
+
451
+ // ✅ Correct: Use Observable array methods
452
+ items.push(newItem);
453
+
454
+ // ✅ Also correct: Set new array
455
+ items.set([...items.val(), newItem]);
456
+ ```
457
+
458
+ ## Integration with Other Features
459
+
460
+ ### With Conditional Rendering
461
+
462
+ ```javascript
463
+ const items = Observable.array([]);
464
+ const showEmptyState = items.check(arr => arr.length === 0);
465
+
466
+ const ItemList = Div([
467
+ ShowIf(showEmptyState,
468
+ Div({ class: 'empty-state' }, 'No items found')
469
+ ),
470
+ HideIf(showEmptyState,
471
+ ForEachArray(items, item => ItemComponent(item), 'id')
472
+ )
473
+ ]);
474
+ ```
475
+
476
+ ### With Forms and Validation
477
+
478
+ ```javascript
479
+ const formFields = Observable.array([
480
+ { name: 'firstName', label: 'First Name', value: '', required: true },
481
+ { name: 'lastName', label: 'Last Name', value: '', required: true },
482
+ { name: 'email', label: 'Email', value: '', required: true }
483
+ ]);
484
+
485
+ const DynamicForm = Form([
486
+ ForEachArray(formFields, field =>
487
+ Div({ class: 'form-group' }, [
488
+ Label(field.label + (field.required ? ' *' : '')),
489
+ Input({
490
+ name: field.name,
491
+ value: field.value,
492
+ required: field.required
493
+ }),
494
+ ShowIf(field.error,
495
+ Div({ class: 'error' }, field.error)
496
+ )
497
+ ]),
498
+ 'name'
499
+ ),
500
+ Button({ type: 'submit' }, 'Submit')
501
+ ]);
502
+ ```
503
+
504
+ ## Advanced Patterns
505
+
506
+ ### Infinite Scrolling
507
+
508
+ ```javascript
509
+ const items = Observable.array([]);
510
+ const isLoading = Observable(false);
511
+ const hasMore = Observable(true);
512
+
513
+ const loadMoreItems = async () => {
514
+ if (isLoading.val()) return;
515
+
516
+ isLoading.set(true);
517
+ try {
518
+ const newItems = await fetchItems(items.val().length);
519
+ if (newItems.length === 0) {
520
+ hasMore.set(false);
521
+ } else {
522
+ items.push(...newItems);
523
+ }
524
+ } finally {
525
+ isLoading.set(false);
526
+ }
527
+ };
528
+
529
+ const InfiniteList = Div([
530
+ ForEachArray(items, item => ItemComponent(item), 'id'),
531
+ ShowIf(isLoading, LoadingSpinner()),
532
+ ShowIf(hasMore.check(more => more && !isLoading.val()),
533
+ Button('Load More').nd.onClick(loadMoreItems)
534
+ )
535
+ ]);
536
+
537
+ ```
538
+
539
+ ### Drag and Drop Reordering
540
+
541
+ ```javascript
542
+ const draggableItems = Observable.array([
543
+ { id: 1, text: 'Item 1' },
544
+ { id: 2, text: 'Item 2' },
545
+ { id: 3, text: 'Item 3' }
546
+ ]);
547
+
548
+ let draggedIndex = null;
549
+
550
+ const DraggableList = Div([
551
+ ForEachArray(draggableItems, (item, indexObservable) =>
552
+ Div({
553
+ class: 'draggable-item',
554
+ draggable: true
555
+ }, item.text)
556
+ .nd.onDragStart((e) => {
557
+ draggedIndex = indexObservable.val();
558
+ })
559
+ .nd.onDragOver((e) => e.preventDefault())
560
+ .nd.onDrop((e) => {
561
+ e.preventDefault();
562
+ const dropIndex = indexObservable.val();
563
+ if (draggedIndex !== null && draggedIndex !== dropIndex) {
564
+ const items = draggableItems.val();
565
+ const draggedItem = items[draggedIndex];
566
+
567
+ // Remove from old position
568
+ items.splice(draggedIndex, 1);
569
+ // Insert at new position
570
+ items.splice(dropIndex, 0, draggedItem);
571
+
572
+ draggableItems.set([...items]);
573
+ }
574
+ draggedIndex = null;
575
+ }),
576
+ 'id'
577
+ )
578
+ ]);
579
+ ```
580
+
581
+ ## Debugging List Rendering
582
+
583
+ ### Logging Updates
584
+
585
+ ```javascript
586
+ const items = Observable.array([]);
587
+
588
+ // Log all array operations
589
+ items.subscribe((newItems, oldItems, operations) => {
590
+ console.log('Array operation:', operations);
591
+ console.log('Old items:', oldItems);
592
+ console.log('New items:', newItems);
593
+ });
594
+
595
+ const DebugList = ForEachArray(items, (item, index) => {
596
+ console.log('Rendering item:', item, 'at index:', index?.val());
597
+ return ItemComponent(item);
598
+ }, 'id');
599
+ ```
600
+
601
+ ## Next Steps
602
+
603
+ Now that you understand list rendering, explore these related topics:
604
+
605
+ - **[Conditional Rendering](conditional-rendering.md)** - Show/hide content dynamically
606
+ - **[State Management](state-management.md)** - Managing application state
607
+ - **[Memory Management](memory-management.md)** - Understanding cleanup and memory
@@ -87,4 +87,4 @@ window.addEventListener('beforeunload', () => {
87
87
 
88
88
  ## Next Steps
89
89
 
90
- - **[Anchor](docs/anchor.md)** - Anchor
90
+ - **[Anchor](anchor.md)** - Anchor
@@ -66,7 +66,7 @@ console.log(userProxy.name.val()); // "Alice"
66
66
  userProxy.name.set("Bob");
67
67
 
68
68
  // Get all values as plain object
69
- console.log(userProxy.$val()); // { name: "Bob", age: 25 }
69
+ console.log(userProxy.$value); // { name: "Bob", age: 25 }
70
70
  console.log(Observable.value(userProxy)); // { name: "Bob", age: 25 }
71
71
 
72
72
  // Observable(object) creates a SINGLE observable containing the whole object
@@ -104,7 +104,7 @@ user.name.set("Bob");
104
104
  user.age.$value = 30; // Using proxy syntax
105
105
 
106
106
  // Get the complete object value
107
- console.log(user.$val()); // { name: "Bob", age: 30, email: "alice@example.com" }
107
+ console.log(user.$value); // { name: "Bob", age: 30, email: "alice@example.com" }
108
108
  console.log(Observable.value(user)); // Same as above
109
109
 
110
110
  // Listen to individual property changes
@@ -164,9 +164,9 @@ const decrement = () => (count.$value--);
164
164
 
165
165
  // Reactive interface
166
166
  const app = Div({ class: "counter" }, [
167
- Button("-").nd.on.click(decrement),
167
+ Button("-").nd.onClick(decrement),
168
168
  Span({ class: "count" }, count), // Automatic display
169
- Button("+").nd.on.click(increment)
169
+ Button("+").nd.onClick(increment)
170
170
  ]);
171
171
  ```
172
172
 
@@ -356,8 +356,8 @@ const updateProfile = Observable.batch((profileData) => {
356
356
  // ✅ Correct: Single batch dependency
357
357
  const profileSummary = Observable.computed(() => {
358
358
  return {
359
- user: user.$val(),
360
- settings: settings.$val(),
359
+ user: user.$value,
360
+ settings: settings.$value,
361
361
  lastUpdated: Date.now()
362
362
  };
363
363
  }, updateProfile); // ← Single batch function
@@ -436,7 +436,7 @@ const levelUp = Observable.batch(() => {
436
436
 
437
437
  // Complex computed that should only run after complete transitions
438
438
  const gameStatusMessage = Observable.computed(() => {
439
- const state = gameState.$val();
439
+ const state = gameState.$value;
440
440
  return `Level ${state.level}: ${state.score} points, ${state.lives} lives remaining`;
441
441
  }, levelUp); // ← Only updates when levelUp() is called
442
442
  ```
@@ -536,10 +536,11 @@ console.log(Observable.value(complexData)); // Plain object with extracted value
536
536
 
537
537
  Now that you understand NativeDocument's observable, explore these advanced topics:
538
538
 
539
- - **[Elements](docs/elements.md)** - Creating and composing UI
540
- - **[Conditional Rendering](docs/conditional-rendering.md)** - Dynamic content
541
- - **[Routing](docs/routing.md)** - Navigation and URL management
542
- - **[State Management](docs/state-management.md)** - Global state patterns
543
- - **[Lifecycle Events](docs/lifecycle-events.md)** - Lifecycle events
544
- - **[Memory Management](docs/memory-management.md)** - Memory management
545
- - **[Anchor](docs/anchor.md)** - Anchor
539
+ - **[Elements](elements.md)** - Creating and composing UI
540
+ - **[Conditional Rendering](conditional-rendering.md)** - Dynamic content
541
+ - **[List Rendering](list-rendering.md)** - (ForEach | ForEachArray) and dynamic lists
542
+ - **[Routing](routing.md)** - Navigation and URL management
543
+ - **[State Management](state-management.md)** - Global state patterns
544
+ - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
545
+ - **[Memory Management](memory-management.md)** - Memory management
546
+ - **[Anchor](anchor.md)** - Anchor