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.
- package/PlayGround/Readme.md +208 -0
- package/PlayGround/play.html +846 -0
- package/Readme.md +13 -1
- package/index.js +12 -6
- package/package.json +1 -1
|
@@ -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
|
+

|
|
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í const nombre = $('Mundo'); $$(() => { document.getElementById('saludo').textContent = `¡Hola ${nombre()}!`; }); // Cambia el nombre después de 2 segundos 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
|
-
|
|
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
|
-
//
|
|
2
|
-
// Directly exports your plugin from sigpro.js
|
|
1
|
+
// index.js
|
|
3
2
|
|
|
4
|
-
//
|
|
5
|
-
export
|
|
3
|
+
// 1. Re-export all named core logic (signals, effects, html, etc.)
|
|
4
|
+
export * from './sigpro.js';
|
|
6
5
|
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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 };
|