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
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
# State Management
|
|
2
|
+
|
|
3
|
+
NativeDocument's state management system provides tools for managing application-wide state that persists across components and route changes. The Store system enables shared state with reactive updates, while Observables handle local component state.
|
|
4
|
+
|
|
5
|
+
## Understanding State Management
|
|
6
|
+
|
|
7
|
+
State management in NativeDocument operates on two levels: local component state using Observables, and global application state using the Store system. The Store allows multiple components to share and react to the same state changes.
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
import { Store, Observable } from 'native-document';
|
|
11
|
+
|
|
12
|
+
// Create global state
|
|
13
|
+
const userStore = Store.create('user', {
|
|
14
|
+
id: null,
|
|
15
|
+
name: '',
|
|
16
|
+
email: '',
|
|
17
|
+
isLoggedIn: false
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Components automatically update when store changes
|
|
21
|
+
const UserGreeting = () => {
|
|
22
|
+
const user = Store.use('user');
|
|
23
|
+
|
|
24
|
+
return ShowIf(user.check(u => u.isLoggedIn),
|
|
25
|
+
() => Div(['Welcome back, ', user.$value.name, '!'])
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Store Creation
|
|
31
|
+
|
|
32
|
+
Create named stores that can be accessed from anywhere in your application:
|
|
33
|
+
|
|
34
|
+
### Basic Store Creation
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// Create a simple store
|
|
38
|
+
const themeStore = Store.create('theme', 'light');
|
|
39
|
+
|
|
40
|
+
// Create an object store
|
|
41
|
+
const appStore = Store.create('app', {
|
|
42
|
+
currentPage: 'home',
|
|
43
|
+
sidebarOpen: false,
|
|
44
|
+
notifications: []
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Create with initial complex data
|
|
48
|
+
const cartStore = Store.create('cart', {
|
|
49
|
+
items: [],
|
|
50
|
+
total: 0,
|
|
51
|
+
currency: 'USD',
|
|
52
|
+
discountCode: null
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Store with Computed Values
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
const cartStore = Store.create('cart', {
|
|
60
|
+
items: [],
|
|
61
|
+
taxRate: 0.08
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Computed total that updates automatically
|
|
65
|
+
const cartTotal = Observable.computed(() => {
|
|
66
|
+
const cart = cartStore.$value;
|
|
67
|
+
const subtotal = cart.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
|
68
|
+
const tax = subtotal * cart.taxRate;
|
|
69
|
+
return subtotal + tax;
|
|
70
|
+
}, [cartStore]);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Using Stores
|
|
74
|
+
|
|
75
|
+
Access and react to store changes using Store.use() or Store.follow():
|
|
76
|
+
|
|
77
|
+
### Store.use() - Primary Access Method
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
const UserProfile = () => {
|
|
81
|
+
// Get reactive reference to store
|
|
82
|
+
const user = Store.use('user');
|
|
83
|
+
|
|
84
|
+
return Div([
|
|
85
|
+
H1(['User Profile: ', user.check(u => u.name)]),
|
|
86
|
+
P(['Email: ', user.check(u => u.email)]),
|
|
87
|
+
P(['Status: ', user.check(u => u.isLoggedIn ? 'Online' : 'Offline')])
|
|
88
|
+
]);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Multiple components can use the same store
|
|
92
|
+
const UserMenu = () => {
|
|
93
|
+
const user = Store.use('user');
|
|
94
|
+
|
|
95
|
+
return Nav([
|
|
96
|
+
ShowIf(user.check(u => u.isLoggedIn), [
|
|
97
|
+
Link({ to: '/profile' }, 'My Profile'),
|
|
98
|
+
Button('Logout').nd.on.click(() => {
|
|
99
|
+
user.set({ ...user.$value, isLoggedIn: false });
|
|
100
|
+
})
|
|
101
|
+
])
|
|
102
|
+
]);
|
|
103
|
+
};
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Store.follow() - Alternative Access
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
const NotificationBadge = () => {
|
|
110
|
+
// Follow is alias for use
|
|
111
|
+
const notifications = Store.follow('notifications');
|
|
112
|
+
|
|
113
|
+
return ShowIf(notifications.check(list => list.length > 0),
|
|
114
|
+
() => Span({ class: 'badge' }, notifications.$value.length)
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Updating Store State
|
|
120
|
+
|
|
121
|
+
Modify store state using the returned observable's methods:
|
|
122
|
+
|
|
123
|
+
### Direct Updates
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
const ThemeToggle = () => {
|
|
127
|
+
const theme = Store.use('theme');
|
|
128
|
+
|
|
129
|
+
return Button('Toggle Theme').nd.on.click(() => {
|
|
130
|
+
const current = theme.$value;
|
|
131
|
+
theme.set(current === 'light' ? 'dark' : 'light');
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Object Store Updates
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
const LoginForm = () => {
|
|
140
|
+
const user = Store.use('user');
|
|
141
|
+
const email = Observable('');
|
|
142
|
+
const password = Observable('');
|
|
143
|
+
|
|
144
|
+
const handleLogin = () => {
|
|
145
|
+
// Update multiple properties
|
|
146
|
+
user.set({
|
|
147
|
+
...user.$value,
|
|
148
|
+
email: email.$value,
|
|
149
|
+
isLoggedIn: true,
|
|
150
|
+
name: 'User Name' // This would come from API
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return Form([
|
|
155
|
+
Input({ type: 'email', value: email, placeholder: 'Email' }),
|
|
156
|
+
Input({ type: 'password', value: password, placeholder: 'Password' }),
|
|
157
|
+
Button('Login').nd.on.click(handleLogin)
|
|
158
|
+
]);
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Partial Updates
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
const UserSettings = () => {
|
|
166
|
+
const user = Store.use('user');
|
|
167
|
+
|
|
168
|
+
const updateName = (newName) => {
|
|
169
|
+
// Only update specific fields
|
|
170
|
+
user.set({
|
|
171
|
+
...user.$value,
|
|
172
|
+
name: newName
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const updatePreferences = (prefs) => {
|
|
177
|
+
user.set({
|
|
178
|
+
...user.$value,
|
|
179
|
+
preferences: {
|
|
180
|
+
...user.$value.preferences,
|
|
181
|
+
...prefs
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return Div([
|
|
187
|
+
Input({
|
|
188
|
+
value: user.check(u => u.name),
|
|
189
|
+
placeholder: 'Name'
|
|
190
|
+
}).nd.on.input(e => updateName(e.target.value)),
|
|
191
|
+
|
|
192
|
+
Button('Dark Mode').nd.on.click(() =>
|
|
193
|
+
updatePreferences({ theme: 'dark' })
|
|
194
|
+
)
|
|
195
|
+
]);
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Store Access Patterns
|
|
200
|
+
|
|
201
|
+
### Direct Store Access
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
// Get store reference without reactivity
|
|
205
|
+
const userStore = Store.get('user');
|
|
206
|
+
|
|
207
|
+
// Check current value
|
|
208
|
+
if (userStore.$value.isLoggedIn) {
|
|
209
|
+
console.log('User is logged in');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Subscribe to changes manually
|
|
213
|
+
userStore.subscribe(newUser => {
|
|
214
|
+
console.log('User changed:', newUser);
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Real-World Examples
|
|
219
|
+
|
|
220
|
+
## Store Cleanup
|
|
221
|
+
|
|
222
|
+
Properly clean up stores when they're no longer needed:
|
|
223
|
+
|
|
224
|
+
### Manual Cleanup
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
// Remove store and all its subscribers
|
|
228
|
+
Store.delete('temporaryStore');
|
|
229
|
+
|
|
230
|
+
// Clean up specific subscriber
|
|
231
|
+
const userStore = Store.use('user');
|
|
232
|
+
userStore.destroy(); // Clean up this subscriber instance
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Performance Considerations
|
|
236
|
+
|
|
237
|
+
### Efficient Store Updates
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
// Good: Batch related updates
|
|
241
|
+
const updateUserProfile = (name, email, preferences) => {
|
|
242
|
+
const user = Store.use('user');
|
|
243
|
+
user.set({
|
|
244
|
+
...user.$value,
|
|
245
|
+
name,
|
|
246
|
+
email,
|
|
247
|
+
preferences: {
|
|
248
|
+
...user.$value.preferences,
|
|
249
|
+
...preferences
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Less efficient: Multiple separate updates
|
|
255
|
+
const updateUserProfileSlow = (name, email, preferences) => {
|
|
256
|
+
const user = Store.use('user');
|
|
257
|
+
user.set({ ...user.$value, name }); // Triggers update
|
|
258
|
+
user.set({ ...user.$value, email }); // Triggers update
|
|
259
|
+
user.set({ ...user.$value, preferences }); // Triggers update
|
|
260
|
+
};
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Selective Subscriptions
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
// Subscribe to specific parts of store
|
|
267
|
+
const UserName = () => {
|
|
268
|
+
const user = Store.use('user');
|
|
269
|
+
|
|
270
|
+
// Only re-render when name changes
|
|
271
|
+
const userName = user.check(u => u.name);
|
|
272
|
+
|
|
273
|
+
return Span(userName);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// More efficient than re-rendering entire user profile
|
|
277
|
+
// when only name is needed
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Best Practices
|
|
281
|
+
|
|
282
|
+
### 1. Use Descriptive Store Names
|
|
283
|
+
|
|
284
|
+
```javascript
|
|
285
|
+
// Good: Clear, descriptive names
|
|
286
|
+
const userAuthStore = Store.create('userAuth', defaultUser);
|
|
287
|
+
const shoppingCartStore = Store.create('shoppingCart', defaultCart);
|
|
288
|
+
const appSettingsStore = Store.create('appSettings', defaultSettings);
|
|
289
|
+
|
|
290
|
+
// Less clear: Generic names
|
|
291
|
+
const store1 = Store.create('data', {});
|
|
292
|
+
const state = Store.create('state', {});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### 2. Initialize with Complete Data Structures
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
// Good: Complete initial structure
|
|
299
|
+
const userStore = Store.create('user', {
|
|
300
|
+
id: null,
|
|
301
|
+
name: '',
|
|
302
|
+
email: '',
|
|
303
|
+
preferences: {
|
|
304
|
+
theme: 'light',
|
|
305
|
+
notifications: true
|
|
306
|
+
},
|
|
307
|
+
isLoggedIn: false
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Problematic: Incomplete structure
|
|
311
|
+
const userStore = Store.create('user', {});
|
|
312
|
+
// Later code might fail when accessing user.preferences.theme
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### 3. Create Service Objects for Complex Operations
|
|
316
|
+
|
|
317
|
+
```javascript
|
|
318
|
+
// Good: Organized actions
|
|
319
|
+
const AuthService= (function() {
|
|
320
|
+
const authUser = Store.create('user', {});
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
login: (credentials) => { /* ... */ },
|
|
324
|
+
logout: () => { /* ... */ },
|
|
325
|
+
updateProfile: (data) => { /* ... */ },
|
|
326
|
+
updatePreferences: (prefs) => { /* ... */ }
|
|
327
|
+
};
|
|
328
|
+
}());
|
|
329
|
+
|
|
330
|
+
// Instead of scattered update logic throughout components
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### 4. Use Store for Cross-Component State Only
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
// Good: Local state for component-specific data
|
|
337
|
+
const ContactForm = () => {
|
|
338
|
+
const name = Observable(''); // Local state
|
|
339
|
+
const email = Observable(''); // Local state
|
|
340
|
+
const user = Store.use('user'); // Global state
|
|
341
|
+
|
|
342
|
+
return Form([/* ... */]);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// Avoid: Putting everything in stores
|
|
346
|
+
// Don't store form input state globally unless shared
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Debugging Stores
|
|
350
|
+
|
|
351
|
+
### Store State Inspection
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
// Log current store state
|
|
355
|
+
console.log('Current user:', Store.get('user').$value);
|
|
356
|
+
|
|
357
|
+
// Monitor store changes
|
|
358
|
+
Store.get('user').subscribe(newUser => {
|
|
359
|
+
console.log('User changed:', newUser);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Check all followers of a store
|
|
363
|
+
const userData = Store.getWithSubscribers('user');
|
|
364
|
+
console.log('Store value:', userData.observer.$value);
|
|
365
|
+
console.log('Followers count:', userData.subscribers.size);
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Common Patterns
|
|
369
|
+
|
|
370
|
+
### Persistent Store
|
|
371
|
+
|
|
372
|
+
```javascript
|
|
373
|
+
const createPersistentStore = (name, defaultValue, storageKey) => {
|
|
374
|
+
// Load from localStorage
|
|
375
|
+
const saved = localStorage.getItem(storageKey);
|
|
376
|
+
const initialValue = saved ? JSON.parse(saved) : defaultValue;
|
|
377
|
+
|
|
378
|
+
const store = Store.create(name, initialValue);
|
|
379
|
+
|
|
380
|
+
// Save changes to localStorage
|
|
381
|
+
store.subscribe(newValue => {
|
|
382
|
+
localStorage.setItem(storageKey, JSON.stringify(newValue));
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
return store;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// Usage
|
|
389
|
+
createPersistentStore('userPreferences', {
|
|
390
|
+
theme: 'light',
|
|
391
|
+
language: 'en'
|
|
392
|
+
}, 'app_preferences');
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Store Composition
|
|
396
|
+
|
|
397
|
+
```javascript
|
|
398
|
+
// Combine multiple stores for complex state
|
|
399
|
+
const createAppState = () => {
|
|
400
|
+
const auth = Store.create('auth', defaultAuth);
|
|
401
|
+
const cart = Store.create('cart', defaultCart);
|
|
402
|
+
const settings = Store.create('settings', defaultSettings);
|
|
403
|
+
|
|
404
|
+
// Computed store that combines others
|
|
405
|
+
const appStatus = Observable.computed(() => {
|
|
406
|
+
return {
|
|
407
|
+
isLoggedIn: auth.$value.user !== null,
|
|
408
|
+
cartItems: cart.$value.items.length,
|
|
409
|
+
theme: settings.$value.theme
|
|
410
|
+
};
|
|
411
|
+
}, [auth, cart, settings]);
|
|
412
|
+
|
|
413
|
+
return { auth, cart, settings, appStatus };
|
|
414
|
+
};
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Next Steps
|
|
418
|
+
|
|
419
|
+
Now that you understand state management, explore these related topics:
|
|
420
|
+
|
|
421
|
+
- **[Lifecycle Events](docs/lifecycle-events.md)** - Lifecycle events
|
|
422
|
+
- **[Memory Management](docs/memory-management.md)** - Memory management
|
|
423
|
+
- **[Anchor](docs/anchor.md)** - Anchor
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Validation
|
|
2
|
+
|
|
3
|
+
NativeDocument provides a comprehensive runtime validation system that helps catch errors early and ensures function arguments meet expected types and constraints.
|
|
4
|
+
|
|
5
|
+
## Function Argument Validation
|
|
6
|
+
|
|
7
|
+
### Using .args() Method
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// Original function
|
|
11
|
+
function createUser(name, age, email) {
|
|
12
|
+
console.log(`Creating user: ${name}, ${age}, ${email}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// .args() returns a function that validates arguments before calling original
|
|
16
|
+
const createUserWithValidation = createUser.args(
|
|
17
|
+
ArgTypes.string('name'),
|
|
18
|
+
ArgTypes.number('age'),
|
|
19
|
+
ArgTypes.string('email')
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Usage
|
|
23
|
+
createUserWithValidation("John", 25, "john@example.com"); // Valid - calls original function
|
|
24
|
+
createUserWithValidation("John", "25", "john@example.com"); // Throws validation error
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### WithValidation Wrapper (Equivalent)
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
// These two approaches are equivalent:
|
|
31
|
+
|
|
32
|
+
// Method 1: Using .args()
|
|
33
|
+
const createUserWithValidation1 = createUser.args(
|
|
34
|
+
ArgTypes.string('name'),
|
|
35
|
+
ArgTypes.number('age'),
|
|
36
|
+
ArgTypes.string('email')
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Method 2: Using withValidation()
|
|
40
|
+
const createUserWithValidation2 = withValidation(createUser, [
|
|
41
|
+
ArgTypes.string('name'),
|
|
42
|
+
ArgTypes.number('age'),
|
|
43
|
+
ArgTypes.string('email')
|
|
44
|
+
], 'createUser');
|
|
45
|
+
|
|
46
|
+
// Both return a function that validates arguments before calling the original
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## ArgTypes Reference
|
|
50
|
+
|
|
51
|
+
### Basic Types
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
ArgTypes.string('name') // Must be string
|
|
55
|
+
ArgTypes.number('age') // Must be number
|
|
56
|
+
ArgTypes.boolean('isActive') // Must be boolean
|
|
57
|
+
ArgTypes.function('callback') // Must be function
|
|
58
|
+
ArgTypes.object('config') // Must be object
|
|
59
|
+
ArgTypes.objectNotNull('data') // Must be object and not null
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### NativeDocument Types
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
ArgTypes.observable('state') // Must be Observable instance
|
|
66
|
+
ArgTypes.element('domNode') // Must be HTML element
|
|
67
|
+
ArgTypes.children('content') // Valid children (elements, strings, numbers, observables)
|
|
68
|
+
ArgTypes.attributes('attrs') // Valid attributes object
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Optional Arguments
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
function greet(name, greeting) {
|
|
75
|
+
return `${greeting || 'Hello'} ${name}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Create function with argument validation
|
|
79
|
+
const greetWithValidation = greet.args(
|
|
80
|
+
ArgTypes.string('name'),
|
|
81
|
+
ArgTypes.optional(ArgTypes.string('greeting'))
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
greetWithValidation("John"); // Valid - greeting is optional
|
|
85
|
+
greetWithValidation("John", "Hi"); // Valid
|
|
86
|
+
greetWithValidation("John", 123); // Error - greeting must be string if provided
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### OneOf Validation
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
function setTheme(theme, config) {
|
|
93
|
+
// Implementation
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Create function with argument validation
|
|
97
|
+
const setThemeWithValidation = setTheme.args(
|
|
98
|
+
ArgTypes.oneOf('theme',
|
|
99
|
+
ArgTypes.string('theme'),
|
|
100
|
+
ArgTypes.object('theme')
|
|
101
|
+
),
|
|
102
|
+
ArgTypes.object('config')
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
setThemeWithValidation("dark", {}); // Valid - string theme
|
|
106
|
+
setThemeWithValidation({name: "custom"}, {}); // Valid - object theme
|
|
107
|
+
setThemeWithValidation(123, {}); // Error - must be string or object
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Error Handling
|
|
111
|
+
|
|
112
|
+
### Validation Errors
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
function processData(items, callback) {
|
|
116
|
+
// Implementation
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Create function with argument validation
|
|
120
|
+
const processDataWithValidation = processData.args(
|
|
121
|
+
ArgTypes.children('items'),
|
|
122
|
+
ArgTypes.function('callback')
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
processDataWithValidation("invalid", "not a function");
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.log(error.message);
|
|
129
|
+
// "Argument validation failed
|
|
130
|
+
// processData: Invalid argument 'items' at position 1, expected children, got String
|
|
131
|
+
// processData: Invalid argument 'callback' at position 2, expected function, got String"
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Error Boundary Pattern
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
// Original function with argument validation
|
|
139
|
+
const createUserWithValidation = createUser.args(
|
|
140
|
+
ArgTypes.string('name'),
|
|
141
|
+
ArgTypes.number('age'),
|
|
142
|
+
ArgTypes.string('email')
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Add error boundary to validation function
|
|
146
|
+
const safeCreateUser = createUserWithValidation.errorBoundary((error) => {
|
|
147
|
+
console.error("User creation failed:", error.message);
|
|
148
|
+
return null; // Return fallback value
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Won't throw, returns null on validation error
|
|
152
|
+
const result = safeCreateUser("John", "invalid age", "email");
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Custom Validation
|
|
156
|
+
|
|
157
|
+
### Creating Custom ArgTypes
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
// Custom validator for email format
|
|
161
|
+
const emailType = (name) => ({
|
|
162
|
+
name,
|
|
163
|
+
type: 'email',
|
|
164
|
+
validate: (value) => {
|
|
165
|
+
return typeof value === 'string' && /\S+@\S+\.\S+/.test(value);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
function registerUser(email, password) {
|
|
170
|
+
// Implementation
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
registerUser.args(
|
|
174
|
+
emailType('email'),
|
|
175
|
+
ArgTypes.string('password')
|
|
176
|
+
);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Best Practices
|
|
180
|
+
|
|
181
|
+
1. **Validate early** - Add validation to public functions and components
|
|
182
|
+
2. **Use descriptive names** - Make error messages clear with good argument names
|
|
183
|
+
3. **Combine with error boundaries** - Handle validation errors gracefully
|
|
184
|
+
4. **Validate complex objects** - Use ArgTypes.objectNotNull for required objects
|
|
185
|
+
5. **Make optional explicit** - Use ArgTypes.optional() for clarity
|
|
186
|
+
6. **Custom validators** - Create reusable validators for domain-specific types
|
|
187
|
+
|
|
188
|
+
## Next Steps
|
|
189
|
+
|
|
190
|
+
- **[Advanced Features](advanced-features.md)** - Error boundaries and debugging tools
|
|
191
|
+
- **[API Reference](api-reference.md)** - Complete ArgTypes and validation API
|
|
192
|
+
- **[Memory Management](memory-management.md)** - Debugging memory issues with validation
|
|
193
|
+
- **[Lifecycle Events](lifecycle-events.md)** - Validate lifecycle callback arguments
|
package/elements.js
CHANGED
package/index.js
CHANGED
|
@@ -2,7 +2,9 @@ export { default as HtmlElementWrapper, ElementCreator } from './src/wrappers/Ht
|
|
|
2
2
|
|
|
3
3
|
import './src/utils/prototypes.js';
|
|
4
4
|
|
|
5
|
+
export * from './src/utils/plugins-manager';
|
|
5
6
|
export * from './src/utils/args-types';
|
|
7
|
+
export * from './src/utils/validator'
|
|
6
8
|
export * from './src/data/Observable';
|
|
7
9
|
export * from './src/data/Store';
|
|
8
10
|
import * as elements from './elements';
|