juxscript 1.1.116 → 1.1.118
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/dom-structure-map.json +1 -1
- package/lib/styles/nav.css +322 -0
- package/lib/styles/stacks.css +0 -2
- package/machinery/compiler3.js +196 -187
- package/package.json +1 -1
package/dom-structure-map.json
CHANGED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
2
|
+
* NAV COMPONENT STYLES
|
|
3
|
+
* Modern navigation with horizontal/vertical layouts
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
5
|
+
|
|
6
|
+
/* Base Nav Container */
|
|
7
|
+
.jux-nav {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
position: relative;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Nav Items Container */
|
|
14
|
+
.jux-nav-items {
|
|
15
|
+
display: flex;
|
|
16
|
+
list-style: none;
|
|
17
|
+
margin: 0;
|
|
18
|
+
padding: 0;
|
|
19
|
+
gap: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Individual Nav Item Wrapper */
|
|
23
|
+
.jux-nav-item-wrapper {
|
|
24
|
+
position: relative;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Nav Links */
|
|
30
|
+
.jux-nav-link {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
padding: 8px 20px;
|
|
34
|
+
color: #374151; /* gray-700 */
|
|
35
|
+
font-weight: 500;
|
|
36
|
+
font-size: 15px;
|
|
37
|
+
text-decoration: none;
|
|
38
|
+
transition: all 0.2s ease;
|
|
39
|
+
border-radius: 6px;
|
|
40
|
+
position: relative;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.jux-nav-link:hover {
|
|
44
|
+
color: #7c3aed; /* purple-600 */
|
|
45
|
+
background-color: rgba(124, 58, 237, 0.05);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.jux-nav-link:active {
|
|
49
|
+
transform: scale(0.98);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Active/Current Link State */
|
|
53
|
+
.jux-nav-link.active,
|
|
54
|
+
.jux-nav-link[aria-current="page"] {
|
|
55
|
+
color: #7c3aed;
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Underline effect on hover */
|
|
60
|
+
.jux-nav-link::after {
|
|
61
|
+
content: '';
|
|
62
|
+
position: absolute;
|
|
63
|
+
bottom: 0;
|
|
64
|
+
left: 50%;
|
|
65
|
+
width: 0;
|
|
66
|
+
height: 2px;
|
|
67
|
+
background-color: #7c3aed;
|
|
68
|
+
transform: translateX(-50%);
|
|
69
|
+
transition: width 0.3s ease;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.jux-nav-link:hover::after,
|
|
73
|
+
.jux-nav-link.active::after {
|
|
74
|
+
width: 80%;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
78
|
+
* HORIZONTAL NAV (Default)
|
|
79
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
80
|
+
|
|
81
|
+
.jux-nav-horizontal .jux-nav-items {
|
|
82
|
+
flex-direction: row;
|
|
83
|
+
gap: 8px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
87
|
+
* VERTICAL NAV
|
|
88
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
89
|
+
|
|
90
|
+
.jux-nav-vertical {
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
align-items: stretch;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.jux-nav-vertical .jux-nav-items {
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
gap: 4px;
|
|
98
|
+
width: 100%;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.jux-nav-vertical .jux-nav-item-wrapper {
|
|
102
|
+
width: 100%;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.jux-nav-vertical .jux-nav-link {
|
|
106
|
+
width: 100%;
|
|
107
|
+
justify-content: flex-start;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.jux-nav-vertical .jux-nav-link::after {
|
|
111
|
+
display: none; /* No underline in vertical mode */
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
115
|
+
* SIZE VARIANTS
|
|
116
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
117
|
+
|
|
118
|
+
/* Small Nav */
|
|
119
|
+
.jux-nav-sm .jux-nav-link {
|
|
120
|
+
padding: 6px 14px;
|
|
121
|
+
font-size: 13px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Large Nav */
|
|
125
|
+
.jux-nav-lg .jux-nav-link {
|
|
126
|
+
padding: 12px 24px;
|
|
127
|
+
font-size: 16px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
131
|
+
* STYLE VARIANTS
|
|
132
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
133
|
+
|
|
134
|
+
/* Pills Style */
|
|
135
|
+
.jux-nav-pills .jux-nav-link {
|
|
136
|
+
border-radius: 50px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.jux-nav-pills .jux-nav-link:hover,
|
|
140
|
+
.jux-nav-pills .jux-nav-link.active {
|
|
141
|
+
background-color: #7c3aed;
|
|
142
|
+
color: white;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.jux-nav-pills .jux-nav-link::after {
|
|
146
|
+
display: none; /* No underline for pills */
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Tabs Style */
|
|
150
|
+
.jux-nav-tabs {
|
|
151
|
+
border-bottom: 2px solid #e5e7eb;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.jux-nav-tabs .jux-nav-link {
|
|
155
|
+
border-radius: 0;
|
|
156
|
+
border-bottom: 2px solid transparent;
|
|
157
|
+
margin-bottom: -2px;
|
|
158
|
+
padding-bottom: 10px;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.jux-nav-tabs .jux-nav-link::after {
|
|
162
|
+
display: none;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.jux-nav-tabs .jux-nav-link:hover {
|
|
166
|
+
background-color: transparent;
|
|
167
|
+
border-bottom-color: #d1d5db;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.jux-nav-tabs .jux-nav-link.active {
|
|
171
|
+
border-bottom-color: #7c3aed;
|
|
172
|
+
background-color: transparent;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* Minimal Style (no background on hover) */
|
|
176
|
+
.jux-nav-minimal .jux-nav-link:hover {
|
|
177
|
+
background-color: transparent;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
181
|
+
* DROPDOWN SUPPORT
|
|
182
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
183
|
+
|
|
184
|
+
.jux-nav-item-wrapper.has-dropdown {
|
|
185
|
+
position: relative;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.jux-nav-dropdown {
|
|
189
|
+
position: absolute;
|
|
190
|
+
top: 100%;
|
|
191
|
+
left: 0;
|
|
192
|
+
min-width: 200px;
|
|
193
|
+
margin-top: 8px;
|
|
194
|
+
padding: 8px;
|
|
195
|
+
background: white;
|
|
196
|
+
border-radius: 8px;
|
|
197
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
|
198
|
+
opacity: 0;
|
|
199
|
+
visibility: hidden;
|
|
200
|
+
transform: translateY(-10px);
|
|
201
|
+
transition: all 0.2s ease;
|
|
202
|
+
z-index: 1000;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.jux-nav-item-wrapper.has-dropdown:hover .jux-nav-dropdown,
|
|
206
|
+
.jux-nav-item-wrapper.has-dropdown:focus-within .jux-nav-dropdown {
|
|
207
|
+
opacity: 1;
|
|
208
|
+
visibility: visible;
|
|
209
|
+
transform: translateY(0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.jux-nav-dropdown-item {
|
|
213
|
+
display: block;
|
|
214
|
+
padding: 10px 16px;
|
|
215
|
+
color: #374151;
|
|
216
|
+
text-decoration: none;
|
|
217
|
+
border-radius: 6px;
|
|
218
|
+
transition: all 0.15s ease;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.jux-nav-dropdown-item:hover {
|
|
222
|
+
background-color: rgba(124, 58, 237, 0.08);
|
|
223
|
+
color: #7c3aed;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Dropdown Arrow Indicator */
|
|
227
|
+
.jux-nav-link.has-dropdown-arrow::after {
|
|
228
|
+
content: '▼';
|
|
229
|
+
margin-left: 6px;
|
|
230
|
+
font-size: 10px;
|
|
231
|
+
transition: transform 0.2s ease;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.jux-nav-item-wrapper.has-dropdown:hover .jux-nav-link.has-dropdown-arrow::after {
|
|
235
|
+
transform: rotate(180deg);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
239
|
+
* MOBILE RESPONSIVE
|
|
240
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
241
|
+
|
|
242
|
+
@media (max-width: 768px) {
|
|
243
|
+
.jux-nav-horizontal.jux-nav-mobile-vertical .jux-nav-items {
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
width: 100%;
|
|
246
|
+
gap: 4px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.jux-nav-horizontal.jux-nav-mobile-vertical .jux-nav-item-wrapper {
|
|
250
|
+
width: 100%;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.jux-nav-horizontal.jux-nav-mobile-vertical .jux-nav-link {
|
|
254
|
+
width: 100%;
|
|
255
|
+
justify-content: flex-start;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
260
|
+
* DARK MODE SUPPORT
|
|
261
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
262
|
+
|
|
263
|
+
.dark .jux-nav-link {
|
|
264
|
+
color: #d1d5db; /* gray-300 */
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.dark .jux-nav-link:hover {
|
|
268
|
+
color: #a78bfa; /* purple-400 */
|
|
269
|
+
background-color: rgba(167, 139, 250, 0.1);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.dark .jux-nav-link.active {
|
|
273
|
+
color: #a78bfa;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.dark .jux-nav-tabs {
|
|
277
|
+
border-bottom-color: #374151;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.dark .jux-nav-dropdown {
|
|
281
|
+
background: #1f2937;
|
|
282
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.dark .jux-nav-dropdown-item {
|
|
286
|
+
color: #d1d5db;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.dark .jux-nav-dropdown-item:hover {
|
|
290
|
+
background-color: rgba(167, 139, 250, 0.15);
|
|
291
|
+
color: #a78bfa;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
295
|
+
* ANIMATIONS
|
|
296
|
+
* ═══════════════════════════════════════════════════════════════════ */
|
|
297
|
+
|
|
298
|
+
@keyframes nav-fade-in {
|
|
299
|
+
from {
|
|
300
|
+
opacity: 0;
|
|
301
|
+
transform: translateY(-5px);
|
|
302
|
+
}
|
|
303
|
+
to {
|
|
304
|
+
opacity: 1;
|
|
305
|
+
transform: translateY(0);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.jux-nav-items.animate-in {
|
|
310
|
+
animation: nav-fade-in 0.3s ease-out;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.jux-nav-item-wrapper {
|
|
314
|
+
animation: nav-fade-in 0.3s ease-out backwards;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.jux-nav-item-wrapper:nth-child(1) { animation-delay: 0ms; }
|
|
318
|
+
.jux-nav-item-wrapper:nth-child(2) { animation-delay: 50ms; }
|
|
319
|
+
.jux-nav-item-wrapper:nth-child(3) { animation-delay: 100ms; }
|
|
320
|
+
.jux-nav-item-wrapper:nth-child(4) { animation-delay: 150ms; }
|
|
321
|
+
.jux-nav-item-wrapper:nth-child(5) { animation-delay: 200ms; }
|
|
322
|
+
.jux-nav-item-wrapper:nth-child(6) { animation-delay: 250ms; }
|
package/lib/styles/stacks.css
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
.jux-vstack {
|
|
8
8
|
display: flex;
|
|
9
9
|
flex-direction: column;
|
|
10
|
-
gap: 1rem;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
.jux-vstack-tight { gap: 0.5rem; }
|
|
@@ -19,7 +18,6 @@
|
|
|
19
18
|
display: flex;
|
|
20
19
|
flex-direction: row;
|
|
21
20
|
align-items: center;
|
|
22
|
-
gap: 1rem;
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
.jux-hstack-tight { gap: 0.5rem; }
|
package/machinery/compiler3.js
CHANGED
|
@@ -202,6 +202,180 @@ export class JuxCompiler {
|
|
|
202
202
|
return issues;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* ✅ Generate routes based on folder structure
|
|
207
|
+
*/
|
|
208
|
+
_generateRouter(views) {
|
|
209
|
+
let routeMap = '';
|
|
210
|
+
|
|
211
|
+
views.forEach(v => {
|
|
212
|
+
// ✅ Generate route from folder structure
|
|
213
|
+
// abc/juxabc.jux -> /abc/juxabc
|
|
214
|
+
// index.jux -> /
|
|
215
|
+
// abc/index.jux -> /abc
|
|
216
|
+
|
|
217
|
+
const routePath = this._generateRoutePath(v.file);
|
|
218
|
+
const functionName = this._generateFunctionName(v.name);
|
|
219
|
+
|
|
220
|
+
routeMap += ` '${routePath}': render${functionName},\n`;
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return `
|
|
224
|
+
// --- JUX SOURCE LOADER ---
|
|
225
|
+
var __juxSources = null;
|
|
226
|
+
async function __juxLoadSources() {
|
|
227
|
+
if (__juxSources) return __juxSources;
|
|
228
|
+
try {
|
|
229
|
+
var res = await fetch('/__jux_sources.json');
|
|
230
|
+
__juxSources = await res.json();
|
|
231
|
+
} catch (e) {
|
|
232
|
+
__juxSources = {};
|
|
233
|
+
}
|
|
234
|
+
return __juxSources;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function __juxFindSource(stack) {
|
|
238
|
+
var match = stack.match(/render([A-Z][a-zA-Z0-9_]*)/);
|
|
239
|
+
if (match) {
|
|
240
|
+
var funcName = match[1];
|
|
241
|
+
for (var file in __juxSources || {}) {
|
|
242
|
+
var normalized = __juxSources[file].name
|
|
243
|
+
.split('_')
|
|
244
|
+
.map(s => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())
|
|
245
|
+
.join('');
|
|
246
|
+
|
|
247
|
+
if (normalized === funcName) {
|
|
248
|
+
return { file: file, source: __juxSources[file], viewName: funcName };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// --- JUX RUNTIME ERROR OVERLAY ---
|
|
256
|
+
var __juxErrorOverlay = {
|
|
257
|
+
styles: \`
|
|
258
|
+
#__jux-error-overlay {
|
|
259
|
+
position: fixed; inset: 0; z-index: 99999;
|
|
260
|
+
background: rgba(0, 0, 0, 0.4);
|
|
261
|
+
display: flex; align-items: center; justify-content: center;
|
|
262
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
|
|
263
|
+
opacity: 0; transition: opacity 0.2s ease-out; backdrop-filter: blur(2px);
|
|
264
|
+
}
|
|
265
|
+
#__jux-error-overlay.visible { opacity: 1; }
|
|
266
|
+
#__jux-error-overlay * { box-sizing: border-box; }
|
|
267
|
+
.__jux-modal {
|
|
268
|
+
background: #f8f9fa; border-radius: 4px;
|
|
269
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
|
|
270
|
+
max-width: 80vw; width: 90%; max-height: 90vh;
|
|
271
|
+
overflow: hidden; display: flex; flex-direction: column;
|
|
272
|
+
transform: translateY(10px); transition: transform 0.2s ease-out;
|
|
273
|
+
}
|
|
274
|
+
#__jux-error-overlay.visible .__jux-modal { transform: translateY(0); }
|
|
275
|
+
.__jux-header { background: #fff; padding: 20px 24px; border-bottom: 1px solid #e5e7eb; }
|
|
276
|
+
.__jux-header h3 { margin: 0 0 6px; font-weight: 600; font-size: 11px; color: #9ca3af; text-transform: uppercase; }
|
|
277
|
+
.__jux-header h1 { margin: 0 0 8px; font-size: 18px; font-weight: 600; color: #dc2626; line-height: 1.3; }
|
|
278
|
+
.__jux-header .file-info { color: #6b7280; font-size: 13px; }
|
|
279
|
+
.__jux-header .file-info strong { color: #dc2626; font-weight: 600; }
|
|
280
|
+
.__jux-source { padding: 16px 24px; overflow: auto; flex: 1; background: #f8f9fa; }
|
|
281
|
+
.__jux-code { background: #fff; border-radius: 8px; overflow: hidden; border: 1px solid #e5e7eb; }
|
|
282
|
+
.__jux-code-line { display: flex; font-size: 13px; line-height: 1.7; }
|
|
283
|
+
.__jux-code-line.error { background: #fef2f2; }
|
|
284
|
+
.__jux-code-line.error .__jux-line-code { color: #dc2626; font-weight: 500; }
|
|
285
|
+
.__jux-code-line.context { background: #fefce8; }
|
|
286
|
+
.__jux-line-num { min-width: 44px; padding: 4px 12px; text-align: right; color: #9ca3af; background: #f9fafb; border-right: 1px solid #e5e7eb; font-size: 12px; }
|
|
287
|
+
.__jux-code-line.error .__jux-line-num { background: #fef2f2; color: #dc2626; }
|
|
288
|
+
.__jux-line-code { flex: 1; padding: 4px 16px; color: #374151; white-space: pre; overflow-x: auto; }
|
|
289
|
+
.__jux-footer { padding: 16px 24px; background: #fff; border-top: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center; }
|
|
290
|
+
.__jux-tip { color: #6b7280; font-size: 12px; }
|
|
291
|
+
.__jux-dismiss { background: #f3f4f6; color: #374151; border: 1px solid #d1d5db; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; }
|
|
292
|
+
.__jux-dismiss:hover { background: #e5e7eb; }
|
|
293
|
+
.__jux-no-source { color: #6b7280; padding: 24px; text-align: center; }
|
|
294
|
+
.__jux-no-source pre { background: #fff; padding: 16px; border-radius: 8px; margin-top: 16px; font-size: 11px; color: #6b7280; overflow-x: auto; text-align: left; border: 1px solid #e5e7eb; }
|
|
295
|
+
\`,
|
|
296
|
+
|
|
297
|
+
show: async function(error, title) {
|
|
298
|
+
title = title || 'Runtime Error';
|
|
299
|
+
var existing = document.getElementById('__jux-error-overlay');
|
|
300
|
+
if (existing) existing.remove();
|
|
301
|
+
await __juxLoadSources();
|
|
302
|
+
|
|
303
|
+
var overlay = document.createElement('div');
|
|
304
|
+
overlay.id = '__jux-error-overlay';
|
|
305
|
+
var stack = error && error.stack ? error.stack : '';
|
|
306
|
+
var msg = error && error.message ? error.message : String(error);
|
|
307
|
+
var found = __juxFindSource(stack);
|
|
308
|
+
var sourceHtml = '', fileInfo = '';
|
|
309
|
+
|
|
310
|
+
if (found && found.source && found.source.lines) {
|
|
311
|
+
var lines = found.source.lines;
|
|
312
|
+
fileInfo = '<span class="file-info">in <strong>' + found.file + '</strong></span>';
|
|
313
|
+
var errorLineIndex = -1;
|
|
314
|
+
var errorMethod = msg.match(/\\.([a-zA-Z]+)\\s*is not a function/);
|
|
315
|
+
if (errorMethod) {
|
|
316
|
+
for (var i = 0; i < lines.length; i++) {
|
|
317
|
+
if (lines[i].indexOf('.' + errorMethod[1]) > -1) { errorLineIndex = i; break; }
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (errorLineIndex === -1) {
|
|
321
|
+
for (var i = 0; i < lines.length; i++) {
|
|
322
|
+
if (lines[i].indexOf('throw ') > -1) { errorLineIndex = i; break; }
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
var contextStart = Math.max(0, errorLineIndex - 3);
|
|
326
|
+
var contextEnd = Math.min(lines.length - 1, errorLineIndex + 5);
|
|
327
|
+
if (errorLineIndex === -1) { contextStart = 0; contextEnd = Math.min(14, lines.length - 1); }
|
|
328
|
+
|
|
329
|
+
for (var i = contextStart; i <= contextEnd; i++) {
|
|
330
|
+
var isError = (i === errorLineIndex);
|
|
331
|
+
var isContext = !isError && errorLineIndex > -1 && Math.abs(i - errorLineIndex) <= 2;
|
|
332
|
+
var lineClass = isError ? ' error' : (isContext ? ' context' : '');
|
|
333
|
+
var lineCode = lines[i].replace(/</g, '<').replace(/>/g, '>') || ' ';
|
|
334
|
+
sourceHtml += '<div class="__jux-code-line' + lineClass + '"><span class="__jux-line-num">' + (i + 1) + '</span><span class="__jux-line-code">' + lineCode + '</span></div>';
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
sourceHtml = '<div class="__jux-no-source"><p>Could not locate source file.</p><pre>' + stack + '</pre></div>';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
overlay.innerHTML = '<style>' + this.styles + '</style><div class="__jux-modal"><div class="__jux-header"><h3>' + title + '</h3><h1>' + msg + '</h1>' + fileInfo + '</div><div class="__jux-source"><div class="__jux-code">' + sourceHtml + '</div></div><div class="__jux-footer"><span class="__jux-tip">💡 Fix the error and save to reload</span><button class="__jux-dismiss" onclick="document.getElementById(\\'__jux-error-overlay\\').remove()">Dismiss</button></div></div>';
|
|
341
|
+
document.body.appendChild(overlay);
|
|
342
|
+
requestAnimationFrame(function() { overlay.classList.add('visible'); });
|
|
343
|
+
console.error(title + ':', error);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
window.addEventListener('error', function(e) { __juxErrorOverlay.show(e.error || new Error(e.message), 'Uncaught Error'); }, true);
|
|
348
|
+
window.addEventListener('unhandledrejection', function(e) { __juxErrorOverlay.show(e.reason || new Error('Promise rejected'), 'Unhandled Promise Rejection'); }, true);
|
|
349
|
+
|
|
350
|
+
// --- JUX ROUTER ---
|
|
351
|
+
const routes = {\n${routeMap}};
|
|
352
|
+
|
|
353
|
+
async function navigate(path) {
|
|
354
|
+
const view = routes[path];
|
|
355
|
+
if (!view) {
|
|
356
|
+
document.getElementById('app').innerHTML = '<h1 style="padding:40px;">404 - Not Found</h1>';
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
document.getElementById('app').innerHTML = '';
|
|
360
|
+
var overlay = document.getElementById('__jux-error-overlay');
|
|
361
|
+
if (overlay) overlay.remove();
|
|
362
|
+
try { await view(); } catch (err) { __juxErrorOverlay.show(err, 'Jux Render Error'); }
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
document.addEventListener('click', e => {
|
|
366
|
+
const a = e.target.closest('a');
|
|
367
|
+
if (!a || a.dataset.router === 'false') return;
|
|
368
|
+
try { if (new URL(a.href, location.origin).origin !== location.origin) return; } catch { return; }
|
|
369
|
+
e.preventDefault();
|
|
370
|
+
history.pushState({}, '', a.href);
|
|
371
|
+
navigate(new URL(a.href, location.origin).pathname);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
window.addEventListener('popstate', () => navigate(location.pathname));
|
|
375
|
+
navigate(location.pathname);
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
|
|
205
379
|
/**
|
|
206
380
|
* ✅ Generate route path from file path
|
|
207
381
|
* Examples:
|
|
@@ -250,36 +424,18 @@ export class JuxCompiler {
|
|
|
250
424
|
const sourceSnapshot = {};
|
|
251
425
|
|
|
252
426
|
const juxImports = new Set();
|
|
253
|
-
const layoutImports = new Set(); // ✅ Track layout imports separately
|
|
254
|
-
|
|
255
|
-
// Scan for imports
|
|
256
427
|
[...views, ...dataModules, ...sharedModules].forEach(m => {
|
|
257
|
-
// Regular juxscript imports
|
|
258
428
|
for (const match of m.content.matchAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]juxscript['"]/g)) {
|
|
259
429
|
match[1].split(',').map(s => s.trim()).forEach(imp => {
|
|
260
|
-
if (imp)
|
|
261
|
-
// ✅ Separate layout imports
|
|
262
|
-
if (imp === 'VStack' || imp === 'HStack' || imp === 'ZStack') {
|
|
263
|
-
layoutImports.add(imp);
|
|
264
|
-
} else {
|
|
265
|
-
juxImports.add(imp);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
430
|
+
if (imp) juxImports.add(imp);
|
|
268
431
|
});
|
|
269
432
|
}
|
|
270
433
|
});
|
|
271
434
|
|
|
272
|
-
// ✅ Import layouts separately
|
|
273
|
-
if (layoutImports.size > 0) {
|
|
274
|
-
entry += `import { ${[...layoutImports].sort().join(', ')} } from 'juxscript';\n`;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ✅ Import regular components
|
|
278
435
|
if (juxImports.size > 0) {
|
|
279
436
|
entry += `import { ${[...juxImports].sort().join(', ')} } from 'juxscript';\n\n`;
|
|
280
437
|
}
|
|
281
438
|
|
|
282
|
-
// Data and shared modules
|
|
283
439
|
dataModules.forEach(m => {
|
|
284
440
|
entry += `import * as ${this.sanitizeName(m.name)}Data from './jux/${m.file}';\n`;
|
|
285
441
|
});
|
|
@@ -291,18 +447,8 @@ export class JuxCompiler {
|
|
|
291
447
|
dataModules.forEach(m => entry += `Object.assign(window, ${this.sanitizeName(m.name)}Data);\n`);
|
|
292
448
|
sharedModules.forEach(m => entry += `Object.assign(window, ${this.sanitizeName(m.name)}Shared);\n`);
|
|
293
449
|
|
|
294
|
-
// ✅ Expose layouts to window
|
|
295
|
-
if (layoutImports.size > 0) {
|
|
296
|
-
entry += `\n// Expose layout components\n`;
|
|
297
|
-
layoutImports.forEach(layout => {
|
|
298
|
-
entry += `window.${layout} = ${layout};\n`;
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// ✅ Expose regular components
|
|
303
450
|
if (juxImports.size > 0) {
|
|
304
|
-
entry += `\
|
|
305
|
-
entry += `Object.assign(window, { ${[...juxImports].join(', ')} });\n`;
|
|
451
|
+
entry += `\nObject.assign(window, { ${[...juxImports].join(', ')} });\n`;
|
|
306
452
|
}
|
|
307
453
|
|
|
308
454
|
entry += `\n// --- VIEW FUNCTIONS ---\n`;
|
|
@@ -338,25 +484,34 @@ export class JuxCompiler {
|
|
|
338
484
|
}
|
|
339
485
|
|
|
340
486
|
reportValidationIssues() {
|
|
341
|
-
|
|
487
|
+
const issues = this._validationIssues || [];
|
|
488
|
+
const errors = issues.filter(i => i.type === 'error');
|
|
489
|
+
const warnings = issues.filter(i => i.type === 'warning');
|
|
490
|
+
|
|
491
|
+
if (issues.length > 0) {
|
|
492
|
+
console.log('\n⚠️ Validation Issues:\n');
|
|
493
|
+
issues.forEach(issue => {
|
|
494
|
+
const icon = issue.type === 'error' ? '❌' : '⚠️';
|
|
495
|
+
console.log(`${icon} [${issue.view}:${issue.line}] ${issue.message}`);
|
|
496
|
+
});
|
|
497
|
+
console.log('');
|
|
498
|
+
}
|
|
342
499
|
|
|
343
|
-
|
|
344
|
-
this._validationIssues.forEach(issue => {
|
|
345
|
-
const icon = issue.type === 'error' ? '❌' : '⚠️';
|
|
346
|
-
console.log(` ${icon} ${issue.view}:${issue.line} - ${issue.message}`);
|
|
347
|
-
});
|
|
348
|
-
console.log('');
|
|
500
|
+
return { isValid: errors.length === 0, errors, warnings };
|
|
349
501
|
}
|
|
350
502
|
|
|
351
|
-
/**
|
|
352
|
-
* ✅ Generate routes based on folder structure (SINGLE DEFINITION)
|
|
353
|
-
*/
|
|
354
503
|
_generateRouter(views) {
|
|
355
504
|
let routeMap = '';
|
|
356
505
|
|
|
357
506
|
views.forEach(v => {
|
|
507
|
+
// ✅ Generate route from folder structure
|
|
508
|
+
// abc/juxabc.jux -> /abc/juxabc
|
|
509
|
+
// index.jux -> /
|
|
510
|
+
// abc/index.jux -> /abc
|
|
511
|
+
|
|
358
512
|
const routePath = this._generateRoutePath(v.file);
|
|
359
513
|
const functionName = this._generateFunctionName(v.name);
|
|
514
|
+
|
|
360
515
|
routeMap += ` '${routePath}': render${functionName},\n`;
|
|
361
516
|
});
|
|
362
517
|
|
|
@@ -488,8 +643,7 @@ window.addEventListener('error', function(e) { __juxErrorOverlay.show(e.error ||
|
|
|
488
643
|
window.addEventListener('unhandledrejection', function(e) { __juxErrorOverlay.show(e.reason || new Error('Promise rejected'), 'Unhandled Promise Rejection'); }, true);
|
|
489
644
|
|
|
490
645
|
// --- JUX ROUTER ---
|
|
491
|
-
const routes = {
|
|
492
|
-
${routeMap}};
|
|
646
|
+
const routes = {\n${routeMap}};
|
|
493
647
|
|
|
494
648
|
async function navigate(path) {
|
|
495
649
|
const view = routes[path];
|
|
@@ -517,151 +671,6 @@ navigate(location.pathname);
|
|
|
517
671
|
`;
|
|
518
672
|
}
|
|
519
673
|
|
|
520
|
-
/**
|
|
521
|
-
* ✅ Copy source files to dist/jux for esbuild to resolve
|
|
522
|
-
*/
|
|
523
|
-
_copySourceToDist() {
|
|
524
|
-
const distJuxDir = path.join(this.distDir, 'jux');
|
|
525
|
-
|
|
526
|
-
// Ensure dist/jux directory exists
|
|
527
|
-
if (!fs.existsSync(distJuxDir)) {
|
|
528
|
-
fs.mkdirSync(distJuxDir, { recursive: true });
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Copy all .jux and .js files from source to dist
|
|
532
|
-
this._copySourceFilesRecursive(this.srcDir, this.srcDir, distJuxDir);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Recursively copy .jux and .js files preserving folder structure
|
|
537
|
-
*/
|
|
538
|
-
_copySourceFilesRecursive(src, baseDir, distBase) {
|
|
539
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
540
|
-
|
|
541
|
-
entries.forEach(entry => {
|
|
542
|
-
if (entry.name.startsWith('.')) return; // Skip hidden
|
|
543
|
-
|
|
544
|
-
const srcPath = path.join(src, entry.name);
|
|
545
|
-
const relativePath = path.relative(baseDir, srcPath);
|
|
546
|
-
const destPath = path.join(distBase, relativePath);
|
|
547
|
-
|
|
548
|
-
if (entry.isDirectory()) {
|
|
549
|
-
// Create directory if it doesn't exist
|
|
550
|
-
if (!fs.existsSync(destPath)) {
|
|
551
|
-
fs.mkdirSync(destPath, { recursive: true });
|
|
552
|
-
}
|
|
553
|
-
// Recurse into subdirectory
|
|
554
|
-
this._copySourceFilesRecursive(srcPath, baseDir, distBase);
|
|
555
|
-
} else if (entry.name.endsWith('.jux') || entry.name.endsWith('.js')) {
|
|
556
|
-
// Copy .jux and .js files (skip assets)
|
|
557
|
-
if (!this.isAssetFile(entry.name)) {
|
|
558
|
-
// Ensure parent directory exists
|
|
559
|
-
const destDir = path.dirname(destPath);
|
|
560
|
-
if (!fs.existsSync(destDir)) {
|
|
561
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
562
|
-
}
|
|
563
|
-
fs.copyFileSync(srcPath, destPath);
|
|
564
|
-
console.log(` 📋 Copied: ${relativePath}`);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* ✅ Build method with source copy step
|
|
572
|
-
*/
|
|
573
|
-
async build() {
|
|
574
|
-
console.log('🔨 Building JUX application...\n');
|
|
575
|
-
|
|
576
|
-
try {
|
|
577
|
-
const startTime = Date.now();
|
|
578
|
-
|
|
579
|
-
// Scan files
|
|
580
|
-
const { views, dataModules, sharedModules } = this.scanFiles();
|
|
581
|
-
|
|
582
|
-
console.log(`📂 Found ${views.length} views, ${dataModules.length} data modules, ${sharedModules.length} shared modules\n`);
|
|
583
|
-
|
|
584
|
-
// Load juxscript exports for validation
|
|
585
|
-
await this.loadJuxscriptExports();
|
|
586
|
-
|
|
587
|
-
// Ensure dist directory exists
|
|
588
|
-
if (!fs.existsSync(this.distDir)) {
|
|
589
|
-
fs.mkdirSync(this.distDir, { recursive: true });
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// ✅ Copy source files to dist/jux BEFORE generating entry point
|
|
593
|
-
console.log('📋 Copying source files to dist...');
|
|
594
|
-
this._copySourceToDist();
|
|
595
|
-
console.log('✅ Source files copied\n');
|
|
596
|
-
|
|
597
|
-
// Generate entry point
|
|
598
|
-
const entryCode = this.generateEntryPoint(views, dataModules, sharedModules);
|
|
599
|
-
const entryPath = path.join(this.distDir, 'entry.js');
|
|
600
|
-
fs.writeFileSync(entryPath, entryCode);
|
|
601
|
-
|
|
602
|
-
// Bundle with esbuild
|
|
603
|
-
console.log('📦 Bundling with esbuild...');
|
|
604
|
-
await esbuild.build({
|
|
605
|
-
entryPoints: [entryPath],
|
|
606
|
-
bundle: true,
|
|
607
|
-
format: 'esm',
|
|
608
|
-
outfile: path.join(this.distDir, 'bundle.js'),
|
|
609
|
-
platform: 'browser',
|
|
610
|
-
target: 'es2020',
|
|
611
|
-
sourcemap: true,
|
|
612
|
-
external: [],
|
|
613
|
-
// ✅ Tell esbuild how to resolve .jux files
|
|
614
|
-
loader: {
|
|
615
|
-
'.jux': 'js' // Treat .jux files as JavaScript
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
// Generate index.html
|
|
620
|
-
console.log('📄 Generating index.html...');
|
|
621
|
-
const indexHtml = `<!DOCTYPE html>
|
|
622
|
-
<html lang="en">
|
|
623
|
-
<head>
|
|
624
|
-
<meta charset="UTF-8">
|
|
625
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
626
|
-
<title>JUX App</title>
|
|
627
|
-
</head>
|
|
628
|
-
<body>
|
|
629
|
-
<div id="app"></div>
|
|
630
|
-
<script type="module" src="/bundle.js"></script>
|
|
631
|
-
</body>
|
|
632
|
-
</html>`;
|
|
633
|
-
|
|
634
|
-
fs.writeFileSync(path.join(this.distDir, 'index.html'), indexHtml);
|
|
635
|
-
|
|
636
|
-
// Copy public folder
|
|
637
|
-
this.copyPublicFolder();
|
|
638
|
-
|
|
639
|
-
// Write source snapshot for error overlay
|
|
640
|
-
const snapshotPath = path.join(this.distDir, '__jux_sources.json');
|
|
641
|
-
fs.writeFileSync(snapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
|
|
642
|
-
|
|
643
|
-
const endTime = Date.now();
|
|
644
|
-
console.log(`\n✅ Build complete in ${endTime - startTime}ms`);
|
|
645
|
-
console.log(` Output: ${this.distDir}`);
|
|
646
|
-
|
|
647
|
-
this.reportValidationIssues();
|
|
648
|
-
|
|
649
|
-
return {
|
|
650
|
-
success: true,
|
|
651
|
-
errors: this._validationIssues || [],
|
|
652
|
-
distDir: this.distDir
|
|
653
|
-
};
|
|
654
|
-
|
|
655
|
-
} catch (err) {
|
|
656
|
-
console.error('❌ Build failed:', err.message);
|
|
657
|
-
console.error(err.stack);
|
|
658
|
-
return {
|
|
659
|
-
success: false,
|
|
660
|
-
errors: [err.message]
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
|
|
665
674
|
/**
|
|
666
675
|
* Copy public folder contents to dist
|
|
667
676
|
*/
|