sigpro 1.0.0 → 1.0.2

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.
@@ -0,0 +1,208 @@
1
+ # SigPro Playground 🚀
2
+
3
+ An interactive online environment to experiment with **SigPro** - a minimalist reactive library for building reactive user interfaces directly in the browser.
4
+
5
+ ![SigPro Playground](https://via.placeholder.com/800x400?text=SigPro+Playground)
6
+
7
+ ## 🌟 Features
8
+
9
+ - **Zero Setup** - No installation required, works directly in the browser
10
+ - **Live Preview** - See your code results in real-time
11
+ - **Built-in SigPro** - Full library included with `$`, `$$`, `html`, `$component`, and `$router`
12
+ - **Error Console** - Instant feedback on syntax and runtime errors
13
+ - **Code Sharing** - Generate shareable URLs with your code
14
+ - **Auto-execution** - Runs automatically as you type (with debounce)
15
+ - **Keyboard Shortcuts** - Ctrl+Enter to manually execute
16
+ - **Clean Interface** - Modern dark-themed editor inspired by VS Code
17
+
18
+ ## 🎮 Quick Start
19
+
20
+ ### Online Version
21
+ Simply open the `play.html` file in your web browser and start coding!
22
+
23
+ ### Write Your First Code
24
+
25
+ ```javascript
26
+ // Create reactive signals
27
+ const name = $('World');
28
+ const count = $(0);
29
+
30
+ // Create an effect that reacts to changes
31
+ $$(() => {
32
+ document.body.innerHTML = `
33
+ <h1>Hello ${name()}!</h1>
34
+ <p>Count: ${count()}</p>
35
+ <button onclick="count(c => c + 1)">Increment</button>
36
+ `;
37
+ });
38
+
39
+ // Update signals and watch the magic happen
40
+ setTimeout(() => name('SigPro'), 2000);
41
+ ```
42
+
43
+ ## 📖 Usage Guide
44
+
45
+ ### The Editor Panel
46
+
47
+ - **Write Code** - The left panel contains the code editor with syntax highlighting
48
+ - **Auto-execute** - Code runs automatically 1 second after you stop typing
49
+ - **Manual Run** - Click the "Run" button or press `Ctrl+Enter`
50
+ - **Format** - Click "Format" to beautify your code (coming soon)
51
+ - **Share** - Generate a shareable link with your current code
52
+ - **Reset** - Restore the default example
53
+
54
+ ### The Preview Panel
55
+
56
+ - **Live Results** - See your code output in real-time
57
+ - **Error Display** - Any errors appear in the console at the bottom
58
+ - **Clean Slate** - Each execution starts with a fresh environment
59
+
60
+ ### Keyboard Shortcuts
61
+
62
+ | Shortcut | Action |
63
+ |----------|--------|
64
+ | `Ctrl + Enter` | Execute code manually |
65
+ | `Ctrl + S` | Save current code (coming soon) |
66
+
67
+ ## 🎯 Example Code Snippets
68
+
69
+ ### Basic Counter
70
+ ```javascript
71
+ const counter = $(0);
72
+
73
+ setInterval(() => {
74
+ counter(c => c + 1);
75
+ }, 1000);
76
+
77
+ $$(() => {
78
+ document.body.innerHTML = `<h1>Counter: ${counter()}</h1>`;
79
+ });
80
+ ```
81
+
82
+ ### Two-way Binding with html Tag
83
+ ```javascript
84
+ const text = $('Edit me');
85
+
86
+ document.body.appendChild(html`
87
+ <div>
88
+ <input :value="${text}" placeholder="Type something...">
89
+ <p>You typed: ${text}</p>
90
+ </div>
91
+ `);
92
+ ```
93
+
94
+ ### Computed Values
95
+ ```javascript
96
+ const price = $(10);
97
+ const quantity = $(2);
98
+ const total = $(() => price() * quantity());
99
+
100
+ $$(() => {
101
+ document.body.innerHTML = `
102
+ <div>
103
+ <p>Price: $${price()}</p>
104
+ <p>Quantity: ${quantity()}</p>
105
+ <p><strong>Total: $${total()}</strong></p>
106
+ <button onclick="price(p => p + 1)">+ Price</button>
107
+ <button onclick="quantity(q => q + 1)">+ Quantity</button>
108
+ </div>
109
+ `;
110
+ });
111
+ ```
112
+
113
+ ### Custom Component
114
+ ```javascript
115
+ $component('my-button', (props, { emit }) => {
116
+ return html`
117
+ <button @click="${() => emit('click', props.count())}">
118
+ Count is: ${() => props.count()}
119
+ </button>
120
+ `;
121
+ }, ['count']);
122
+
123
+ document.body.appendChild(html`
124
+ <my-button :count="${$(5)}"></my-button>
125
+ `);
126
+ ```
127
+
128
+ ## 🔗 Sharing Code
129
+
130
+ 1. Write your code in the editor
131
+ 2. Click the **Share** button
132
+ 3. Copy the generated URL
133
+ 4. Share it with anyone - they'll see exactly your code!
134
+
135
+ The code is encoded in the URL, so no backend storage is needed.
136
+
137
+ ## 🛠️ Advanced Features
138
+
139
+ ### Using the Router
140
+ ```javascript
141
+ const routes = [
142
+ { path: '/', component: () => html`<h1>Home</h1>` },
143
+ { path: '/about', component: () => html`<h1>About</h1>` },
144
+ { path: /^\/user\/(?<id>.+)/, component: (params) => html`<h1>User: ${params.id}</h1>` }
145
+ ];
146
+
147
+ document.body.appendChild($router(routes));
148
+ $router.go('/about');
149
+ ```
150
+
151
+ ### Working with Lists
152
+ ```javascript
153
+ const items = $(['Apple', 'Banana', 'Orange']);
154
+
155
+ $$(() => {
156
+ document.body.innerHTML = `
157
+ <ul>
158
+ ${items().map(item => `<li>${item}</li>`).join('')}
159
+ </ul>
160
+ <button onclick="items([...items(), 'New Fruit'])">Add</button>
161
+ `;
162
+ });
163
+ ```
164
+
165
+ ## 📦 SigPro API Reference
166
+
167
+ | Function | Description |
168
+ |----------|-------------|
169
+ | `$(value)` | Creates a reactive signal |
170
+ | `$$(effect)` | Creates a reactive effect |
171
+ | `html\`...\`` | Tagged template for reactive HTML |
172
+ | `$component(name, setup, attrs)` | Creates a web component |
173
+ | `$router(routes)` | Creates a hash-based router |
174
+
175
+ ## 🤔 Troubleshooting
176
+
177
+ **Error: "Cannot read property..."**
178
+ - Make sure you're accessing signal values with `signal()`, not `signal`
179
+
180
+ **Nothing shows in preview**
181
+ - Check the error console for syntax errors
182
+ - Make sure you're appending to `document.body` or using `html` correctly
183
+
184
+ **Changes not updating**
185
+ - Verify you're using `$$()` to create effects
186
+ - Signals must be called as functions: `count()` not `count`
187
+
188
+ ## 🌐 Browser Support
189
+
190
+ Works in all modern browsers (Chrome, Firefox, Safari, Edge) that support:
191
+ - JavaScript ES6+
192
+ - Custom Elements (for `$component`)
193
+ - iframe sandboxing
194
+
195
+ ## 📝 License
196
+
197
+ MIT License - feel free to use, modify, and distribute!
198
+
199
+ ## 🤝 Contributing
200
+
201
+ Found a bug or want to improve the playground? Feel free to:
202
+ - Report issues
203
+ - Suggest new features
204
+ - Submit improvements
205
+
206
+ ---
207
+
208
+ **Happy Coding with SigPro!** ⚡
@@ -0,0 +1,846 @@
1
+ <!DOCTYPE html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SigPro Playground - Prueba SigPro Online</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
16
+ background: #0d1117;
17
+ color: #c9d1d9;
18
+ height: 100vh;
19
+ display: flex;
20
+ flex-direction: column;
21
+ }
22
+
23
+ .navbar {
24
+ background: #161b22;
25
+ padding: 0.75rem 1.5rem;
26
+ border-bottom: 1px solid #30363d;
27
+ display: flex;
28
+ justify-content: space-between;
29
+ align-items: center;
30
+ }
31
+
32
+ .logo {
33
+ display: flex;
34
+ align-items: center;
35
+ gap: 0.75rem;
36
+ font-size: 1.25rem;
37
+ font-weight: 600;
38
+ }
39
+
40
+ .logo span {
41
+ background: #2d9cdb;
42
+ color: white;
43
+ padding: 0.25rem 0.75rem;
44
+ border-radius: 20px;
45
+ font-size: 0.875rem;
46
+ }
47
+
48
+ .actions {
49
+ display: flex;
50
+ gap: 0.75rem;
51
+ }
52
+
53
+ .btn {
54
+ padding: 0.5rem 1rem;
55
+ border-radius: 6px;
56
+ border: 1px solid #30363d;
57
+ background: #21262d;
58
+ color: #c9d1d9;
59
+ cursor: pointer;
60
+ font-size: 0.875rem;
61
+ font-weight: 500;
62
+ transition: all 0.2s;
63
+ }
64
+
65
+ .btn:hover {
66
+ background: #30363d;
67
+ border-color: #8b949e;
68
+ }
69
+
70
+ .btn-primary {
71
+ background: #238636;
72
+ border-color: #2ea043;
73
+ color: white;
74
+ }
75
+
76
+ .btn-primary:hover {
77
+ background: #2ea043;
78
+ }
79
+
80
+ .main-container {
81
+ display: flex;
82
+ flex: 1;
83
+ overflow: hidden;
84
+ }
85
+
86
+ .editor-section {
87
+ flex: 1;
88
+ display: flex;
89
+ flex-direction: column;
90
+ border-right: 1px solid #30363d;
91
+ background: #0d1117;
92
+ }
93
+
94
+ .editor-header {
95
+ padding: 0.75rem 1rem;
96
+ background: #161b22;
97
+ border-bottom: 1px solid #30363d;
98
+ display: flex;
99
+ justify-content: space-between;
100
+ align-items: center;
101
+ }
102
+
103
+ .editor-tabs {
104
+ display: flex;
105
+ gap: 1px;
106
+ }
107
+
108
+ .tab {
109
+ padding: 0.5rem 1rem;
110
+ background: #21262d;
111
+ border: 1px solid #30363d;
112
+ border-bottom: none;
113
+ border-radius: 6px 6px 0 0;
114
+ cursor: pointer;
115
+ font-size: 0.875rem;
116
+ }
117
+
118
+ .tab.active {
119
+ background: #0d1117;
120
+ border-bottom-color: #0d1117;
121
+ margin-bottom: -1px;
122
+ color: #2d9cdb;
123
+ }
124
+
125
+ .editor-container {
126
+ flex: 1;
127
+ position: relative;
128
+ overflow: hidden;
129
+ }
130
+
131
+ .code-editor {
132
+ width: 100%;
133
+ height: 100%;
134
+ background: #0d1117;
135
+ color: #c9d1d9;
136
+ font-family: 'Fira Code', 'Courier New', monospace;
137
+ font-size: 14px;
138
+ line-height: 1.6;
139
+ padding: 1rem;
140
+ border: none;
141
+ resize: none;
142
+ outline: none;
143
+ white-space: pre-wrap;
144
+ }
145
+
146
+ .preview-section {
147
+ flex: 1;
148
+ display: flex;
149
+ flex-direction: column;
150
+ background: #0d1117;
151
+ }
152
+
153
+ .preview-header {
154
+ padding: 0.75rem 1rem;
155
+ background: #161b22;
156
+ border-bottom: 1px solid #30363d;
157
+ display: flex;
158
+ justify-content: space-between;
159
+ align-items: center;
160
+ }
161
+
162
+ .preview-iframe {
163
+ flex: 1;
164
+ width: 100%;
165
+ border: none;
166
+ background: white;
167
+ }
168
+
169
+ .error-console {
170
+ background: #1f1f1f;
171
+ border-top: 1px solid #30363d;
172
+ max-height: 150px;
173
+ overflow: auto;
174
+ font-family: monospace;
175
+ font-size: 12px;
176
+ padding: 0.5rem;
177
+ color: #ff7b72;
178
+ }
179
+
180
+ .error-console:empty {
181
+ display: none;
182
+ }
183
+
184
+ .status-bar {
185
+ background: #161b22;
186
+ border-top: 1px solid #30363d;
187
+ padding: 0.25rem 1rem;
188
+ font-size: 0.75rem;
189
+ color: #8b949e;
190
+ display: flex;
191
+ justify-content: space-between;
192
+ }
193
+
194
+ .share-modal {
195
+ display: none;
196
+ position: fixed;
197
+ top: 50%;
198
+ left: 50%;
199
+ transform: translate(-50%, -50%);
200
+ background: #161b22;
201
+ border: 1px solid #30363d;
202
+ border-radius: 8px;
203
+ padding: 2rem;
204
+ z-index: 1000;
205
+ box-shadow: 0 8px 24px rgba(0,0,0,0.5);
206
+ }
207
+
208
+ .share-modal.show {
209
+ display: block;
210
+ }
211
+
212
+ .overlay {
213
+ display: none;
214
+ position: fixed;
215
+ top: 0;
216
+ left: 0;
217
+ right: 0;
218
+ bottom: 0;
219
+ background: rgba(0,0,0,0.7);
220
+ z-index: 999;
221
+ }
222
+
223
+ .overlay.show {
224
+ display: block;
225
+ }
226
+ </style>
227
+ </head>
228
+ <body>
229
+ <div class="navbar">
230
+ <div class="logo">
231
+ ⚡ SigPro Playground
232
+ <span>v1.0.0</span>
233
+ </div>
234
+ <div class="actions">
235
+ <button class="btn" onclick="runCode()">▶ Ejecutar</button>
236
+ <button class="btn" onclick="formatCode()">✨ Formatear</button>
237
+ <button class="btn" onclick="shareCode()">🔗 Compartir</button>
238
+ <button class="btn btn-primary" onclick="resetCode()">↺ Reset</button>
239
+ </div>
240
+ </div>
241
+
242
+ <div class="main-container">
243
+ <!-- Editor Section -->
244
+ <div class="editor-section">
245
+ <div class="editor-header">
246
+ <div class="editor-tabs">
247
+ <div class="tab active">JavaScript (SigPro)</div>
248
+ </div>
249
+ <span style="font-size: 0.75rem; color: #8b949e;">Ctrl + Enter para ejecutar</span>
250
+ </div>
251
+ <div class="editor-container">
252
+ <textarea id="codeEditor" class="code-editor" spellcheck="false" placeholder="// Escribe tu código SigPro aquí&#10;&#10;const nombre = $('Mundo');&#10;&#10;$$(() => {&#10; document.getElementById('saludo').textContent = `¡Hola ${nombre()}!`;&#10;});&#10;&#10;// Cambia el nombre después de 2 segundos&#10;setTimeout(() => nombre('SigPro'), 2000);"></textarea>
253
+ </div>
254
+ </div>
255
+
256
+ <!-- Preview Section -->
257
+ <div class="preview-section">
258
+ <div class="preview-header">
259
+ <span>Resultado</span>
260
+ <div>
261
+ <span id="autoRunIndicator" style="color: #2ea043;">⚡ Auto-ejecución activada</span>
262
+ </div>
263
+ </div>
264
+ <iframe id="preview" class="preview-iframe" sandbox="allow-scripts allow-same-origin allow-forms"></iframe>
265
+ <div id="errorConsole" class="error-console"></div>
266
+ </div>
267
+ </div>
268
+
269
+ <div class="status-bar">
270
+ <span>📦 SigPro - Librería reactiva minimalista</span>
271
+ <span>🔄 Cambios en tiempo real</span>
272
+ </div>
273
+
274
+ <!-- Modal para compartir -->
275
+ <div id="overlay" class="overlay" onclick="closeShareModal()"></div>
276
+ <div id="shareModal" class="share-modal">
277
+ <h3 style="margin-bottom: 1rem;">Comparte tu código</h3>
278
+ <input type="text" id="shareUrl" readonly style="width: 100%; padding: 0.5rem; background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; border-radius: 4px; margin-bottom: 1rem;">
279
+ <div style="display: flex; gap: 0.5rem; justify-content: flex-end;">
280
+ <button class="btn" onclick="copyShareUrl()">Copiar</button>
281
+ <button class="btn btn-primary" onclick="closeShareModal()">Cerrar</button>
282
+ </div>
283
+ </div>
284
+
285
+ <script>
286
+ // ============================================
287
+ // CÓDIGO SIGPRO (COMPLETO)
288
+ // ============================================
289
+
290
+ // Global state for tracking the current reactive effect
291
+ let activeEffect = null;
292
+
293
+ // Queue for batched effect updates
294
+ const effectQueue = new Set();
295
+ let isFlushScheduled = false;
296
+
297
+ const flushEffectQueue = () => {
298
+ isFlushScheduled = false;
299
+ try {
300
+ for (const effect of effectQueue) {
301
+ effect.run();
302
+ }
303
+ effectQueue.clear();
304
+ } catch (error) {
305
+ console.error("SigPro Flush Error:", error);
306
+ }
307
+ };
308
+
309
+ window.$ = (initialValue) => {
310
+ const subscribers = new Set();
311
+
312
+ if (typeof initialValue === "function") {
313
+ let isDirty = true;
314
+ let cachedValue;
315
+
316
+ const computedEffect = {
317
+ dependencies: new Set(),
318
+ cleanupHandlers: new Set(),
319
+ markDirty: () => {
320
+ if (!isDirty) {
321
+ isDirty = true;
322
+ subscribers.forEach((subscriber) => {
323
+ if (subscriber.markDirty) subscriber.markDirty();
324
+ effectQueue.add(subscriber);
325
+ });
326
+ }
327
+ },
328
+ run: () => {
329
+ computedEffect.dependencies.forEach((dependencySet) => dependencySet.delete(computedEffect));
330
+ computedEffect.dependencies.clear();
331
+
332
+ const previousEffect = activeEffect;
333
+ activeEffect = computedEffect;
334
+ try {
335
+ cachedValue = initialValue();
336
+ } finally {
337
+ activeEffect = previousEffect;
338
+ isDirty = false;
339
+ }
340
+ },
341
+ };
342
+
343
+ return () => {
344
+ if (activeEffect) {
345
+ subscribers.add(activeEffect);
346
+ activeEffect.dependencies.add(subscribers);
347
+ }
348
+ if (isDirty) computedEffect.run();
349
+ return cachedValue;
350
+ };
351
+ }
352
+
353
+ return (...args) => {
354
+ if (args.length) {
355
+ const nextValue = typeof args[0] === "function" ? args[0](initialValue) : args[0];
356
+ if (!Object.is(initialValue, nextValue)) {
357
+ initialValue = nextValue;
358
+ subscribers.forEach((subscriber) => {
359
+ if (subscriber.markDirty) subscriber.markDirty();
360
+ effectQueue.add(subscriber);
361
+ });
362
+ if (!isFlushScheduled && effectQueue.size) {
363
+ isFlushScheduled = true;
364
+ queueMicrotask(flushEffectQueue);
365
+ }
366
+ }
367
+ }
368
+ if (activeEffect) {
369
+ subscribers.add(activeEffect);
370
+ activeEffect.dependencies.add(subscribers);
371
+ }
372
+ return initialValue;
373
+ };
374
+ };
375
+
376
+ window.$$ = (effectFn) => {
377
+ const effect = {
378
+ dependencies: new Set(),
379
+ cleanupHandlers: new Set(),
380
+ run() {
381
+ this.cleanupHandlers.forEach((handler) => handler());
382
+ this.cleanupHandlers.clear();
383
+
384
+ this.dependencies.forEach((dependencySet) => dependencySet.delete(this));
385
+ this.dependencies.clear();
386
+
387
+ const previousEffect = activeEffect;
388
+ activeEffect = this;
389
+ try {
390
+ const result = effectFn();
391
+ if (typeof result === "function") this.cleanupFunction = result;
392
+ } finally {
393
+ activeEffect = previousEffect;
394
+ }
395
+ },
396
+ stop() {
397
+ this.cleanupHandlers.forEach((handler) => handler());
398
+ this.dependencies.forEach((dependencySet) => dependencySet.delete(this));
399
+ this.cleanupFunction?.();
400
+ },
401
+ };
402
+
403
+ if (activeEffect) activeEffect.cleanupHandlers.add(() => effect.stop());
404
+ effect.run();
405
+ return () => effect.stop();
406
+ };
407
+
408
+ window.html = (strings, ...values) => {
409
+ const templateCache = html._templateCache ?? (html._templateCache = new WeakMap());
410
+
411
+ const getNodeByPath = (root, path) =>
412
+ path.reduce((node, index) => node?.childNodes?.[index], root);
413
+
414
+ const applyTextContent = (node, values) => {
415
+ const parts = node.textContent.split("{{part}}");
416
+ const parent = node.parentNode;
417
+ let valueIndex = 0;
418
+
419
+ parts.forEach((part, index) => {
420
+ if (part) parent.insertBefore(document.createTextNode(part), node);
421
+ if (index < parts.length - 1) {
422
+ const currentValue = values[valueIndex++];
423
+ const startMarker = document.createComment("s");
424
+ const endMarker = document.createComment("e");
425
+ parent.insertBefore(startMarker, node);
426
+ parent.insertBefore(endMarker, node);
427
+
428
+ let lastResult;
429
+ $$(() => {
430
+ let result = typeof currentValue === "function" ? currentValue() : currentValue;
431
+ if (result === lastResult) return;
432
+ lastResult = result;
433
+
434
+ if (typeof result !== "object" && !Array.isArray(result)) {
435
+ const textNode = startMarker.nextSibling;
436
+ if (textNode !== endMarker && textNode?.nodeType === 3) {
437
+ textNode.textContent = result ?? "";
438
+ } else {
439
+ while (startMarker.nextSibling !== endMarker)
440
+ parent.removeChild(startMarker.nextSibling);
441
+ parent.insertBefore(document.createTextNode(result ?? ""), endMarker);
442
+ }
443
+ return;
444
+ }
445
+
446
+ while (startMarker.nextSibling !== endMarker)
447
+ parent.removeChild(startMarker.nextSibling);
448
+
449
+ const items = Array.isArray(result) ? result : [result];
450
+ const fragment = document.createDocumentFragment();
451
+ items.forEach(item => {
452
+ if (item == null || item === false) return;
453
+ const nodeItem = item instanceof Node ? item : document.createTextNode(item);
454
+ fragment.appendChild(nodeItem);
455
+ });
456
+ parent.insertBefore(fragment, endMarker);
457
+ });
458
+ }
459
+ });
460
+ node.remove();
461
+ };
462
+
463
+ let cachedTemplate = templateCache.get(strings);
464
+ if (!cachedTemplate) {
465
+ const template = document.createElement("template");
466
+ template.innerHTML = strings.join("{{part}}");
467
+
468
+ const dynamicNodes = [];
469
+ const treeWalker = document.createTreeWalker(template.content, 133);
470
+
471
+ const getNodePath = (node) => {
472
+ const path = [];
473
+ while (node && node !== template.content) {
474
+ let index = 0;
475
+ for (let sibling = node.previousSibling; sibling; sibling = sibling.previousSibling)
476
+ index++;
477
+ path.push(index);
478
+ node = node.parentNode;
479
+ }
480
+ return path.reverse();
481
+ };
482
+
483
+ let currentNode;
484
+ while ((currentNode = treeWalker.nextNode())) {
485
+ let isDynamic = false;
486
+ const nodeInfo = {
487
+ type: currentNode.nodeType,
488
+ path: getNodePath(currentNode),
489
+ parts: []
490
+ };
491
+
492
+ if (currentNode.nodeType === 1) {
493
+ for (let i = 0; i < currentNode.attributes.length; i++) {
494
+ const attribute = currentNode.attributes[i];
495
+ if (attribute.value.includes("{{part}}")) {
496
+ nodeInfo.parts.push({ name: attribute.name });
497
+ isDynamic = true;
498
+ }
499
+ }
500
+ } else if (currentNode.nodeType === 3 && currentNode.textContent.includes("{{part}}")) {
501
+ isDynamic = true;
502
+ }
503
+
504
+ if (isDynamic) dynamicNodes.push(nodeInfo);
505
+ }
506
+
507
+ templateCache.set(strings, (cachedTemplate = { template, dynamicNodes }));
508
+ }
509
+
510
+ const fragment = cachedTemplate.template.content.cloneNode(true);
511
+ let valueIndex = 0;
512
+
513
+ const targets = cachedTemplate.dynamicNodes.map((nodeInfo) => ({
514
+ node: getNodeByPath(fragment, nodeInfo.path),
515
+ info: nodeInfo
516
+ }));
517
+
518
+ targets.forEach(({ node, info }) => {
519
+ if (!node) return;
520
+
521
+ if (info.type === 1) {
522
+ info.parts.forEach((part) => {
523
+ const currentValue = values[valueIndex++];
524
+ const attributeName = part.name;
525
+ const firstChar = attributeName[0];
526
+
527
+ if (firstChar === "@") {
528
+ node.addEventListener(attributeName.slice(1), currentValue);
529
+ } else if (firstChar === ":") {
530
+ const propertyName = attributeName.slice(1);
531
+ const eventType = node.type === "checkbox" || node.type === "radio" ? "change" : "input";
532
+
533
+ $$(() => {
534
+ const value = typeof currentValue === "function" ? currentValue() : currentValue;
535
+ if (node[propertyName] !== value) node[propertyName] = value;
536
+ });
537
+
538
+ node.addEventListener(eventType, () => {
539
+ const value = eventType === "change" ? node.checked : node.value;
540
+ if (typeof currentValue === "function") currentValue(value);
541
+ });
542
+ } else if (firstChar === "?") {
543
+ const attrName = attributeName.slice(1);
544
+ $$(() => {
545
+ const result = typeof currentValue === "function" ? currentValue() : currentValue;
546
+ node.toggleAttribute(attrName, !!result);
547
+ });
548
+ } else if (firstChar === ".") {
549
+ const propertyName = attributeName.slice(1);
550
+ $$(() => {
551
+ let result = typeof currentValue === "function" ? currentValue() : currentValue;
552
+ node[propertyName] = result;
553
+ if (result != null && typeof result !== "object" && typeof result !== "boolean") {
554
+ node.setAttribute(propertyName, result);
555
+ }
556
+ });
557
+ } else {
558
+ if (typeof currentValue === "function") {
559
+ $$(() => node.setAttribute(attributeName, currentValue()));
560
+ } else {
561
+ node.setAttribute(attributeName, currentValue);
562
+ }
563
+ }
564
+ });
565
+ } else if (info.type === 3) {
566
+ const placeholderCount = node.textContent.split("{{part}}").length - 1;
567
+ applyTextContent(node, values.slice(valueIndex, valueIndex + placeholderCount));
568
+ valueIndex += placeholderCount;
569
+ }
570
+ });
571
+
572
+ return fragment;
573
+ };
574
+
575
+ window.$component = (tagName, setupFunction, observedAttributes = []) => {
576
+ if (customElements.get(tagName)) return;
577
+
578
+ customElements.define(
579
+ tagName,
580
+ class extends HTMLElement {
581
+ static get observedAttributes() {
582
+ return observedAttributes;
583
+ }
584
+
585
+ constructor() {
586
+ super();
587
+ this._propertySignals = {};
588
+ this.cleanupFunctions = [];
589
+ observedAttributes.forEach((attr) => (this._propertySignals[attr] = $(undefined)));
590
+ }
591
+
592
+ connectedCallback() {
593
+ const frozenChildren = [...this.childNodes];
594
+ this.innerHTML = "";
595
+
596
+ observedAttributes.forEach((attr) => {
597
+ const initialValue = this.hasOwnProperty(attr) ? this[attr] : this.getAttribute(attr);
598
+
599
+ Object.defineProperty(this, attr, {
600
+ get: () => this._propertySignals[attr](),
601
+ set: (value) => {
602
+ const processedValue = value === "false" ? false : value === "" && attr !== "value" ? true : value;
603
+ this._propertySignals[attr](processedValue);
604
+ },
605
+ configurable: true,
606
+ });
607
+
608
+ if (initialValue !== null && initialValue !== undefined) this[attr] = initialValue;
609
+ });
610
+
611
+ const context = {
612
+ select: (selector) => this.querySelector(selector),
613
+ slot: (name) =>
614
+ frozenChildren.filter((node) => {
615
+ const slotName = node.nodeType === 1 ? node.getAttribute("slot") : null;
616
+ return name ? slotName === name : !slotName;
617
+ }),
618
+ emit: (name, detail) => this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true })),
619
+ host: this,
620
+ onUnmount: (cleanupFn) => this.cleanupFunctions.push(cleanupFn),
621
+ };
622
+
623
+ const result = setupFunction(this._propertySignals, context);
624
+ if (result instanceof Node) this.appendChild(result);
625
+ }
626
+
627
+ attributeChangedCallback(name, oldValue, newValue) {
628
+ if (this[name] !== newValue) this[name] = newValue;
629
+ }
630
+
631
+ disconnectedCallback() {
632
+ this.cleanupFunctions.forEach((cleanupFn) => cleanupFn());
633
+ this.cleanupFunctions = [];
634
+ }
635
+ },
636
+ );
637
+ };
638
+
639
+ window.$router = (routes) => {
640
+ const getCurrentPath = () => window.location.hash.replace(/^#/, "") || "/";
641
+
642
+ const currentPath = $(getCurrentPath());
643
+ const container = document.createElement("div");
644
+ container.style.display = "contents";
645
+
646
+ window.addEventListener("hashchange", () => {
647
+ const nextPath = getCurrentPath();
648
+ if (currentPath() !== nextPath) currentPath(nextPath);
649
+ });
650
+
651
+ $$(() => {
652
+ const path = currentPath();
653
+ let matchedRoute = null;
654
+ let routeParams = {};
655
+
656
+ for (const route of routes) {
657
+ if (route.path instanceof RegExp) {
658
+ const match = path.match(route.path);
659
+ if (match) {
660
+ matchedRoute = route;
661
+ routeParams = match.groups || { id: match[1] };
662
+ break;
663
+ }
664
+ } else if (route.path === path) {
665
+ matchedRoute = route;
666
+ break;
667
+ }
668
+ }
669
+
670
+ const previousEffect = activeEffect;
671
+ activeEffect = null;
672
+
673
+ try {
674
+ const view = matchedRoute
675
+ ? matchedRoute.component(routeParams)
676
+ : html`
677
+ <h1>404</h1>
678
+ `;
679
+
680
+ container.replaceChildren(
681
+ view instanceof Node ? view : document.createTextNode(view ?? "")
682
+ );
683
+ } finally {
684
+ activeEffect = previousEffect;
685
+ }
686
+ });
687
+
688
+ return container;
689
+ };
690
+
691
+ $router.go = (path) => {
692
+ const targetPath = path.startsWith("/") ? path : `/${path}`;
693
+ if (window.location.hash !== `#${targetPath}`) window.location.hash = targetPath;
694
+ };
695
+
696
+ // ============================================
697
+ // LÓGICA DEL PLAYGROUND
698
+ // ============================================
699
+
700
+ const codeEditor = document.getElementById('codeEditor');
701
+ const preview = document.getElementById('preview');
702
+ const errorConsole = document.getElementById('errorConsole');
703
+ let autoRunTimeout;
704
+
705
+ // Ejecutar código al cargar la página
706
+ window.onload = () => {
707
+ runCode();
708
+ };
709
+
710
+ // Auto-ejecución mientras se escribe
711
+ codeEditor.addEventListener('input', () => {
712
+ clearTimeout(autoRunTimeout);
713
+ autoRunTimeout = setTimeout(runCode, 1000);
714
+ });
715
+
716
+ // Ejecutar con Ctrl+Enter
717
+ codeEditor.addEventListener('keydown', (e) => {
718
+ if (e.ctrlKey && e.key === 'Enter') {
719
+ e.preventDefault();
720
+ runCode();
721
+ }
722
+ });
723
+
724
+ function runCode() {
725
+ const code = codeEditor.value;
726
+
727
+ // Crear el contenido del iframe
728
+ const htmlContent = `
729
+ <!DOCTYPE html>
730
+ <html>
731
+ <head>
732
+ <meta charset="UTF-8">
733
+ <style>
734
+ body {
735
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
736
+ padding: 20px;
737
+ margin: 0;
738
+ background: white;
739
+ }
740
+ * { box-sizing: border-box; }
741
+ </style>
742
+ </head>
743
+ <body>
744
+ <div id="app"></div>
745
+ <script>
746
+ // Copia de SigPro para el iframe
747
+ ${$?.toString()}
748
+ ${$$?.toString()}
749
+ ${html?.toString()}
750
+ ${$component?.toString()}
751
+ ${$router?.toString()}
752
+
753
+ // Configuración adicional
754
+ window.onerror = function(msg, url, line, col, error) {
755
+ parent.postMessage({
756
+ type: 'error',
757
+ error: msg + ' (línea ' + line + ')'
758
+ }, '*');
759
+ };
760
+
761
+ // Ejecutar el código del usuario
762
+ try {
763
+ ${code}
764
+ } catch (error) {
765
+ parent.postMessage({
766
+ type: 'error',
767
+ error: error.toString()
768
+ }, '*');
769
+ }
770
+ <\/script>
771
+ </body>
772
+ </html>
773
+ `;
774
+
775
+ // Actualizar el iframe
776
+ preview.srcdoc = htmlContent;
777
+ errorConsole.innerHTML = '';
778
+ }
779
+
780
+ // Escuchar errores del iframe
781
+ window.addEventListener('message', (event) => {
782
+ if (event.data.type === 'error') {
783
+ errorConsole.innerHTML = '❌ ' + event.data.error;
784
+ errorConsole.style.display = 'block';
785
+ }
786
+ });
787
+
788
+ function formatCode() {
789
+ // Formateo básico del código
790
+ const code = codeEditor.value;
791
+ // Aquí podrías integrar prettier si quieres
792
+ alert('Función de formateo próximamente');
793
+ }
794
+
795
+ function shareCode() {
796
+ const code = codeEditor.value;
797
+ const encoded = btoa(encodeURIComponent(code));
798
+ const url = window.location.href.split('?')[0] + '?code=' + encoded;
799
+
800
+ document.getElementById('shareUrl').value = url;
801
+ document.getElementById('shareModal').classList.add('show');
802
+ document.getElementById('overlay').classList.add('show');
803
+ }
804
+
805
+ function copyShareUrl() {
806
+ const shareUrl = document.getElementById('shareUrl');
807
+ shareUrl.select();
808
+ document.execCommand('copy');
809
+ alert('URL copiada al portapapeles');
810
+ }
811
+
812
+ function closeShareModal() {
813
+ document.getElementById('shareModal').classList.remove('show');
814
+ document.getElementById('overlay').classList.remove('show');
815
+ }
816
+
817
+ function resetCode() {
818
+ codeEditor.value = `// Escribe tu código SigPro aquí
819
+ const nombre = $('Mundo');
820
+ const contador = $(0);
821
+
822
+ $$(() => {
823
+ document.body.innerHTML = \`
824
+ <h1>¡Hola \${nombre()}!</h1>
825
+ <p>Contador: \${contador()}</p>
826
+ <button onclick="contador(c => c + 1)">Incrementar</button>
827
+ \`;
828
+ });`;
829
+ runCode();
830
+ }
831
+
832
+ // Cargar código desde URL si existe
833
+ const urlParams = new URLSearchParams(window.location.search);
834
+ const encodedCode = urlParams.get('code');
835
+ if (encodedCode) {
836
+ try {
837
+ const decoded = decodeURIComponent(atob(encodedCode));
838
+ codeEditor.value = decoded;
839
+ runCode();
840
+ } catch (e) {
841
+ console.error('Error al cargar código compartido');
842
+ }
843
+ }
844
+ </script>
845
+ </body>
846
+ </html>
package/Readme.md CHANGED
@@ -57,7 +57,17 @@ What emerged is a library that proves we've reached a turning point: the web is
57
57
  *"Stop fighting the platform. Start building with it."*
58
58
 
59
59
  ## 📦 Installation
60
- Copy sigpro.js where you want to use it.
60
+
61
+ ```
62
+ npm install sigpro
63
+ ```
64
+ or
65
+ ```
66
+ bun add sigpro
67
+ ```
68
+ or more simple:
69
+
70
+ copy "sigpro.js" file where you want to use it.
61
71
 
62
72
  ## 🎯 Philosophy
63
73
 
@@ -1542,3 +1552,5 @@ function useFetch(url) {
1542
1552
 
1543
1553
 
1544
1554
 
1555
+
1556
+
package/index.js CHANGED
@@ -1,8 +1,14 @@
1
- // This is the main entry point of the package
2
- // Directly exports your plugin from sigpro.js
1
+ // index.js
3
2
 
4
- // If your sigpro.js already exports the plugin as default, you can do:
5
- export { default } from './sigpro.js';
3
+ // 1. Re-export all named core logic (signals, effects, html, etc.)
4
+ export * from './sigpro.js';
6
5
 
7
- // Or if you prefer CommonJS:
8
- // module.exports = require('./sigpro.js');
6
+ // 2. Import and re-export the Vite Router Plugin
7
+ // This allows users to import { sigproRouter } directly from the package
8
+ import sigproRouter from './SigProRouterPlugin/vite-plugin.sigpro.js';
9
+ export { sigproRouter };
10
+
11
+ // 3. Default export for the global namespace (optional)
12
+ // Combines core logic and the router plugin into a single object
13
+ import * as sigpro from './sigpro.js';
14
+ export default { ...sigpro, sigproRouter };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigpro",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "del": "rm node_modules/.vite/deps -r",