native-document 1.0.166 → 1.0.168

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 (108) hide show
  1. package/.vitepress/config.js +166 -0
  2. package/CHANGELOG.md +153 -0
  3. package/components.js +2 -1
  4. package/dist/native-document.components.min.js +495 -228
  5. package/dist/native-document.dev.js +7 -0
  6. package/dist/native-document.dev.js.map +1 -1
  7. package/dist/native-document.min.js +1 -1
  8. package/docs/advanced-components.md +213 -608
  9. package/docs/anchor.md +173 -312
  10. package/docs/cache.md +95 -803
  11. package/docs/cli.md +179 -0
  12. package/docs/components/accordion.md +172 -0
  13. package/docs/components/alert.md +99 -0
  14. package/docs/components/avatar.md +160 -0
  15. package/docs/components/badge.md +102 -0
  16. package/docs/components/breadcrumb.md +89 -0
  17. package/docs/components/button.md +183 -0
  18. package/docs/components/card.md +69 -0
  19. package/docs/components/context-menu.md +118 -0
  20. package/docs/components/data-table.md +345 -0
  21. package/docs/components/dropdown.md +214 -0
  22. package/docs/components/form/autocomplete-field.md +81 -0
  23. package/docs/components/form/checkbox-field.md +41 -0
  24. package/docs/components/form/checkbox-group-field.md +54 -0
  25. package/docs/components/form/color-field.md +64 -0
  26. package/docs/components/form/date-field.md +92 -0
  27. package/docs/components/form/field-collection.md +63 -0
  28. package/docs/components/form/file-field.md +203 -0
  29. package/docs/components/form/form-control.md +87 -0
  30. package/docs/components/form/image-field.md +90 -0
  31. package/docs/components/form/index.md +115 -0
  32. package/docs/components/form/number-field.md +65 -0
  33. package/docs/components/form/radio-field.md +51 -0
  34. package/docs/components/form/select-field.md +123 -0
  35. package/docs/components/form/slider.md +136 -0
  36. package/docs/components/form/string-field.md +134 -0
  37. package/docs/components/form/textarea-field.md +65 -0
  38. package/docs/components/form-fields.md +372 -0
  39. package/docs/components/getting-started.md +264 -0
  40. package/docs/components/index.md +337 -0
  41. package/docs/components/layout.md +279 -0
  42. package/docs/components/list.md +73 -0
  43. package/docs/components/menu.md +215 -0
  44. package/docs/components/modal.md +156 -0
  45. package/docs/components/pagination.md +95 -0
  46. package/docs/components/popover.md +131 -0
  47. package/docs/components/progress.md +111 -0
  48. package/docs/components/shortcut-manager.md +221 -0
  49. package/docs/components/simple-table.md +107 -0
  50. package/docs/components/skeleton.md +155 -0
  51. package/docs/components/spinner.md +100 -0
  52. package/docs/components/splitter.md +133 -0
  53. package/docs/components/stepper.md +163 -0
  54. package/docs/components/switch.md +113 -0
  55. package/docs/components/tabs.md +153 -0
  56. package/docs/components/toast.md +119 -0
  57. package/docs/components/tooltip.md +151 -0
  58. package/docs/components/traits.md +261 -0
  59. package/docs/conditional-rendering.md +170 -588
  60. package/docs/contributing.md +300 -25
  61. package/docs/core-concepts.md +205 -374
  62. package/docs/elements.md +251 -367
  63. package/docs/extending-native-document-element.md +192 -207
  64. package/docs/filters.md +153 -1122
  65. package/docs/getting-started.md +193 -267
  66. package/docs/i18n.md +241 -0
  67. package/docs/index.md +76 -0
  68. package/docs/lifecycle-events.md +143 -75
  69. package/docs/list-rendering.md +227 -852
  70. package/docs/memory-management.md +134 -47
  71. package/docs/native-document-element.md +337 -186
  72. package/docs/native-fetch.md +99 -630
  73. package/docs/observable-resource.md +364 -0
  74. package/docs/observables.md +592 -526
  75. package/docs/routing.md +244 -653
  76. package/docs/state-management.md +134 -241
  77. package/docs/svg-elements.md +231 -0
  78. package/docs/theming.md +409 -0
  79. package/docs/tutorials/.gitkeep +0 -0
  80. package/docs/validation.md +95 -97
  81. package/docs/vitepress-conventions.md +219 -0
  82. package/package.json +34 -13
  83. package/readme.md +269 -89
  84. package/src/components/card/Card.js +93 -39
  85. package/src/components/card/index.js +1 -1
  86. package/src/components/list/HasListItem.js +171 -0
  87. package/src/components/list/List.js +41 -107
  88. package/src/components/list/ListDivider.js +39 -0
  89. package/src/components/list/ListGroup.js +76 -59
  90. package/src/components/list/ListItem.js +117 -69
  91. package/src/components/list/index.js +3 -1
  92. package/src/components/list/types/ListItem.d.ts +45 -34
  93. package/src/components/spacer/Spacer.js +1 -1
  94. package/src/core/data/ObservableResource.js +5 -0
  95. package/src/core/data/observable-helpers/observable.prototypes.js +2 -0
  96. package/src/ui/components/card/CardRender.js +133 -0
  97. package/src/ui/components/card/card.css +169 -0
  98. package/src/ui/components/contextmenu/ContextmenuRender.js +1 -1
  99. package/src/ui/components/list/ListRender.js +18 -0
  100. package/src/ui/components/list/divider/ListDividerRender.js +10 -0
  101. package/src/ui/components/list/divider/list-divider.css +12 -0
  102. package/src/ui/components/list/group/ListGroupRender.js +61 -0
  103. package/src/ui/components/list/group/list-group.css +62 -0
  104. package/src/ui/components/list/item/ListItemRender.js +238 -0
  105. package/src/ui/components/list/item/list-item.css +191 -0
  106. package/src/ui/components/list/list.css +24 -0
  107. package/src/ui/components/spacer/SpacerRender.js +10 -0
  108. package/src/ui/index.js +8 -0
@@ -1,722 +1,304 @@
1
- # Conditional Rendering
2
-
3
- Conditional rendering in NativeDocument allows you to dynamically show, hide, or switch between different pieces of content based on reactive state. Unlike traditional approaches that require manual DOM manipulation, NativeDocument's conditional rendering utilities automatically update the interface when observable values change.
1
+ ---
2
+ title: Conditional Rendering
3
+ description: Dynamically show, hide, or switch between content based on reactive state - ShowIf, Switch, Match, When, and more
4
+ ---
4
5
 
5
- ## Understanding Conditional Rendering
6
+ # Conditional Rendering
6
7
 
7
- All conditional rendering functions in NativeDocument work with Observables and automatically manage DOM updates. When the condition changes, content is efficiently added or removed from the DOM without affecting other elements.
8
+ NativeDocument's conditional rendering utilities automatically update the DOM when observable values change. All functions work with observables and manage DOM updates for you.
8
9
 
9
10
  ```javascript
10
- import { ShowIf, Button, Div, Observable } from 'native-document';
11
-
12
- const isVisible = Observable(false);
13
-
14
- // Content automatically appears/disappears based on isVisible
15
- const conditionalContent = ShowIf(isVisible, 'This content toggles!');
11
+ import { ShowIf, HideIf, ShowWhen, Switch, Match, When } from 'native-document/elements';
16
12
  ```
17
13
 
18
- ## ShowIf - Basic Conditional Display
14
+ ---
19
15
 
20
- The `ShowIf` function renders content only when the Observable condition is truthy. When false, the content is completely removed from the DOM.
16
+ ## `ShowIf` - Basic Conditional Display
17
+
18
+ Renders content only when the condition is truthy. When false, the content is removed from the DOM.
21
19
 
22
20
  ```javascript
23
- const user = Observable({ name: 'Alice', isLoggedIn: false });
21
+ const isVisible = Observable(false);
24
22
 
25
- const App = Div([
26
- Button('Login').nd.onClick(() =>
27
- user.set({ ...user.val(), isLoggedIn: true })
28
- ),
29
- Button('Logout').nd.onClick(() =>
30
- user.set({ ...user.val(), isLoggedIn: false })
31
- ),
32
-
33
- // Shows only when user is logged in
34
- ShowIf(user.check(u => u.isLoggedIn), 'Welcome back!')
35
- ]);
23
+ ShowIf(isVisible, Div('Hello!'))
24
+
25
+ // With a boolean (non-reactive)
26
+ ShowIf(true, Div('Always visible'))
36
27
  ```
37
28
 
38
- ### Dynamic Content Generation
29
+ ### Function children
39
30
 
40
- Use functions to create content that reflects current observable values:
31
+ Pass a function to create content that reflects current observable values:
41
32
 
42
33
  ```javascript
43
34
  const notifications = Observable.array([]);
44
35
 
45
- const notificationBadge = ShowIf(
46
- notifications.check(list => list.length > 0),
47
- () => Span({ class: 'badge' }, `${notifications.val().length} new`)
48
- );
49
-
50
- // When notifications change, the badge updates automatically
51
- notifications.push('New message');
36
+ ShowIf(notifications.isNotEmpty(),
37
+ () => Span({ class: 'badge' }, notifications.toLength())
38
+ )
52
39
  ```
53
40
 
54
- ### Using Observable Checkers
55
-
56
- Observable checkers create derived conditions for cleaner code:
41
+ ### Options
57
42
 
58
43
  ```javascript
59
- const temperature = Observable(25);
60
- const isCold = temperature.check(temp => temp < 15);
61
- const isHot = temperature.check(temp => temp > 30);
62
-
63
- const WeatherApp = Div([
64
- Div(['Current temperature: ', temperature, '°C']),
65
- ShowIf(isCold, Div({ class: 'cold' }, '🧥 It\'s cold! Wear a jacket.')),
66
- ShowIf(isHot, Div({ class: 'hot' }, '☀️ It\'s hot! Stay hydrated.')),
67
- Div([
68
- Button('-').nd.onClick(() => --temperature.$value),
69
- Button('+').nd.onClick(() => ++temperature.$value),
70
- ])
71
- ]);
44
+ ShowIf(condition, content, {
45
+ comment: 'my-component', // label visible in DOM comments (debug)
46
+ shouldKeepInCache: false // default true - set to false to recreate on each show
47
+ })
72
48
  ```
73
49
 
74
- ## HideIf and HideIfNot - Inverse Conditions
75
-
76
- These functions provide convenient inverses to `ShowIf`:
50
+ ### Practical example
77
51
 
78
52
  ```javascript
79
- const isLoading = Observable(true);
80
- const hasData = Observable(false);
81
-
82
- const DataDisplay = Div([
83
- // Hide content while loading
84
- HideIf(isLoading, Div('Data is ready to display')),
85
-
86
- // Show loading message only while loading
87
- // Equivalent to ShowIf()
88
- HideIfNot(isLoading, Div({ class: 'spinner' }, 'Loading...')),
53
+ const user = Observable({ name: 'Alice', isLoggedIn: false });
54
+
55
+ Div([
56
+ Button('Login').nd.onClick(() =>
57
+ user.set({ ...user.val(), isLoggedIn: true })
58
+ ),
59
+ ShowIf(user.is(u => u.isLoggedIn),
60
+ () => Div(['Welcome back, ', user.select(u => u.name), '!'])
61
+ )
89
62
  ]);
90
63
  ```
91
64
 
92
- **Understanding the differences:**
93
- ```javascript
94
- const condition = Observable(true);
95
-
96
- // These are equivalent:
97
- ShowIf(condition, content)
98
- HideIfNot(condition, content)
99
-
100
- // These are equivalent:
101
- HideIf(condition, content)
102
- ShowIf(condition.check(val => !val), content)
103
- ```
65
+ ---
104
66
 
105
- ## ShowWhen - Observable Value Matching
67
+ ## `HideIf` / `HideIfNot` - Inverse Conditions
106
68
 
107
- `ShowWhen` is a specialized conditional function that shows content when an Observable matches a specific value. It's particularly useful for state machines and enum-based conditions.
69
+ Convenient inverses of `ShowIf`:
108
70
 
109
- ### Basic Usage
110
71
  ```javascript
111
- const status = Observable('idle');
72
+ const isLoading = Observable(true);
112
73
 
113
- // Show content when status equals 'loading'
114
- const loadingIndicator = ShowWhen(status, 'loading',
115
- Div({ class: 'spinner' }, 'Loading...')
116
- );
74
+ // Hide content while loading
75
+ HideIf(isLoading, Div('Data is ready'))
117
76
 
118
- // Show content when status equals 'success'
119
- const successMessage = ShowWhen(status, 'success',
120
- Div({ class: 'success' }, '✅ Success!')
121
- );
77
+ // Show content only while loading (equivalent to ShowIf)
78
+ HideIfNot(isLoading, Div('Loading...'))
122
79
  ```
123
80
 
124
- ### Two Syntax Options
81
+ Equivalences:
125
82
 
126
- **Option 1: Three arguments (Observable, value, content)**
127
83
  ```javascript
128
- const theme = Observable('light');
129
-
130
- ShowWhen(theme, 'dark',
131
- Div({ class: 'dark-mode-indicator' }, '🌙 Dark Mode')
132
- );
84
+ ShowIf(condition, content) // same as HideIfNot(condition, content)
85
+ HideIf(condition, content) // same as ShowIf(condition.isFalsy(), content)
133
86
  ```
134
87
 
135
- **Option 2: Two arguments (ObservableWhen result, content)**
136
- ```javascript
137
- const theme = Observable('light');
138
- const isDark = theme.when('dark'); // Returns ObservableWhen
88
+ ---
139
89
 
140
- ShowWhen(isDark,
141
- Div({ class: 'dark-mode-indicator' }, '🌙 Dark Mode')
142
- );
143
- ```
90
+ ## `ShowWhen` - Value Matching
144
91
 
145
- ### Practical Example: Status-Based UI
146
- ```javascript
147
- const connectionStatus = Observable('disconnected');
92
+ Shows content when an observable matches a specific value.
148
93
 
149
- const StatusIndicator = Div({ class: 'status-bar' }, [
150
- ShowWhen(connectionStatus, 'connecting',
151
- Span({ class: 'status connecting' }, '🔄 Connecting...')
152
- ),
153
- ShowWhen(connectionStatus, 'connected',
154
- Span({ class: 'status connected' }, '✅ Connected')
155
- ),
156
- ShowWhen(connectionStatus, 'disconnected',
157
- Span({ class: 'status disconnected' }, '❌ Disconnected')
158
- ),
159
- ShowWhen(connectionStatus, 'error',
160
- Span({ class: 'status error' }, '⚠️ Connection Error')
161
- )
162
- ]);
163
-
164
- // Update status
165
- setTimeout(() => connectionStatus.set('connecting'), 1000);
166
- setTimeout(() => connectionStatus.set('connected'), 3000);
167
- ```
94
+ **Two-argument form** - pass an `ObservableWhen` result from `.when()`:
168
95
 
169
- Both can handle multiple states, but they serve different purposes:
170
96
  ```javascript
171
- const phase = Observable('loading');
172
-
173
- // ShowWhen: Multiple independent conditions
174
- Div([
175
- ShowWhen(phase, 'loading', LoadingSpinner()),
176
- ShowWhen(phase, 'success', SuccessMessage()),
177
- ShowWhen(phase, 'error', ErrorMessage())
178
- ]);
97
+ const theme = Observable('light');
98
+ const isDark = theme.when('dark'); // ObservableWhen result
179
99
 
180
- // Match: Single content area that switches
181
- Match(phase, {
182
- loading: LoadingSpinner(),
183
- success: SuccessMessage(),
184
- error: ErrorMessage()
185
- });
100
+ ShowWhen(isDark, Div('Dark mode active'))
186
101
  ```
187
102
 
188
- ## Switch - Binary Content Switching
189
-
190
- `Switch` efficiently toggles between exactly two pieces of content based on a boolean condition:
103
+ **Three-argument form** - pass the observable, the target value, and the content:
191
104
 
192
105
  ```javascript
193
- const isDarkMode = Observable(false);
194
-
195
- const ThemeToggle = Div([
196
- Button('Toggle Theme').nd.onClick(() => isDarkMode.set(!isDarkMode.val())),
197
-
198
- Switch(isDarkMode,
199
- Div({ class: 'dark-indicator' }, '🌙 Dark Mode'), // when true
200
- Div({ class: 'light-indicator' }, '☀️ Light Mode') // when false
201
- )
202
- ]);
106
+ ShowWhen(theme, 'dark', Div('Dark mode active'))
203
107
  ```
204
108
 
205
- ### Dynamic Switch Content
206
-
207
- Functions allow for reactive content that updates with current values:
109
+ ### Practical example
208
110
 
209
111
  ```javascript
210
- const user = Observable({ name: 'Guest', isAuthenticated: false });
112
+ const status = Observable('disconnected');
211
113
 
212
- const UserGreeting = Switch(
213
- user.check(u => u.isAuthenticated),
214
- () => Div({ class: 'welcome' }, `Welcome back, ${user.val().name}!`),
215
- () => Div({ class: 'login-prompt' }, 'Please sign in to continue')
216
- );
114
+ Div({ class: 'status-bar' }, [
115
+ ShowWhen(status, 'connecting', Span('Connecting...')),
116
+ ShowWhen(status, 'connected', Span('Connected')),
117
+ ShowWhen(status, 'disconnected', Span('Disconnected')),
118
+ ShowWhen(status, 'error', Span('Connection Error'))
119
+ ]);
217
120
  ```
218
121
 
219
- ## Match - Multiple Condition Handling
220
-
221
- `Match` provides switch-case like functionality for handling multiple states:
222
-
223
- ```javascript
224
- const requestStatus = Observable('idle');
225
-
226
- const StatusDisplay = Match(requestStatus, {
227
- idle: Div({ class: 'status-idle' }, 'Ready to make request'),
228
- loading: Div({ class: 'status-loading' }, [
229
- Span({ class: 'spinner' }),
230
- ' Loading...'
231
- ]),
232
- success: Div({ class: 'status-success' }, '✅ Request completed'),
233
- error: Div({ class: 'status-error' }, '❌ Request failed'),
234
- timeout: Div({ class: 'status-timeout' }, '⏰ Request timed out')
235
- });
236
- ```
122
+ ---
237
123
 
238
- ### Dynamic Match Content
124
+ ## `Switch` - Binary Content
239
125
 
240
- Functions in Match provide access to current observable values:
126
+ Toggles between exactly two pieces of content based on a boolean observable:
241
127
 
242
128
  ```javascript
243
- const gameState = Observable({
244
- phase: 'menu',
245
- score: 0,
246
- level: 1
247
- });
248
-
249
- const GameDisplay = Match(gameState.check(state => state.phase), {
250
- menu: () => Div({ class: 'game-menu' }, [
251
- H1('Welcome to the Game'),
252
- Button('Start Game').nd.onClick(() =>
253
- gameState.set({ ...gameState.val(), phase: 'playing' })
254
- )
255
- ]),
256
-
257
- playing: () => Div({ class: 'game-ui' }, [
258
- Div(['Score: ', gameState.check(s => s.score)]),
259
- Div(['Level: ', gameState.check(s => s.level)]),
260
- Button('Pause').nd.onClick(() =>
261
- gameState.set({ ...gameState.val(), phase: 'paused' })
262
- ),
263
- Button('Game Over').nd.onClick(() =>
264
- gameState.set({ ...gameState.val(), phase: 'gameOver' })
265
- )
266
- ]),
267
-
268
- paused: () => Div({ class: 'game-paused' }, [
269
- H2('Game Paused'),
270
- Button('Resume').nd.onClick(() =>
271
- gameState.set({ ...gameState.val(), phase: 'playing' })
272
- )
273
- ]),
274
-
275
- gameOver: () => Div({ class: 'game-over' }, [
276
- H2('Game Over'),
277
- Div(['Final Score: ', gameState.check(s => s.score)]),
278
- Button('Play Again').nd.onClick(() =>
279
- gameState.set({ phase: 'menu', score: 0, level: 1 })
280
- )
281
- ])
282
- });
283
- ```
284
-
285
- ### Match with Default Cases
286
-
287
- Handle unexpected values with default cases:
129
+ const isDarkMode = Observable(false);
288
130
 
289
- ```javascript
290
- const userRole = Observable('guest');
291
-
292
- const RoleBasedMenu = Match(userRole, {
293
- admin: AdminMenu(),
294
- moderator: ModeratorMenu(),
295
- user: UserMenu(),
296
- // Default case for unknown roles
297
- default: GuestMenu()
298
- });
131
+ Switch(isDarkMode,
132
+ Div('Dark mode'), // when true
133
+ Div('Light mode') // when false
134
+ )
299
135
  ```
300
136
 
301
- ## When - Fluent Builder Pattern
302
-
303
- `When` provides a chainable interface for conditional rendering:
137
+ With function children:
304
138
 
305
139
  ```javascript
306
- const score = Observable(85);
140
+ const user = Observable({ name: 'Guest', isLoggedIn: false });
307
141
 
308
- const GradeDisplay = When(score.check(s => s >= 90))
309
- .show(() => Div({ class: 'grade-a' }, `Excellent! Score: ${score.val()}`))
310
- .otherwise(() => Div({ class: 'grade-b' }, `Score: ${score.val()}`));
142
+ Switch(user.is(u => u.isLoggedIn),
143
+ () => Div(['Welcome back, ', user.select(u => u.name)]),
144
+ () => Div('Please sign in')
145
+ )
311
146
  ```
312
147
 
313
- ### Complex Conditions with When
148
+ > `Switch` is built on top of `Match` using `.toBoolean()`.
314
149
 
315
- ```javascript
316
- const user = Observable({
317
- age: 25,
318
- hasLicense: true,
319
- hasInsurance: false
320
- });
321
-
322
- const DrivingEligibility = When(user.check(u =>
323
- u.age >= 18 && u.hasLicense && u.hasInsurance
324
- ))
325
- .show(() => Div({ class: 'eligible' }, [
326
- '✅ You can drive legally',
327
- Div(`Age: ${user.val().age}, License: Yes, Insurance: Yes`)
328
- ]))
329
- .otherwise(() => {
330
- const u = user.val();
331
- const issues = [];
332
- if (u.age < 18) issues.push('Must be 18 or older');
333
- if (!u.hasLicense) issues.push('Need a valid license');
334
- if (!u.hasInsurance) issues.push('Need insurance coverage');
335
-
336
- return Div({ class: 'not-eligible' }, [
337
- '❌ Cannot drive legally',
338
- Div(['Issues: ', issues.join(', ')])
339
- ]);
340
- });
341
- ```
150
+ ---
342
151
 
343
- ## Practical Examples
152
+ ## `Match` - Multiple States
344
153
 
345
- ### Form Validation with Progressive Disclosure
154
+ Handles multiple states like a switch-case. Each key maps to a content value or function:
346
155
 
347
156
  ```javascript
348
- const formData = Observable.object({
349
- email: '',
350
- password: '',
351
- confirmPassword: ''
352
- });
353
-
354
- const isValidEmail = formData.email.check(e =>
355
- e.includes('@') && e.includes('.') && e.length > 5
356
- );
357
-
358
- const isValidPassword = formData.password.check(p => p.length >= 8);
359
-
360
- const passwordsMatch = Observable.computed(() => {
361
- const data = formData.$value;
362
- return data.password === data.confirmPassword && data.password.length > 0;
363
- }, [formData.password, formData.confirmPassword]);
364
-
365
- const canSubmit = Observable.computed(() =>
366
- isValidEmail.val() && isValidPassword.val() && passwordsMatch.val(),
367
- [isValidEmail, isValidPassword, passwordsMatch]
368
- );
157
+ const status = Observable('idle');
369
158
 
370
- const RegistrationForm = Div({ class: 'registration-form' }, [
371
- // Email field with validation
372
- Input({
373
- type: 'email',
374
- placeholder: 'Email',
375
- value: formData.email
376
- }),
377
- ShowIf(formData.email.check(e => e.length > 0 && !isValidEmail.val()),
378
- Div({ class: 'error' }, 'Please enter a valid email address')
379
- ),
380
-
381
- // Password field
382
- Input({
383
- type: 'password',
384
- placeholder: 'Password',
385
- value: formData.password
386
- }),
387
- ShowIf(formData.password.check(p => p.length > 0 && p.length < 8),
388
- Div({ class: 'error' }, 'Password must be at least 8 characters')
389
- ),
390
-
391
- // Confirm password (only show after password is valid)
392
- ShowIf(isValidPassword, [
393
- Input({
394
- type: 'password',
395
- placeholder: 'Confirm Password',
396
- value: formData.confirmPassword
397
- }),
398
- ShowIf(formData.confirmPassword.check(p => p.length > 0 && !passwordsMatch.val()),
399
- Div({ class: 'error' }, 'Passwords do not match')
400
- )
401
- ]),
402
-
403
- // Submit button
404
- Switch(canSubmit,
405
- Button('Create Account').nd.onClick(() => {
406
- console.log('Creating account...', formData.$value);
407
- }),
408
- Button({ disabled: true, class: 'disabled' }, 'Create Account')
409
- )
410
- ]);
159
+ Match(status, {
160
+ idle: Div('Ready'),
161
+ loading: Div('Loading...'),
162
+ success: Div('Done!'),
163
+ error: Div('Something went wrong'),
164
+ default: Div('Unknown state')
165
+ })
411
166
  ```
412
167
 
413
- ### Progressive User Interface
168
+ With function values for access to current observable state:
414
169
 
415
170
  ```javascript
416
- const appState = Observable.object({
417
- user: null,
418
- currentView: 'welcome',
419
- settings: { theme: 'light', notifications: true }
420
- });
171
+ const phase = Observable('menu');
421
172
 
422
- const isLoggedIn = appState.user.check(user => user !== null);
423
- const isGuest = appState.user.check(user => user === null);
424
-
425
- const App = Div({ class: 'app' }, [
426
- // Header - changes based on auth state
427
- Header({ class: 'app-header' }, [
428
- H1('My App'),
429
- Switch(isLoggedIn,
430
- // Authenticated header
431
- () => Div({ class: 'user-menu' }, [
432
- Span(['Welcome, ', appState.user.val().name]),
433
- Button('Settings').nd.onClick(() =>
434
- appState.currentView.set('settings')
435
- ),
436
- Button('Logout').nd.onClick(() => {
437
- appState.user.set(null);
438
- appState.currentView.set('welcome');
439
- })
440
- ]),
441
- // Guest header
442
- Div({ class: 'auth-buttons' }, [
443
- Button('Sign In').nd.onClick(() =>
444
- appState.currentView.set('login')
445
- ),
446
- Button('Sign Up').nd.onClick(() =>
447
- appState.currentView.set('register')
448
- )
449
- ])
450
- )
173
+ Match(phase, {
174
+ menu: () => Div([
175
+ H1('Welcome'),
176
+ Button('Start').nd.onClick(() => phase.set('playing'))
451
177
  ]),
452
-
453
- // Main content area
454
- Match(appState.currentView, {
455
- welcome: WelcomeView(),
456
- login: LoginView(appState),
457
- register: RegisterView(appState),
458
- dashboard: When(isLoggedIn)
459
- .show(() => DashboardView(appState.user.val()))
460
- .otherwise(() => {
461
- appState.currentView.set('welcome');
462
- return Div('Redirecting...');
463
- }),
464
- settings: When(isLoggedIn)
465
- .show(() => SettingsView(appState))
466
- .otherwise(() => {
467
- appState.currentView.set('welcome');
468
- return Div('Please log in to access settings');
469
- })
470
- })
471
- ]);
472
- ```
473
-
474
- ## Performance Considerations
475
-
476
- ### Efficient Content Updates
477
-
478
- NativeDocument optimizes conditional rendering by only updating the DOM when conditions actually change:
479
-
480
- ```javascript
481
- const isVisible = Observable(true);
482
-
483
- // This content is created once and reused
484
- const expensiveContent = ShowIf(isVisible, () => {
485
- console.log('Creating expensive content'); // Only called when becoming visible
486
- return createComplexComponent();
487
- });
488
-
489
- // Toggling rapidly won't recreate content unnecessarily
490
- isVisible.set(false);
491
- isVisible.set(true); // Content is recreated
492
- isVisible.set(true); // No recreation, already visible
178
+ playing: () => Div([
179
+ Div(['Score: ', score]),
180
+ Button('Pause').nd.onClick(() => phase.set('paused'))
181
+ ]),
182
+ paused: () => Div([
183
+ H2('Paused'),
184
+ Button('Resume').nd.onClick(() => phase.set('playing'))
185
+ ])
186
+ })
493
187
  ```
494
188
 
495
- ### Memory Management
189
+ ### Dynamic add / remove
496
190
 
497
- Functions in conditional rendering are called only when needed, preventing memory waste:
191
+ `Match` exposes `.add()` and `.remove()` methods to update the available states at runtime:
498
192
 
499
193
  ```javascript
500
- const currentTab = Observable('home');
501
-
502
- // Heavy components only created when their tab is active
503
- const TabContent = Match(currentTab, {
504
- home: () => {
505
- console.log('Creating home tab');
506
- return HomeTabComponent(); // Only created when needed
507
- },
508
- profile: () => {
509
- console.log('Creating profile tab');
510
- return ProfileTabComponent(); // Only created when needed
511
- },
512
- settings: () => {
513
- console.log('Creating settings tab');
514
- return SettingsTabComponent(); // Only created when needed
515
- }
194
+ const view = Match(status, {
195
+ idle: Div('Ready'),
196
+ loading: Div('Loading...')
516
197
  });
517
- ```
518
-
519
- ### Avoiding Unnecessary Computations
520
-
521
- Use Observable.computed for expensive condition calculations:
522
198
 
523
- ```javascript
524
- const items = Observable.array([...largeDataset]);
525
- const searchTerm = Observable('');
526
-
527
- // Computed once, reused in multiple places
528
- const filteredItems = Observable.computed(() => {
529
- return items.val().filter(item =>
530
- item.name.toLowerCase().includes(searchTerm.val().toLowerCase())
531
- );
532
- }, [items, searchTerm]);
533
-
534
- const hasResults = filteredItems.check(results => results.length > 0);
199
+ // Add a new state
200
+ view.nd.add('success', Div('Done!'));
201
+ view.nd.add('success', Div('Done!'), true); // third arg: immediately switch to this state
535
202
 
536
- const SearchResults = Div([
537
- ShowIf(hasResults, () =>
538
- ForEach(filteredItems, item => ItemComponent(item))
539
- ),
540
- HideIf(hasResults, 'No results found')
541
- ]);
203
+ // Remove a state
204
+ view.nd.remove('idle');
542
205
  ```
543
206
 
544
- ## Best Practices
207
+ ### `shouldKeepInCache`
545
208
 
546
- ### 1. Choose the Right Tool
209
+ By default, `Match` caches each rendered state. Pass `false` to recreate content on every switch:
547
210
 
548
211
  ```javascript
549
- // Good: Use ShowIf for simple boolean conditions
550
- ShowIf(isVisible, content)
551
-
552
- // Good: Use Switch for binary choices
553
- Switch(isLoggedIn, welcomeMessage, loginPrompt)
554
-
555
- // Good: Use Match for multiple states
556
- Match(status, { loading: '...', success: '...', error: '...' })
557
-
558
- // Less ideal: Using Match for simple boolean
559
- Match(isVisible, { true: content, false: null })
212
+ Match(status, { loading: Div('...'), success: Div('Done') }, false)
560
213
  ```
561
214
 
562
- ### 2. Use Functions for Dynamic Content
215
+ ---
563
216
 
564
- ```javascript
565
- // Good: Function creates fresh content with current values
566
- ShowIf(user.check(u => u.isAdmin),
567
- () => Div(`Admin: ${user.val().name}`)
568
- )
217
+ ## `When` - Fluent Builder
569
218
 
570
- // Problematic: Static content won't update
571
- ShowIf(user.check(u => u.isAdmin),
572
- Div(`Admin: ${user.val().name}`) // Name won't update if user changes
573
- )
574
- ```
575
-
576
- ### 3. Combine Conditions Logically
219
+ A chainable interface for conditional rendering:
577
220
 
578
221
  ```javascript
579
- // Good: Use computed observables for complex conditions
580
- const canEdit = Observable.computed(() => {
581
- const u = user.val();
582
- const p = post.val();
583
- return u.isLoggedIn && (u.role === 'admin' || u.id === p.authorId);
584
- }, [user, post]);
585
-
586
- ShowIf(canEdit, editButton)
222
+ const score = Observable(85);
587
223
 
588
- // Less maintainable: Inline complex logic
589
- ShowIf(user.check(u => u.isLoggedIn && u.role === 'admin'), editButton)
224
+ When(score.isGreaterThanOrEqualTo(90))
225
+ .show(() => Div('Excellent!'))
226
+ .otherwise(() => Div('Keep going'))
590
227
  ```
591
228
 
592
- ### 4. Handle Edge Cases Gracefully
229
+ Convert to a DOM element explicitly with `.toNdElement()`:
593
230
 
594
231
  ```javascript
595
- // Good: Defensive programming
596
- ShowIf(data.check(d => d && d.items && d.items.length > 0),
597
- () => ForEach(data.val().items, renderItem)
598
- )
232
+ const greeting = When(isLoggedIn)
233
+ .show(() => Div('Welcome back!'))
234
+ .otherwise(() => Div('Please sign in'));
599
235
 
600
- // Risky: Might throw errors on null/undefined
601
- ShowIf(data.check(d => d.items.length > 0), content)
236
+ // .otherwise() already returns the element
237
+ // .toNdElement() is available if you build the chain without calling .otherwise()
238
+ const el = greeting.toNdElement();
602
239
  ```
603
240
 
604
- ### 5. Use Meaningful Variable Names
241
+ ---
605
242
 
606
- ```javascript
607
- // Good: Clear intent
608
- const isUserAuthenticated = user.check(u => u.token !== null);
609
- const hasUnreadMessages = messages.check(m => m.some(msg => !msg.read));
243
+ ## Choosing the Right Tool
610
244
 
611
- ShowIf(hasUnreadMessages, notificationBadge)
245
+ | Situation | Use |
246
+ |---|---|
247
+ | Simple show / hide | `ShowIf` |
248
+ | Inverse show / hide | `HideIf` / `HideIfNot` |
249
+ | Show when value matches | `ShowWhen` |
250
+ | Two options (true / false) | `Switch` |
251
+ | Multiple named states | `Match` |
252
+ | Fluent chaining style | `When` |
612
253
 
613
- // Less clear: Generic names
614
- const check1 = user.check(u => u.token !== null);
615
- const check2 = messages.check(m => m.some(msg => !msg.read));
616
- ```
254
+ ---
617
255
 
618
- ## Common Patterns
256
+ ## Best Practices
619
257
 
620
- ### Loading States with Error Handling
258
+ **Use functions for dynamic content** - static values are evaluated once at creation time:
621
259
 
622
260
  ```javascript
623
- const requestState = Observable.object({
624
- status: 'idle', // idle, loading, success, error
625
- data: null,
626
- error: null
627
- });
261
+ // Good - content reflects current value when shown
262
+ ShowIf(isAdmin, () => Div(user.select(u => u.name)))
628
263
 
629
- const DataView = Match(requestState.status, {
630
- idle: Button('Load Data').nd.onClick(loadData),
631
- loading: Div({ class: 'loading' }, 'Loading...'),
632
- success: () => DataDisplay(requestState.data.val()),
633
- error: () => Div({ class: 'error' }, [
634
- 'Error: ', requestState.error,
635
- Button('Retry').nd.onClick(loadData)
636
- ])
637
- });
264
+ // Risky - name captured at creation, won't update
265
+ ShowIf(isAdmin, Div(user.val().name))
638
266
  ```
639
267
 
640
- ### Feature Flags and Progressive Enhancement
268
+ **Use `Observable.computed()` for complex conditions:**
641
269
 
642
270
  ```javascript
643
- const features = Observable.object({
644
- darkMode: true,
645
- newDashboard: false,
646
- betaFeatures: false
647
- });
271
+ const canEdit = Observable.computed((u, p) =>
272
+ u.isLoggedIn && (u.role === 'admin' || u.id === p.authorId),
273
+ [user, post]
274
+ );
648
275
 
649
- const App = Div([
650
- // Theme switching
651
- Switch(features.darkMode,
652
- Div({ class: 'app dark-theme' }, content),
653
- Div({ class: 'app light-theme' }, content)
654
- ),
655
-
656
- // Beta features for power users
657
- ShowIf(features.betaFeatures, BetaPanel()),
658
-
659
- // A/B testing new dashboard
660
- Switch(features.newDashboard,
661
- NewDashboard(),
662
- LegacyDashboard()
663
- )
664
- ]);
276
+ ShowIf(canEdit, editButton)
665
277
  ```
666
278
 
667
- ## Debugging Conditional Rendering
668
-
669
- ### Using Console Logs in Conditions
279
+ **Use shorthand checkers instead of manual conditions:**
670
280
 
671
281
  ```javascript
672
- const debugCondition = condition.check(value => {
673
- console.log('Condition evaluated:', value);
674
- return value > 10;
675
- });
282
+ // Good
283
+ ShowIf(list.isEmpty(), Div('No items'))
284
+ ShowIf(name.isTruthy(), Div(['Hello, ', name]))
676
285
 
677
- ShowIf(debugCondition, content);
286
+ // Avoid
287
+ ShowIf(list.check(l => l.length === 0), Div('No items'))
678
288
  ```
679
289
 
680
- ### Tracking State Changes
681
-
682
- ```javascript
683
- const status = Observable('idle');
684
-
685
- // Log all status changes
686
- status.subscribe(newStatus => {
687
- console.log('Status changed to:', newStatus);
688
- });
689
-
690
- // Debug what content is being rendered
691
- const StatusContent = Match(status, {
692
- idle: () => {
693
- console.log('Rendering idle state');
694
- return IdleComponent();
695
- },
696
- loading: () => {
697
- console.log('Rendering loading state');
698
- return LoadingComponent();
699
- }
700
- });
701
- ```
290
+ ---
702
291
 
703
292
  ## Next Steps
704
293
 
705
- Now that you understand conditional rendering, explore these related topics:
706
-
707
- - **[List Rendering](list-rendering.md)** - (ForEach | ForEachArray) and dynamic lists
708
- - **[Routing](routing.md)** - Navigation and URL management
709
- - **[State Management](state-management.md)** - Global state patterns
710
- - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
711
- - **[NDElement](native-document-element.md)** - Native Document Element
712
- - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
713
- - **[Advanced Components](advanced-components.md)** - Template caching and singleton views
714
- - **[Args Validation](validation.md)** - Function Argument Validation
715
- - **[Memory Management](memory-management.md)** - Memory management
716
- - **[Anchor](anchor.md)** - Anchor
294
+ - **[List Rendering](./list-rendering.md)** - ForEach and dynamic lists
295
+ - **[Observables](./observables.md)** - Reactive state management
296
+ - **[Elements](./elements.md)** - Creating and composing UI
297
+ - **[State Management](./state-management.md)** - Global state patterns
298
+ - **[Anchor](./anchor.md)** - How conditional rendering works under the hood
717
299
 
718
300
  ## Utilities
719
301
 
720
- - **[Cache](docs/utils/cache.md)** - Lazy initialization and singleton patterns
721
- - **[NativeFetch](docs/utils/native-fetch.md)** - HTTP client with interceptors
722
- - **[Filters](docs/utils/filters.md)** - Data filtering helpers
302
+ - **[Cache](./cache.md)** - Lazy initialization and singleton patterns
303
+ - **[NativeFetch](./native-fetch.md)** - HTTP client with interceptors
304
+ - **[Filters](./filters.md)** - Data filtering helpers