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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "totalComponents": 69,
3
- "generatedAt": "2026-02-13T03:22:32.979Z",
3
+ "generatedAt": "2026-02-13T04:58:53.712Z",
4
4
  "components": [
5
5
  {
6
6
  "file": "alert.js",
@@ -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; }
@@ -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; }
@@ -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, '&lt;').replace(/>/g, '&gt;') || ' ';
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 += `\n// Expose components\n`;
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
- if (!this._validationIssues || this._validationIssues.length === 0) return;
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
- console.log('\n⚠️ Validation Issues:\n');
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
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.116",
3
+ "version": "1.1.118",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",