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,952 +0,0 @@
1
- # Storage API 💾
2
-
3
- SigPro provides persistent signals that automatically synchronize with browser storage APIs. This allows you to create reactive state that survives page reloads and browser sessions with zero additional code.
4
-
5
- ## Core Concepts
6
-
7
- ### What is Persistent Storage?
8
-
9
- Persistent signals are special signals that:
10
- - **Initialize from storage** (localStorage/sessionStorage) if a saved value exists
11
- - **Auto-save** whenever the signal value changes
12
- - **Handle JSON serialization** automatically
13
- - **Clean up** when set to `null` or `undefined`
14
-
15
- ### Storage Types
16
-
17
- | Storage | Persistence | Use Case |
18
- |---------|-------------|----------|
19
- | `localStorage` | Forever (until cleared) | User preferences, themes, saved data |
20
- | `sessionStorage` | Until tab/window closes | Form drafts, temporary state |
21
-
22
- ## `$.storage(key, initialValue, [storage])`
23
-
24
- Creates a persistent signal that syncs with browser storage.
25
-
26
- ```javascript
27
- import { $ } from 'sigpro';
28
-
29
- // localStorage (default)
30
- const theme = $.storage('theme', 'light');
31
- const user = $.storage('user', null);
32
- const settings = $.storage('settings', { notifications: true });
33
-
34
- // sessionStorage
35
- const draft = $.storage('draft', '', sessionStorage);
36
- const formData = $.storage('form', {}, sessionStorage);
37
- ```
38
-
39
- ## 📋 API Reference
40
-
41
- ### Parameters
42
-
43
- | Parameter | Type | Default | Description |
44
- |-----------|------|---------|-------------|
45
- | `key` | `string` | required | Storage key name |
46
- | `initialValue` | `any` | required | Default value if none stored |
47
- | `storage` | `Storage` | `localStorage` | Storage type (`localStorage` or `sessionStorage`) |
48
-
49
- ### Returns
50
-
51
- | Return | Description |
52
- |--------|-------------|
53
- | `Function` | Signal function (getter/setter) with persistence |
54
-
55
- ## 🎯 Basic Examples
56
-
57
- ### Theme Preference
58
-
59
- ```javascript
60
- import { $, html } from 'sigpro';
61
-
62
- // Persistent theme signal
63
- const theme = $.storage('theme', 'light');
64
-
65
- // Apply theme to document
66
- $.effect(() => {
67
- document.body.className = `theme-${theme()}`;
68
- });
69
-
70
- // Toggle theme
71
- const toggleTheme = () => {
72
- theme(t => t === 'light' ? 'dark' : 'light');
73
- };
74
-
75
- // Template
76
- html`
77
- <div>
78
- <p>Current theme: ${theme}</p>
79
- <button @click=${toggleTheme}>
80
- Toggle Theme
81
- </button>
82
- </div>
83
- `;
84
- ```
85
-
86
- ### User Preferences
87
-
88
- ```javascript
89
- import { $ } from 'sigpro';
90
-
91
- // Complex preferences object
92
- const preferences = $.storage('preferences', {
93
- language: 'en',
94
- fontSize: 'medium',
95
- notifications: true,
96
- compactView: false,
97
- sidebarOpen: true
98
- });
99
-
100
- // Update single preference
101
- const setPreference = (key, value) => {
102
- preferences({
103
- ...preferences(),
104
- [key]: value
105
- });
106
- };
107
-
108
- // Usage
109
- setPreference('language', 'es');
110
- setPreference('fontSize', 'large');
111
- console.log(preferences().language); // 'es'
112
- ```
113
-
114
- ### Form Draft
115
-
116
- ```javascript
117
- import { $, html } from 'sigpro';
118
-
119
- // Session-based draft (clears when tab closes)
120
- const draft = $.storage('contact-form', {
121
- name: '',
122
- email: '',
123
- message: ''
124
- }, sessionStorage);
125
-
126
- // Auto-save on input
127
- const handleInput = (field, value) => {
128
- draft({
129
- ...draft(),
130
- [field]: value
131
- });
132
- };
133
-
134
- // Clear draft after submit
135
- const handleSubmit = async () => {
136
- await submitForm(draft());
137
- draft(null); // Clears from storage
138
- };
139
-
140
- // Template
141
- html`
142
- <form @submit=${handleSubmit}>
143
- <input
144
- type="text"
145
- :value=${() => draft().name}
146
- @input=${(e) => handleInput('name', e.target.value)}
147
- placeholder="Name"
148
- />
149
- <input
150
- type="email"
151
- :value=${() => draft().email}
152
- @input=${(e) => handleInput('email', e.target.value)}
153
- placeholder="Email"
154
- />
155
- <textarea
156
- :value=${() => draft().message}
157
- @input=${(e) => handleInput('message', e.target.value)}
158
- placeholder="Message"
159
- ></textarea>
160
- <button type="submit">Send</button>
161
- </form>
162
- `;
163
- ```
164
-
165
- ## 🚀 Advanced Examples
166
-
167
- ### Authentication State
168
-
169
- ```javascript
170
- import { $, html } from 'sigpro';
171
-
172
- // Persistent auth state
173
- const auth = $.storage('auth', {
174
- token: null,
175
- user: null,
176
- expiresAt: null
177
- });
178
-
179
- // Computed helpers
180
- const isAuthenticated = $(() => {
181
- const { token, expiresAt } = auth();
182
- if (!token || !expiresAt) return false;
183
- return new Date(expiresAt) > new Date();
184
- });
185
-
186
- const user = $(() => auth().user);
187
-
188
- // Login function
189
- const login = async (email, password) => {
190
- const response = await fetch('/api/login', {
191
- method: 'POST',
192
- body: JSON.stringify({ email, password })
193
- });
194
-
195
- if (response.ok) {
196
- const { token, user, expiresIn } = await response.json();
197
- auth({
198
- token,
199
- user,
200
- expiresAt: new Date(Date.now() + expiresIn * 1000).toISOString()
201
- });
202
- return true;
203
- }
204
- return false;
205
- };
206
-
207
- // Logout
208
- const logout = () => {
209
- auth(null); // Clear from storage
210
- };
211
-
212
- // Auto-refresh token
213
- $.effect(() => {
214
- if (!isAuthenticated()) return;
215
-
216
- const { expiresAt } = auth();
217
- const expiresIn = new Date(expiresAt) - new Date();
218
- const refreshTime = expiresIn - 60000; // 1 minute before expiry
219
-
220
- if (refreshTime > 0) {
221
- const timer = setTimeout(refreshToken, refreshTime);
222
- return () => clearTimeout(timer);
223
- }
224
- });
225
-
226
- // Navigation guard
227
- $.effect(() => {
228
- if (!isAuthenticated() && window.location.pathname !== '/login') {
229
- $.router.go('/login');
230
- }
231
- });
232
- ```
233
-
234
- ### Multi-tab Synchronization
235
-
236
- ```javascript
237
- import { $ } from 'sigpro';
238
-
239
- // Storage key for cross-tab communication
240
- const STORAGE_KEY = 'app-state';
241
-
242
- // Create persistent signal
243
- const appState = $.storage(STORAGE_KEY, {
244
- count: 0,
245
- lastUpdated: null
246
- });
247
-
248
- // Listen for storage events (changes from other tabs)
249
- window.addEventListener('storage', (event) => {
250
- if (event.key === STORAGE_KEY && event.newValue) {
251
- try {
252
- // Update signal without triggering save loop
253
- const newValue = JSON.parse(event.newValue);
254
- appState(newValue);
255
- } catch (e) {
256
- console.error('Failed to parse storage event:', e);
257
- }
258
- }
259
- });
260
-
261
- // Update state (syncs across all tabs)
262
- const increment = () => {
263
- appState({
264
- count: appState().count + 1,
265
- lastUpdated: new Date().toISOString()
266
- });
267
- };
268
-
269
- // Tab counter
270
- const tabCount = $(1);
271
-
272
- // Track number of tabs open
273
- window.addEventListener('storage', (event) => {
274
- if (event.key === 'tab-heartbeat') {
275
- tabCount(parseInt(event.newValue) || 1);
276
- }
277
- });
278
-
279
- // Send heartbeat
280
- setInterval(() => {
281
- localStorage.setItem('tab-heartbeat', tabCount());
282
- }, 1000);
283
- ```
284
-
285
- ### Settings Manager
286
-
287
- ```javascript
288
- import { $, html } from 'sigpro';
289
-
290
- // Settings schema
291
- const settingsSchema = {
292
- theme: {
293
- type: 'select',
294
- options: ['light', 'dark', 'system'],
295
- default: 'system'
296
- },
297
- fontSize: {
298
- type: 'range',
299
- min: 12,
300
- max: 24,
301
- default: 16
302
- },
303
- notifications: {
304
- type: 'checkbox',
305
- default: true
306
- },
307
- language: {
308
- type: 'select',
309
- options: ['en', 'es', 'fr', 'de'],
310
- default: 'en'
311
- }
312
- };
313
-
314
- // Persistent settings
315
- const settings = $.storage('app-settings',
316
- Object.entries(settingsSchema).reduce((acc, [key, config]) => ({
317
- ...acc,
318
- [key]: config.default
319
- }), {})
320
- );
321
-
322
- // Settings component
323
- const SettingsPanel = () => {
324
- return html`
325
- <div class="settings-panel">
326
- <h2>Settings</h2>
327
-
328
- ${Object.entries(settingsSchema).map(([key, config]) => {
329
- switch(config.type) {
330
- case 'select':
331
- return html`
332
- <div class="setting">
333
- <label>${key}:</label>
334
- <select
335
- :value=${() => settings()[key]}
336
- @change=${(e) => updateSetting(key, e.target.value)}
337
- >
338
- ${config.options.map(opt => html`
339
- <option value="${opt}" ?selected=${() => settings()[key] === opt}>
340
- ${opt}
341
- </option>
342
- `)}
343
- </select>
344
- </div>
345
- `;
346
-
347
- case 'range':
348
- return html`
349
- <div class="setting">
350
- <label>${key}: ${() => settings()[key]}</label>
351
- <input
352
- type="range"
353
- min="${config.min}"
354
- max="${config.max}"
355
- :value=${() => settings()[key]}
356
- @input=${(e) => updateSetting(key, parseInt(e.target.value))}
357
- />
358
- </div>
359
- `;
360
-
361
- case 'checkbox':
362
- return html`
363
- <div class="setting">
364
- <label>
365
- <input
366
- type="checkbox"
367
- :checked=${() => settings()[key]}
368
- @change=${(e) => updateSetting(key, e.target.checked)}
369
- />
370
- ${key}
371
- </label>
372
- </div>
373
- `;
374
- }
375
- })}
376
-
377
- <button @click=${resetDefaults}>Reset to Defaults</button>
378
- </div>
379
- `;
380
- };
381
-
382
- // Helper functions
383
- const updateSetting = (key, value) => {
384
- settings({
385
- ...settings(),
386
- [key]: value
387
- });
388
- };
389
-
390
- const resetDefaults = () => {
391
- const defaults = Object.entries(settingsSchema).reduce((acc, [key, config]) => ({
392
- ...acc,
393
- [key]: config.default
394
- }), {});
395
- settings(defaults);
396
- };
397
-
398
- // Apply settings globally
399
- $.effect(() => {
400
- const { theme, fontSize } = settings();
401
-
402
- // Apply theme
403
- document.documentElement.setAttribute('data-theme', theme);
404
-
405
- // Apply font size
406
- document.documentElement.style.fontSize = `${fontSize}px`;
407
- });
408
- ```
409
-
410
- ### Shopping Cart Persistence
411
-
412
- ```javascript
413
- import { $, html } from 'sigpro';
414
-
415
- // Persistent shopping cart
416
- const cart = $.storage('shopping-cart', {
417
- items: [],
418
- lastUpdated: null
419
- });
420
-
421
- // Computed values
422
- const cartItems = $(() => cart().items);
423
- const itemCount = $(() => cartItems().reduce((sum, item) => sum + item.quantity, 0));
424
- const subtotal = $(() => cartItems().reduce((sum, item) => sum + (item.price * item.quantity), 0));
425
- const tax = $(() => subtotal() * 0.1);
426
- const total = $(() => subtotal() + tax());
427
-
428
- // Cart actions
429
- const addToCart = (product, quantity = 1) => {
430
- const existing = cartItems().findIndex(item => item.id === product.id);
431
-
432
- if (existing >= 0) {
433
- // Update quantity
434
- const newItems = [...cartItems()];
435
- newItems[existing] = {
436
- ...newItems[existing],
437
- quantity: newItems[existing].quantity + quantity
438
- };
439
-
440
- cart({
441
- items: newItems,
442
- lastUpdated: new Date().toISOString()
443
- });
444
- } else {
445
- // Add new item
446
- cart({
447
- items: [...cartItems(), { ...product, quantity }],
448
- lastUpdated: new Date().toISOString()
449
- });
450
- }
451
- };
452
-
453
- const removeFromCart = (productId) => {
454
- cart({
455
- items: cartItems().filter(item => item.id !== productId),
456
- lastUpdated: new Date().toISOString()
457
- });
458
- };
459
-
460
- const updateQuantity = (productId, quantity) => {
461
- if (quantity <= 0) {
462
- removeFromCart(productId);
463
- } else {
464
- const newItems = cartItems().map(item =>
465
- item.id === productId ? { ...item, quantity } : item
466
- );
467
-
468
- cart({
469
- items: newItems,
470
- lastUpdated: new Date().toISOString()
471
- });
472
- }
473
- };
474
-
475
- const clearCart = () => {
476
- cart({
477
- items: [],
478
- lastUpdated: new Date().toISOString()
479
- });
480
- };
481
-
482
- // Cart expiration (7 days)
483
- const CART_EXPIRY_DAYS = 7;
484
-
485
- $.effect(() => {
486
- const lastUpdated = cart().lastUpdated;
487
- if (lastUpdated) {
488
- const expiryDate = new Date(lastUpdated);
489
- expiryDate.setDate(expiryDate.getDate() + CART_EXPIRY_DAYS);
490
-
491
- if (new Date() > expiryDate) {
492
- clearCart();
493
- }
494
- }
495
- });
496
-
497
- // Cart display component
498
- const CartDisplay = () => html`
499
- <div class="cart">
500
- <h3>Shopping Cart (${itemCount} items)</h3>
501
-
502
- ${cartItems().map(item => html`
503
- <div class="cart-item">
504
- <span>${item.name}</span>
505
- <span>$${item.price} x ${item.quantity}</span>
506
- <span>$${item.price * item.quantity}</span>
507
- <button @click=${() => removeFromCart(item.id)}>Remove</button>
508
- <input
509
- type="number"
510
- min="1"
511
- :value=${item.quantity}
512
- @change=${(e) => updateQuantity(item.id, parseInt(e.target.value))}
513
- />
514
- </div>
515
- `)}
516
-
517
- <div class="cart-totals">
518
- <p>Subtotal: $${subtotal}</p>
519
- <p>Tax (10%): $${tax}</p>
520
- <p><strong>Total: $${total}</strong></p>
521
- </div>
522
-
523
- ${() => cartItems().length > 0 ? html`
524
- <button @click=${checkout}>Checkout</button>
525
- <button @click=${clearCart}>Clear Cart</button>
526
- ` : html`
527
- <p>Your cart is empty</p>
528
- `}
529
- </div>
530
- `;
531
- ```
532
-
533
- ### Recent Searches History
534
-
535
- ```javascript
536
- import { $, html } from 'sigpro';
537
-
538
- // Persistent search history (max 10 items)
539
- const searchHistory = $.storage('search-history', []);
540
-
541
- // Add search to history
542
- const addSearch = (query) => {
543
- if (!query.trim()) return;
544
-
545
- const current = searchHistory();
546
- const newHistory = [
547
- { query, timestamp: new Date().toISOString() },
548
- ...current.filter(item => item.query !== query)
549
- ].slice(0, 10); // Keep only last 10
550
-
551
- searchHistory(newHistory);
552
- };
553
-
554
- // Clear history
555
- const clearHistory = () => {
556
- searchHistory([]);
557
- };
558
-
559
- // Remove specific item
560
- const removeFromHistory = (query) => {
561
- searchHistory(searchHistory().filter(item => item.query !== query));
562
- };
563
-
564
- // Search component
565
- const SearchWithHistory = () => {
566
- const searchInput = $('');
567
-
568
- const handleSearch = () => {
569
- const query = searchInput();
570
- if (query) {
571
- addSearch(query);
572
- performSearch(query);
573
- searchInput('');
574
- }
575
- };
576
-
577
- return html`
578
- <div class="search-container">
579
- <div class="search-box">
580
- <input
581
- type="search"
582
- :value=${searchInput}
583
- @keydown.enter=${handleSearch}
584
- placeholder="Search..."
585
- />
586
- <button @click=${handleSearch}>Search</button>
587
- </div>
588
-
589
- ${() => searchHistory().length > 0 ? html`
590
- <div class="search-history">
591
- <h4>Recent Searches</h4>
592
- ${searchHistory().map(item => html`
593
- <div class="history-item">
594
- <button
595
- class="history-query"
596
- @click=${() => {
597
- searchInput(item.query);
598
- handleSearch();
599
- }}
600
- >
601
- 🔍 ${item.query}
602
- </button>
603
- <small>${new Date(item.timestamp).toLocaleString()}</small>
604
- <button
605
- class="remove-btn"
606
- @click=${() => removeFromHistory(item.query)}
607
- >
608
-
609
- </button>
610
- </div>
611
- `)}
612
- <button class="clear-btn" @click=${clearHistory}>
613
- Clear History
614
- </button>
615
- </div>
616
- ` : ''}
617
- </div>
618
- `;
619
- };
620
- ```
621
-
622
- ### Multiple Profiles / Accounts
623
-
624
- ```javascript
625
- import { $, html } from 'sigpro';
626
-
627
- // Profile manager
628
- const profiles = $.storage('user-profiles', {
629
- current: 'default',
630
- list: {
631
- default: {
632
- name: 'Default',
633
- theme: 'light',
634
- preferences: {}
635
- }
636
- }
637
- });
638
-
639
- // Switch profile
640
- const switchProfile = (profileId) => {
641
- profiles({
642
- ...profiles(),
643
- current: profileId
644
- });
645
- };
646
-
647
- // Create profile
648
- const createProfile = (name) => {
649
- const id = `profile-${Date.now()}`;
650
- profiles({
651
- current: id,
652
- list: {
653
- ...profiles().list,
654
- [id]: {
655
- name,
656
- theme: 'light',
657
- preferences: {},
658
- createdAt: new Date().toISOString()
659
- }
660
- }
661
- });
662
- return id;
663
- };
664
-
665
- // Delete profile
666
- const deleteProfile = (profileId) => {
667
- if (profileId === 'default') return; // Can't delete default
668
-
669
- const newList = { ...profiles().list };
670
- delete newList[profileId];
671
-
672
- profiles({
673
- current: 'default',
674
- list: newList
675
- });
676
- };
677
-
678
- // Get current profile data
679
- const currentProfile = $(() => {
680
- const { current, list } = profiles();
681
- return list[current] || list.default;
682
- });
683
-
684
- // Profile-aware settings
685
- const profileTheme = $(() => currentProfile().theme);
686
- const profilePreferences = $(() => currentProfile().preferences);
687
-
688
- // Update profile data
689
- const updateCurrentProfile = (updates) => {
690
- const { current, list } = profiles();
691
- profiles({
692
- current,
693
- list: {
694
- ...list,
695
- [current]: {
696
- ...list[current],
697
- ...updates
698
- }
699
- }
700
- });
701
- };
702
-
703
- // Profile selector component
704
- const ProfileSelector = () => html`
705
- <div class="profile-selector">
706
- <select
707
- :value=${() => profiles().current}
708
- @change=${(e) => switchProfile(e.target.value)}
709
- >
710
- ${Object.entries(profiles().list).map(([id, profile]) => html`
711
- <option value="${id}">${profile.name}</option>
712
- `)}
713
- </select>
714
-
715
- <button @click=${() => {
716
- const name = prompt('Enter profile name:');
717
- if (name) createProfile(name);
718
- }}>
719
- New Profile
720
- </button>
721
- </div>
722
- `;
723
- ```
724
-
725
- ## 🛡️ Error Handling
726
-
727
- ### Storage Errors
728
-
729
- ```javascript
730
- import { $ } from 'sigpro';
731
-
732
- // Safe storage wrapper
733
- const safeStorage = (key, initialValue, storage = localStorage) => {
734
- try {
735
- return $.storage(key, initialValue, storage);
736
- } catch (error) {
737
- console.warn(`Storage failed for ${key}, using in-memory fallback:`, error);
738
- return $(initialValue);
739
- }
740
- };
741
-
742
- // Usage with fallback
743
- const theme = safeStorage('theme', 'light');
744
- const user = safeStorage('user', null);
745
- ```
746
-
747
- ### Quota Exceeded Handling
748
-
749
- ```javascript
750
- import { $ } from 'sigpro';
751
-
752
- const createManagedStorage = (key, initialValue, maxSize = 1024 * 100) => { // 100KB limit
753
- const signal = $.storage(key, initialValue);
754
-
755
- // Monitor size
756
- const size = $(0);
757
-
758
- $.effect(() => {
759
- try {
760
- const value = signal();
761
- const json = JSON.stringify(value);
762
- const bytes = new Blob([json]).size;
763
-
764
- size(bytes);
765
-
766
- if (bytes > maxSize) {
767
- console.warn(`Storage for ${key} exceeded ${maxSize} bytes`);
768
- // Could implement cleanup strategy here
769
- }
770
- } catch (e) {
771
- console.error('Size check failed:', e);
772
- }
773
- });
774
-
775
- return { signal, size };
776
- };
777
-
778
- // Usage
779
- const { signal: largeData, size } = createManagedStorage('app-data', {}, 50000);
780
- ```
781
-
782
- ## 📊 Storage Limits
783
-
784
- | Storage Type | Typical Limit | Notes |
785
- |--------------|---------------|-------|
786
- | `localStorage` | 5-10MB | Varies by browser |
787
- | `sessionStorage` | 5-10MB | Cleared when tab closes |
788
- | `cookies` | 4KB | Not recommended for SigPro |
789
-
790
- ## 🎯 Best Practices
791
-
792
- ### 1. Validate Stored Data
793
-
794
- ```javascript
795
- import { $ } from 'sigpro';
796
-
797
- // Schema validation
798
- const createValidatedStorage = (key, schema, defaultValue, storage) => {
799
- const signal = $.storage(key, defaultValue, storage);
800
-
801
- // Wrap to validate on read/write
802
- const validated = (...args) => {
803
- if (args.length) {
804
- // Validate before writing
805
- const value = args[0];
806
- if (typeof value === 'function') {
807
- // Handle functional updates
808
- return validated(validated());
809
- }
810
-
811
- // Basic validation
812
- const isValid = Object.keys(schema).every(key => {
813
- const validator = schema[key];
814
- return !validator || validator(value[key]);
815
- });
816
-
817
- if (!isValid) {
818
- console.warn('Invalid data, skipping storage write');
819
- return signal();
820
- }
821
- }
822
-
823
- return signal(...args);
824
- };
825
-
826
- return validated;
827
- };
828
-
829
- // Usage
830
- const userSchema = {
831
- name: v => v && v.length > 0,
832
- age: v => v >= 18 && v <= 120,
833
- email: v => /@/.test(v)
834
- };
835
-
836
- const user = createValidatedStorage('user', userSchema, {
837
- name: '',
838
- age: 25,
839
- email: ''
840
- });
841
- ```
842
-
843
- ### 2. Handle Versioning
844
-
845
- ```javascript
846
- import { $ } from 'sigpro';
847
-
848
- const VERSION = 2;
849
-
850
- const createVersionedStorage = (key, migrations, storage) => {
851
- const raw = $.storage(key, { version: VERSION, data: {} }, storage);
852
-
853
- const migrate = (data) => {
854
- let current = data;
855
- const currentVersion = current.version || 1;
856
-
857
- for (let v = currentVersion; v < VERSION; v++) {
858
- const migrator = migrations[v];
859
- if (migrator) {
860
- current = migrator(current);
861
- }
862
- }
863
-
864
- return current;
865
- };
866
-
867
- // Migrate if needed
868
- const stored = raw();
869
- if (stored.version !== VERSION) {
870
- const migrated = migrate(stored);
871
- raw(migrated);
872
- }
873
-
874
- return raw;
875
- };
876
-
877
- // Usage
878
- const migrations = {
879
- 1: (old) => ({
880
- version: 2,
881
- data: {
882
- ...old.data,
883
- preferences: old.preferences || {}
884
- }
885
- })
886
- };
887
-
888
- const settings = createVersionedStorage('app-settings', migrations);
889
- ```
890
-
891
- ### 3. Encrypt Sensitive Data
892
-
893
- ```javascript
894
- import { $ } from 'sigpro';
895
-
896
- // Simple encryption (use proper crypto in production)
897
- const encrypt = (text) => {
898
- return btoa(text); // Base64 - NOT secure, just example
899
- };
900
-
901
- const decrypt = (text) => {
902
- try {
903
- return atob(text);
904
- } catch {
905
- return null;
906
- }
907
- };
908
-
909
- const createSecureStorage = (key, initialValue, storage) => {
910
- const encryptedKey = `enc_${key}`;
911
- const signal = $.storage(encryptedKey, null, storage);
912
-
913
- const secure = (...args) => {
914
- if (args.length) {
915
- // Encrypt before storing
916
- const value = args[0];
917
- const encrypted = encrypt(JSON.stringify(value));
918
- return signal(encrypted);
919
- }
920
-
921
- // Decrypt when reading
922
- const encrypted = signal();
923
- if (!encrypted) return initialValue;
924
-
925
- try {
926
- const decrypted = decrypt(encrypted);
927
- return decrypted ? JSON.parse(decrypted) : initialValue;
928
- } catch {
929
- return initialValue;
930
- }
931
- };
932
-
933
- return secure;
934
- };
935
-
936
- // Usage
937
- const secureToken = createSecureStorage('auth-token', null);
938
- secureToken('sensitive-data-123'); // Stored encrypted
939
- ```
940
-
941
- ## 📈 Performance Considerations
942
-
943
- | Operation | Cost | Notes |
944
- |-----------|------|-------|
945
- | Initial read | O(1) | Single storage read |
946
- | Write | O(1) + JSON.stringify | Auto-save on change |
947
- | Large objects | O(n) | Stringify/parse overhead |
948
- | Multiple keys | O(k) | k = number of keys |
949
-
950
- ---
951
-
952
- > **Pro Tip:** Use `sessionStorage` for temporary data like form drafts, and `localStorage` for persistent user preferences. Always validate data when reading from storage to handle corrupted values gracefully.