juxscript 1.1.70 → 1.1.72

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.
@@ -16,7 +16,7 @@ export interface ModalOptions {
16
16
  icon?: EmojiIconType | undefined;
17
17
  close?: boolean;
18
18
  backdropClose?: boolean;
19
- position?: 'bottom' | 'center' | 'top' | 'fullscreen' | 'popover';
19
+ position?: 'bottom' | 'center' | 'top' | 'fullscreen' | 'popover' | 'popover-top' | 'popover-bottom' | 'popover-left' | 'popover-right';
20
20
  size?: 'small' | 'medium' | 'large';
21
21
  actions?: Array<{ label: string; variant?: string; click?: () => void } | Array<BaseComponent<any>>>;
22
22
  style?: string;
@@ -29,7 +29,7 @@ type ModalState = {
29
29
  icon: EmojiIconType | undefined;
30
30
  close: boolean;
31
31
  backdropClose: boolean;
32
- position?: 'bottom' | 'center' | 'top' | 'fullscreen' | 'popover';
32
+ position?: 'bottom' | 'center' | 'top' | 'fullscreen' | 'popover' | 'popover-top' | 'popover-bottom' | 'popover-left' | 'popover-right';
33
33
  size: string;
34
34
  open: boolean;
35
35
  actions: Array<{ label: string; variant?: string; click?: () => void } | Array<BaseComponent<any>>>;
@@ -102,6 +102,19 @@ export class Modal extends BaseComponent<ModalState> {
102
102
  modal.className = modal.className.replace(/jux-modal-(small|medium|large)/, `jux-modal-${value}`);
103
103
  break;
104
104
 
105
+ case 'position':
106
+ // Remove existing position classes
107
+ this._overlay.className = this._overlay.className
108
+ .split(' ')
109
+ .filter(c => !c.startsWith('jux-modal-position-'))
110
+ .join(' ');
111
+
112
+ // Add new position class
113
+ if (value) {
114
+ this._overlay.classList.add(`jux-modal-position-${value}`);
115
+ }
116
+ break;
117
+
105
118
  case 'actions':
106
119
  this._rebuildActions();
107
120
  break;
@@ -255,6 +268,11 @@ export class Modal extends BaseComponent<ModalState> {
255
268
  return this;
256
269
  }
257
270
 
271
+ position(value: 'bottom' | 'center' | 'top' | 'fullscreen' | 'popover' | 'popover-top' | 'popover-bottom' | 'popover-left' | 'popover-right'): this {
272
+ this.state.position = value;
273
+ return this;
274
+ }
275
+
258
276
  actions(value: Array<{ label: string; variant?: string; click?: () => void }>): this {
259
277
  this.state.actions = value;
260
278
  return this;
@@ -357,13 +375,19 @@ export class Modal extends BaseComponent<ModalState> {
357
375
  render(targetId?: string | HTMLElement | BaseComponent<any>): this {
358
376
  const container = this._setupContainer(targetId);
359
377
 
360
- const { open, title, content, icon, size, close, backdropClose, actions, style, class: className } = this.state;
378
+ const { open, title, content, icon, size, position, close, backdropClose, actions, style, class: className } = this.state;
361
379
  const hasOpenSync = this._syncBindings.some(b => b.property === 'open');
362
380
 
363
381
  const overlay = document.createElement('div');
364
382
  overlay.className = 'jux-modal-overlay';
365
383
  overlay.id = this._id;
366
384
  overlay.style.display = open ? 'flex' : 'none';
385
+
386
+ // Add position class
387
+ if (position) {
388
+ overlay.classList.add(`jux-modal-position-${position}`);
389
+ }
390
+
367
391
  if (className) overlay.className += ` ${className}`;
368
392
  if (style) overlay.setAttribute('style', style);
369
393
  this._overlay = overlay;
@@ -0,0 +1,402 @@
1
+ /* ═══════════════════════════════════════════════════════════════════
2
+ * MODAL BASE STYLES
3
+ * ═══════════════════════════════════════════════════════════════════ */
4
+
5
+ /* Modal Overlay - Base positioning */
6
+ .jux-modal-overlay {
7
+ position: fixed;
8
+ inset: 0;
9
+ z-index: 50;
10
+ background-color: rgba(0, 0, 0, 0.8);
11
+ backdrop-filter: blur(2px);
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ }
16
+
17
+ /* Modal Container */
18
+ .jux-modal {
19
+ position: relative;
20
+ background-color: #ffffff;
21
+ border: 1px solid #e4e4e7;
22
+ border-radius: 0.5rem;
23
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
24
+ width: 100%;
25
+ max-width: 32rem;
26
+ z-index: 51;
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: 1rem;
30
+ padding: 1.5rem;
31
+ }
32
+
33
+ .jux-modal-small { max-width: 24rem; }
34
+ .jux-modal-medium { max-width: 32rem; }
35
+ .jux-modal-large { max-width: 48rem; }
36
+
37
+ /* ═══════════════════════════════════════════════════════════════════
38
+ * POSITION VARIANTS
39
+ * ═══════════════════════════════════════════════════════════════════ */
40
+
41
+ /* Center (default) */
42
+ .jux-modal-position-center {
43
+ align-items: center;
44
+ justify-content: center;
45
+ }
46
+
47
+ /* Top position */
48
+ .jux-modal-position-top {
49
+ align-items: flex-start;
50
+ justify-content: center;
51
+ padding-top: 2rem;
52
+ }
53
+
54
+ .jux-modal-position-top .jux-modal {
55
+ animation: modalSlideDown 0.3s ease-out;
56
+ }
57
+
58
+ @keyframes modalSlideDown {
59
+ from {
60
+ transform: translateY(-20px);
61
+ opacity: 0;
62
+ }
63
+ to {
64
+ transform: translateY(0);
65
+ opacity: 1;
66
+ }
67
+ }
68
+
69
+ /* Bottom position (bottom sheet) */
70
+ .jux-modal-position-bottom {
71
+ align-items: flex-end;
72
+ justify-content: center;
73
+ padding: 0;
74
+ }
75
+
76
+ .jux-modal-position-bottom .jux-modal {
77
+ border-radius: 0.75rem 0.75rem 0 0;
78
+ max-height: 90vh;
79
+ overflow-y: auto;
80
+ width: 100%;
81
+ max-width: 100%;
82
+ animation: modalSlideUp 0.3s ease-out;
83
+ }
84
+
85
+ @keyframes modalSlideUp {
86
+ from {
87
+ transform: translateY(100%);
88
+ opacity: 0;
89
+ }
90
+ to {
91
+ transform: translateY(0);
92
+ opacity: 1;
93
+ }
94
+ }
95
+
96
+ /* Fullscreen position */
97
+ .jux-modal-position-fullscreen {
98
+ align-items: stretch;
99
+ justify-content: stretch;
100
+ padding: 0;
101
+ }
102
+
103
+ .jux-modal-position-fullscreen .jux-modal {
104
+ max-width: 100%;
105
+ width: 100%;
106
+ height: 100%;
107
+ border-radius: 0;
108
+ margin: 0;
109
+ max-height: 100vh;
110
+ animation: modalFadeIn 0.2s ease-out;
111
+ }
112
+
113
+ @keyframes modalFadeIn {
114
+ from {
115
+ opacity: 0;
116
+ }
117
+ to {
118
+ opacity: 1;
119
+ }
120
+ }
121
+
122
+ /* ═══════════════════════════════════════════════════════════════════
123
+ * POPOVER VARIANTS (Tooltip-style)
124
+ * ═══════════════════════════════════════════════════════════════════ */
125
+
126
+ .jux-modal-position-popover-top,
127
+ .jux-modal-position-popover-bottom,
128
+ .jux-modal-position-popover-left,
129
+ .jux-modal-position-popover-right {
130
+ background-color: rgba(0, 0, 0, 0.2);
131
+ backdrop-filter: none;
132
+ }
133
+
134
+ .jux-modal-position-popover-top .jux-modal,
135
+ .jux-modal-position-popover-bottom .jux-modal,
136
+ .jux-modal-position-popover-left .jux-modal,
137
+ .jux-modal-position-popover-right .jux-modal {
138
+ max-width: 20rem;
139
+ padding: 1rem;
140
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.2), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
141
+ border-radius: 0.5rem;
142
+ }
143
+
144
+ /* Popover Top */
145
+ .jux-modal-position-popover-top {
146
+ align-items: flex-start;
147
+ justify-content: center;
148
+ padding-top: 1rem;
149
+ }
150
+
151
+ .jux-modal-position-popover-top .jux-modal {
152
+ animation: popoverSlideDown 0.2s ease-out;
153
+ }
154
+
155
+ @keyframes popoverSlideDown {
156
+ from {
157
+ transform: translateY(-10px);
158
+ opacity: 0;
159
+ }
160
+ to {
161
+ transform: translateY(0);
162
+ opacity: 1;
163
+ }
164
+ }
165
+
166
+ /* Popover Bottom */
167
+ .jux-modal-position-popover-bottom {
168
+ align-items: flex-end;
169
+ justify-content: center;
170
+ padding-bottom: 1rem;
171
+ }
172
+
173
+ .jux-modal-position-popover-bottom .jux-modal {
174
+ animation: popoverSlideUp 0.2s ease-out;
175
+ }
176
+
177
+ @keyframes popoverSlideUp {
178
+ from {
179
+ transform: translateY(10px);
180
+ opacity: 0;
181
+ }
182
+ to {
183
+ transform: translateY(0);
184
+ opacity: 1;
185
+ }
186
+ }
187
+
188
+ /* Popover Left */
189
+ .jux-modal-position-popover-left {
190
+ align-items: center;
191
+ justify-content: flex-start;
192
+ padding-left: 1rem;
193
+ }
194
+
195
+ .jux-modal-position-popover-left .jux-modal {
196
+ animation: popoverSlideRight 0.2s ease-out;
197
+ }
198
+
199
+ @keyframes popoverSlideRight {
200
+ from {
201
+ transform: translateX(-10px);
202
+ opacity: 0;
203
+ }
204
+ to {
205
+ transform: translateX(0);
206
+ opacity: 1;
207
+ }
208
+ }
209
+
210
+ /* Popover Right */
211
+ .jux-modal-position-popover-right {
212
+ align-items: center;
213
+ justify-content: flex-end;
214
+ padding-right: 1rem;
215
+ }
216
+
217
+ .jux-modal-position-popover-right .jux-modal {
218
+ animation: popoverSlideLeft 0.2s ease-out;
219
+ }
220
+
221
+ @keyframes popoverSlideLeft {
222
+ from {
223
+ transform: translateX(10px);
224
+ opacity: 0;
225
+ }
226
+ to {
227
+ transform: translateX(0);
228
+ opacity: 1;
229
+ }
230
+ }
231
+
232
+ /* Smaller close button for popovers */
233
+ .jux-modal-position-popover-top .jux-modal-close,
234
+ .jux-modal-position-popover-bottom .jux-modal-close,
235
+ .jux-modal-position-popover-left .jux-modal-close,
236
+ .jux-modal-position-popover-right .jux-modal-close {
237
+ width: 1.25rem;
238
+ height: 1.25rem;
239
+ font-size: 1rem;
240
+ top: 0.5rem;
241
+ right: 0.5rem;
242
+ }
243
+
244
+ /* ═══════════════════════════════════════════════════════════════════
245
+ * MODAL CONTENT SECTIONS
246
+ * ═══════════════════════════════════════════════════════════════════ */
247
+
248
+ /* Header */
249
+ .jux-modal-header {
250
+ display: flex;
251
+ align-items: flex-start;
252
+ gap: 0.75rem;
253
+ padding: 0;
254
+ text-align: left;
255
+ }
256
+
257
+ .jux-modal-header-icon {
258
+ display: flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ flex-shrink: 0;
262
+ color: #09090b;
263
+ margin-top: 0.125rem;
264
+ }
265
+
266
+ .jux-modal-header-title {
267
+ flex: 1;
268
+ font-size: 1.125rem;
269
+ font-weight: 600;
270
+ line-height: 1.2;
271
+ color: #09090b;
272
+ }
273
+
274
+ /* Body / Content */
275
+ .jux-modal-content {
276
+ padding: 0;
277
+ font-size: 0.875rem;
278
+ color: #71717a;
279
+ line-height: 1.5;
280
+ display: flex;
281
+ flex-direction: column;
282
+ gap: 1rem;
283
+ }
284
+
285
+ /* Footer with Actions */
286
+ .jux-modal-footer {
287
+ padding: 0;
288
+ padding-top: 0.5rem;
289
+ display: flex;
290
+ flex-direction: row;
291
+ justify-content: flex-end;
292
+ gap: 0.5rem;
293
+ flex-wrap: wrap;
294
+ }
295
+
296
+ /* Modal Action Buttons */
297
+ .jux-modal-action {
298
+ display: inline-flex;
299
+ align-items: center;
300
+ justify-content: center;
301
+ white-space: nowrap;
302
+ border-radius: 0.375rem;
303
+ font-size: 0.875rem;
304
+ font-weight: 500;
305
+ height: 2.5rem;
306
+ padding: 0 1rem;
307
+ cursor: pointer;
308
+ transition: all 0.15s ease;
309
+ border: 1px solid transparent;
310
+ }
311
+
312
+ .jux-modal-action-ghost {
313
+ background-color: transparent;
314
+ color: #09090b;
315
+ border: none;
316
+ }
317
+ .jux-modal-action-ghost:hover {
318
+ background-color: #f4f4f5;
319
+ color: #18181b;
320
+ }
321
+
322
+ .jux-modal-action-secondary {
323
+ border: 1px solid #e4e4e7;
324
+ background-color: #ffffff;
325
+ color: #09090b;
326
+ }
327
+ .jux-modal-action-secondary:hover {
328
+ background-color: #f4f4f5;
329
+ color: #18181b;
330
+ }
331
+
332
+ .jux-modal-action-primary {
333
+ background-color: #18181b;
334
+ color: #fafafa;
335
+ border: none;
336
+ }
337
+ .jux-modal-action-primary:hover {
338
+ background-color: #27272a;
339
+ }
340
+
341
+ .jux-modal-action-destructive {
342
+ background-color: #ef4444;
343
+ color: #ffffff;
344
+ border: none;
345
+ }
346
+ .jux-modal-action-destructive:hover {
347
+ background-color: #dc2626;
348
+ }
349
+
350
+ /* Close Button (X) */
351
+ .jux-modal-close {
352
+ position: absolute;
353
+ top: 1rem;
354
+ right: 1rem;
355
+ border-radius: 0.125rem;
356
+ opacity: 0.7;
357
+ transition: opacity 0.15s ease;
358
+ background: transparent;
359
+ border: none;
360
+ cursor: pointer;
361
+ color: #71717a;
362
+ padding: 0.25rem;
363
+ width: 1.5rem;
364
+ height: 1.5rem;
365
+ display: flex;
366
+ align-items: center;
367
+ justify-content: center;
368
+ font-size: 1.25rem;
369
+ line-height: 1;
370
+ }
371
+
372
+ .jux-modal-close:hover {
373
+ opacity: 1;
374
+ color: #09090b;
375
+ }
376
+
377
+ .jux-modal-close:focus-visible {
378
+ outline: 2px solid #18181b;
379
+ outline-offset: 2px;
380
+ }
381
+
382
+ /* ═══════════════════════════════════════════════════════════════════
383
+ * RESPONSIVE ADJUSTMENTS
384
+ * ═══════════════════════════════════════════════════════════════════ */
385
+
386
+ @media (max-width: 640px) {
387
+ .jux-modal-position-top,
388
+ .jux-modal-position-center {
389
+ padding: 1rem;
390
+ }
391
+
392
+ .jux-modal-position-bottom .jux-modal {
393
+ max-height: 95vh;
394
+ }
395
+
396
+ .jux-modal-position-popover-top .jux-modal,
397
+ .jux-modal-position-popover-bottom .jux-modal,
398
+ .jux-modal-position-popover-left .jux-modal,
399
+ .jux-modal-position-popover-right .jux-modal {
400
+ max-width: calc(100vw - 2rem);
401
+ }
402
+ }
@@ -35,7 +35,8 @@ const defaults = {
35
35
  // Resolve absolute paths
36
36
  const paths = {
37
37
  source: path.resolve(PROJECT_ROOT, directories.source),
38
- distribution: path.resolve(PROJECT_ROOT, directories.distribution)
38
+ distribution: path.resolve(PROJECT_ROOT, directories.distribution),
39
+ public: path.resolve(PROJECT_ROOT, 'public') // ✅ Add public path
39
40
  };
40
41
 
41
42
  // ═══════════════════════════════════════════════════════════════
@@ -52,6 +53,13 @@ if (fs.existsSync(paths.source)) {
52
53
  process.exit(1);
53
54
  }
54
55
 
56
+ // ✅ Show public folder status
57
+ if (fs.existsSync(paths.public)) {
58
+ console.log(` ✓ public: ${paths.public}`);
59
+ } else {
60
+ console.log(` ℹ public: ${paths.public} (optional, not found)`);
61
+ }
62
+
55
63
  // ═══════════════════════════════════════════════════════════════
56
64
  // RUN BUILD
57
65
  // ═══════════════════════════════════════════════════════════════
@@ -430,6 +430,9 @@ navigate(location.pathname);
430
430
  }
431
431
  fs.mkdirSync(this.distDir, { recursive: true });
432
432
 
433
+ // ✅ Copy public folder if exists
434
+ this.copyPublicFolder();
435
+
433
436
  const { views, dataModules, sharedModules } = this.scanFiles();
434
437
  console.log(`📁 Found ${views.length} views, ${sharedModules.length} shared, ${dataModules.length} data`);
435
438
 
@@ -497,4 +500,81 @@ navigate(location.pathname);
497
500
  console.log(`\n✅ Build Complete!\n`);
498
501
  return { success: true, errors: [], warnings: validation.warnings };
499
502
  }
503
+
504
+ /**
505
+ * Copy public folder contents to dist
506
+ */
507
+ copyPublicFolder() {
508
+ const projectRoot = process.cwd();
509
+ const publicSrc = path.resolve(projectRoot, 'public');
510
+
511
+ if (!fs.existsSync(publicSrc)) {
512
+ return; // No public folder, skip
513
+ }
514
+
515
+ console.log('📦 Copying public assets...');
516
+
517
+ try {
518
+ this._copyDirRecursive(publicSrc, this.distDir, 0);
519
+ console.log('✅ Public assets copied');
520
+ } catch (err) {
521
+ console.warn('⚠️ Error copying public folder:', err.message);
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Recursively copy directory contents
527
+ */
528
+ _copyDirRecursive(src, dest, depth = 0) {
529
+ const entries = fs.readdirSync(src, { withFileTypes: true });
530
+
531
+ entries.forEach(entry => {
532
+ // Skip hidden files and directories
533
+ if (entry.name.startsWith('.')) return;
534
+
535
+ const srcPath = path.join(src, entry.name);
536
+ const destPath = path.join(dest, entry.name);
537
+
538
+ if (entry.isDirectory()) {
539
+ // Create directory and recurse
540
+ if (!fs.existsSync(destPath)) {
541
+ fs.mkdirSync(destPath, { recursive: true });
542
+ }
543
+ this._copyDirRecursive(srcPath, destPath, depth + 1);
544
+ } else {
545
+ // Copy file
546
+ fs.copyFileSync(srcPath, destPath);
547
+
548
+ // Log files at root level only
549
+ if (depth === 0) {
550
+ const ext = path.extname(entry.name);
551
+ const icon = this._getFileIcon(ext);
552
+ console.log(` ${icon} ${entry.name}`);
553
+ }
554
+ }
555
+ });
556
+ }
557
+
558
+ /**
559
+ * Get icon for file type
560
+ */
561
+ _getFileIcon(ext) {
562
+ const icons = {
563
+ '.html': '📄',
564
+ '.css': '🎨',
565
+ '.js': '📜',
566
+ '.json': '📋',
567
+ '.png': '🖼️',
568
+ '.jpg': '🖼️',
569
+ '.jpeg': '🖼️',
570
+ '.gif': '🖼️',
571
+ '.svg': '🎨',
572
+ '.ico': '🔖',
573
+ '.woff': '🔤',
574
+ '.woff2': '🔤',
575
+ '.ttf': '🔤',
576
+ '.eot': '🔤'
577
+ };
578
+ return icons[ext.toLowerCase()] || '📦';
579
+ }
500
580
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.70",
3
+ "version": "1.1.72",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",