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.
- package/Readme.md +164 -1008
- package/dist/sigpro.editor.js +1 -0
- package/dist/sigpro.grid.js +78 -0
- package/dist/sigpro.js +1 -0
- package/dist/sigpro.ui.css +2 -0
- package/dist/sigpro.ui.js +1 -0
- package/dist/sigpro.utils.js +1 -0
- package/dist/sigpro.vite.js +4 -0
- package/package.json +64 -14
- package/sigpro.d.ts +395 -0
- package/.github/workflows/publish.yml +0 -25
- package/bun.lock +0 -385
- package/docs/404.html +0 -22
- package/docs/api/components.html +0 -595
- package/docs/api/effects.html +0 -787
- package/docs/api/fetch.html +0 -873
- package/docs/api/pages.html +0 -405
- package/docs/api/quick.html +0 -217
- package/docs/api/routing.html +0 -628
- package/docs/api/signals.html +0 -683
- package/docs/api/storage.html +0 -820
- package/docs/assets/api_components.md.BlFwj17l.js +0 -571
- package/docs/assets/api_components.md.BlFwj17l.lean.js +0 -1
- package/docs/assets/api_effects.md.Br_yStBS.js +0 -763
- package/docs/assets/api_effects.md.Br_yStBS.lean.js +0 -1
- package/docs/assets/api_fetch.md.DQLBJSoq.js +0 -849
- package/docs/assets/api_fetch.md.DQLBJSoq.lean.js +0 -1
- package/docs/assets/api_pages.md.BP19nHXw.js +0 -381
- package/docs/assets/api_pages.md.BP19nHXw.lean.js +0 -1
- package/docs/assets/api_quick.md.BDS3ttnt.js +0 -193
- package/docs/assets/api_quick.md.BDS3ttnt.lean.js +0 -1
- package/docs/assets/api_routing.md.7SNAZXtp.js +0 -604
- package/docs/assets/api_routing.md.7SNAZXtp.lean.js +0 -1
- package/docs/assets/api_signals.md.CrW68-BA.js +0 -659
- package/docs/assets/api_signals.md.CrW68-BA.lean.js +0 -1
- package/docs/assets/api_storage.md.COEWBXHk.js +0 -796
- package/docs/assets/api_storage.md.COEWBXHk.lean.js +0 -1
- package/docs/assets/app.DtmzNmNl.js +0 -1
- package/docs/assets/chunks/framework.C8AWLET_.js +0 -19
- package/docs/assets/chunks/theme.yfWKMLQM.js +0 -1
- package/docs/assets/guide_getting-started.md.BeQpK3vd.js +0 -172
- package/docs/assets/guide_getting-started.md.BeQpK3vd.lean.js +0 -1
- package/docs/assets/guide_why.md.DXchYMN-.js +0 -23
- package/docs/assets/guide_why.md.DXchYMN-.lean.js +0 -1
- package/docs/assets/index.md.uvMJmU4o.js +0 -1
- package/docs/assets/index.md.uvMJmU4o.lean.js +0 -1
- package/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/docs/assets/style.DJRheFKp.css +0 -1
- package/docs/assets/ui_intro.md.gZ21GFqo.js +0 -1
- package/docs/assets/ui_intro.md.gZ21GFqo.lean.js +0 -1
- package/docs/assets/vite_plugin.md.gDWEi8f0.js +0 -225
- package/docs/assets/vite_plugin.md.gDWEi8f0.lean.js +0 -1
- package/docs/guide/getting-started.html +0 -196
- package/docs/guide/why.html +0 -47
- package/docs/hashmap.json +0 -1
- package/docs/index.html +0 -25
- package/docs/logo.svg +0 -118
- package/docs/ui/intro.html +0 -25
- package/docs/vite/plugin.html +0 -249
- package/docs/vp-icons.css +0 -1
- package/index.js +0 -3
- package/packages/docs/.vitepress/cache/deps/@theme_index.js +0 -275
- package/packages/docs/.vitepress/cache/deps/@theme_index.js.map +0 -7
- package/packages/docs/.vitepress/cache/deps/_metadata.json +0 -40
- package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js +0 -12951
- package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js.map +0 -7
- package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js +0 -9719
- package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js.map +0 -7
- package/packages/docs/.vitepress/cache/deps/package.json +0 -3
- package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4505
- package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
- package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -583
- package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
- package/packages/docs/.vitepress/cache/deps/vue.js +0 -347
- package/packages/docs/.vitepress/cache/deps/vue.js.map +0 -7
- package/packages/docs/.vitepress/config.js +0 -68
- package/packages/docs/api/components.md +0 -760
- package/packages/docs/api/effects.md +0 -1039
- package/packages/docs/api/fetch.md +0 -998
- package/packages/docs/api/pages.md +0 -497
- package/packages/docs/api/quick.md +0 -436
- package/packages/docs/api/routing.md +0 -784
- package/packages/docs/api/signals.md +0 -899
- package/packages/docs/api/storage.md +0 -952
- package/packages/docs/guide/getting-started.md +0 -308
- package/packages/docs/guide/why.md +0 -135
- package/packages/docs/index.md +0 -84
- package/packages/docs/logo.svg +0 -118
- package/packages/docs/public/logo.svg +0 -118
- package/packages/docs/ui/intro.md +0 -16
- package/packages/docs/vite/plugin.md +0 -423
- package/packages/sigpro/plugin.js +0 -91
- package/packages/sigpro/plugin.min.js +0 -1
- package/packages/sigpro/sigpro.js +0 -631
- package/packages/sigpro/sigpro.min.js +0 -1
- 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!
|