sigpro 1.0.14 → 1.2.40

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 (112) hide show
  1. package/Readme.md +164 -1008
  2. package/dist/sigpro.db.js +1 -0
  3. package/dist/sigpro.editor.js +1 -0
  4. package/dist/sigpro.grid.js +78 -0
  5. package/dist/sigpro.js +1 -0
  6. package/dist/sigpro.locale.js +1 -0
  7. package/dist/sigpro.router.js +1 -0
  8. package/dist/sigpro.ui.css +2 -0
  9. package/dist/sigpro.ui.js +1 -0
  10. package/dist/sigpro.vite.js +4 -0
  11. package/package.json +94 -13
  12. package/sigpro.d.ts +395 -0
  13. package/sigpro.ui.d.ts +308 -0
  14. package/.github/workflows/publish.yml +0 -25
  15. package/bun.lock +0 -385
  16. package/docs/404.html +0 -22
  17. package/docs/api/components.html +0 -595
  18. package/docs/api/effects.html +0 -787
  19. package/docs/api/fetch.html +0 -873
  20. package/docs/api/pages.html +0 -405
  21. package/docs/api/quick.html +0 -217
  22. package/docs/api/routing.html +0 -628
  23. package/docs/api/signals.html +0 -683
  24. package/docs/api/storage.html +0 -820
  25. package/docs/assets/api_components.md.BlFwj17l.js +0 -571
  26. package/docs/assets/api_components.md.BlFwj17l.lean.js +0 -1
  27. package/docs/assets/api_effects.md.Br_yStBS.js +0 -763
  28. package/docs/assets/api_effects.md.Br_yStBS.lean.js +0 -1
  29. package/docs/assets/api_fetch.md.DQLBJSoq.js +0 -849
  30. package/docs/assets/api_fetch.md.DQLBJSoq.lean.js +0 -1
  31. package/docs/assets/api_pages.md.BP19nHXw.js +0 -381
  32. package/docs/assets/api_pages.md.BP19nHXw.lean.js +0 -1
  33. package/docs/assets/api_quick.md.BDS3ttnt.js +0 -193
  34. package/docs/assets/api_quick.md.BDS3ttnt.lean.js +0 -1
  35. package/docs/assets/api_routing.md.7SNAZXtp.js +0 -604
  36. package/docs/assets/api_routing.md.7SNAZXtp.lean.js +0 -1
  37. package/docs/assets/api_signals.md.CrW68-BA.js +0 -659
  38. package/docs/assets/api_signals.md.CrW68-BA.lean.js +0 -1
  39. package/docs/assets/api_storage.md.COEWBXHk.js +0 -796
  40. package/docs/assets/api_storage.md.COEWBXHk.lean.js +0 -1
  41. package/docs/assets/app.DtmzNmNl.js +0 -1
  42. package/docs/assets/chunks/framework.C8AWLET_.js +0 -19
  43. package/docs/assets/chunks/theme.yfWKMLQM.js +0 -1
  44. package/docs/assets/guide_getting-started.md.BeQpK3vd.js +0 -172
  45. package/docs/assets/guide_getting-started.md.BeQpK3vd.lean.js +0 -1
  46. package/docs/assets/guide_why.md.DXchYMN-.js +0 -23
  47. package/docs/assets/guide_why.md.DXchYMN-.lean.js +0 -1
  48. package/docs/assets/index.md.uvMJmU4o.js +0 -1
  49. package/docs/assets/index.md.uvMJmU4o.lean.js +0 -1
  50. package/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  51. package/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  52. package/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  53. package/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  54. package/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  55. package/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  56. package/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  57. package/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  58. package/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  59. package/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  60. package/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  61. package/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  62. package/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  63. package/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  64. package/docs/assets/style.DJRheFKp.css +0 -1
  65. package/docs/assets/ui_intro.md.gZ21GFqo.js +0 -1
  66. package/docs/assets/ui_intro.md.gZ21GFqo.lean.js +0 -1
  67. package/docs/assets/vite_plugin.md.gDWEi8f0.js +0 -225
  68. package/docs/assets/vite_plugin.md.gDWEi8f0.lean.js +0 -1
  69. package/docs/guide/getting-started.html +0 -196
  70. package/docs/guide/why.html +0 -47
  71. package/docs/hashmap.json +0 -1
  72. package/docs/index.html +0 -25
  73. package/docs/logo.svg +0 -118
  74. package/docs/ui/intro.html +0 -25
  75. package/docs/vite/plugin.html +0 -249
  76. package/docs/vp-icons.css +0 -1
  77. package/index.js +0 -3
  78. package/packages/docs/.vitepress/cache/deps/@theme_index.js +0 -275
  79. package/packages/docs/.vitepress/cache/deps/@theme_index.js.map +0 -7
  80. package/packages/docs/.vitepress/cache/deps/_metadata.json +0 -40
  81. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js +0 -12951
  82. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js.map +0 -7
  83. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js +0 -9719
  84. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js.map +0 -7
  85. package/packages/docs/.vitepress/cache/deps/package.json +0 -3
  86. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4505
  87. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
  88. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -583
  89. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
  90. package/packages/docs/.vitepress/cache/deps/vue.js +0 -347
  91. package/packages/docs/.vitepress/cache/deps/vue.js.map +0 -7
  92. package/packages/docs/.vitepress/config.js +0 -68
  93. package/packages/docs/api/components.md +0 -760
  94. package/packages/docs/api/effects.md +0 -1039
  95. package/packages/docs/api/fetch.md +0 -998
  96. package/packages/docs/api/pages.md +0 -497
  97. package/packages/docs/api/quick.md +0 -436
  98. package/packages/docs/api/routing.md +0 -784
  99. package/packages/docs/api/signals.md +0 -899
  100. package/packages/docs/api/storage.md +0 -952
  101. package/packages/docs/guide/getting-started.md +0 -308
  102. package/packages/docs/guide/why.md +0 -135
  103. package/packages/docs/index.md +0 -84
  104. package/packages/docs/logo.svg +0 -118
  105. package/packages/docs/public/logo.svg +0 -118
  106. package/packages/docs/ui/intro.md +0 -16
  107. package/packages/docs/vite/plugin.md +0 -423
  108. package/packages/sigpro/plugin.js +0 -91
  109. package/packages/sigpro/plugin.min.js +0 -1
  110. package/packages/sigpro/sigpro.js +0 -631
  111. package/packages/sigpro/sigpro.min.js +0 -1
  112. package/vite.config.js +0 -24
@@ -1,784 +0,0 @@
1
- # Routing API 🌐
2
-
3
- SigPro includes a simple yet powerful hash-based router designed for Single Page Applications (SPAs). It works everywhere with zero server configuration and integrates seamlessly with `$.page` for automatic cleanup.
4
-
5
- ## Why Hash-Based Routing?
6
-
7
- Hash routing (`#/about`) works **everywhere** - no server configuration needed. Perfect for:
8
- - Static sites and SPAs
9
- - GitHub Pages, Netlify, any static hosting
10
- - Local development without a server
11
- - Projects that need to work immediately
12
-
13
- ## `$.router(routes)`
14
-
15
- Creates a hash-based router that renders the matching component and handles navigation.
16
-
17
- ```javascript
18
- import { $, html } from 'sigpro';
19
- import HomePage from './pages/Home.js';
20
- import AboutPage from './pages/About.js';
21
- import UserPage from './pages/User.js';
22
-
23
- const routes = [
24
- { path: '/', component: HomePage },
25
- { path: '/about', component: AboutPage },
26
- { path: '/user/:id', component: UserPage },
27
- ];
28
-
29
- // Mount the router
30
- document.body.appendChild($.router(routes));
31
- ```
32
-
33
- ## 📋 API Reference
34
-
35
- ### `$.router(routes)`
36
-
37
- | Parameter | Type | Description |
38
- |-----------|------|-------------|
39
- | `routes` | `Array<Route>` | Array of route configurations |
40
-
41
- **Returns:** `HTMLDivElement` - Container that renders the current page
42
-
43
- ### `$.router.go(path)`
44
-
45
- | Parameter | Type | Description |
46
- |-----------|------|-------------|
47
- | `path` | `string` | Route path to navigate to (automatically adds leading slash) |
48
-
49
- ### Route Object
50
-
51
- | Property | Type | Description |
52
- |----------|------|-------------|
53
- | `path` | `string` or `RegExp` | Route pattern to match |
54
- | `component` | `Function` | Function that returns page content (receives `params`) |
55
-
56
- ## 🎯 Route Patterns
57
-
58
- ### String Paths (Simple Routes)
59
-
60
- ```javascript
61
- const routes = [
62
- // Static routes
63
- { path: '/', component: HomePage },
64
- { path: '/about', component: AboutPage },
65
- { path: '/contact', component: ContactPage },
66
-
67
- // Routes with parameters
68
- { path: '/user/:id', component: UserPage },
69
- { path: '/user/:id/posts', component: UserPostsPage },
70
- { path: '/user/:id/posts/:postId', component: PostPage },
71
- { path: '/search/:query/page/:num', component: SearchPage },
72
- ];
73
- ```
74
-
75
- ### RegExp Paths (Advanced Routing)
76
-
77
- ```javascript
78
- const routes = [
79
- // Match numeric IDs only
80
- { path: /^\/users\/(?<id>\d+)$/, component: UserPage },
81
-
82
- // Match product slugs (letters, numbers, hyphens)
83
- { path: /^\/products\/(?<slug>[a-z0-9-]+)$/, component: ProductPage },
84
-
85
- // Match blog posts by year/month
86
- { path: /^\/blog\/(?<year>\d{4})\/(?<month>\d{2})$/, component: BlogArchive },
87
-
88
- // Match optional language prefix
89
- { path: /^\/(?<lang>en|es|fr)?\/?about$/, component: AboutPage },
90
-
91
- // Match UUID format
92
- { path: /^\/items\/(?<uuid>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/,
93
- component: ItemPage },
94
- ];
95
- ```
96
-
97
- ## 📦 Basic Examples
98
-
99
- ### Simple Router Setup
100
-
101
- ```javascript
102
- // main.js
103
- import { $, html } from 'sigpro';
104
- import Home from './pages/Home.js';
105
- import About from './pages/About.js';
106
- import Contact from './pages/Contact.js';
107
-
108
- const routes = [
109
- { path: '/', component: Home },
110
- { path: '/about', component: About },
111
- { path: '/contact', component: Contact },
112
- ];
113
-
114
- const router = $.router(routes);
115
-
116
- // Mount to DOM
117
- document.body.appendChild(router);
118
- ```
119
-
120
- ### Page Components with Parameters
121
-
122
- ```javascript
123
- // pages/User.js
124
- import { $, html } from 'sigpro';
125
-
126
- export default (params) => $.page(() => {
127
- // /user/42 → params = { id: '42' }
128
- // /user/john/posts/123 → params = { id: 'john', postId: '123' }
129
- const userId = params.id;
130
- const userData = $(null);
131
-
132
- $.effect(() => {
133
- fetch(`/api/users/${userId}`)
134
- .then(res => res.json())
135
- .then(data => userData(data));
136
- });
137
-
138
- return html`
139
- <div class="user-page">
140
- <h1>User Profile: ${userId}</h1>
141
- ${() => userData() ? html`
142
- <p>Name: ${userData().name}</p>
143
- <p>Email: ${userData().email}</p>
144
- ` : html`<p>Loading...</p>`}
145
- </div>
146
- `;
147
- });
148
- ```
149
-
150
- ### Navigation
151
-
152
- ```javascript
153
- import { $, html } from 'sigpro';
154
-
155
- // In templates
156
- const NavBar = () => html`
157
- <nav>
158
- <a href="#/">Home</a>
159
- <a href="#/about">About</a>
160
- <a href="#/contact">Contact</a>
161
- <a href="#/user/42">Profile</a>
162
- <a href="#/search/js/page/1">Search</a>
163
-
164
- <!-- Programmatic navigation -->
165
- <button @click=${() => $.router.go('/about')}>
166
- Go to About
167
- </button>
168
-
169
- <button @click=${() => $.router.go('contact')}>
170
- Go to Contact (auto-adds leading slash)
171
- </button>
172
- </nav>
173
- `;
174
- ```
175
-
176
- ## 🚀 Advanced Examples
177
-
178
- ### Complete Application with Layout
179
-
180
- ```javascript
181
- // App.js
182
- import { $, html } from 'sigpro';
183
- import HomePage from './pages/Home.js';
184
- import AboutPage from './pages/About.js';
185
- import UserPage from './pages/User.js';
186
- import SettingsPage from './pages/Settings.js';
187
- import NotFound from './pages/NotFound.js';
188
-
189
- // Layout component with navigation
190
- const Layout = (content) => html`
191
- <div class="app">
192
- <header class="header">
193
- <h1>My SigPro App</h1>
194
- <nav class="nav">
195
- <a href="#/" class:active=${() => isActive('/')}>Home</a>
196
- <a href="#/about" class:active=${() => isActive('/about')}>About</a>
197
- <a href="#/user/42" class:active=${() => isActive('/user/42')}>Profile</a>
198
- <a href="#/settings" class:active=${() => isActive('/settings')}>Settings</a>
199
- </nav>
200
- </header>
201
-
202
- <main class="main">
203
- ${content}
204
- </main>
205
-
206
- <footer class="footer">
207
- <p>© 2024 SigPro App</p>
208
- </footer>
209
- </div>
210
- `;
211
-
212
- // Helper to check active route
213
- const isActive = (path) => {
214
- const current = window.location.hash.replace(/^#/, '') || '/';
215
- return current === path;
216
- };
217
-
218
- // Routes with layout
219
- const routes = [
220
- { path: '/', component: (params) => Layout(HomePage(params)) },
221
- { path: '/about', component: (params) => Layout(AboutPage(params)) },
222
- { path: '/user/:id', component: (params) => Layout(UserPage(params)) },
223
- { path: '/settings', component: (params) => Layout(SettingsPage(params)) },
224
- { path: '/:path(.*)', component: (params) => Layout(NotFound(params)) }, // Catch-all
225
- ];
226
-
227
- // Create and mount router
228
- const router = $.router(routes);
229
- document.body.appendChild(router);
230
- ```
231
-
232
- ### Nested Routes
233
-
234
- ```javascript
235
- // pages/Settings.js (parent route)
236
- import { $, html } from 'sigpro';
237
- import SettingsGeneral from './settings/General.js';
238
- import SettingsSecurity from './settings/Security.js';
239
- import SettingsNotifications from './settings/Notifications.js';
240
-
241
- export default (params) => $.page(() => {
242
- const section = params.section || 'general';
243
-
244
- const sections = {
245
- general: SettingsGeneral,
246
- security: SettingsSecurity,
247
- notifications: SettingsNotifications
248
- };
249
-
250
- const CurrentSection = sections[section];
251
-
252
- return html`
253
- <div class="settings">
254
- <h1>Settings</h1>
255
-
256
- <div class="settings-layout">
257
- <nav class="settings-sidebar">
258
- <a href="#/settings/general" class:active=${() => section === 'general'}>
259
- General
260
- </a>
261
- <a href="#/settings/security" class:active=${() => section === 'security'}>
262
- Security
263
- </a>
264
- <a href="#/settings/notifications" class:active=${() => section === 'notifications'}>
265
- Notifications
266
- </a>
267
- </nav>
268
-
269
- <div class="settings-content">
270
- ${CurrentSection(params)}
271
- </div>
272
- </div>
273
- </div>
274
- `;
275
- });
276
-
277
- // pages/settings/General.js
278
- export default (params) => $.page(() => {
279
- return html`
280
- <div>
281
- <h2>General Settings</h2>
282
- <form>...</form>
283
- </div>
284
- `;
285
- });
286
-
287
- // Main router with nested routes
288
- const routes = [
289
- { path: '/', component: HomePage },
290
- { path: '/settings/:section?', component: SettingsPage }, // Optional section param
291
- ];
292
- ```
293
-
294
- ### Protected Routes (Authentication)
295
-
296
- ```javascript
297
- // auth.js
298
- import { $ } from 'sigpro';
299
-
300
- const isAuthenticated = $(false);
301
- const user = $(null);
302
-
303
- export const checkAuth = async () => {
304
- const token = localStorage.getItem('token');
305
- if (token) {
306
- try {
307
- const response = await fetch('/api/verify');
308
- if (response.ok) {
309
- const userData = await response.json();
310
- user(userData);
311
- isAuthenticated(true);
312
- return true;
313
- }
314
- } catch (e) {
315
- // Handle error
316
- }
317
- }
318
- isAuthenticated(false);
319
- user(null);
320
- return false;
321
- };
322
-
323
- export const requireAuth = (component) => (params) => {
324
- if (isAuthenticated()) {
325
- return component(params);
326
- }
327
- // Redirect to login
328
- $.router.go('/login');
329
- return null;
330
- };
331
-
332
- export { isAuthenticated, user };
333
- ```
334
-
335
- ```javascript
336
- // pages/Dashboard.js (protected route)
337
- import { $, html } from 'sigpro';
338
- import { requireAuth, user } from '../auth.js';
339
-
340
- const Dashboard = (params) => $.page(() => {
341
- return html`
342
- <div class="dashboard">
343
- <h1>Welcome, ${() => user()?.name}!</h1>
344
- <p>This is your protected dashboard.</p>
345
- </div>
346
- `;
347
- });
348
-
349
- export default requireAuth(Dashboard);
350
- ```
351
-
352
- ```javascript
353
- // main.js with protected routes
354
- import { $, html } from 'sigpro';
355
- import { checkAuth } from './auth.js';
356
- import HomePage from './pages/Home.js';
357
- import LoginPage from './pages/Login.js';
358
- import DashboardPage from './pages/Dashboard.js';
359
- import AdminPage from './pages/Admin.js';
360
-
361
- // Check auth on startup
362
- checkAuth();
363
-
364
- const routes = [
365
- { path: '/', component: HomePage },
366
- { path: '/login', component: LoginPage },
367
- { path: '/dashboard', component: DashboardPage }, // Protected
368
- { path: '/admin', component: AdminPage }, // Protected
369
- ];
370
-
371
- document.body.appendChild($.router(routes));
372
- ```
373
-
374
- ### Route Transitions
375
-
376
- ```javascript
377
- // with-transitions.js
378
- import { $, html } from 'sigpro';
379
-
380
- export const createRouterWithTransitions = (routes) => {
381
- const transitioning = $(false);
382
- const currentView = $(null);
383
- const nextView = $(null);
384
-
385
- const container = document.createElement('div');
386
- container.style.display = 'contents';
387
-
388
- const renderWithTransition = async (newView) => {
389
- if (currentView() === newView) return;
390
-
391
- transitioning(true);
392
- nextView(newView);
393
-
394
- // Fade out
395
- container.style.transition = 'opacity 0.2s';
396
- container.style.opacity = '0';
397
-
398
- await new Promise(resolve => setTimeout(resolve, 200));
399
-
400
- // Update content
401
- container.replaceChildren(newView);
402
- currentView(newView);
403
-
404
- // Fade in
405
- container.style.opacity = '1';
406
-
407
- await new Promise(resolve => setTimeout(resolve, 200));
408
- transitioning(false);
409
- container.style.transition = '';
410
- };
411
-
412
- const router = $.router(routes.map(route => ({
413
- ...route,
414
- component: (params) => {
415
- const view = route.component(params);
416
- renderWithTransition(view);
417
- return document.createComment('router-placeholder');
418
- }
419
- })));
420
-
421
- return router;
422
- };
423
- ```
424
-
425
- ### Breadcrumbs Navigation
426
-
427
- ```javascript
428
- // with-breadcrumbs.js
429
- import { $, html } from 'sigpro';
430
-
431
- export const createBreadcrumbs = (routes) => {
432
- const breadcrumbs = $([]);
433
-
434
- const updateBreadcrumbs = (path) => {
435
- const parts = path.split('/').filter(Boolean);
436
- const crumbs = [];
437
- let currentPath = '';
438
-
439
- parts.forEach((part, index) => {
440
- currentPath += `/${part}`;
441
-
442
- // Find matching route
443
- const route = routes.find(r => {
444
- if (r.path.includes(':')) {
445
- const pattern = r.path.replace(/:[^/]+/g, part);
446
- return pattern === currentPath;
447
- }
448
- return r.path === currentPath;
449
- });
450
-
451
- crumbs.push({
452
- path: currentPath,
453
- label: route?.name || part.charAt(0).toUpperCase() + part.slice(1),
454
- isLast: index === parts.length - 1
455
- });
456
- });
457
-
458
- breadcrumbs(crumbs);
459
- };
460
-
461
- // Listen to route changes
462
- window.addEventListener('hashchange', () => {
463
- const path = window.location.hash.replace(/^#/, '') || '/';
464
- updateBreadcrumbs(path);
465
- });
466
-
467
- // Initial update
468
- updateBreadcrumbs(window.location.hash.replace(/^#/, '') || '/');
469
-
470
- return breadcrumbs;
471
- };
472
- ```
473
-
474
- ```javascript
475
- // Usage in layout
476
- import { createBreadcrumbs } from './with-breadcrumbs.js';
477
-
478
- const breadcrumbs = createBreadcrumbs(routes);
479
-
480
- const Layout = (content) => html`
481
- <div class="app">
482
- <nav class="breadcrumbs">
483
- ${() => breadcrumbs().map(crumb => html`
484
- ${!crumb.isLast ? html`
485
- <a href="#${crumb.path}">${crumb.label}</a>
486
- <span class="separator">/</span>
487
- ` : html`
488
- <span class="current">${crumb.label}</span>
489
- `}
490
- `)}
491
- </nav>
492
-
493
- <main>
494
- ${content}
495
- </main>
496
- </div>
497
- `;
498
- ```
499
-
500
- ### Query Parameters
501
-
502
- ```javascript
503
- // with-query-params.js
504
- export const getQueryParams = () => {
505
- const hash = window.location.hash;
506
- const queryStart = hash.indexOf('?');
507
- if (queryStart === -1) return {};
508
-
509
- const queryString = hash.slice(queryStart + 1);
510
- const params = new URLSearchParams(queryString);
511
- const result = {};
512
-
513
- for (const [key, value] of params) {
514
- result[key] = value;
515
- }
516
-
517
- return result;
518
- };
519
-
520
- export const updateQueryParams = (params) => {
521
- const hash = window.location.hash.split('?')[0];
522
- const queryString = new URLSearchParams(params).toString();
523
- window.location.hash = queryString ? `${hash}?${queryString}` : hash;
524
- };
525
- ```
526
-
527
- ```javascript
528
- // Search page with query params
529
- import { $, html } from 'sigpro';
530
- import { getQueryParams, updateQueryParams } from './with-query-params.js';
531
-
532
- export default (params) => $.page(() => {
533
- // Get initial query from URL
534
- const queryParams = getQueryParams();
535
- const searchQuery = $(queryParams.q || '');
536
- const page = $(parseInt(queryParams.page) || 1);
537
- const results = $([]);
538
-
539
- // Update URL when search changes
540
- $.effect(() => {
541
- updateQueryParams({
542
- q: searchQuery() || undefined,
543
- page: page() > 1 ? page() : undefined
544
- });
545
- });
546
-
547
- // Fetch results when search or page changes
548
- $.effect(() => {
549
- if (searchQuery()) {
550
- fetch(`/api/search?q=${searchQuery()}&page=${page()}`)
551
- .then(res => res.json())
552
- .then(data => results(data));
553
- }
554
- });
555
-
556
- return html`
557
- <div class="search-page">
558
- <h1>Search</h1>
559
-
560
- <input
561
- type="search"
562
- :value=${searchQuery}
563
- placeholder="Search..."
564
- @input=${(e) => {
565
- searchQuery(e.target.value);
566
- page(1); // Reset to first page on new search
567
- }}
568
- />
569
-
570
- <div class="results">
571
- ${results().map(item => html`
572
- <div class="result">${item.title}</div>
573
- `)}
574
- </div>
575
-
576
- ${() => results().length ? html`
577
- <div class="pagination">
578
- <button
579
- ?disabled=${() => page() <= 1}
580
- @click=${() => page(p => p - 1)}
581
- >
582
- Previous
583
- </button>
584
-
585
- <span>Page ${page}</span>
586
-
587
- <button
588
- ?disabled=${() => results().length < 10}
589
- @click=${() => page(p => p + 1)}
590
- >
591
- Next
592
- </button>
593
- </div>
594
- ` : ''}
595
- </div>
596
- `;
597
- });
598
- ```
599
-
600
- ### Lazy Loading Routes
601
-
602
- ```javascript
603
- // lazy.js
604
- export const lazy = (loader) => {
605
- let component = null;
606
-
607
- return async (params) => {
608
- if (!component) {
609
- const module = await loader();
610
- component = module.default;
611
- }
612
- return component(params);
613
- };
614
- };
615
- ```
616
-
617
- ```javascript
618
- // main.js with lazy loading
619
- import { $, html } from 'sigpro';
620
- import { lazy } from './lazy.js';
621
- import Layout from './Layout.js';
622
-
623
- const routes = [
624
- { path: '/', component: lazy(() => import('./pages/Home.js')) },
625
- { path: '/about', component: lazy(() => import('./pages/About.js')) },
626
- { path: '/dashboard', component: lazy(() => import('./pages/Dashboard.js')) },
627
- {
628
- path: '/admin',
629
- component: lazy(() => import('./pages/Admin.js')),
630
- // Show loading state
631
- loading: () => html`<div class="loading">Loading admin panel...</div>`
632
- },
633
- ];
634
-
635
- // Wrap with layout
636
- const routesWithLayout = routes.map(route => ({
637
- ...route,
638
- component: (params) => Layout(route.component(params))
639
- }));
640
-
641
- document.body.appendChild($.router(routesWithLayout));
642
- ```
643
-
644
- ### Route Guards / Middleware
645
-
646
- ```javascript
647
- // middleware.js
648
- export const withGuard = (component, guard) => (params) => {
649
- const result = guard(params);
650
- if (result === true) {
651
- return component(params);
652
- } else if (typeof result === 'string') {
653
- $.router.go(result);
654
- return null;
655
- }
656
- return result; // Custom component (e.g., AccessDenied)
657
- };
658
-
659
- // Guards
660
- export const roleGuard = (requiredRole) => (params) => {
661
- const userRole = localStorage.getItem('userRole');
662
- if (userRole === requiredRole) return true;
663
- if (!userRole) return '/login';
664
- return AccessDeniedPage(params);
665
- };
666
-
667
- export const authGuard = () => (params) => {
668
- const token = localStorage.getItem('token');
669
- return token ? true : '/login';
670
- };
671
-
672
- export const pendingChangesGuard = (hasPendingChanges) => (params) => {
673
- if (hasPendingChanges()) {
674
- return ConfirmLeavePage(params);
675
- }
676
- return true;
677
- };
678
- ```
679
-
680
- ```javascript
681
- // Usage
682
- import { withGuard, authGuard, roleGuard } from './middleware.js';
683
-
684
- const routes = [
685
- { path: '/', component: HomePage },
686
- { path: '/profile', component: withGuard(ProfilePage, authGuard()) },
687
- {
688
- path: '/admin',
689
- component: withGuard(AdminPage, roleGuard('admin'))
690
- },
691
- ];
692
- ```
693
-
694
- ## 📊 Route Matching Priority
695
-
696
- Routes are matched in the order they are defined. More specific routes should come first:
697
-
698
- ```javascript
699
- const routes = [
700
- // More specific first
701
- { path: '/user/:id/edit', component: EditUserPage },
702
- { path: '/user/:id/posts', component: UserPostsPage },
703
- { path: '/user/:id', component: UserPage },
704
-
705
- // Static routes
706
- { path: '/about', component: AboutPage },
707
- { path: '/contact', component: ContactPage },
708
-
709
- // Catch-all last
710
- { path: '/:path(.*)', component: NotFoundPage },
711
- ];
712
- ```
713
-
714
- ## 🎯 Complete Example
715
-
716
- ```javascript
717
- // main.js - Complete application
718
- import { $, html } from 'sigpro';
719
- import { lazy } from './utils/lazy.js';
720
- import { withGuard, authGuard } from './utils/middleware.js';
721
- import Layout from './components/Layout.js';
722
-
723
- // Lazy load pages
724
- const HomePage = lazy(() => import('./pages/Home.js'));
725
- const AboutPage = lazy(() => import('./pages/About.js'));
726
- const LoginPage = lazy(() => import('./pages/Login.js'));
727
- const DashboardPage = lazy(() => import('./pages/Dashboard.js'));
728
- const UserPage = lazy(() => import('./pages/User.js'));
729
- const SettingsPage = lazy(() => import('./pages/Settings.js'));
730
- const NotFoundPage = lazy(() => import('./pages/NotFound.js'));
731
-
732
- // Route configuration
733
- const routes = [
734
- { path: '/', component: HomePage, name: 'Home' },
735
- { path: '/about', component: AboutPage, name: 'About' },
736
- { path: '/login', component: LoginPage, name: 'Login' },
737
- {
738
- path: '/dashboard',
739
- component: withGuard(DashboardPage, authGuard()),
740
- name: 'Dashboard'
741
- },
742
- {
743
- path: '/user/:id',
744
- component: UserPage,
745
- name: 'User Profile'
746
- },
747
- {
748
- path: '/settings/:section?',
749
- component: withGuard(SettingsPage, authGuard()),
750
- name: 'Settings'
751
- },
752
- { path: '/:path(.*)', component: NotFoundPage, name: 'Not Found' },
753
- ];
754
-
755
- // Wrap all routes with layout
756
- const routesWithLayout = routes.map(route => ({
757
- ...route,
758
- component: (params) => Layout(route.component(params))
759
- }));
760
-
761
- // Create and mount router
762
- const router = $.router(routesWithLayout);
763
- document.body.appendChild(router);
764
-
765
- // Navigation helper (available globally)
766
- window.navigate = $.router.go;
767
- ```
768
-
769
- ## 📊 Summary
770
-
771
- | Feature | Description |
772
- |---------|-------------|
773
- | **Hash-based** | Works everywhere, no server config |
774
- | **Route Parameters** | `:param` syntax for dynamic segments |
775
- | **RegExp Support** | Advanced pattern matching |
776
- | **Query Parameters** | Support for `?key=value` in URLs |
777
- | **Programmatic Navigation** | `$.router.go(path)` |
778
- | **Auto-cleanup** | Works with `$.page` for memory management |
779
- | **Zero Dependencies** | Pure vanilla JavaScript |
780
- | **Lazy Loading Ready** | Easy code splitting |
781
-
782
- ---
783
-
784
- > **Pro Tip:** Order matters in route definitions - put more specific routes (with parameters) before static ones, and always include a catch-all route (404) at the end.