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