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,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.