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
package/Readme.md
CHANGED
|
@@ -1,1008 +1,164 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
[![
|
|
8
|
-
|
|
9
|
-
[
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
});
|
|
166
|
-
// Log: "Count is: 0"
|
|
167
|
-
|
|
168
|
-
count(1);
|
|
169
|
-
// Log: "Count is: 1"
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
#### Effect with Cleanup
|
|
173
|
-
|
|
174
|
-
```javascript
|
|
175
|
-
import { $ } from 'sigpro';
|
|
176
|
-
|
|
177
|
-
const userId = $(1);
|
|
178
|
-
|
|
179
|
-
$.effect(() => {
|
|
180
|
-
const id = userId();
|
|
181
|
-
|
|
182
|
-
// Simulate subscription
|
|
183
|
-
const timer = setInterval(() => {
|
|
184
|
-
console.log('Polling user', id);
|
|
185
|
-
}, 1000);
|
|
186
|
-
|
|
187
|
-
// Return cleanup function
|
|
188
|
-
return () => clearInterval(timer);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
userId(2); // Previous timer cleared, new one created
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
**Parameters:**
|
|
195
|
-
- `effectFn`: Function to execute. Can return a cleanup function
|
|
196
|
-
|
|
197
|
-
**Returns:** Function to stop the effect
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
### `$.page(setupFunction)` - Pages
|
|
202
|
-
|
|
203
|
-
Creates a page with automatic cleanup of all signals and effects when navigated away.
|
|
204
|
-
|
|
205
|
-
```javascript
|
|
206
|
-
// pages/about.js
|
|
207
|
-
import { html, $ } from "sigpro";
|
|
208
|
-
|
|
209
|
-
export default $.page(() => {
|
|
210
|
-
const count = $(0);
|
|
211
|
-
const loading = $(false);
|
|
212
|
-
|
|
213
|
-
$.effect(() => {
|
|
214
|
-
if (loading()) {
|
|
215
|
-
// Fetch data...
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
return html`
|
|
220
|
-
<div>
|
|
221
|
-
<h1>About Page</h1>
|
|
222
|
-
<p>Count: ${count}</p>
|
|
223
|
-
<button @click=${() => count(c => c + 1)}>Increment</button>
|
|
224
|
-
</div>
|
|
225
|
-
`;
|
|
226
|
-
});
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
**With parameters:**
|
|
230
|
-
```javascript
|
|
231
|
-
// pages/user.js
|
|
232
|
-
export default $.page(({ params }) => {
|
|
233
|
-
const userId = params.id;
|
|
234
|
-
const userData = $(null);
|
|
235
|
-
|
|
236
|
-
$.effect(() => {
|
|
237
|
-
fetch(`/api/users/${userId}`)
|
|
238
|
-
.then(r => r.json())
|
|
239
|
-
.then(userData);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
return html`<div>User: ${userData}</div>`;
|
|
243
|
-
});
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
**Parameters:**
|
|
247
|
-
- `setupFunction`: Function that returns the page content. Receives `{ params, onUnmount }`
|
|
248
|
-
|
|
249
|
-
**Returns:** A function that creates page instances with props
|
|
250
|
-
|
|
251
|
-
---
|
|
252
|
-
|
|
253
|
-
### `$.component(tagName, setupFunction, observedAttributes, useShadowDOM)` - Web Components
|
|
254
|
-
|
|
255
|
-
Creates Custom Elements with reactive properties. Choose between **Light DOM** (default) or **Shadow DOM** for style encapsulation.
|
|
256
|
-
|
|
257
|
-
### Parameters
|
|
258
|
-
|
|
259
|
-
| Parameter | Type | Default | Description |
|
|
260
|
-
|-----------|------|---------|-------------|
|
|
261
|
-
| `tagName` | `string` | (required) | Custom element tag name (must include a hyphen, e.g., `my-button`) |
|
|
262
|
-
| `setupFunction` | `Function` | (required) | Function that renders the component |
|
|
263
|
-
| `observedAttributes` | `string[]` | `[]` | Observed attributes that react to changes |
|
|
264
|
-
| `useShadowDOM` | `boolean` | `false` | `true` = Shadow DOM (encapsulated), `false` = Light DOM (inherits styles) |
|
|
265
|
-
|
|
266
|
-
---
|
|
267
|
-
|
|
268
|
-
#### 🏠 **Light DOM** (`useShadowDOM = false`) - Default
|
|
269
|
-
|
|
270
|
-
The component **inherits global styles** from the application. Ideal for components that should visually integrate with the rest of the interface.
|
|
271
|
-
|
|
272
|
-
##### Example: Button with Tailwind CSS
|
|
273
|
-
|
|
274
|
-
```javascript
|
|
275
|
-
// button-tailwind.js
|
|
276
|
-
import { $, html } from 'sigpro';
|
|
277
|
-
|
|
278
|
-
$.component('tw-button', (props, { slot, emit }) => {
|
|
279
|
-
const variant = props.variant() || 'primary';
|
|
280
|
-
|
|
281
|
-
const variants = {
|
|
282
|
-
primary: 'bg-blue-500 hover:bg-blue-600 text-white',
|
|
283
|
-
secondary: 'bg-gray-500 hover:bg-gray-600 text-white',
|
|
284
|
-
outline: 'border border-blue-500 text-blue-500 hover:bg-blue-50'
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
return html`
|
|
288
|
-
<button
|
|
289
|
-
class="px-4 py-2 rounded font-semibold transition-colors ${variants[variant]}"
|
|
290
|
-
@click=${() => emit('click')}
|
|
291
|
-
>
|
|
292
|
-
${slot()}
|
|
293
|
-
</button>
|
|
294
|
-
`;
|
|
295
|
-
}, ['variant']); // Observe the 'variant' attribute
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
**Usage in HTML:**
|
|
299
|
-
```html
|
|
300
|
-
<!-- These buttons will inherit global Tailwind styles -->
|
|
301
|
-
<tw-button variant="primary" @click=${handleClick}>
|
|
302
|
-
Save changes
|
|
303
|
-
</tw-button>
|
|
304
|
-
|
|
305
|
-
<tw-button variant="outline">
|
|
306
|
-
Cancel
|
|
307
|
-
</tw-button>
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
##### Example: Form Input with Validation
|
|
311
|
-
|
|
312
|
-
```javascript
|
|
313
|
-
// form-input.js
|
|
314
|
-
$.component('form-input', (props, { emit }) => {
|
|
315
|
-
const handleInput = (e) => {
|
|
316
|
-
const value = e.target.value;
|
|
317
|
-
props.value(value);
|
|
318
|
-
emit('update', value);
|
|
319
|
-
|
|
320
|
-
// Simple validation
|
|
321
|
-
if (props.pattern()) {
|
|
322
|
-
const regex = new RegExp(props.pattern());
|
|
323
|
-
const isValid = regex.test(value);
|
|
324
|
-
emit('validate', isValid);
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
return html`
|
|
329
|
-
<div class="form-group">
|
|
330
|
-
<label class="form-label">${props.label()}</label>
|
|
331
|
-
<input
|
|
332
|
-
type="${props.type() || 'text'}"
|
|
333
|
-
class="form-control ${props.error() ? 'is-invalid' : ''}"
|
|
334
|
-
:value=${props.value}
|
|
335
|
-
@input=${handleInput}
|
|
336
|
-
placeholder="${props.placeholder() || ''}"
|
|
337
|
-
?disabled=${props.disabled}
|
|
338
|
-
/>
|
|
339
|
-
${props.error() ? html`
|
|
340
|
-
<div class="invalid-feedback">${props.error()}</div>
|
|
341
|
-
` : ''}
|
|
342
|
-
</div>
|
|
343
|
-
`;
|
|
344
|
-
}, ['label', 'type', 'value', 'error', 'placeholder', 'disabled', 'pattern']);
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
**Usage:**
|
|
348
|
-
```html
|
|
349
|
-
<form-input
|
|
350
|
-
label="Email"
|
|
351
|
-
type="email"
|
|
352
|
-
:value=${email}
|
|
353
|
-
pattern="^[^\s@]+@[^\s@]+\.[^\s@]+$"
|
|
354
|
-
@update=${(e) => email(e.detail)}
|
|
355
|
-
@validate=${(e) => setEmailValid(e.detail)}
|
|
356
|
-
>
|
|
357
|
-
</form-input>
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
##### Example: Card that uses global design system
|
|
361
|
-
|
|
362
|
-
```javascript
|
|
363
|
-
// content-card.js
|
|
364
|
-
$.component('content-card', (props, { slot }) => {
|
|
365
|
-
return html`
|
|
366
|
-
<div class="card shadow-sm">
|
|
367
|
-
<div class="card-header bg-white">
|
|
368
|
-
<h3 class="card-title h5 mb-0">${props.title()}</h3>
|
|
369
|
-
</div>
|
|
370
|
-
<div class="card-body">
|
|
371
|
-
${slot()}
|
|
372
|
-
</div>
|
|
373
|
-
${props.footer() ? html`
|
|
374
|
-
<div class="card-footer bg-white">
|
|
375
|
-
${props.footer()}
|
|
376
|
-
</div>
|
|
377
|
-
` : ''}
|
|
378
|
-
</div>
|
|
379
|
-
`;
|
|
380
|
-
}, ['title', 'footer']);
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
**Usage:**
|
|
384
|
-
```html
|
|
385
|
-
<content-card title="Recent Activity">
|
|
386
|
-
<p>Your dashboard updates will appear here.</p>
|
|
387
|
-
</content-card>
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
---
|
|
391
|
-
|
|
392
|
-
#### 🛡️ **Shadow DOM** (`useShadowDOM = true`) - Encapsulated
|
|
393
|
-
|
|
394
|
-
The component **encapsulates its styles** completely. External styles don't affect it, and its styles don't leak out. Perfect for:
|
|
395
|
-
- UI libraries distributed across projects
|
|
396
|
-
- Third-party widgets
|
|
397
|
-
- Components with very specific styling needs
|
|
398
|
-
|
|
399
|
-
##### Example: Calendar Component (Distributable UI)
|
|
400
|
-
|
|
401
|
-
```javascript
|
|
402
|
-
// ui-calendar.js
|
|
403
|
-
$.component('ui-calendar', (props, { select }) => {
|
|
404
|
-
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
405
|
-
const currentDate = props.date() ? new Date(props.date()) : new Date();
|
|
406
|
-
|
|
407
|
-
return html`
|
|
408
|
-
<style>
|
|
409
|
-
/* These styles are ENCAPSULATED - won't affect the page */
|
|
410
|
-
.calendar {
|
|
411
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
412
|
-
background: white;
|
|
413
|
-
border-radius: 12px;
|
|
414
|
-
padding: 20px;
|
|
415
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
416
|
-
width: 320px;
|
|
417
|
-
}
|
|
418
|
-
.header {
|
|
419
|
-
display: flex;
|
|
420
|
-
justify-content: space-between;
|
|
421
|
-
align-items: center;
|
|
422
|
-
margin-bottom: 20px;
|
|
423
|
-
}
|
|
424
|
-
.month {
|
|
425
|
-
font-size: 1.2rem;
|
|
426
|
-
font-weight: 600;
|
|
427
|
-
color: #2c3e50;
|
|
428
|
-
}
|
|
429
|
-
.nav-btn {
|
|
430
|
-
background: none;
|
|
431
|
-
border: none;
|
|
432
|
-
font-size: 1.2rem;
|
|
433
|
-
cursor: pointer;
|
|
434
|
-
padding: 4px 12px;
|
|
435
|
-
border-radius: 6px;
|
|
436
|
-
transition: background 0.2s;
|
|
437
|
-
}
|
|
438
|
-
.nav-btn:hover {
|
|
439
|
-
background: #f0f0f0;
|
|
440
|
-
}
|
|
441
|
-
.weekdays {
|
|
442
|
-
display: grid;
|
|
443
|
-
grid-template-columns: repeat(7, 1fr);
|
|
444
|
-
text-align: center;
|
|
445
|
-
font-weight: 500;
|
|
446
|
-
color: #7f8c8d;
|
|
447
|
-
margin-bottom: 10px;
|
|
448
|
-
font-size: 0.85rem;
|
|
449
|
-
}
|
|
450
|
-
.days {
|
|
451
|
-
display: grid;
|
|
452
|
-
grid-template-columns: repeat(7, 1fr);
|
|
453
|
-
gap: 4px;
|
|
454
|
-
}
|
|
455
|
-
.day {
|
|
456
|
-
aspect-ratio: 1;
|
|
457
|
-
display: flex;
|
|
458
|
-
align-items: center;
|
|
459
|
-
justify-content: center;
|
|
460
|
-
cursor: pointer;
|
|
461
|
-
border-radius: 50%;
|
|
462
|
-
transition: all 0.2s;
|
|
463
|
-
font-size: 0.9rem;
|
|
464
|
-
}
|
|
465
|
-
.day:hover {
|
|
466
|
-
background: #e3f2fd;
|
|
467
|
-
}
|
|
468
|
-
.day.selected {
|
|
469
|
-
background: #2196f3;
|
|
470
|
-
color: white;
|
|
471
|
-
font-weight: 500;
|
|
472
|
-
}
|
|
473
|
-
.day.today {
|
|
474
|
-
border: 2px solid #2196f3;
|
|
475
|
-
font-weight: 600;
|
|
476
|
-
}
|
|
477
|
-
.day.other-month {
|
|
478
|
-
color: #bdc3c7;
|
|
479
|
-
}
|
|
480
|
-
</style>
|
|
481
|
-
|
|
482
|
-
<div class="calendar">
|
|
483
|
-
<div class="header">
|
|
484
|
-
<button class="nav-btn" @click=${() => handlePrevMonth()}>←</button>
|
|
485
|
-
<span class="month">${currentDate.toLocaleString('default', { month: 'long', year: 'numeric' })}</span>
|
|
486
|
-
<button class="nav-btn" @click=${() => handleNextMonth()}>→</button>
|
|
487
|
-
</div>
|
|
488
|
-
|
|
489
|
-
<div class="weekdays">
|
|
490
|
-
${days.map(day => html`<span>${day}</span>`)}
|
|
491
|
-
</div>
|
|
492
|
-
|
|
493
|
-
<div class="days">
|
|
494
|
-
${generateDays(currentDate).map(day => html`
|
|
495
|
-
<div
|
|
496
|
-
class="day ${day.classes}"
|
|
497
|
-
@click=${() => selectDate(day.date)}
|
|
498
|
-
>
|
|
499
|
-
${day.number}
|
|
500
|
-
</div>
|
|
501
|
-
`)}
|
|
502
|
-
</div>
|
|
503
|
-
</div>
|
|
504
|
-
`;
|
|
505
|
-
}, ['date'], true); // true = use Shadow DOM
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
**Usage - anywhere, anytime, looks identical:**
|
|
509
|
-
```html
|
|
510
|
-
<!-- Same calendar, same styles, in ANY website -->
|
|
511
|
-
<ui-calendar date="2024-03-15"></ui-calendar>
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
##### Example: Third-party Chat Widget
|
|
515
|
-
|
|
516
|
-
```javascript
|
|
517
|
-
// chat-widget.js
|
|
518
|
-
$.component('chat-widget', (props, { select }) => {
|
|
519
|
-
return html`
|
|
520
|
-
<style>
|
|
521
|
-
/* Completely isolated - won't affect host page */
|
|
522
|
-
:host {
|
|
523
|
-
all: initial; /* Reset all styles */
|
|
524
|
-
display: block;
|
|
525
|
-
}
|
|
526
|
-
.chat-container {
|
|
527
|
-
position: fixed;
|
|
528
|
-
bottom: 20px;
|
|
529
|
-
right: 20px;
|
|
530
|
-
width: 320px;
|
|
531
|
-
height: 480px;
|
|
532
|
-
background: white;
|
|
533
|
-
border-radius: 16px;
|
|
534
|
-
box-shadow: 0 8px 30px rgba(0,0,0,0.2);
|
|
535
|
-
display: flex;
|
|
536
|
-
flex-direction: column;
|
|
537
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
538
|
-
z-index: 2147483647; /* Max z-index */
|
|
539
|
-
}
|
|
540
|
-
.header {
|
|
541
|
-
padding: 16px;
|
|
542
|
-
background: #075e54;
|
|
543
|
-
color: white;
|
|
544
|
-
border-radius: 16px 16px 0 0;
|
|
545
|
-
display: flex;
|
|
546
|
-
align-items: center;
|
|
547
|
-
gap: 12px;
|
|
548
|
-
}
|
|
549
|
-
.avatar {
|
|
550
|
-
width: 40px;
|
|
551
|
-
height: 40px;
|
|
552
|
-
background: #128c7e;
|
|
553
|
-
border-radius: 50%;
|
|
554
|
-
display: flex;
|
|
555
|
-
align-items: center;
|
|
556
|
-
justify-content: center;
|
|
557
|
-
font-weight: bold;
|
|
558
|
-
font-size: 1.2rem;
|
|
559
|
-
}
|
|
560
|
-
.title {
|
|
561
|
-
font-weight: 600;
|
|
562
|
-
}
|
|
563
|
-
.subtitle {
|
|
564
|
-
font-size: 0.8rem;
|
|
565
|
-
opacity: 0.8;
|
|
566
|
-
}
|
|
567
|
-
.messages {
|
|
568
|
-
flex: 1;
|
|
569
|
-
padding: 16px;
|
|
570
|
-
overflow-y: auto;
|
|
571
|
-
background: #e5ddd5;
|
|
572
|
-
display: flex;
|
|
573
|
-
flex-direction: column;
|
|
574
|
-
gap: 8px;
|
|
575
|
-
}
|
|
576
|
-
.message {
|
|
577
|
-
max-width: 80%;
|
|
578
|
-
padding: 8px 12px;
|
|
579
|
-
border-radius: 12px;
|
|
580
|
-
font-size: 0.9rem;
|
|
581
|
-
word-wrap: break-word;
|
|
582
|
-
}
|
|
583
|
-
.message.received {
|
|
584
|
-
background: white;
|
|
585
|
-
align-self: flex-start;
|
|
586
|
-
border-bottom-left-radius: 4px;
|
|
587
|
-
}
|
|
588
|
-
.message.sent {
|
|
589
|
-
background: #dcf8c6;
|
|
590
|
-
align-self: flex-end;
|
|
591
|
-
border-bottom-right-radius: 4px;
|
|
592
|
-
}
|
|
593
|
-
.footer {
|
|
594
|
-
padding: 12px;
|
|
595
|
-
background: #f0f0f0;
|
|
596
|
-
border-radius: 0 0 16px 16px;
|
|
597
|
-
display: flex;
|
|
598
|
-
gap: 8px;
|
|
599
|
-
}
|
|
600
|
-
.input {
|
|
601
|
-
flex: 1;
|
|
602
|
-
padding: 8px 12px;
|
|
603
|
-
border: none;
|
|
604
|
-
border-radius: 20px;
|
|
605
|
-
outline: none;
|
|
606
|
-
font-size: 0.9rem;
|
|
607
|
-
}
|
|
608
|
-
.send-btn {
|
|
609
|
-
width: 40px;
|
|
610
|
-
height: 40px;
|
|
611
|
-
border: none;
|
|
612
|
-
border-radius: 50%;
|
|
613
|
-
background: #075e54;
|
|
614
|
-
color: white;
|
|
615
|
-
cursor: pointer;
|
|
616
|
-
display: flex;
|
|
617
|
-
align-items: center;
|
|
618
|
-
justify-content: center;
|
|
619
|
-
transition: background 0.2s;
|
|
620
|
-
}
|
|
621
|
-
.send-btn:hover {
|
|
622
|
-
background: #128c7e;
|
|
623
|
-
}
|
|
624
|
-
.send-btn:disabled {
|
|
625
|
-
opacity: 0.5;
|
|
626
|
-
cursor: not-allowed;
|
|
627
|
-
}
|
|
628
|
-
</style>
|
|
629
|
-
|
|
630
|
-
<div class="chat-container">
|
|
631
|
-
<div class="header">
|
|
632
|
-
<div class="avatar">💬</div>
|
|
633
|
-
<div>
|
|
634
|
-
<div class="title">Support Chat</div>
|
|
635
|
-
<div class="subtitle">Online</div>
|
|
636
|
-
</div>
|
|
637
|
-
</div>
|
|
638
|
-
|
|
639
|
-
<div class="messages" id="messageContainer">
|
|
640
|
-
${props.messages().map(msg => html`
|
|
641
|
-
<div class="message ${msg.type}">${msg.text}</div>
|
|
642
|
-
`)}
|
|
643
|
-
</div>
|
|
644
|
-
|
|
645
|
-
<div class="footer">
|
|
646
|
-
<input
|
|
647
|
-
type="text"
|
|
648
|
-
class="input"
|
|
649
|
-
placeholder="Type a message..."
|
|
650
|
-
:value=${props.currentMessage}
|
|
651
|
-
@keydown.enter=${() => sendMessage()}
|
|
652
|
-
/>
|
|
653
|
-
<button
|
|
654
|
-
class="send-btn"
|
|
655
|
-
?disabled=${!props.currentMessage()}
|
|
656
|
-
@click=${() => sendMessage()}
|
|
657
|
-
>
|
|
658
|
-
➤
|
|
659
|
-
</button>
|
|
660
|
-
</div>
|
|
661
|
-
</div>
|
|
662
|
-
`;
|
|
663
|
-
}, ['messages', 'currentMessage'], true);
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
**Usage - embed in ANY website:**
|
|
667
|
-
```html
|
|
668
|
-
<chat-widget .messages=${chatHistory} .currentMessage=${newMessage}></chat-widget>
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
---
|
|
672
|
-
|
|
673
|
-
### 🎯 **Quick Decision Guide**
|
|
674
|
-
|
|
675
|
-
| Use Light DOM (`false`) when... | Use Shadow DOM (`true`) when... |
|
|
676
|
-
|--------------------------------|-------------------------------|
|
|
677
|
-
| ✅ Component is part of your main app | ✅ Building a UI library for others |
|
|
678
|
-
| ✅ Using global CSS (Tailwind, Bootstrap) | ✅ Creating embeddable widgets |
|
|
679
|
-
| ✅ Need to inherit theme variables | ✅ Styles must be pixel-perfect everywhere |
|
|
680
|
-
| ✅ Working with existing design system | ✅ Component has complex, specific styles |
|
|
681
|
-
| ✅ Quick prototyping | ✅ Distributing to different projects |
|
|
682
|
-
| ✅ Form elements that should match site | ✅ Need style isolation/encapsulation |
|
|
683
|
-
|
|
684
|
-
### 💡 **Pro Tips**
|
|
685
|
-
|
|
686
|
-
1. **Light DOM components** are great for app-specific UI that should feel "native" to your site
|
|
687
|
-
2. **Shadow DOM components** are perfect for reusable "products" that must look identical everywhere
|
|
688
|
-
3. You can mix both in the same app - choose per component based on needs
|
|
689
|
-
4. Shadow DOM also provides DOM isolation - great for complex widgets
|
|
690
|
-
|
|
691
|
-
```javascript
|
|
692
|
-
// Mix and match in the same app!
|
|
693
|
-
$.component('app-header', setup, ['title']); // Light DOM
|
|
694
|
-
$.component('user-menu', setup, ['items']); // Light DOM
|
|
695
|
-
$.component('chat-widget', setup, ['messages'], true); // Shadow DOM
|
|
696
|
-
$.component('data-grid', setup, ['columns', 'data'], true); // Shadow DOM
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
---
|
|
700
|
-
|
|
701
|
-
### `$.fetch(url, data, [loading])` - Fetch
|
|
702
|
-
|
|
703
|
-
Simple fetch wrapper with automatic JSON handling and optional loading signal.
|
|
704
|
-
|
|
705
|
-
```javascript
|
|
706
|
-
import { $ } from 'sigpro';
|
|
707
|
-
|
|
708
|
-
const loading = $(false);
|
|
709
|
-
|
|
710
|
-
async function loadUser(id) {
|
|
711
|
-
const data = await $.fetch(`/api/users/${id}`, null, loading);
|
|
712
|
-
if (data) userData(data);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// In your UI
|
|
716
|
-
html`<div>${() => loading() ? 'Loading...' : userData()?.name}</div>`;
|
|
717
|
-
```
|
|
718
|
-
|
|
719
|
-
**Parameters:**
|
|
720
|
-
- `url`: Endpoint URL
|
|
721
|
-
- `data`: Data to send (auto JSON.stringify'd)
|
|
722
|
-
- `loading`: Optional signal function to track loading state
|
|
723
|
-
|
|
724
|
-
**Returns:** `Promise<Object|null>` - Parsed JSON response or null on error
|
|
725
|
-
|
|
726
|
-
---
|
|
727
|
-
|
|
728
|
-
### `$.storage(key, initialValue, [storage])` - Persistent Signal
|
|
729
|
-
|
|
730
|
-
Signal that automatically syncs with localStorage or sessionStorage.
|
|
731
|
-
|
|
732
|
-
```javascript
|
|
733
|
-
import { $ } from 'sigpro';
|
|
734
|
-
|
|
735
|
-
// Automatically saves to localStorage
|
|
736
|
-
const theme = $.storage('theme', 'light');
|
|
737
|
-
const user = $.storage('user', null);
|
|
738
|
-
|
|
739
|
-
theme('dark'); // Saved to localStorage
|
|
740
|
-
// Page refresh... theme() returns 'dark'
|
|
741
|
-
|
|
742
|
-
// Use sessionStorage instead
|
|
743
|
-
const tempData = $.storage('temp', {}, sessionStorage);
|
|
744
|
-
```
|
|
745
|
-
|
|
746
|
-
**Parameters:**
|
|
747
|
-
- `key`: Storage key name
|
|
748
|
-
- `initialValue`: Default value if none stored
|
|
749
|
-
- `storage`: Storage type (default: `localStorage`, options: `sessionStorage`)
|
|
750
|
-
|
|
751
|
-
**Returns:** Signal function that persists to storage on changes
|
|
752
|
-
|
|
753
|
-
---
|
|
754
|
-
|
|
755
|
-
### `$.router(routes)` - Hash-Based Router
|
|
756
|
-
|
|
757
|
-
Creates a simple, powerful hash-based router for Single Page Applications (SPAs) with **automatic page cleanup** and **zero configuration**. Built on native browser APIs - no dependencies, no complex setup.
|
|
758
|
-
|
|
759
|
-
### Why Hash-Based?
|
|
760
|
-
|
|
761
|
-
Hash routing (`#/about`) works **everywhere** - no server configuration needed. It's perfect for:
|
|
762
|
-
- Static sites and SPAs
|
|
763
|
-
- GitHub Pages, Netlify, any static hosting
|
|
764
|
-
- Local development without a server
|
|
765
|
-
- Projects that need to work immediately
|
|
766
|
-
|
|
767
|
-
### Basic Usage
|
|
768
|
-
|
|
769
|
-
```javascript
|
|
770
|
-
import { $, html } from 'sigpro';
|
|
771
|
-
import HomePage from './pages/HomePage.js';
|
|
772
|
-
import AboutPage from './pages/AboutPage.js';
|
|
773
|
-
import UserPage from './pages/UserPage.js';
|
|
774
|
-
import NotFound from './pages/NotFound.js';
|
|
775
|
-
|
|
776
|
-
// Define your routes
|
|
777
|
-
const routes = [
|
|
778
|
-
{ path: '/', component: () => HomePage() },
|
|
779
|
-
{ path: '/about', component: () => AboutPage() },
|
|
780
|
-
{ path: '/users/:id', component: (params) => UserPage(params) },
|
|
781
|
-
{ path: /^\/posts\/(?<id>\d+)$/, component: (params) => PostPage(params) },
|
|
782
|
-
];
|
|
783
|
-
|
|
784
|
-
// Create and mount the router
|
|
785
|
-
const router = $.router(routes);
|
|
786
|
-
document.body.appendChild(router);
|
|
787
|
-
```
|
|
788
|
-
|
|
789
|
-
---
|
|
790
|
-
|
|
791
|
-
### 📋 Route Definition
|
|
792
|
-
|
|
793
|
-
Each route is an object with two properties:
|
|
794
|
-
|
|
795
|
-
| Property | Type | Description |
|
|
796
|
-
|----------|------|-------------|
|
|
797
|
-
| `path` | `string` or `RegExp` | Route pattern to match |
|
|
798
|
-
| `component` | `Function` | Function that returns page content (receives `params`) |
|
|
799
|
-
|
|
800
|
-
#### String Paths (Simple Routes)
|
|
801
|
-
|
|
802
|
-
```javascript
|
|
803
|
-
{ path: '/', component: () => HomePage() }
|
|
804
|
-
{ path: '/about', component: () => AboutPage() }
|
|
805
|
-
{ path: '/contact', component: () => ContactPage() }
|
|
806
|
-
{ path: '/users/:id', component: (params) => UserPage(params) } // With parameter
|
|
807
|
-
```
|
|
808
|
-
|
|
809
|
-
String paths support:
|
|
810
|
-
- **Static segments**: `/about`, `/contact`, `/products`
|
|
811
|
-
- **Named parameters**: `:id`, `:slug`, `:username` (captured in `params`)
|
|
812
|
-
|
|
813
|
-
#### RegExp Paths (Advanced Routing)
|
|
814
|
-
|
|
815
|
-
```javascript
|
|
816
|
-
// Match numeric IDs only
|
|
817
|
-
{ path: /^\/users\/(?<id>\d+)$/, component: (params) => UserPage(params) }
|
|
818
|
-
|
|
819
|
-
// Match product slugs (letters, numbers, hyphens)
|
|
820
|
-
{ path: /^\/products\/(?<slug>[a-z0-9-]+)$/, component: (params) => ProductPage(params) }
|
|
821
|
-
|
|
822
|
-
// Match blog posts by year/month
|
|
823
|
-
{ path: /^\/blog\/(?<year>\d{4})\/(?<month>\d{2})$/, component: (params) => BlogArchive(params) }
|
|
824
|
-
|
|
825
|
-
// Match optional language prefix
|
|
826
|
-
{ path: /^\/(?<lang>en|es|fr)?\/?about$/, component: (params) => AboutPage(params) }
|
|
827
|
-
```
|
|
828
|
-
|
|
829
|
-
RegExp gives you **full control** over route matching with named capture groups.
|
|
830
|
-
|
|
831
|
-
---
|
|
832
|
-
|
|
833
|
-
## 🧭 `$.router(routes)` - Simple Router with Parameters
|
|
834
|
-
|
|
835
|
-
Creates a hash-based router with support for `:param` parameters. Automatically cleans up pages when navigating away.
|
|
836
|
-
|
|
837
|
-
### 📋 Route Parameters (Human-Friendly)
|
|
838
|
-
|
|
839
|
-
```javascript
|
|
840
|
-
const routes = [
|
|
841
|
-
{ path: '/', component: HomePage },
|
|
842
|
-
{ path: '/about', component: AboutPage },
|
|
843
|
-
{ path: '/user/:id', component: UserPage }, // /user/42 → { id: '42' }
|
|
844
|
-
{ path: '/user/:id/posts', component: UserPostsPage }, // /user/42/posts → { id: '42' }
|
|
845
|
-
{ path: '/user/:id/posts/:pid', component: PostPage }, // /user/42/posts/123 → { id: '42', pid: '123' }
|
|
846
|
-
{ path: '/search/:query/page/:num', component: SearchPage }, // /search/js/page/2 → { query: 'js', num: '2' }
|
|
847
|
-
];
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
### 🎯 Accessing Parameters in Pages
|
|
851
|
-
|
|
852
|
-
Parameters are automatically extracted and passed to your page component:
|
|
853
|
-
|
|
854
|
-
```javascript
|
|
855
|
-
// pages/UserPage.js
|
|
856
|
-
import { $, html } from 'sigpro';
|
|
857
|
-
|
|
858
|
-
export default (params) => $.page(() => {
|
|
859
|
-
// /user/42 → params = { id: '42' }
|
|
860
|
-
const userId = params.id;
|
|
861
|
-
const userData = $(null);
|
|
862
|
-
|
|
863
|
-
return html`
|
|
864
|
-
<div>
|
|
865
|
-
<h1>User Profile: ${userId}</h1>
|
|
866
|
-
<p>Loading user data...</p>
|
|
867
|
-
</div>
|
|
868
|
-
`;
|
|
869
|
-
});
|
|
870
|
-
|
|
871
|
-
// pages/PostPage.js
|
|
872
|
-
export default (params) => $.page(() => {
|
|
873
|
-
// /user/42/posts/123 → params = { id: '42', pid: '123' }
|
|
874
|
-
const { id, pid } = params;
|
|
875
|
-
|
|
876
|
-
return html`
|
|
877
|
-
<div>
|
|
878
|
-
<h1>Post ${pid} from user ${id}</h1>
|
|
879
|
-
</div>
|
|
880
|
-
`;
|
|
881
|
-
});
|
|
882
|
-
```
|
|
883
|
-
|
|
884
|
-
### 🧭 Navigation
|
|
885
|
-
|
|
886
|
-
```javascript
|
|
887
|
-
// Programmatic navigation
|
|
888
|
-
$.router.go('/user/42');
|
|
889
|
-
$.router.go('/search/javascript/page/2');
|
|
890
|
-
$.router.go('about'); // Same as '/about' (auto-adds leading slash)
|
|
891
|
-
|
|
892
|
-
// Link navigation (in templates)
|
|
893
|
-
html`
|
|
894
|
-
<nav>
|
|
895
|
-
<a href="#/">Home</a>
|
|
896
|
-
<a href="#/user/42">Profile</a>
|
|
897
|
-
<button @click=${() => $.router.go('/contact')}>Contact</button>
|
|
898
|
-
</nav>
|
|
899
|
-
`;
|
|
900
|
-
```
|
|
901
|
-
|
|
902
|
-
### 🔄 Automatic Page Cleanup
|
|
903
|
-
|
|
904
|
-
```javascript
|
|
905
|
-
export default (params) => $.page(({ onUnmount }) => {
|
|
906
|
-
// Set up interval
|
|
907
|
-
const interval = setInterval(() => {
|
|
908
|
-
fetchData(params.id);
|
|
909
|
-
}, 5000);
|
|
910
|
-
|
|
911
|
-
// Auto-cleaned when navigating away
|
|
912
|
-
onUnmount(() => clearInterval(interval));
|
|
913
|
-
|
|
914
|
-
return html`<div>Page content</div>`;
|
|
915
|
-
});
|
|
916
|
-
```
|
|
917
|
-
|
|
918
|
-
### 📦 Usage in Templates
|
|
919
|
-
|
|
920
|
-
```javascript
|
|
921
|
-
import { $, html } from 'sigpro';
|
|
922
|
-
import HomePage from './pages/Home.js';
|
|
923
|
-
import UserPage from './pages/User.js';
|
|
924
|
-
|
|
925
|
-
const routes = [
|
|
926
|
-
{ path: '/', component: HomePage },
|
|
927
|
-
{ path: '/user/:id', component: UserPage },
|
|
928
|
-
];
|
|
929
|
-
|
|
930
|
-
// Mount router directly in your template
|
|
931
|
-
const App = () => html`
|
|
932
|
-
<div class="app">
|
|
933
|
-
<header>My App</header>
|
|
934
|
-
<main>
|
|
935
|
-
${$.router(routes)} <!-- Router renders here -->
|
|
936
|
-
</main>
|
|
937
|
-
</div>
|
|
938
|
-
`;
|
|
939
|
-
|
|
940
|
-
document.body.appendChild(App());
|
|
941
|
-
```
|
|
942
|
-
|
|
943
|
-
### 🎯 API Reference
|
|
944
|
-
|
|
945
|
-
#### `$.router(routes)`
|
|
946
|
-
- **routes**: `Array<{path: string, component: Function}>` - Route configurations with `:param` support
|
|
947
|
-
- **Returns**: `HTMLDivElement` - Container that renders the current page
|
|
948
|
-
|
|
949
|
-
#### `$.router.go(path)`
|
|
950
|
-
- **path**: `string` - Route path (automatically adds leading slash)
|
|
951
|
-
|
|
952
|
-
### 💡 Pro Tips
|
|
953
|
-
|
|
954
|
-
1. **Order matters** - Define more specific routes first:
|
|
955
|
-
```javascript
|
|
956
|
-
[
|
|
957
|
-
{ path: '/user/:id/edit', component: EditUser }, // More specific first
|
|
958
|
-
{ path: '/user/:id', component: ViewUser }, // Then generic
|
|
959
|
-
]
|
|
960
|
-
```
|
|
961
|
-
|
|
962
|
-
2. **Cleanup is automatic** - All effects, intervals, and event listeners in `$.page` are cleaned up
|
|
963
|
-
|
|
964
|
-
3. **Zero config** - Just define routes and use them
|
|
965
|
-
|
|
966
|
-
---
|
|
967
|
-
|
|
968
|
-
### `html` - Template Literal Tag
|
|
969
|
-
|
|
970
|
-
Creates reactive DOM fragments using template literals.
|
|
971
|
-
|
|
972
|
-
#### Basic Usage
|
|
973
|
-
|
|
974
|
-
```javascript
|
|
975
|
-
import { $, html } from 'sigpro';
|
|
976
|
-
|
|
977
|
-
const count = $(0);
|
|
978
|
-
|
|
979
|
-
const fragment = html`
|
|
980
|
-
<div>
|
|
981
|
-
<h1>Count: ${count}</h1>
|
|
982
|
-
<button @click=${() => count(c => c + 1)}>+</button>
|
|
983
|
-
</div>
|
|
984
|
-
`;
|
|
985
|
-
```
|
|
986
|
-
|
|
987
|
-
#### Directive Reference
|
|
988
|
-
|
|
989
|
-
| Directive | Example | Description |
|
|
990
|
-
|-----------|---------|-------------|
|
|
991
|
-
| `@event` | `@click=${handler}` | Event listener |
|
|
992
|
-
| `:property` | `:value=${signal}` | Two-way binding |
|
|
993
|
-
| `?attribute` | `?disabled=${signal}` | Boolean attribute |
|
|
994
|
-
| `.property` | `.scrollTop=${signal}` | Property binding |
|
|
995
|
-
|
|
996
|
-
**Two-way binding example:**
|
|
997
|
-
```javascript
|
|
998
|
-
const text = $('');
|
|
999
|
-
|
|
1000
|
-
html`
|
|
1001
|
-
<input :value=${text} />
|
|
1002
|
-
<p>You typed: ${text}</p>
|
|
1003
|
-
`;
|
|
1004
|
-
```
|
|
1005
|
-
|
|
1006
|
-
## 📝 License
|
|
1007
|
-
|
|
1008
|
-
MIT © natxocc
|
|
1
|
+
Blazing fast, zero-overhead, vanilla JS renderer with atomic reactivity.
|
|
2
|
+
|
|
3
|
+
# `SigPro`
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/sigpro)
|
|
6
|
+

|
|
7
|
+
[](https://github.com/natxocc/sigpro/blob/main/LICENSE)
|
|
8
|
+
|
|
9
|
+
[**Explore the Docs →**](https://sigpro.natxocc.com/#/)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Why SigPro?
|
|
14
|
+
|
|
15
|
+
After years of building within closed ecosystems like **React, Vue, or Svelte**—spending countless hours mastering their internal rules and extra development time on abstractions—we must face an inevitable truth: all those complex "wrappers" must ultimately be translated back into Pure JavaScript for the browser to understand.
|
|
16
|
+
|
|
17
|
+
That extra development time and the cognitive load of "learning the framework" instead of "learning the language" is exactly what **SigPro** eliminates. If the final destination is always JS, why not use a Pure JS-based system that drastically simplifies coding with a readable, vanilla, and remarkably fast architecture?
|
|
18
|
+
|
|
19
|
+
* **Atomic Precision:** Powered by a *Signal-based* architecture. State is bound directly to DOM nodes—when a value changes, **only that specific node updates**.
|
|
20
|
+
* **Zero-Hydration Bottlenecks:** No 100KB bundles or complex build steps. SigPro is pure, optimized JavaScript tailored for the browser's native engine.
|
|
21
|
+
* **Pure Vanilla JS Performance:** High-octane performance without the need for transpilers or heavy transformations. It runs natively in the browser just as well as it does in complex build pipelines.
|
|
22
|
+
* **Build-Tool Agnostic:** Total freedom. Use it with **Vite, Webpack, or Rollup** for enterprise projects, or simply import it via a **`<script>` tag** for rapid prototyping. No tooling required.
|
|
23
|
+
* **Vite-Powered DX:** First-class Vite support with **file-based routing** out of the box. The official `sigpro/vite` plugin automatically scans your `src/pages` directory and generates reactive routes—no manual route configuration needed.
|
|
24
|
+
* **Zero-Scale Bloat:** Unlike other frameworks where the bundle grows exponentially, SigPro's footprint remains **flat and predictable**. You only pay for the code you write.
|
|
25
|
+
* **Premium DX (Developer Experience):** Forget boilerplate imports. SigPro injects an elegant, functional syntax (`div()`, `button()`, `span()`) directly into your scope for a **"Zero-Import"** workflow.
|
|
26
|
+
* **Fully Loaded:** Built-in Hash Routing, native **`localStorage` persistence**, and automatic lifecycle management (cleanups) included in less than 2KB.
|
|
27
|
+
* **Tree-Shakable:** Optimized for modern bundlers. Import only what you use, or load the full engine for rapid prototyping.
|
|
28
|
+
|
|
29
|
+
-----
|
|
30
|
+
|
|
31
|
+
## Real-World Benchmarks
|
|
32
|
+
|
|
33
|
+
SigPro isn't just "fast on paper." In the industry-standard **JS Framework Benchmark**, it consistently outperforms the most popular libraries by operating at near-native speeds with almost zero memory overhead.
|
|
34
|
+
|
|
35
|
+
### Execution Speed (CPU)
|
|
36
|
+
*Lower is better. Measured in milliseconds (ms).*
|
|
37
|
+
|
|
38
|
+
| Benchmark Test | **SigPro** | SolidJS | Vue 3 | React 18 |
|
|
39
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
40
|
+
| **Surgical Update** (10th row) | **46.8ms** | ~48ms | ~75ms | ~158ms |
|
|
41
|
+
| **Direct Selection** (on click) | **17.5ms** | ~18ms | ~32ms | ~65ms |
|
|
42
|
+
| **Initial Render** (1k rows) | **~35ms** | ~32ms | ~45ms | ~70ms |
|
|
43
|
+
|
|
44
|
+
### Memory Footprint
|
|
45
|
+
*Lower is better. Measured in Megabytes (MB) after 1k rows.*
|
|
46
|
+
|
|
47
|
+
| Metric | **SigPro** | Vanilla JS | Svelte | React |
|
|
48
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
49
|
+
| **Ready Memory** (Idle) | **1.05 MB** | 1.01 MB | ~2.8 MB | ~10.4 MB |
|
|
50
|
+
| **Run Memory** (1k rows) | **4.90 MB** | 4.25 MB | ~10.2 MB | ~28.5 MB |
|
|
51
|
+
|
|
52
|
+
> **The Verdict:** SigPro delivers **Vanilla-like memory consumption** while maintaining **Surgical reactivity** that rivals (and often beats) SolidJS in granular updates.
|
|
53
|
+
|
|
54
|
+
-----
|
|
55
|
+
|
|
56
|
+
## Elegance in Action
|
|
57
|
+
|
|
58
|
+
Create reactive, persistent components with a syntax that feels like Vanilla JS, but works like magic:
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
<div id="app"></div>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
import { $, mount } from "sigpro";
|
|
66
|
+
|
|
67
|
+
const Counter = () => {
|
|
68
|
+
// Simple signal
|
|
69
|
+
const value = $(100);
|
|
70
|
+
// One-line persistence: state survives page reloads automatically
|
|
71
|
+
const count = $(0, "user-counter-pref");
|
|
72
|
+
// Computed: automatically updated when count() or value() changes
|
|
73
|
+
const doubleValue = $(()=> value() * count());
|
|
74
|
+
|
|
75
|
+
// Create fast HTML with pure JS
|
|
76
|
+
return div({ class: "card" }, [
|
|
77
|
+
h1(() => `Count: ${count()}, Reference: ${value()}, Double x Ref: ${doubleValue()}`),
|
|
78
|
+
p("Atomic updates. Zero re-renders of the parent tree."),
|
|
79
|
+
button({ onclick: () => count(c => c + 1)}, "Increment +1")
|
|
80
|
+
]);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
mount(Counter, "#app");
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
-----
|
|
87
|
+
|
|
88
|
+
## Performance Without Compromise
|
|
89
|
+
|
|
90
|
+
| Feature | **SigPro** | React / Vue | Svelte |
|
|
91
|
+
| :--- | :--- | :--- | :--- |
|
|
92
|
+
| **Payload (Gzipped)** | **<3KB** | ~30KB - 50KB | ~5KB (Compiled Runtime) |
|
|
93
|
+
| **State Logic** | **Atomic Signals** | Virtual DOM Diffing | Compiler Dirty Bits |
|
|
94
|
+
| **Update Speed** | **Direct Node Access** | Component Re-render | Block Reconciliation |
|
|
95
|
+
| **Native Persistence** | **Included ($)** | Requires Plugins | Manual |
|
|
96
|
+
| **Dependencies** | **Zero** | Many | Build Toolchain |
|
|
97
|
+
| **Lifecycle Mgmt** | **Automatic (Cleanup Root)** | Manual / Hook-based | Manual / Hook-based |
|
|
98
|
+
| **Routing** | **Reactive Hash (Router) + File-based (Vite)** | Virtual Router (External) | File-based / External |
|
|
99
|
+
| **Learning Curve** | **Zero (Vanilla JS)** | Steep (JSX/Templates) | Medium (Directives) |
|
|
100
|
+
|
|
101
|
+
-----
|
|
102
|
+
|
|
103
|
+
## Scalable Architecture with Vite
|
|
104
|
+
|
|
105
|
+
SigPro scales from micro-widgets to full enterprise dashboards. With the official Vite plugin, routing becomes effortless:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
src/
|
|
109
|
+
├── 📂 pages/ # File-based routing (auto-scanned)
|
|
110
|
+
│ ├── index.js # → /
|
|
111
|
+
│ ├── about.js # → /about
|
|
112
|
+
│ ├── blog/
|
|
113
|
+
│ │ ├── index.js # → /blog
|
|
114
|
+
│ │ └── [slug].js # → /blog/:slug
|
|
115
|
+
│ └── docs/
|
|
116
|
+
│ └── [...all].js # → /docs/*
|
|
117
|
+
├── 📂 components/ # Reusable components
|
|
118
|
+
└── 📄 main.js # App Entry & Mounting
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Vite Plugin Setup:**
|
|
122
|
+
```javascript
|
|
123
|
+
// vite.config.js
|
|
124
|
+
import { defineConfig } from 'vite';
|
|
125
|
+
import { sigproRouter } from 'sigpro/router';
|
|
126
|
+
|
|
127
|
+
export default defineConfig({
|
|
128
|
+
plugins: [sigproRouter()]
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The plugin automatically:
|
|
133
|
+
- Scans your `src/pages` directory recursively
|
|
134
|
+
- Generates route definitions from file paths
|
|
135
|
+
- Supports dynamic segments (`[param]`) and catch-all routes (`[...param]`)
|
|
136
|
+
- Provides automatic 404 fallback
|
|
137
|
+
- Enables lazy-loading out of the box
|
|
138
|
+
|
|
139
|
+
-----
|
|
140
|
+
|
|
141
|
+
## Quick Start
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npm install sigpro
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**With Vite (Recommended for larger apps):**
|
|
148
|
+
```bash
|
|
149
|
+
npm create vite@latest my-app -- --template vanilla
|
|
150
|
+
cd my-app
|
|
151
|
+
npm install sigpro
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Or without build tools:**
|
|
155
|
+
```html
|
|
156
|
+
<script src="https://unpkg.com/sigpro"></script>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
-----
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT © 2026 **SigPro Team**.
|
|
164
|
+
*Engineered for speed, designed for clarity, built for the modern web.*
|