sigpro 1.0.14 → 1.2.39

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 (109) hide show
  1. package/Readme.md +164 -1008
  2. package/dist/sigpro.editor.js +1 -0
  3. package/dist/sigpro.grid.js +78 -0
  4. package/dist/sigpro.js +1 -0
  5. package/dist/sigpro.ui.css +2 -0
  6. package/dist/sigpro.ui.js +1 -0
  7. package/dist/sigpro.utils.js +1 -0
  8. package/dist/sigpro.vite.js +4 -0
  9. package/package.json +64 -14
  10. package/sigpro.d.ts +395 -0
  11. package/.github/workflows/publish.yml +0 -25
  12. package/bun.lock +0 -385
  13. package/docs/404.html +0 -22
  14. package/docs/api/components.html +0 -595
  15. package/docs/api/effects.html +0 -787
  16. package/docs/api/fetch.html +0 -873
  17. package/docs/api/pages.html +0 -405
  18. package/docs/api/quick.html +0 -217
  19. package/docs/api/routing.html +0 -628
  20. package/docs/api/signals.html +0 -683
  21. package/docs/api/storage.html +0 -820
  22. package/docs/assets/api_components.md.BlFwj17l.js +0 -571
  23. package/docs/assets/api_components.md.BlFwj17l.lean.js +0 -1
  24. package/docs/assets/api_effects.md.Br_yStBS.js +0 -763
  25. package/docs/assets/api_effects.md.Br_yStBS.lean.js +0 -1
  26. package/docs/assets/api_fetch.md.DQLBJSoq.js +0 -849
  27. package/docs/assets/api_fetch.md.DQLBJSoq.lean.js +0 -1
  28. package/docs/assets/api_pages.md.BP19nHXw.js +0 -381
  29. package/docs/assets/api_pages.md.BP19nHXw.lean.js +0 -1
  30. package/docs/assets/api_quick.md.BDS3ttnt.js +0 -193
  31. package/docs/assets/api_quick.md.BDS3ttnt.lean.js +0 -1
  32. package/docs/assets/api_routing.md.7SNAZXtp.js +0 -604
  33. package/docs/assets/api_routing.md.7SNAZXtp.lean.js +0 -1
  34. package/docs/assets/api_signals.md.CrW68-BA.js +0 -659
  35. package/docs/assets/api_signals.md.CrW68-BA.lean.js +0 -1
  36. package/docs/assets/api_storage.md.COEWBXHk.js +0 -796
  37. package/docs/assets/api_storage.md.COEWBXHk.lean.js +0 -1
  38. package/docs/assets/app.DtmzNmNl.js +0 -1
  39. package/docs/assets/chunks/framework.C8AWLET_.js +0 -19
  40. package/docs/assets/chunks/theme.yfWKMLQM.js +0 -1
  41. package/docs/assets/guide_getting-started.md.BeQpK3vd.js +0 -172
  42. package/docs/assets/guide_getting-started.md.BeQpK3vd.lean.js +0 -1
  43. package/docs/assets/guide_why.md.DXchYMN-.js +0 -23
  44. package/docs/assets/guide_why.md.DXchYMN-.lean.js +0 -1
  45. package/docs/assets/index.md.uvMJmU4o.js +0 -1
  46. package/docs/assets/index.md.uvMJmU4o.lean.js +0 -1
  47. package/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  48. package/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  49. package/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  50. package/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  51. package/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  52. package/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  53. package/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  54. package/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  55. package/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  56. package/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  57. package/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  58. package/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  59. package/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  60. package/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  61. package/docs/assets/style.DJRheFKp.css +0 -1
  62. package/docs/assets/ui_intro.md.gZ21GFqo.js +0 -1
  63. package/docs/assets/ui_intro.md.gZ21GFqo.lean.js +0 -1
  64. package/docs/assets/vite_plugin.md.gDWEi8f0.js +0 -225
  65. package/docs/assets/vite_plugin.md.gDWEi8f0.lean.js +0 -1
  66. package/docs/guide/getting-started.html +0 -196
  67. package/docs/guide/why.html +0 -47
  68. package/docs/hashmap.json +0 -1
  69. package/docs/index.html +0 -25
  70. package/docs/logo.svg +0 -118
  71. package/docs/ui/intro.html +0 -25
  72. package/docs/vite/plugin.html +0 -249
  73. package/docs/vp-icons.css +0 -1
  74. package/index.js +0 -3
  75. package/packages/docs/.vitepress/cache/deps/@theme_index.js +0 -275
  76. package/packages/docs/.vitepress/cache/deps/@theme_index.js.map +0 -7
  77. package/packages/docs/.vitepress/cache/deps/_metadata.json +0 -40
  78. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js +0 -12951
  79. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js.map +0 -7
  80. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js +0 -9719
  81. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js.map +0 -7
  82. package/packages/docs/.vitepress/cache/deps/package.json +0 -3
  83. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4505
  84. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
  85. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -583
  86. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
  87. package/packages/docs/.vitepress/cache/deps/vue.js +0 -347
  88. package/packages/docs/.vitepress/cache/deps/vue.js.map +0 -7
  89. package/packages/docs/.vitepress/config.js +0 -68
  90. package/packages/docs/api/components.md +0 -760
  91. package/packages/docs/api/effects.md +0 -1039
  92. package/packages/docs/api/fetch.md +0 -998
  93. package/packages/docs/api/pages.md +0 -497
  94. package/packages/docs/api/quick.md +0 -436
  95. package/packages/docs/api/routing.md +0 -784
  96. package/packages/docs/api/signals.md +0 -899
  97. package/packages/docs/api/storage.md +0 -952
  98. package/packages/docs/guide/getting-started.md +0 -308
  99. package/packages/docs/guide/why.md +0 -135
  100. package/packages/docs/index.md +0 -84
  101. package/packages/docs/logo.svg +0 -118
  102. package/packages/docs/public/logo.svg +0 -118
  103. package/packages/docs/ui/intro.md +0 -16
  104. package/packages/docs/vite/plugin.md +0 -423
  105. package/packages/sigpro/plugin.js +0 -91
  106. package/packages/sigpro/plugin.min.js +0 -1
  107. package/packages/sigpro/sigpro.js +0 -631
  108. package/packages/sigpro/sigpro.min.js +0 -1
  109. package/vite.config.js +0 -24
@@ -1,899 +0,0 @@
1
- # Signals API 📡
2
-
3
- Signals are the heart of SigPro's reactivity system. They are reactive values that automatically track dependencies and notify subscribers when they change. This enables fine-grained updates without virtual DOM diffing.
4
-
5
- ## Core Concepts
6
-
7
- ### What is a Signal?
8
-
9
- A signal is a function that holds a value and notifies dependents when that value changes. Signals can be:
10
-
11
- - **Basic signals** - Hold simple values (numbers, strings, objects)
12
- - **Computed signals** - Derive values from other signals
13
- - **Persistent signals** - Automatically sync with localStorage/sessionStorage
14
-
15
- ### How Reactivity Works
16
-
17
- SigPro uses automatic dependency tracking:
18
-
19
- 1. When you read a signal inside an effect, the effect becomes a subscriber
20
- 2. When the signal's value changes, all subscribers are notified
21
- 3. Updates are batched using microtasks for optimal performance
22
- 4. Only the exact nodes that depend on changed values are updated
23
-
24
- ## `$(initialValue)`
25
-
26
- Creates a reactive signal. The behavior changes based on the type of `initialValue`:
27
-
28
- - If `initialValue` is a **function**, creates a computed signal
29
- - Otherwise, creates a basic signal
30
-
31
- ```javascript
32
- import { $ } from 'sigpro';
33
-
34
- // Basic signal
35
- const count = $(0);
36
-
37
- // Computed signal
38
- const firstName = $('John');
39
- const lastName = $('Doe');
40
- const fullName = $(() => `${firstName()} ${lastName()}`);
41
- ```
42
-
43
- ## 📋 API Reference
44
-
45
- ### Basic Signals
46
-
47
- | Pattern | Example | Description |
48
- |---------|---------|-------------|
49
- | Create | `const count = $(0)` | Create signal with initial value |
50
- | Get | `count()` | Read current value |
51
- | Set | `count(5)` | Set new value directly |
52
- | Update | `count(prev => prev + 1)` | Update based on previous value |
53
-
54
- ### Computed Signals
55
-
56
- | Pattern | Example | Description |
57
- |---------|---------|-------------|
58
- | Create | `const total = $(() => price() * quantity())` | Derive value from other signals |
59
- | Get | `total()` | Read computed value (auto-updates) |
60
-
61
- ### Signal Methods
62
-
63
- | Method | Description | Example |
64
- |--------|-------------|---------|
65
- | `signal()` | Gets current value | `count()` |
66
- | `signal(newValue)` | Sets new value | `count(5)` |
67
- | `signal(prev => new)` | Updates using previous value | `count(c => c + 1)` |
68
-
69
- ## 🎯 Basic Examples
70
-
71
- ### Counter Signal
72
-
73
- ```javascript
74
- import { $ } from 'sigpro';
75
-
76
- const count = $(0);
77
-
78
- console.log(count()); // 0
79
-
80
- count(5);
81
- console.log(count()); // 5
82
-
83
- count(prev => prev + 1);
84
- console.log(count()); // 6
85
- ```
86
-
87
- ### Object Signal
88
-
89
- ```javascript
90
- import { $ } from 'sigpro';
91
-
92
- const user = $({
93
- name: 'John',
94
- age: 30,
95
- email: 'john@example.com'
96
- });
97
-
98
- // Read
99
- console.log(user().name); // 'John'
100
-
101
- // Update (immutable pattern)
102
- user({
103
- ...user(),
104
- age: 31
105
- });
106
-
107
- // Partial update with function
108
- user(prev => ({
109
- ...prev,
110
- email: 'john.doe@example.com'
111
- }));
112
- ```
113
-
114
- ### Array Signal
115
-
116
- ```javascript
117
- import { $ } from 'sigpro';
118
-
119
- const todos = $(['Learn SigPro', 'Build an app']);
120
-
121
- // Add item
122
- todos([...todos(), 'Deploy to production']);
123
-
124
- // Remove item
125
- todos(todos().filter((_, i) => i !== 1));
126
-
127
- // Update item
128
- todos(todos().map((todo, i) =>
129
- i === 0 ? 'Master SigPro' : todo
130
- ));
131
- ```
132
-
133
- ## 🔄 Computed Signals
134
-
135
- Computed signals automatically update when their dependencies change:
136
-
137
- ```javascript
138
- import { $ } from 'sigpro';
139
-
140
- const price = $(10);
141
- const quantity = $(2);
142
- const tax = $(0.21);
143
-
144
- // Computed signals
145
- const subtotal = $(() => price() * quantity());
146
- const taxAmount = $(() => subtotal() * tax());
147
- const total = $(() => subtotal() + taxAmount());
148
-
149
- console.log(total()); // 24.2
150
-
151
- price(15);
152
- console.log(total()); // 36.3 (automatically updated)
153
-
154
- quantity(3);
155
- console.log(total()); // 54.45 (automatically updated)
156
- ```
157
-
158
- ### Computed with Multiple Dependencies
159
-
160
- ```javascript
161
- import { $ } from 'sigpro';
162
-
163
- const firstName = $('John');
164
- const lastName = $('Doe');
165
- const prefix = $('Mr.');
166
-
167
- const fullName = $(() => {
168
- // Computed signals can contain logic
169
- const name = `${firstName()} ${lastName()}`;
170
- return prefix() ? `${prefix()} ${name}` : name;
171
- });
172
-
173
- console.log(fullName()); // 'Mr. John Doe'
174
-
175
- prefix('');
176
- console.log(fullName()); // 'John Doe'
177
- ```
178
-
179
- ### Computed with Conditional Logic
180
-
181
- ```javascript
182
- import { $ } from 'sigpro';
183
-
184
- const user = $({ role: 'admin', permissions: [] });
185
- const isAdmin = $(() => user().role === 'admin');
186
- const hasPermission = $(() =>
187
- isAdmin() || user().permissions.includes('edit')
188
- );
189
-
190
- console.log(hasPermission()); // true
191
-
192
- user({ role: 'user', permissions: ['view'] });
193
- console.log(hasPermission()); // false (can't edit)
194
-
195
- user({ role: 'user', permissions: ['view', 'edit'] });
196
- console.log(hasPermission()); // true (now has permission)
197
- ```
198
-
199
- ## 🧮 Advanced Signal Patterns
200
-
201
- ### Derived State Pattern
202
-
203
- ```javascript
204
- import { $ } from 'sigpro';
205
-
206
- // Shopping cart example
207
- const cart = $([
208
- { id: 1, name: 'Product 1', price: 10, quantity: 2 },
209
- { id: 2, name: 'Product 2', price: 15, quantity: 1 },
210
- ]);
211
-
212
- // Derived values
213
- const itemCount = $(() =>
214
- cart().reduce((sum, item) => sum + item.quantity, 0)
215
- );
216
-
217
- const subtotal = $(() =>
218
- cart().reduce((sum, item) => sum + (item.price * item.quantity), 0)
219
- );
220
-
221
- const tax = $(() => subtotal() * 0.21);
222
- const total = $(() => subtotal() + tax());
223
-
224
- // Update cart
225
- cart([
226
- ...cart(),
227
- { id: 3, name: 'Product 3', price: 20, quantity: 1 }
228
- ]);
229
-
230
- // All derived values auto-update
231
- console.log(itemCount()); // 4
232
- console.log(total()); // (10*2 + 15*1 + 20*1) * 1.21 = 78.65
233
- ```
234
-
235
- ### Validation Pattern
236
-
237
- ```javascript
238
- import { $ } from 'sigpro';
239
-
240
- const email = $('');
241
- const password = $('');
242
- const confirmPassword = $('');
243
-
244
- // Validation signals
245
- const isEmailValid = $(() => {
246
- const value = email();
247
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
248
- });
249
-
250
- const isPasswordValid = $(() => {
251
- const value = password();
252
- return value.length >= 8;
253
- });
254
-
255
- const doPasswordsMatch = $(() =>
256
- password() === confirmPassword()
257
- );
258
-
259
- const isFormValid = $(() =>
260
- isEmailValid() && isPasswordValid() && doPasswordsMatch()
261
- );
262
-
263
- // Update form
264
- email('user@example.com');
265
- password('secure123');
266
- confirmPassword('secure123');
267
-
268
- console.log(isFormValid()); // true
269
-
270
- // Validation messages
271
- const emailError = $(() =>
272
- email() && !isEmailValid() ? 'Invalid email format' : ''
273
- );
274
- ```
275
-
276
- ### Filtering and Search Pattern
277
-
278
- ```javascript
279
- import { $ } from 'sigpro';
280
-
281
- const items = $([
282
- { id: 1, name: 'Apple', category: 'fruit' },
283
- { id: 2, name: 'Banana', category: 'fruit' },
284
- { id: 3, name: 'Carrot', category: 'vegetable' },
285
- { id: 4, name: 'Date', category: 'fruit' },
286
- ]);
287
-
288
- const searchTerm = $('');
289
- const categoryFilter = $('all');
290
-
291
- // Filtered items (computed)
292
- const filteredItems = $(() => {
293
- let result = items();
294
-
295
- // Apply search filter
296
- if (searchTerm()) {
297
- const term = searchTerm().toLowerCase();
298
- result = result.filter(item =>
299
- item.name.toLowerCase().includes(term)
300
- );
301
- }
302
-
303
- // Apply category filter
304
- if (categoryFilter() !== 'all') {
305
- result = result.filter(item =>
306
- item.category === categoryFilter()
307
- );
308
- }
309
-
310
- return result;
311
- });
312
-
313
- // Stats
314
- const fruitCount = $(() =>
315
- items().filter(item => item.category === 'fruit').length
316
- );
317
-
318
- const vegCount = $(() =>
319
- items().filter(item => item.category === 'vegetable').length
320
- );
321
-
322
- // Update filters
323
- searchTerm('a');
324
- console.log(filteredItems().map(i => i.name)); // ['Apple', 'Banana', 'Carrot', 'Date']
325
-
326
- categoryFilter('fruit');
327
- console.log(filteredItems().map(i => i.name)); // ['Apple', 'Banana', 'Date']
328
- ```
329
-
330
- ### Pagination Pattern
331
-
332
- ```javascript
333
- import { $ } from 'sigpro';
334
-
335
- const allItems = $([...Array(100).keys()].map(i => `Item ${i + 1}`));
336
- const currentPage = $(1);
337
- const itemsPerPage = $(10);
338
-
339
- // Paginated items (computed)
340
- const paginatedItems = $(() => {
341
- const start = (currentPage() - 1) * itemsPerPage();
342
- const end = start + itemsPerPage();
343
- return allItems().slice(start, end);
344
- });
345
-
346
- // Pagination metadata
347
- const totalPages = $(() =>
348
- Math.ceil(allItems().length / itemsPerPage())
349
- );
350
-
351
- const hasNextPage = $(() =>
352
- currentPage() < totalPages()
353
- );
354
-
355
- const hasPrevPage = $(() =>
356
- currentPage() > 1
357
- );
358
-
359
- const pageRange = $(() => {
360
- const current = currentPage();
361
- const total = totalPages();
362
- const delta = 2;
363
-
364
- let range = [];
365
- for (let i = Math.max(2, current - delta);
366
- i <= Math.min(total - 1, current + delta);
367
- i++) {
368
- range.push(i);
369
- }
370
-
371
- if (current - delta > 2) range = ['...', ...range];
372
- if (current + delta < total - 1) range = [...range, '...'];
373
-
374
- return [1, ...range, total];
375
- });
376
-
377
- // Navigation
378
- const nextPage = () => {
379
- if (hasNextPage()) currentPage(c => c + 1);
380
- };
381
-
382
- const prevPage = () => {
383
- if (hasPrevPage()) currentPage(c => c - 1);
384
- };
385
-
386
- const goToPage = (page) => {
387
- if (page >= 1 && page <= totalPages()) {
388
- currentPage(page);
389
- }
390
- };
391
- ```
392
-
393
- ## 🔧 Advanced Signal Features
394
-
395
- ### Signal Equality Comparison
396
-
397
- Signals use `Object.is` for change detection. Only notify subscribers when values are actually different:
398
-
399
- ```javascript
400
- import { $ } from 'sigpro';
401
-
402
- const count = $(0);
403
-
404
- // These won't trigger updates:
405
- count(0); // Same value
406
- count(prev => prev); // Returns same value
407
-
408
- // These will trigger updates:
409
- count(1); // Different value
410
- count(prev => prev + 0); // Still 0? Actually returns 0? Wait...
411
- // Be careful with functional updates!
412
- ```
413
-
414
- ### Batch Updates
415
-
416
- Multiple signal updates are batched into a single microtask:
417
-
418
- ```javascript
419
- import { $ } from 'sigpro';
420
-
421
- const firstName = $('John');
422
- const lastName = $('Doe');
423
- const fullName = $(() => `${firstName()} ${lastName()}`);
424
-
425
- $.effect(() => {
426
- console.log('Full name:', fullName());
427
- });
428
- // Logs: 'Full name: John Doe'
429
-
430
- // Multiple updates in same tick - only one effect run!
431
- firstName('Jane');
432
- lastName('Smith');
433
- // Only logs once: 'Full name: Jane Smith'
434
- ```
435
-
436
- ### Infinite Loop Protection
437
-
438
- SigPro includes protection against infinite reactive loops:
439
-
440
- ```javascript
441
- import { $ } from 'sigpro';
442
-
443
- const a = $(1);
444
- const b = $(2);
445
-
446
- // This would create a loop, but SigPro prevents it
447
- $.effect(() => {
448
- a(b()); // Reading b
449
- b(a()); // Reading a - loop detected!
450
- });
451
- // Throws: "SigPro: Infinite reactive loop detected."
452
- ```
453
-
454
- ## 📊 Performance Characteristics
455
-
456
- | Operation | Complexity | Notes |
457
- |-----------|------------|-------|
458
- | Signal read | O(1) | Direct value access |
459
- | Signal write | O(n) | n = number of subscribers |
460
- | Computed read | O(1) or O(m) | m = computation complexity |
461
- | Effect run | O(s) | s = number of signal reads |
462
-
463
- ## 🎯 Best Practices
464
-
465
- ### 1. Keep Signals Focused
466
-
467
- ```javascript
468
- // ❌ Avoid large monolithic signals
469
- const state = $({
470
- user: null,
471
- posts: [],
472
- theme: 'light',
473
- notifications: []
474
- });
475
-
476
- // ✅ Split into focused signals
477
- const user = $(null);
478
- const posts = $([]);
479
- const theme = $('light');
480
- const notifications = $([]);
481
- ```
482
-
483
- ### 2. Use Computed for Derived State
484
-
485
- ```javascript
486
- // ❌ Don't compute in templates/effects
487
- $.effect(() => {
488
- const total = items().reduce((sum, i) => sum + i.price, 0);
489
- updateUI(total);
490
- });
491
-
492
- // ✅ Compute with signals
493
- const total = $(() => items().reduce((sum, i) => sum + i.price, 0));
494
- $.effect(() => updateUI(total()));
495
- ```
496
-
497
- ### 3. Immutable Updates
498
-
499
- ```javascript
500
- // ❌ Don't mutate objects/arrays
501
- const user = $({ name: 'John' });
502
- user().name = 'Jane'; // Won't trigger updates!
503
-
504
- // ✅ Create new objects/arrays
505
- user({ ...user(), name: 'Jane' });
506
-
507
- // ❌ Don't mutate arrays
508
- const todos = $(['a', 'b']);
509
- todos().push('c'); // Won't trigger updates!
510
-
511
- // ✅ Create new arrays
512
- todos([...todos(), 'c']);
513
- ```
514
-
515
- ### 4. Functional Updates for Dependencies
516
-
517
- ```javascript
518
- // ❌ Avoid if new value depends on current
519
- count(count() + 1);
520
-
521
- // ✅ Use functional update
522
- count(prev => prev + 1);
523
- ```
524
-
525
- ### 5. Clean Up Effects
526
-
527
- ```javascript
528
- import { $ } from 'sigpro';
529
-
530
- const userId = $(1);
531
-
532
- // Effects auto-clean in pages, but you can stop manually
533
- const stop = $.effect(() => {
534
- fetchUser(userId());
535
- });
536
-
537
- // Later, if needed
538
- stop();
539
- ```
540
-
541
- ## 🚀 Real-World Examples
542
-
543
- ### Form State Management
544
-
545
- ```javascript
546
- import { $ } from 'sigpro';
547
-
548
- // Form state
549
- const formData = $({
550
- username: '',
551
- email: '',
552
- age: '',
553
- newsletter: false
554
- });
555
-
556
- // Touched fields (for validation UI)
557
- const touched = $({
558
- username: false,
559
- email: false,
560
- age: false
561
- });
562
-
563
- // Validation rules
564
- const validations = {
565
- username: (value) =>
566
- value.length >= 3 ? null : 'Username must be at least 3 characters',
567
- email: (value) =>
568
- /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? null : 'Invalid email',
569
- age: (value) =>
570
- !value || (value >= 18 && value <= 120) ? null : 'Age must be 18-120'
571
- };
572
-
573
- // Validation signals
574
- const errors = $(() => {
575
- const data = formData();
576
- const result = {};
577
-
578
- Object.keys(validations).forEach(field => {
579
- const error = validations[field](data[field]);
580
- if (error) result[field] = error;
581
- });
582
-
583
- return result;
584
- });
585
-
586
- const isValid = $(() => Object.keys(errors()).length === 0);
587
-
588
- // Field helpers
589
- const fieldProps = (field) => ({
590
- value: formData()[field],
591
- error: touched()[field] ? errors()[field] : null,
592
- onChange: (e) => {
593
- const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
594
- formData({
595
- ...formData(),
596
- [field]: value
597
- });
598
- },
599
- onBlur: () => {
600
- touched({
601
- ...touched(),
602
- [field]: true
603
- });
604
- }
605
- });
606
-
607
- // Form submission
608
- const submitAttempts = $(0);
609
- const isSubmitting = $(false);
610
-
611
- const handleSubmit = async () => {
612
- submitAttempts(s => s + 1);
613
-
614
- if (!isValid()) {
615
- // Mark all fields as touched to show errors
616
- touched(Object.keys(formData()).reduce((acc, field) => ({
617
- ...acc,
618
- [field]: true
619
- }), {}));
620
- return;
621
- }
622
-
623
- isSubmitting(true);
624
- try {
625
- await saveForm(formData());
626
- // Reset form on success
627
- formData({ username: '', email: '', age: '', newsletter: false });
628
- touched({ username: false, email: false, age: false });
629
- } finally {
630
- isSubmitting(false);
631
- }
632
- };
633
- ```
634
-
635
- ### Todo App with Filters
636
-
637
- ```javascript
638
- import { $ } from 'sigpro';
639
-
640
- // State
641
- const todos = $([
642
- { id: 1, text: 'Learn SigPro', completed: true },
643
- { id: 2, text: 'Build an app', completed: false },
644
- { id: 3, text: 'Write docs', completed: false }
645
- ]);
646
-
647
- const filter = $('all'); // 'all', 'active', 'completed'
648
- const newTodoText = $('');
649
-
650
- // Computed values
651
- const filteredTodos = $(() => {
652
- const all = todos();
653
-
654
- switch(filter()) {
655
- case 'active':
656
- return all.filter(t => !t.completed);
657
- case 'completed':
658
- return all.filter(t => t.completed);
659
- default:
660
- return all;
661
- }
662
- });
663
-
664
- const activeCount = $(() =>
665
- todos().filter(t => !t.completed).length
666
- );
667
-
668
- const completedCount = $(() =>
669
- todos().filter(t => t.completed).length
670
- );
671
-
672
- const hasCompleted = $(() => completedCount() > 0);
673
-
674
- // Actions
675
- const addTodo = () => {
676
- const text = newTodoText().trim();
677
- if (text) {
678
- todos([
679
- ...todos(),
680
- {
681
- id: Date.now(),
682
- text,
683
- completed: false
684
- }
685
- ]);
686
- newTodoText('');
687
- }
688
- };
689
-
690
- const toggleTodo = (id) => {
691
- todos(todos().map(todo =>
692
- todo.id === id
693
- ? { ...todo, completed: !todo.completed }
694
- : todo
695
- ));
696
- };
697
-
698
- const deleteTodo = (id) => {
699
- todos(todos().filter(todo => todo.id !== id));
700
- };
701
-
702
- const clearCompleted = () => {
703
- todos(todos().filter(todo => !todo.completed));
704
- };
705
-
706
- const toggleAll = () => {
707
- const allCompleted = activeCount() === 0;
708
- todos(todos().map(todo => ({
709
- ...todo,
710
- completed: !allCompleted
711
- })));
712
- };
713
- ```
714
-
715
- ### Shopping Cart
716
-
717
- ```javascript
718
- import { $ } from 'sigpro';
719
-
720
- // Products catalog
721
- const products = $([
722
- { id: 1, name: 'Laptop', price: 999, stock: 5 },
723
- { id: 2, name: 'Mouse', price: 29, stock: 20 },
724
- { id: 3, name: 'Keyboard', price: 79, stock: 10 },
725
- { id: 4, name: 'Monitor', price: 299, stock: 3 }
726
- ]);
727
-
728
- // Cart state
729
- const cart = $({});
730
- const selectedProduct = $(null);
731
- const quantity = $(1);
732
-
733
- // Computed cart values
734
- const cartItems = $(() => {
735
- const items = [];
736
- Object.entries(cart()).forEach(([productId, qty]) => {
737
- const product = products().find(p => p.id === parseInt(productId));
738
- if (product) {
739
- items.push({
740
- ...product,
741
- quantity: qty,
742
- subtotal: product.price * qty
743
- });
744
- }
745
- });
746
- return items;
747
- });
748
-
749
- const itemCount = $(() =>
750
- cartItems().reduce((sum, item) => sum + item.quantity, 0)
751
- );
752
-
753
- const subtotal = $(() =>
754
- cartItems().reduce((sum, item) => sum + item.subtotal, 0)
755
- );
756
-
757
- const tax = $(() => subtotal() * 0.10);
758
- const shipping = $(() => subtotal() > 100 ? 0 : 10);
759
- const total = $(() => subtotal() + tax() + shipping());
760
-
761
- const isCartEmpty = $(() => itemCount() === 0);
762
-
763
- // Cart actions
764
- const addToCart = (product, qty = 1) => {
765
- const currentQty = cart()[product.id] || 0;
766
- const newQty = currentQty + qty;
767
-
768
- if (newQty <= product.stock) {
769
- cart({
770
- ...cart(),
771
- [product.id]: newQty
772
- });
773
- return true;
774
- }
775
- return false;
776
- };
777
-
778
- const updateQuantity = (productId, newQty) => {
779
- const product = products().find(p => p.id === productId);
780
- if (newQty <= product.stock) {
781
- if (newQty <= 0) {
782
- removeFromCart(productId);
783
- } else {
784
- cart({
785
- ...cart(),
786
- [productId]: newQty
787
- });
788
- }
789
- }
790
- };
791
-
792
- const removeFromCart = (productId) => {
793
- const newCart = { ...cart() };
794
- delete newCart[productId];
795
- cart(newCart);
796
- };
797
-
798
- const clearCart = () => cart({});
799
-
800
- // Stock management
801
- const productStock = (productId) => {
802
- const product = products().find(p => p.id === productId);
803
- if (!product) return 0;
804
- const inCart = cart()[productId] || 0;
805
- return product.stock - inCart;
806
- };
807
-
808
- const isInStock = (productId, qty = 1) => {
809
- return productStock(productId) >= qty;
810
- };
811
- ```
812
-
813
- ## 📈 Debugging Signals
814
-
815
- ### Logging Signal Changes
816
-
817
- ```javascript
818
- import { $ } from 'sigpro';
819
-
820
- // Wrap a signal to log changes
821
- const withLogging = (signal, name) => {
822
- return (...args) => {
823
- if (args.length) {
824
- const oldValue = signal();
825
- const result = signal(...args);
826
- console.log(`${name}:`, oldValue, '->', signal());
827
- return result;
828
- }
829
- return signal();
830
- };
831
- };
832
-
833
- // Usage
834
- const count = withLogging($(0), 'count');
835
- count(5); // Logs: "count: 0 -> 5"
836
- ```
837
-
838
- ### Signal Inspector
839
-
840
- ```javascript
841
- import { $ } from 'sigpro';
842
-
843
- // Create an inspectable signal
844
- const createInspector = () => {
845
- const signals = new Map();
846
-
847
- const createSignal = (initialValue, name) => {
848
- const signal = $(initialValue);
849
- signals.set(signal, { name, subscribers: new Set() });
850
-
851
- // Wrap to track subscribers
852
- const wrapped = (...args) => {
853
- if (!args.length && activeEffect) {
854
- const info = signals.get(wrapped);
855
- info.subscribers.add(activeEffect);
856
- }
857
- return signal(...args);
858
- };
859
-
860
- return wrapped;
861
- };
862
-
863
- const getInfo = () => {
864
- const info = {};
865
- signals.forEach((data, signal) => {
866
- info[data.name] = {
867
- subscribers: data.subscribers.size,
868
- value: signal()
869
- };
870
- });
871
- return info;
872
- };
873
-
874
- return { createSignal, getInfo };
875
- };
876
-
877
- // Usage
878
- const inspector = createInspector();
879
- const count = inspector.createSignal(0, 'count');
880
- const doubled = inspector.createSignal(() => count() * 2, 'doubled');
881
-
882
- console.log(inspector.getInfo());
883
- // { count: { subscribers: 0, value: 0 }, doubled: { subscribers: 0, value: 0 } }
884
- ```
885
-
886
- ## 📊 Summary
887
-
888
- | Feature | Description |
889
- |---------|-------------|
890
- | **Basic Signals** | Hold values and notify on change |
891
- | **Computed Signals** | Auto-updating derived values |
892
- | **Automatic Tracking** | Dependencies tracked automatically |
893
- | **Batch Updates** | Multiple updates batched in microtask |
894
- | **Infinite Loop Protection** | Prevents reactive cycles |
895
- | **Zero Dependencies** | Pure vanilla JavaScript |
896
-
897
- ---
898
-
899
- > **Pro Tip:** Signals are the foundation of reactivity in SigPro. Master them, and you've mastered 80% of the library!