native-document 1.0.92 → 1.0.94

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 (85) hide show
  1. package/dist/native-document.components.min.js +1088 -65
  2. package/dist/native-document.dev.js +695 -142
  3. package/dist/native-document.dev.js.map +1 -1
  4. package/dist/native-document.devtools.min.js +1 -1
  5. package/dist/native-document.min.js +1 -1
  6. package/docs/advanced-components.md +814 -0
  7. package/docs/anchor.md +71 -11
  8. package/docs/cache.md +888 -0
  9. package/docs/conditional-rendering.md +91 -1
  10. package/docs/core-concepts.md +9 -2
  11. package/docs/elements.md +127 -2
  12. package/docs/extending-native-document-element.md +7 -1
  13. package/docs/filters.md +1216 -0
  14. package/docs/getting-started.md +12 -3
  15. package/docs/lifecycle-events.md +10 -2
  16. package/docs/list-rendering.md +453 -54
  17. package/docs/memory-management.md +9 -7
  18. package/docs/native-document-element.md +30 -9
  19. package/docs/native-fetch.md +744 -0
  20. package/docs/observables.md +135 -6
  21. package/docs/routing.md +7 -1
  22. package/docs/state-management.md +7 -1
  23. package/docs/validation.md +8 -1
  24. package/elements.js +1 -0
  25. package/eslint.config.js +3 -3
  26. package/index.def.js +350 -0
  27. package/package.json +3 -2
  28. package/readme.md +53 -14
  29. package/src/components/$traits/HasItems.js +42 -1
  30. package/src/components/BaseComponent.js +4 -1
  31. package/src/components/accordion/Accordion.js +112 -8
  32. package/src/components/accordion/AccordionItem.js +93 -4
  33. package/src/components/alert/Alert.js +164 -4
  34. package/src/components/avatar/Avatar.js +236 -22
  35. package/src/components/menu/index.js +1 -2
  36. package/src/core/data/ObservableArray.js +120 -2
  37. package/src/core/data/ObservableChecker.js +50 -0
  38. package/src/core/data/ObservableItem.js +124 -4
  39. package/src/core/data/ObservableWhen.js +36 -6
  40. package/src/core/data/observable-helpers/array.js +12 -3
  41. package/src/core/data/observable-helpers/computed.js +17 -4
  42. package/src/core/data/observable-helpers/object.js +19 -3
  43. package/src/core/elements/content-formatter.js +138 -1
  44. package/src/core/elements/control/for-each-array.js +20 -2
  45. package/src/core/elements/control/for-each.js +17 -5
  46. package/src/core/elements/control/show-if.js +31 -15
  47. package/src/core/elements/control/show-when.js +23 -0
  48. package/src/core/elements/control/switch.js +40 -10
  49. package/src/core/elements/description-list.js +14 -0
  50. package/src/core/elements/form.js +188 -4
  51. package/src/core/elements/html5-semantics.js +44 -1
  52. package/src/core/elements/img.js +22 -10
  53. package/src/core/elements/index.js +5 -0
  54. package/src/core/elements/interactive.js +19 -1
  55. package/src/core/elements/list.js +28 -1
  56. package/src/core/elements/medias.js +29 -0
  57. package/src/core/elements/meta-data.js +34 -0
  58. package/src/core/elements/table.js +59 -0
  59. package/src/core/utils/cache.js +5 -0
  60. package/src/core/utils/helpers.js +7 -2
  61. package/src/core/utils/memoize.js +25 -16
  62. package/src/core/utils/prototypes.js +3 -2
  63. package/src/core/wrappers/AttributesWrapper.js +1 -1
  64. package/src/core/wrappers/HtmlElementWrapper.js +2 -2
  65. package/src/core/wrappers/NDElement.js +42 -2
  66. package/src/core/wrappers/NdPrototype.js +4 -0
  67. package/src/core/wrappers/TemplateCloner.js +14 -11
  68. package/src/core/wrappers/prototypes/bind-class-extensions.js +1 -1
  69. package/src/core/wrappers/prototypes/nd-element-extensions.js +3 -0
  70. package/src/router/Route.js +9 -4
  71. package/src/router/Router.js +28 -9
  72. package/src/router/errors/RouterError.js +0 -1
  73. package/types/control-flow.d.ts +9 -6
  74. package/types/elements.d.ts +496 -111
  75. package/types/filters/index.d.ts +4 -0
  76. package/types/forms.d.ts +85 -48
  77. package/types/images.d.ts +16 -9
  78. package/types/nd-element.d.ts +5 -238
  79. package/types/observable.d.ts +9 -3
  80. package/types/router.d.ts +5 -1
  81. package/types/template-cloner.ts +1 -0
  82. package/types/validator.ts +11 -1
  83. package/utils.d.ts +2 -1
  84. package/utils.js +4 -4
  85. package/src/core/utils/service.js +0 -6
@@ -0,0 +1,814 @@
1
+ # Advanced Components
2
+
3
+ NativeDocument provides advanced component patterns for optimizing rendering performance through template cloning and data binding. The `useCache()` utility creates reusable component templates with a sophisticated binding system that efficiently updates only the dynamic parts.
4
+
5
+ ## Overview
6
+
7
+ Advanced component utilities include:
8
+ - **`useCache(fn)`** - Create cached components with data binding
9
+ - **`useSingleton(fn)`** - Create singleton views with updateable sections
10
+ - **Binder API** - Powerful data binding system for templates
11
+
12
+ ## Import
13
+ ```javascript
14
+ import { useCache, useSingleton } from 'native-document';
15
+ ```
16
+
17
+ ## useCache() - Cached Components with Binding
18
+
19
+ `useCache()` creates a component template that is built once and then cloned efficiently. The component function receives a **binder** object (`$binder`) that creates bindings for dynamic data.
20
+
21
+ ### Basic Concept
22
+ ```javascript
23
+ import { useCache } from 'native-document';
24
+ import { Div, Span } from 'native-document/src/elements';
25
+
26
+ // Component function receives $binder
27
+ const UserCard = useCache(($binder) => {
28
+ // Create bindings for dynamic data
29
+ const name = $binder.value('name');
30
+ const age = $binder.value('age');
31
+
32
+ // Build template with bindings
33
+ return Div({ class: 'user-card' }, [
34
+ Span('Name: '),
35
+ Span(name),
36
+ Span(' - Age: '),
37
+ Span(age)
38
+ ]);
39
+ });
40
+
41
+ // Usage - pass data object
42
+ const card1 = UserCard({ name: 'Alice', age: 25 });
43
+ const card2 = UserCard({ name: 'Bob', age: 30 });
44
+ // card2 is cloned from template, bindings update automatically
45
+ ```
46
+
47
+ ### How Arguments Are Passed
48
+
49
+ Binder functions receive arguments exactly as the user passes them:
50
+ ```javascript
51
+ import { useCache } from 'native-document';
52
+ import { Div } from 'native-document/src/elements';
53
+
54
+ const Component = useCache(($binder) => {
55
+ // Function receives arguments exactly as passed
56
+ const value = $binder.value((data) => {
57
+ // data = { name: 'Alice', age: 25 }
58
+ return data.name;
59
+ });
60
+
61
+ return Div(value);
62
+ });
63
+
64
+ // Usage - object passed directly
65
+ const instance = Component({ name: 'Alice', age: 25 });
66
+ ```
67
+
68
+ ## Binder Methods
69
+
70
+ ### $binder.value() - Property Binding
71
+
72
+ Bind to a property name or use a transform function:
73
+ ```javascript
74
+ import { useCache } from 'native-document';
75
+ import { Div, Span } from 'native-document/src/elements';
76
+
77
+ const ProductCard = useCache(($binder) => {
78
+ // Bind to property by key name
79
+ const name = $binder.value('name');
80
+ const price = $binder.value('price');
81
+
82
+ // Bind with transform function
83
+ // Function receives the data object directly
84
+ const formattedPrice = $binder.value((product) => {
85
+ return `$${product.price.toFixed(2)}`;
86
+ });
87
+
88
+ const status = $binder.value((product) => {
89
+ return product.stock > 0 ? 'In Stock' : 'Out of Stock';
90
+ });
91
+
92
+ return Div({ class: 'product-card' }, [
93
+ Span({ class: 'name' }, name),
94
+ Span({ class: 'price' }, formattedPrice),
95
+ Span({ class: 'status' }, status)
96
+ ]);
97
+ });
98
+
99
+ // Usage
100
+ const card = ProductCard({ name: 'Phone', price: 599, stock: 10 });
101
+ // Displays: Phone, $599.00, In Stock
102
+ ```
103
+
104
+ ### $binder.property() - Alias for value()
105
+ ```javascript
106
+ import { useCache } from 'native-document';
107
+
108
+ const Component = useCache(($binder) => {
109
+ // property() is an alias for value()
110
+ const name = $binder.property('name');
111
+ const title = $binder.value('title');
112
+
113
+ // Both work the same way
114
+ return Div([Span(name), Span(title)]);
115
+ });
116
+ ```
117
+
118
+ ### $binder.class() - Class Binding
119
+
120
+ Bind CSS classes dynamically based on data:
121
+ ```javascript
122
+ import { useCache } from 'native-document';
123
+ import { Div, Span } from 'native-document/src/elements';
124
+
125
+ const TaskItem = useCache(($binder) => {
126
+ const text = $binder.value('text');
127
+
128
+ // Function receives the data object directly
129
+ const completedClass = $binder.class((task) => {
130
+ return task.completed;
131
+ });
132
+
133
+ const priorityClass = $binder.class((task) => {
134
+ return task.priority === 'high';
135
+ });
136
+
137
+ return Div({
138
+ class: {
139
+ 'task': true,
140
+ 'completed': completedClass,
141
+ 'high-priority': priorityClass
142
+ }
143
+ }, [
144
+ Span(text)
145
+ ]);
146
+ });
147
+
148
+ // Usage
149
+ const task = TaskItem({ text: 'Fix bug', completed: true, priority: 'high' });
150
+ // <div class="task completed high-priority">...</div>
151
+ ```
152
+
153
+ ### $binder.class() with Observable.when()
154
+ ```javascript
155
+ import { useCache } from 'native-document';
156
+ import { Tr, Td } from 'native-document/src/elements';
157
+ import { Observable } from 'native-document';
158
+
159
+ const selectedId = Observable(null);
160
+
161
+ const TableRow = useCache(($binder) => {
162
+ const id = $binder.value('id');
163
+ const name = $binder.value('name');
164
+
165
+ // Bind class to observable condition
166
+ const isSelected = $binder.class((item) => {
167
+ return selectedId.when(item.id);
168
+ });
169
+
170
+ return Tr({
171
+ class: { 'selected': isSelected }
172
+ }, [
173
+ Td(id),
174
+ Td(name)
175
+ ]);
176
+ });
177
+
178
+ // Usage
179
+ const row1 = TableRow({ id: 1, name: 'Item 1' });
180
+ const row2 = TableRow({ id: 2, name: 'Item 2' });
181
+
182
+ // Change selection - classes update automatically
183
+ selectedId.set(1); // row1 gets 'selected' class
184
+ selectedId.set(2); // row2 gets 'selected' class, row1 loses it
185
+ ```
186
+
187
+ ### $binder.style() - Style Binding
188
+
189
+ Bind inline styles dynamically:
190
+ ```javascript
191
+ import { useCache } from 'native-document';
192
+ import { Div } from 'native-document/src/elements';
193
+
194
+ const ProgressBar = useCache(($binder) => {
195
+ const percentage = $binder.value('percentage');
196
+
197
+ // Function receives the data object directly
198
+ const widthStyle = $binder.style((progress) => {
199
+ return progress.percentage + '%';
200
+ });
201
+
202
+ const colorStyle = $binder.style((progress) => {
203
+ return progress.percentage >= 100 ? 'green' : 'blue';
204
+ });
205
+
206
+ return Div({ class: 'progress-bar' }, [
207
+ Div({
208
+ class: 'progress-fill',
209
+ style: {
210
+ width: widthStyle,
211
+ backgroundColor: colorStyle
212
+ }
213
+ })
214
+ ]);
215
+ });
216
+
217
+ // Usage
218
+ const bar = ProgressBar({ percentage: 75 });
219
+ // <div style="width: 75%; background-color: blue"></div>
220
+ ```
221
+
222
+ ### $binder.attr() - Attribute Binding
223
+
224
+ Bind element attributes dynamically. Takes only a function that returns the attribute value:
225
+ ```javascript
226
+ import { useCache } from 'native-document';
227
+ import { Div, Img, A } from 'native-document/src/elements';
228
+
229
+ const ProductCard = useCache(($binder) => {
230
+ const name = $binder.value('name');
231
+
232
+ // attr() takes ONLY a function
233
+ // Function receives the data object directly
234
+ const imageSrc = $binder.attr((product) => {
235
+ return product.imageUrl;
236
+ });
237
+
238
+ const detailsHref = $binder.attr((product) => {
239
+ return `/products/${product.id}`;
240
+ });
241
+
242
+ // Use as object property with attribute name
243
+ return Div({ class: 'product' }, [
244
+ Img({ src: imageSrc }),
245
+ A({ href: detailsHref }, name)
246
+ ]);
247
+ });
248
+
249
+ // Usage
250
+ const card = ProductCard({
251
+ id: 123,
252
+ name: 'Phone',
253
+ imageUrl: '/images/phone.jpg'
254
+ });
255
+ // <img src="/images/phone.jpg">
256
+ // <a href="/products/123">Phone</a>
257
+ ```
258
+
259
+ ### $binder.attach() - Event Handler Binding
260
+
261
+ Attach event handlers that receive the event followed by user arguments:
262
+ ```javascript
263
+ import { useCache } from 'native-document';
264
+ import { Div, Button, Span } from 'native-document/src/elements';
265
+
266
+ const TodoItem = useCache(($binder) => {
267
+ const text = $binder.value('text');
268
+
269
+ // Handler receives (event, ...userArguments)
270
+ const handleToggle = $binder.attach((event, todo) => {
271
+ console.log('Toggle clicked:', event);
272
+ console.log('Todo data:', todo);
273
+ updateTodo(todo.id, { completed: !todo.completed });
274
+ });
275
+
276
+ const handleDelete = $binder.attach((event, todo) => {
277
+ console.log('Delete clicked:', event);
278
+ deleteTodo(todo.id);
279
+ });
280
+
281
+ return Div({ class: 'todo' }, [
282
+ Span(text).nd.attach('onClick', handleToggle),
283
+ Button('Delete').nd.attach('onClick', handleDelete)
284
+ ]);
285
+ });
286
+
287
+ // Usage
288
+ const todo = TodoItem({ id: 1, text: 'Buy milk', completed: false });
289
+ // Clicking triggers handlers with the todo object
290
+ ```
291
+
292
+ ### Using .nd.attach()
293
+
294
+ The `.nd.attach(eventName, handler)` method connects binder handlers to elements:
295
+ ```javascript
296
+ import { useCache } from 'native-document';
297
+ import { Button } from 'native-document/src/elements';
298
+
299
+ const ActionButton = useCache(($binder) => {
300
+ const label = $binder.value('label');
301
+
302
+ // Handler receives event + user data
303
+ const clickHandler = $binder.attach((event, data) => {
304
+ console.log('Action:', data.action);
305
+ console.log('Event:', event);
306
+ performAction(data.action);
307
+ });
308
+
309
+ // Use .nd.attach(eventName, handler)
310
+ return Button(label).nd.attach('onClick', clickHandler);
311
+ });
312
+
313
+ // Usage
314
+ const btn = ActionButton({ label: 'Save', action: 'save' });
315
+ // Clicking logs: "Action: save"
316
+ ```
317
+
318
+ ## Complete Example
319
+
320
+ Complete table row component from the codebase:
321
+ ```javascript
322
+ import { useCache } from 'native-document';
323
+ import { Tr, Td, Link, Button } from 'native-document/src/elements';
324
+ import { Observable, ForEachArray, TBody } from 'native-document';
325
+
326
+ // App state
327
+ const AppService = {
328
+ data: Observable.array([
329
+ { id: 1, label: 'Item 1' },
330
+ { id: 2, label: 'Item 2' },
331
+ { id: 3, label: 'Item 3' }
332
+ ]),
333
+ selected: Observable(null),
334
+
335
+ select(id) {
336
+ this.selected.set(id);
337
+ },
338
+
339
+ remove(id) {
340
+ const index = this.data.val().findIndex(item => item.id === id);
341
+ if (index > -1) {
342
+ this.data.remove(index);
343
+ }
344
+ }
345
+ };
346
+
347
+ // Cached table row with bindings
348
+ const TableRowBuilder = useCache(($binder) => {
349
+ // Class binding with Observable.when()
350
+ const isSelected = $binder.class((item) => {
351
+ return AppService.selected.when(item.id);
352
+ });
353
+
354
+ // Value bindings
355
+ const id = $binder.value('id');
356
+ const label = $binder.value('label');
357
+
358
+ // Event handler bindings - receive (event, item)
359
+ const rowClick = $binder.attach((event, item) => {
360
+ AppService.select(item.id);
361
+ });
362
+
363
+ const removeClick = $binder.attach((event, item) => {
364
+ AppService.remove(item.id);
365
+ });
366
+
367
+ return Tr({ class: { 'selected': isSelected } }, [
368
+ Td({ class: 'col-md-1' }, id),
369
+ Td({ class: 'col-md-4' },
370
+ Link(label).nd.attach('onClick', rowClick)
371
+ ),
372
+ Td({ class: 'col-md-1' },
373
+ Button('×').nd.attach('onClick', removeClick)
374
+ ),
375
+ Td({ class: 'col-md-6' })
376
+ ]);
377
+ });
378
+
379
+ // Usage with ForEachArray
380
+ const TableBody = TBody(
381
+ ForEachArray(AppService.data, TableRowBuilder)
382
+ );
383
+ ```
384
+
385
+ ## Complex Binding Examples
386
+
387
+ ### Multiple Bindings
388
+ ```javascript
389
+ import { useCache } from 'native-document';
390
+ import { Div, H3, Span, Img } from 'native-document/src/elements';
391
+
392
+ const UserProfile = useCache(($binder) => {
393
+ // Simple value bindings
394
+ const email = $binder.value('email');
395
+
396
+ // Computed value bindings - function receives user object directly
397
+ const fullName = $binder.value((user) => {
398
+ return `${user.firstName} ${user.lastName}`;
399
+ });
400
+
401
+ const memberSince = $binder.value((user) => {
402
+ const date = new Date(user.joinedAt);
403
+ return date.toLocaleDateString();
404
+ });
405
+
406
+ // Attribute binding - function receives user object directly
407
+ const avatarSrc = $binder.attr((user) => {
408
+ return user.avatarUrl;
409
+ });
410
+
411
+ // Class binding - function receives user object directly
412
+ const isPremium = $binder.class((user) => {
413
+ return user.plan === 'premium';
414
+ });
415
+
416
+ return Div({
417
+ class: {
418
+ 'user-profile': true,
419
+ 'premium': isPremium
420
+ }
421
+ }, [
422
+ Img({ src: avatarSrc, class: 'avatar' }),
423
+ H3(fullName),
424
+ Span({ class: 'email' }, email),
425
+ Span({ class: 'member-since' }, ['Member since: ', memberSince])
426
+ ]);
427
+ });
428
+
429
+ // Usage
430
+ const profile = UserProfile({
431
+ firstName: 'Alice',
432
+ lastName: 'Johnson',
433
+ email: 'alice@example.com',
434
+ avatarUrl: '/avatars/alice.jpg',
435
+ plan: 'premium',
436
+ joinedAt: '2023-01-15'
437
+ });
438
+ ```
439
+
440
+ ### Style Bindings
441
+ ```javascript
442
+ import { useCache } from 'native-document';
443
+ import { Div } from 'native-document/src/elements';
444
+
445
+ const ColoredBox = useCache(($binder) => {
446
+ const text = $binder.value('text');
447
+
448
+ // Multiple style bindings - each receives box object
449
+ const bgColor = $binder.style((box) => {
450
+ return box.color;
451
+ });
452
+
453
+ const boxWidth = $binder.style((box) => {
454
+ return box.width + 'px';
455
+ });
456
+
457
+ const boxHeight = $binder.style((box) => {
458
+ return box.height + 'px';
459
+ });
460
+
461
+ return Div({
462
+ style: {
463
+ backgroundColor: bgColor,
464
+ width: boxWidth,
465
+ height: boxHeight,
466
+ display: 'flex',
467
+ alignItems: 'center',
468
+ justifyContent: 'center'
469
+ }
470
+ }, text);
471
+ });
472
+
473
+ // Usage
474
+ const box = ColoredBox({
475
+ text: 'Hello',
476
+ color: '#3498db',
477
+ width: 200,
478
+ height: 100
479
+ });
480
+ ```
481
+
482
+ ### Conditional Content
483
+ ```javascript
484
+ import { useCache } from 'native-document';
485
+ import { Div, Span, Badge } from 'native-document/src/elements';
486
+
487
+ const ProductCard = useCache(($binder) => {
488
+ const name = $binder.value('name');
489
+
490
+ // Computed price - receives product object
491
+ const displayPrice = $binder.value((product) => {
492
+ return product.salePrice
493
+ ? `$${product.salePrice} (was $${product.price})`
494
+ : `$${product.price}`;
495
+ });
496
+
497
+ // Conditional classes - receive product object
498
+ const isOnSale = $binder.class((product) => {
499
+ return product.salePrice !== null;
500
+ });
501
+
502
+ const isOutOfStock = $binder.class((product) => {
503
+ return product.stock === 0;
504
+ });
505
+
506
+ // Stock badge text - receives product object
507
+ const stockBadge = $binder.value((product) => {
508
+ if (product.stock === 0) return 'Out of Stock';
509
+ if (product.stock < 5) return 'Low Stock';
510
+ return 'In Stock';
511
+ });
512
+
513
+ return Div({
514
+ class: {
515
+ 'product': true,
516
+ 'on-sale': isOnSale,
517
+ 'out-of-stock': isOutOfStock
518
+ }
519
+ }, [
520
+ Span({ class: 'name' }, name),
521
+ Span({ class: 'price' }, displayPrice),
522
+ Badge(stockBadge)
523
+ ]);
524
+ });
525
+
526
+ // Usage
527
+ const product = ProductCard({
528
+ name: 'Phone',
529
+ price: 599,
530
+ salePrice: 499,
531
+ stock: 3
532
+ });
533
+ ```
534
+
535
+ ### Multiple Arguments
536
+ ```javascript
537
+ import { useCache } from 'native-document';
538
+ import { Div, H3, P } from 'native-document/src/elements';
539
+
540
+ // Component that accepts multiple arguments
541
+ const ArticleCard = useCache(($binder) => {
542
+ // When using multiple args, functions receive them all
543
+ const title = $binder.value((article, options) => {
544
+ return options.uppercase ? article.title.toUpperCase() : article.title;
545
+ });
546
+
547
+ const excerpt = $binder.value((article, options) => {
548
+ const length = options.excerptLength || 100;
549
+ return article.content.substring(0, length) + '...';
550
+ });
551
+
552
+ return Div({ class: 'article' }, [
553
+ H3(title),
554
+ P(excerpt)
555
+ ]);
556
+ });
557
+
558
+ // Usage with multiple arguments
559
+ const card = ArticleCard(
560
+ { title: 'My Article', content: 'Long article content...' },
561
+ { uppercase: true, excerptLength: 150 }
562
+ );
563
+ ```
564
+
565
+ ## useSingleton() - Singleton Views
566
+
567
+ `useSingleton()` creates a view that is rendered only once, then updated through named sections.
568
+
569
+ ### Basic Usage
570
+ ```javascript
571
+ import { useSingleton } from 'native-document';
572
+ import { Div, H1 } from 'native-document/src/elements';
573
+
574
+ const Dashboard = useSingleton((view) => {
575
+ return Div({ class: 'dashboard' }, [
576
+ H1('Dashboard'),
577
+
578
+ // Named section - can be updated later
579
+ view.createSection('content'),
580
+
581
+ Div({ class: 'footer' }, 'Footer')
582
+ ]);
583
+ });
584
+
585
+ // First render - creates the view
586
+ const dashboard = Dashboard.render();
587
+ document.body.appendChild(dashboard);
588
+
589
+ // Update specific section
590
+ Dashboard.render([
591
+ Div(['Updated content at ', new Date().toLocaleTimeString()])
592
+ ]);
593
+ ```
594
+
595
+ ### createSection(name, transformFn?)
596
+
597
+ Creates a named section that can be updated. Optional transform function wraps the content:
598
+ ```javascript
599
+ import { useSingleton } from 'native-document';
600
+ import { Div, Section, Header, Main } from 'native-document/src/elements';
601
+
602
+ const Layout = useSingleton((view) => {
603
+ return Div([
604
+ Header([
605
+ // Section without wrapper
606
+ view.createSection('header')
607
+ ]),
608
+ Main([
609
+ // Section with wrapper function
610
+ view.createSection('main', (content) => {
611
+ return Section({ class: 'main-section' }, content);
612
+ })
613
+ ])
614
+ ]);
615
+ });
616
+
617
+ // Render layout
618
+ const layout = Layout.render();
619
+ document.body.appendChild(layout);
620
+
621
+ // Update header - content inserted directly
622
+ Layout.render([H1('My App')]);
623
+
624
+ // Update main - content wrapped in Section
625
+ Layout.render([P('Main content')]);
626
+ // Result: <section class="main-section"><p>Main content</p></section>
627
+ ```
628
+
629
+ ### Multiple Sections
630
+ ```javascript
631
+ import { useSingleton } from 'native-document';
632
+ import { Div, Header, Main, Aside, H1, P, Ul, Li } from 'native-document/src/elements';
633
+
634
+ const AppLayout = useSingleton((view) => {
635
+ return Div({ class: 'app' }, [
636
+ Header([
637
+ view.createSection('header')
638
+ ]),
639
+ Div({ class: 'container' }, [
640
+ Main([
641
+ view.createSection('main')
642
+ ]),
643
+ Aside([
644
+ view.createSection('sidebar')
645
+ ])
646
+ ])
647
+ ]);
648
+ });
649
+
650
+ // Render initial layout
651
+ const app = AppLayout.render();
652
+ document.body.appendChild(app);
653
+
654
+ // Update sections sequentially
655
+ Dashboard.render([H1('My App')]); // Updates header
656
+ Dashboard.render([P('Main content')]); // Updates main
657
+ Dashboard.render([Ul([Li('Item 1')])]); // Updates sidebar
658
+ ```
659
+
660
+ ## Best Practices
661
+
662
+ ### 1. Use Descriptive Binding Names
663
+ ```javascript
664
+ import { useCache } from 'native-document';
665
+
666
+ // ✅ Good: Clear variable names
667
+ const UserCard = useCache(($binder) => {
668
+ const userName = $binder.value('name');
669
+ const userEmail = $binder.value('email');
670
+ const isPremiumUser = $binder.class((user) => user.premium);
671
+
672
+ return Div({ class: { 'premium': isPremiumUser } }, [
673
+ Span(userName),
674
+ Span(userEmail)
675
+ ]);
676
+ });
677
+
678
+ // ❌ Bad: Generic names
679
+ const UserCard = useCache(($binder) => {
680
+ const v1 = $binder.value('name');
681
+ const v2 = $binder.value('email');
682
+ const c1 = $binder.class((user) => user.premium);
683
+ });
684
+ ```
685
+
686
+ ### 2. Remember Arguments Are Passed Directly
687
+ ```javascript
688
+ import { useCache } from 'native-document';
689
+
690
+ // ✅ Good: Direct access to data
691
+ const Card = useCache(($binder) => {
692
+ const value = $binder.value((item) => {
693
+ return item.property; // item is the object passed
694
+ });
695
+ });
696
+
697
+ // Component call
698
+ Card({ property: 'value' });
699
+ ```
700
+
701
+ ### 3. Use .nd.attach() for Event Handlers
702
+ ```javascript
703
+ import { useCache } from 'native-document';
704
+ import { Button } from 'native-document/src/elements';
705
+
706
+ // ✅ Good: Use binder.attach with .nd.attach()
707
+ const ActionButton = useCache(($binder) => {
708
+ const label = $binder.value('label');
709
+
710
+ // Handler receives (event, data)
711
+ const handleClick = $binder.attach((event, data) => {
712
+ console.log('Clicked:', data);
713
+ });
714
+
715
+ return Button(label).nd.attach('onClick', handleClick);
716
+ });
717
+
718
+ // ❌ Bad: Direct event handler (won't receive data)
719
+ const ActionButton = useCache(($binder) => {
720
+ const label = $binder.value('label');
721
+
722
+ return Button(label).nd.onClick((event) => {
723
+ // No access to data here
724
+ });
725
+ });
726
+ ```
727
+
728
+ ### 4. Combine with Observable.when()
729
+ ```javascript
730
+ import { useCache } from 'native-document';
731
+ import { Observable } from 'native-document';
732
+ import { Li } from 'native-document/src/elements';
733
+
734
+ const activeId = Observable(null);
735
+
736
+ // ✅ Good: Use Observable.when() for reactive class binding
737
+ const ListItem = useCache(($binder) => {
738
+ const label = $binder.value('label');
739
+
740
+ // Class updates automatically when activeId changes
741
+ const isActive = $binder.class((item) => {
742
+ return activeId.when(item.id);
743
+ });
744
+
745
+ return Li({ class: { 'active': isActive } }, label);
746
+ });
747
+ ```
748
+
749
+ ### 5. Keep Transform Functions Simple
750
+ ```javascript
751
+ import { useCache } from 'native-document';
752
+
753
+ // ✅ Good: Simple, focused transforms
754
+ const Card = useCache(($binder) => {
755
+ const price = $binder.value((product) => {
756
+ return `$${product.price.toFixed(2)}`;
757
+ });
758
+
759
+ const inStock = $binder.class((product) => {
760
+ return product.stock > 0;
761
+ });
762
+
763
+ return Div({ class: { 'in-stock': inStock } }, price);
764
+ });
765
+
766
+ // ❌ Bad: Complex logic
767
+ const Card = useCache(($binder) => {
768
+ const value = $binder.value((product) => {
769
+ // Multiple API calls
770
+ // Complex calculations
771
+ // Side effects
772
+ return result;
773
+ });
774
+ });
775
+ ```
776
+
777
+ ## Binder API Reference
778
+
779
+ | Method | Parameters | Returns | Description |
780
+ |--------|------------|---------|-------------|
781
+ | `$binder.value(key)` | `key: string` | `TemplateBinding` | Bind to property by name |
782
+ | `$binder.value(fn)` | `fn: (...args) => any` | `TemplateBinding` | Bind with transform function |
783
+ | `$binder.property(key)` | `key: string` | `TemplateBinding` | Alias for value() |
784
+ | `$binder.class(fn)` | `fn: (...args) => boolean` | `TemplateBinding` | Bind CSS class |
785
+ | `$binder.style(fn)` | `fn: (...args) => string` | `TemplateBinding` | Bind inline style value |
786
+ | `$binder.attr(fn)` | `fn: (...args) => string` | `TemplateBinding` | Bind attribute value |
787
+ | `$binder.attach(fn)` | `fn: (event, ...args) => void` | `TemplateBinding` | Bind event handler |
788
+
789
+ **Note:** All binder methods (except `attach`) receive arguments exactly as passed by the user. The `attach` method receives the DOM event as the first parameter, followed by user arguments.
790
+
791
+ ## Next Steps
792
+
793
+ Explore related concepts and utilities:
794
+
795
+ ## Next Steps
796
+
797
+ - **[Getting Started](getting-started.md)** - Installation and first steps
798
+ - **[Core Concepts](core-concepts.md)** - Understanding the fundamentals
799
+ - **[Observables](observables.md)** - Reactive state management
800
+ - **[Elements](elements.md)** - Creating and composing UI
801
+ - **[Conditional Rendering](conditional-rendering.md)** - Dynamic content
802
+ - **[List Rendering](list-rendering.md)** - (ForEach | ForEachArray) and dynamic lists
803
+ - **[Routing](routing.md)** - Navigation and URL management
804
+ - **[State Management](state-management.md)** - Global state patterns
805
+ - **[NDElement](native-document-element.md)** - Native Document Element
806
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
807
+ - **[Args Validation](validation.md)** - Function Argument Validation
808
+ - **[Memory Management](memory-management.md)** - Memory management
809
+
810
+ ## Utilities
811
+
812
+ - **[Cache](docs/utils/cache.md)** - Lazy initialization and singleton patterns
813
+ - **[NativeFetch](docs/utils/native-fetch.md)** - HTTP client with interceptors
814
+ - **[Filters](docs/utils/filters.md)** - Data filtering helpers