juxscript 1.0.19 → 1.0.21
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/bin/cli.js +121 -72
- package/lib/components/alert.ts +212 -165
- package/lib/components/badge.ts +93 -103
- package/lib/components/base/BaseComponent.ts +397 -0
- package/lib/components/base/FormInput.ts +322 -0
- package/lib/components/button.ts +63 -122
- package/lib/components/card.ts +109 -155
- package/lib/components/charts/areachart.ts +315 -0
- package/lib/components/charts/barchart.ts +421 -0
- package/lib/components/charts/doughnutchart.ts +263 -0
- package/lib/components/charts/lib/BaseChart.ts +402 -0
- package/lib/components/charts/lib/chart-types.ts +159 -0
- package/lib/components/charts/lib/chart-utils.ts +160 -0
- package/lib/components/charts/lib/chart.ts +707 -0
- package/lib/components/checkbox.ts +264 -127
- package/lib/components/code.ts +75 -108
- package/lib/components/container.ts +113 -130
- package/lib/components/data.ts +37 -5
- package/lib/components/datepicker.ts +195 -147
- package/lib/components/dialog.ts +187 -157
- package/lib/components/divider.ts +85 -191
- package/lib/components/docs-data.json +544 -2027
- package/lib/components/dropdown.ts +178 -136
- package/lib/components/element.ts +227 -171
- package/lib/components/fileupload.ts +285 -228
- package/lib/components/guard.ts +92 -0
- package/lib/components/heading.ts +46 -69
- package/lib/components/helpers.ts +13 -6
- package/lib/components/hero.ts +107 -95
- package/lib/components/icon.ts +160 -0
- package/lib/components/icons.ts +175 -0
- package/lib/components/include.ts +153 -5
- package/lib/components/input.ts +174 -374
- package/lib/components/kpicard.ts +16 -16
- package/lib/components/list.ts +378 -240
- package/lib/components/loading.ts +142 -211
- package/lib/components/menu.ts +103 -97
- package/lib/components/modal.ts +138 -144
- package/lib/components/nav.ts +169 -90
- package/lib/components/paragraph.ts +49 -150
- package/lib/components/progress.ts +118 -200
- package/lib/components/radio.ts +297 -149
- package/lib/components/script.ts +19 -87
- package/lib/components/select.ts +184 -186
- package/lib/components/sidebar.ts +152 -140
- package/lib/components/style.ts +19 -82
- package/lib/components/switch.ts +258 -188
- package/lib/components/table.ts +1117 -170
- package/lib/components/tabs.ts +162 -145
- package/lib/components/theme-toggle.ts +108 -169
- package/lib/components/tooltip.ts +86 -157
- package/lib/components/write.ts +108 -127
- package/lib/jux.ts +86 -41
- package/machinery/build.js +466 -0
- package/machinery/compiler.js +354 -105
- package/machinery/server.js +23 -100
- package/machinery/watcher.js +153 -130
- package/package.json +1 -2
- package/presets/base.css +1166 -0
- package/presets/notion.css +2 -1975
- package/lib/adapters/base-adapter.js +0 -35
- package/lib/adapters/index.js +0 -33
- package/lib/adapters/mysql-adapter.js +0 -65
- package/lib/adapters/postgres-adapter.js +0 -70
- package/lib/adapters/sqlite-adapter.js +0 -56
- package/lib/components/areachart.ts +0 -1246
- package/lib/components/areachartsmooth.ts +0 -1380
- package/lib/components/barchart.ts +0 -1250
- package/lib/components/chart.ts +0 -127
- package/lib/components/doughnutchart.ts +0 -1191
- package/lib/components/footer.ts +0 -165
- package/lib/components/header.ts +0 -187
- package/lib/components/layout.ts +0 -239
- package/lib/components/main.ts +0 -137
- package/lib/layouts/default.jux +0 -8
- package/lib/layouts/figma.jux +0 -0
- /package/lib/{themes → components/charts/lib}/charts.js +0 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { BaseComponent } from './base/BaseComponent.js';
|
|
2
|
+
import { renderIcon, renderEmoji } from './icons.js';
|
|
3
|
+
|
|
4
|
+
// Event definitions
|
|
5
|
+
const TRIGGER_EVENTS = [] as const;
|
|
6
|
+
const CALLBACK_EVENTS = [] as const;
|
|
7
|
+
|
|
8
|
+
export interface IconOptions {
|
|
9
|
+
value: string;
|
|
10
|
+
size?: string;
|
|
11
|
+
color?: string;
|
|
12
|
+
useEmoji?: boolean;
|
|
13
|
+
style?: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type IconState = {
|
|
18
|
+
value: string;
|
|
19
|
+
size: string;
|
|
20
|
+
color: string;
|
|
21
|
+
useEmoji: boolean;
|
|
22
|
+
style: string;
|
|
23
|
+
class: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export class Icon extends BaseComponent<IconState> {
|
|
27
|
+
constructor(id: string, options: IconOptions) {
|
|
28
|
+
super(id, {
|
|
29
|
+
value: options.value,
|
|
30
|
+
size: options.size ?? '24px',
|
|
31
|
+
color: options.color ?? '',
|
|
32
|
+
useEmoji: options.useEmoji ?? false,
|
|
33
|
+
style: options.style ?? '',
|
|
34
|
+
class: options.class ?? ''
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected getTriggerEvents(): readonly string[] {
|
|
39
|
+
return TRIGGER_EVENTS;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected getCallbackEvents(): readonly string[] {
|
|
43
|
+
return CALLBACK_EVENTS;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
47
|
+
* FLUENT API
|
|
48
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
49
|
+
|
|
50
|
+
// ✅ Inherited from BaseComponent
|
|
51
|
+
|
|
52
|
+
value(value: string): this {
|
|
53
|
+
this.state.value = value;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
size(value: string): this {
|
|
58
|
+
this.state.size = value;
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
color(value: string): this {
|
|
63
|
+
this.state.color = value;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
useEmoji(value: boolean): this {
|
|
68
|
+
this.state.useEmoji = value;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
73
|
+
* RENDER
|
|
74
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
75
|
+
|
|
76
|
+
render(targetId?: string): this {
|
|
77
|
+
const container = this._setupContainer(targetId);
|
|
78
|
+
|
|
79
|
+
const { value, size, color, useEmoji, style, class: className } = this.state;
|
|
80
|
+
|
|
81
|
+
const wrapper = document.createElement('span');
|
|
82
|
+
wrapper.className = 'jux-icon';
|
|
83
|
+
wrapper.id = this._id;
|
|
84
|
+
if (className) wrapper.className += ` ${className}`;
|
|
85
|
+
if (style) wrapper.setAttribute('style', style);
|
|
86
|
+
|
|
87
|
+
const iconElement = useEmoji ? renderEmoji(value) : renderIcon(value);
|
|
88
|
+
iconElement.style.width = size;
|
|
89
|
+
iconElement.style.height = size;
|
|
90
|
+
if (color) iconElement.style.color = color;
|
|
91
|
+
|
|
92
|
+
wrapper.appendChild(iconElement);
|
|
93
|
+
|
|
94
|
+
this._wireStandardEvents(wrapper);
|
|
95
|
+
|
|
96
|
+
// Wire sync bindings
|
|
97
|
+
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
98
|
+
if (property === 'value') {
|
|
99
|
+
const transform = toComponent || ((v: any) => String(v));
|
|
100
|
+
|
|
101
|
+
stateObj.subscribe((val: any) => {
|
|
102
|
+
const transformed = transform(val);
|
|
103
|
+
this.state.value = transformed;
|
|
104
|
+
|
|
105
|
+
wrapper.innerHTML = '';
|
|
106
|
+
const newIcon = this.state.useEmoji ? renderEmoji(transformed) : renderIcon(transformed);
|
|
107
|
+
newIcon.style.width = this.state.size;
|
|
108
|
+
newIcon.style.height = this.state.size;
|
|
109
|
+
if (this.state.color) newIcon.style.color = this.state.color;
|
|
110
|
+
wrapper.appendChild(newIcon);
|
|
111
|
+
|
|
112
|
+
requestAnimationFrame(() => {
|
|
113
|
+
if ((window as any).lucide) {
|
|
114
|
+
(window as any).lucide.createIcons();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else if (property === 'size') {
|
|
120
|
+
const transform = toComponent || ((v: any) => String(v));
|
|
121
|
+
|
|
122
|
+
stateObj.subscribe((val: any) => {
|
|
123
|
+
const transformed = transform(val);
|
|
124
|
+
const icon = wrapper.querySelector('img, svg, span');
|
|
125
|
+
if (icon instanceof HTMLElement) {
|
|
126
|
+
icon.style.width = transformed;
|
|
127
|
+
icon.style.height = transformed;
|
|
128
|
+
}
|
|
129
|
+
this.state.size = transformed;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else if (property === 'color') {
|
|
133
|
+
const transform = toComponent || ((v: any) => String(v));
|
|
134
|
+
|
|
135
|
+
stateObj.subscribe((val: any) => {
|
|
136
|
+
const transformed = transform(val);
|
|
137
|
+
const icon = wrapper.querySelector('img, svg, span');
|
|
138
|
+
if (icon instanceof HTMLElement) {
|
|
139
|
+
icon.style.color = transformed;
|
|
140
|
+
}
|
|
141
|
+
this.state.color = transformed;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
container.appendChild(wrapper);
|
|
147
|
+
|
|
148
|
+
requestAnimationFrame(() => {
|
|
149
|
+
if ((window as any).lucide) {
|
|
150
|
+
(window as any).lucide.createIcons();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function icon(id: string, options: IconOptions): Icon {
|
|
159
|
+
return new Icon(id, options);
|
|
160
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon utilities for components
|
|
3
|
+
* Handles emoji-to-Lucide mapping, direct icon names, and image paths
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const EMOJI_TO_LUCIDE: Record<string, string> = {
|
|
7
|
+
"✔️": "check-circle",
|
|
8
|
+
"✓": "check",
|
|
9
|
+
"❌": "x-circle",
|
|
10
|
+
"✗": "x",
|
|
11
|
+
"🔥": "flame",
|
|
12
|
+
"🚀": "rocket",
|
|
13
|
+
"⚙️": "settings",
|
|
14
|
+
"🏠": "home",
|
|
15
|
+
"👤": "user",
|
|
16
|
+
"💡": "lightbulb",
|
|
17
|
+
"🌈": "rainbow",
|
|
18
|
+
"🧪": "flask-conical",
|
|
19
|
+
"✉️": "mail",
|
|
20
|
+
"📞": "phone",
|
|
21
|
+
"🔍": "search",
|
|
22
|
+
"❤️": "heart",
|
|
23
|
+
"⭐": "star",
|
|
24
|
+
"⚠️": "alert-triangle",
|
|
25
|
+
"ℹ️": "info",
|
|
26
|
+
"❓": "help-circle",
|
|
27
|
+
"👁️": "eye",
|
|
28
|
+
"👁️🗨️": "eye-off",
|
|
29
|
+
"☰": "menu",
|
|
30
|
+
"🕐": "clock",
|
|
31
|
+
"📅": "calendar",
|
|
32
|
+
"⬇️": "chevron-down",
|
|
33
|
+
"⬆️": "chevron-up",
|
|
34
|
+
"⬅️": "chevron-left",
|
|
35
|
+
"➡️": "chevron-right",
|
|
36
|
+
"📈": "arrow-up",
|
|
37
|
+
"📉": "arrow-down",
|
|
38
|
+
"⬇": "download",
|
|
39
|
+
"📤": "upload",
|
|
40
|
+
"📄": "file-text",
|
|
41
|
+
"🗑️": "trash-2",
|
|
42
|
+
"✏️": "edit",
|
|
43
|
+
"📋": "clipboard",
|
|
44
|
+
"🔗": "link",
|
|
45
|
+
"↗️": "external-link",
|
|
46
|
+
"☀️": "sun",
|
|
47
|
+
"🌙": "moon",
|
|
48
|
+
"📊": "bar-chart-3",
|
|
49
|
+
"📁": "folder",
|
|
50
|
+
"💰": "coins",
|
|
51
|
+
"📧": "mail",
|
|
52
|
+
"✅": "square-check",
|
|
53
|
+
"🗓️": "calendar-days",
|
|
54
|
+
"💬": "message-circle",
|
|
55
|
+
"🌐": "globe",
|
|
56
|
+
"🔬": "microscope",
|
|
57
|
+
"💊": "pill",
|
|
58
|
+
"🔒": "lock",
|
|
59
|
+
"⚖️": "scale",
|
|
60
|
+
"🔌": "plug",
|
|
61
|
+
"🔐": "lock-keyhole",
|
|
62
|
+
"🏥": "cross",
|
|
63
|
+
"👥": "users",
|
|
64
|
+
"💚": "heart",
|
|
65
|
+
"💸": "banknote",
|
|
66
|
+
"🧾": "receipt",
|
|
67
|
+
"➕": "plus",
|
|
68
|
+
"➖": "minus",
|
|
69
|
+
"💾": "save",
|
|
70
|
+
"🩺": "stethoscope"
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const LUCIDE_CDN_URL = "https://unpkg.com/lucide@latest";
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Render an icon from emoji, icon name, or image path
|
|
77
|
+
* @param value - Emoji (🚀), icon name (rocket), or image path (/icon.png)
|
|
78
|
+
* @returns HTMLElement containing the icon
|
|
79
|
+
*
|
|
80
|
+
* Usage:
|
|
81
|
+
* const icon = renderIcon('🚀'); // Lucide rocket icon
|
|
82
|
+
* const icon = renderIcon('rocket'); // Lucide rocket icon
|
|
83
|
+
* const icon = renderIcon('/icon.png'); // Image element
|
|
84
|
+
*/
|
|
85
|
+
export function renderIcon(value: string): HTMLElement {
|
|
86
|
+
// Check if it's an image path (contains / or . or starts with http)
|
|
87
|
+
if (value.includes('/') || value.includes('.') || value.startsWith('http')) {
|
|
88
|
+
return createImageIcon(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check if it's an emoji that maps to Lucide
|
|
92
|
+
const lucideName = EMOJI_TO_LUCIDE[value];
|
|
93
|
+
if (lucideName) {
|
|
94
|
+
const element = createVectorIcon(lucideName);
|
|
95
|
+
ensureLucideLoaded();
|
|
96
|
+
return element;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check if it's a direct Lucide icon name (lowercase with hyphens)
|
|
100
|
+
if (/^[a-z][a-z0-9-]*$/.test(value)) {
|
|
101
|
+
const element = createVectorIcon(value);
|
|
102
|
+
ensureLucideLoaded();
|
|
103
|
+
return element;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Fallback: render as emoji
|
|
107
|
+
return createEmojiFallback(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Render raw emoji without conversion
|
|
112
|
+
* @param emoji - The emoji character
|
|
113
|
+
* @returns HTMLElement containing just the emoji
|
|
114
|
+
*/
|
|
115
|
+
export function renderEmoji(emoji: string): HTMLElement {
|
|
116
|
+
return createEmojiFallback(emoji);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Ensures Lucide is loaded and icons are rendered
|
|
121
|
+
*/
|
|
122
|
+
function ensureLucideLoaded(): void {
|
|
123
|
+
if ((window as any).lucide) {
|
|
124
|
+
// Already loaded, render immediately
|
|
125
|
+
(window as any).lucide.createIcons();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Not loaded yet, inject script
|
|
130
|
+
if (!document.querySelector(`script[src="${LUCIDE_CDN_URL}"]`)) {
|
|
131
|
+
const script = document.createElement('script');
|
|
132
|
+
script.src = LUCIDE_CDN_URL;
|
|
133
|
+
script.onload = () => {
|
|
134
|
+
if ((window as any).lucide) {
|
|
135
|
+
(window as any).lucide.createIcons();
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
document.head.appendChild(script);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Create Lucide icon element
|
|
144
|
+
*/
|
|
145
|
+
function createVectorIcon(name: string): HTMLElement {
|
|
146
|
+
const iconEl = document.createElement('i');
|
|
147
|
+
iconEl.setAttribute('data-lucide', name);
|
|
148
|
+
iconEl.style.width = '24px';
|
|
149
|
+
iconEl.style.height = '24px';
|
|
150
|
+
iconEl.style.display = 'inline-block';
|
|
151
|
+
return iconEl;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Create image icon element
|
|
156
|
+
*/
|
|
157
|
+
function createImageIcon(src: string): HTMLImageElement {
|
|
158
|
+
const img = document.createElement('img');
|
|
159
|
+
img.src = src;
|
|
160
|
+
img.style.width = '24px';
|
|
161
|
+
img.style.height = '24px';
|
|
162
|
+
img.style.display = 'inline-block';
|
|
163
|
+
img.style.objectFit = 'contain';
|
|
164
|
+
return img;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Render native emoji or text
|
|
169
|
+
*/
|
|
170
|
+
function createEmojiFallback(emoji: string): HTMLSpanElement {
|
|
171
|
+
const span = document.createElement('span');
|
|
172
|
+
span.textContent = emoji;
|
|
173
|
+
span.style.display = 'inline-block';
|
|
174
|
+
return span;
|
|
175
|
+
}
|
|
@@ -5,7 +5,7 @@ import { ErrorHandler } from './error-handler.js';
|
|
|
5
5
|
* Auto-detects resource type from URL and provides simple, fluent API
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
type IncludeType = 'stylesheet' | 'script' | 'image' | 'font' | 'preload' | 'prefetch' | 'module';
|
|
8
|
+
type IncludeType = 'stylesheet' | 'script' | 'image' | 'font' | 'preload' | 'prefetch' | 'module' | 'json';
|
|
9
9
|
type IncludeLocation = 'head' | 'body-start' | 'body-end';
|
|
10
10
|
|
|
11
11
|
interface IncludeOptions {
|
|
@@ -22,6 +22,7 @@ export class Include {
|
|
|
22
22
|
private type: IncludeType;
|
|
23
23
|
private options: IncludeOptions = {};
|
|
24
24
|
private element: HTMLElement | null = null;
|
|
25
|
+
private explicitType: boolean = false; // NEW: Track if type was explicitly set
|
|
25
26
|
|
|
26
27
|
constructor(urlOrFile: string) {
|
|
27
28
|
this.url = urlOrFile;
|
|
@@ -33,10 +34,25 @@ export class Include {
|
|
|
33
34
|
* ------------------------- */
|
|
34
35
|
|
|
35
36
|
private detectType(url: string): IncludeType {
|
|
37
|
+
// Check for common script patterns in URLs (CDN, etc.)
|
|
38
|
+
if (url.includes('tailwindcss') ||
|
|
39
|
+
url.includes('jsdelivr.net/npm') ||
|
|
40
|
+
url.includes('unpkg.com') ||
|
|
41
|
+
url.includes('cdn.') ||
|
|
42
|
+
url.match(/\.(js|mjs)($|\?)/)) {
|
|
43
|
+
return 'script';
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
if (url.endsWith('.css')) return 'stylesheet';
|
|
37
|
-
if (url.endsWith('.
|
|
47
|
+
if (url.endsWith('.json')) return 'json';
|
|
38
48
|
if (url.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i)) return 'image';
|
|
39
49
|
if (url.match(/\.(woff|woff2|ttf|otf|eot)$/i)) return 'font';
|
|
50
|
+
|
|
51
|
+
// Default to script for extensionless URLs from CDN domains
|
|
52
|
+
if (!url.includes('.') || url.match(/^https?:\/\/cdn/)) {
|
|
53
|
+
return 'script';
|
|
54
|
+
}
|
|
55
|
+
|
|
40
56
|
return 'preload';
|
|
41
57
|
}
|
|
42
58
|
|
|
@@ -44,41 +60,144 @@ export class Include {
|
|
|
44
60
|
* Fluent Type Setters
|
|
45
61
|
* ------------------------- */
|
|
46
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Force treat as CSS stylesheet
|
|
65
|
+
* Use when URL doesn't have .css extension
|
|
66
|
+
*/
|
|
47
67
|
withCss(): this {
|
|
48
68
|
this.type = 'stylesheet';
|
|
69
|
+
this.explicitType = true; // Mark as explicit
|
|
49
70
|
return this;
|
|
50
71
|
}
|
|
51
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Force treat as JavaScript
|
|
75
|
+
* Use when URL doesn't have .js extension (CDN scripts, etc.)
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* jux.include('https://cdn.tailwindcss.com').withJs().render();
|
|
79
|
+
* jux.include('https://unpkg.com/alpine').withJs({ defer: true }).render();
|
|
80
|
+
*/
|
|
52
81
|
withJs(options?: { async?: boolean; defer?: boolean }): this {
|
|
53
82
|
this.type = 'script';
|
|
83
|
+
this.explicitType = true; // Mark as explicit
|
|
54
84
|
if (options?.async) this.options.async = true;
|
|
55
85
|
if (options?.defer) this.options.defer = true;
|
|
56
86
|
return this;
|
|
57
87
|
}
|
|
58
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Force treat as ES module
|
|
91
|
+
* Use for module scripts
|
|
92
|
+
*/
|
|
59
93
|
withModule(): this {
|
|
60
94
|
this.type = 'module';
|
|
95
|
+
this.explicitType = true; // Mark as explicit
|
|
61
96
|
return this;
|
|
62
97
|
}
|
|
63
98
|
|
|
64
99
|
withImage(): this {
|
|
65
100
|
this.type = 'image';
|
|
101
|
+
this.explicitType = true;
|
|
66
102
|
return this;
|
|
67
103
|
}
|
|
68
104
|
|
|
69
105
|
withFont(): this {
|
|
70
106
|
this.type = 'font';
|
|
107
|
+
this.explicitType = true;
|
|
71
108
|
return this;
|
|
72
109
|
}
|
|
73
110
|
|
|
74
111
|
withPreload(as?: string): this {
|
|
75
112
|
this.type = 'preload';
|
|
113
|
+
this.explicitType = true;
|
|
76
114
|
if (as) this.options.as = as;
|
|
77
115
|
return this;
|
|
78
116
|
}
|
|
79
117
|
|
|
80
118
|
withPrefetch(): this {
|
|
81
119
|
this.type = 'prefetch';
|
|
120
|
+
this.explicitType = true;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
withJson(): this {
|
|
125
|
+
this.type = 'json';
|
|
126
|
+
this.explicitType = true;
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* -------------------------
|
|
131
|
+
* Convenience aliases for common patterns
|
|
132
|
+
* ------------------------- */
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Shorthand for .withJs()
|
|
136
|
+
* @example jux.include(url).asScript()
|
|
137
|
+
*/
|
|
138
|
+
asScript(options?: { async?: boolean; defer?: boolean }): this {
|
|
139
|
+
return this.withJs(options);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Shorthand for .withCss()
|
|
144
|
+
* @example jux.include(url).asStylesheet()
|
|
145
|
+
*/
|
|
146
|
+
asStylesheet(): this {
|
|
147
|
+
return this.withCss();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* -------------------------
|
|
151
|
+
* JSON Fetching
|
|
152
|
+
* ------------------------- */
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Fetch and parse JSON file
|
|
156
|
+
* Returns a Promise that resolves to the parsed JSON data
|
|
157
|
+
*
|
|
158
|
+
* Usage:
|
|
159
|
+
* const config = await jux.include('config.json').asJson();
|
|
160
|
+
* const data = await jux.include('/api/data').asJson();
|
|
161
|
+
*/
|
|
162
|
+
async asJson<T = any>(): Promise<T> {
|
|
163
|
+
try {
|
|
164
|
+
const response = await fetch(this.url);
|
|
165
|
+
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const data = await response.json();
|
|
171
|
+
console.log(`✓ JSON loaded: ${this.url}`);
|
|
172
|
+
return data;
|
|
173
|
+
} catch (error: any) {
|
|
174
|
+
ErrorHandler.captureError({
|
|
175
|
+
component: 'Include',
|
|
176
|
+
method: 'asJson',
|
|
177
|
+
message: error.message,
|
|
178
|
+
stack: error.stack,
|
|
179
|
+
timestamp: new Date(),
|
|
180
|
+
context: {
|
|
181
|
+
url: this.url,
|
|
182
|
+
error: 'json_fetch_failed'
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Fetch JSON and execute callback with data
|
|
191
|
+
*
|
|
192
|
+
* Usage:
|
|
193
|
+
* jux.include('config.json').onJson(data => {
|
|
194
|
+
* console.log('Config:', data);
|
|
195
|
+
* });
|
|
196
|
+
*/
|
|
197
|
+
onJson<T = any>(callback: (data: T) => void): this {
|
|
198
|
+
this.asJson<T>().then(callback).catch(error => {
|
|
199
|
+
console.error('Failed to load JSON:', error);
|
|
200
|
+
});
|
|
82
201
|
return this;
|
|
83
202
|
}
|
|
84
203
|
|
|
@@ -128,6 +247,12 @@ export class Include {
|
|
|
128
247
|
render(): this {
|
|
129
248
|
if (typeof document === 'undefined') return this;
|
|
130
249
|
|
|
250
|
+
// Don't render JSON type (it's fetched via asJson() instead)
|
|
251
|
+
if (this.type === 'json') {
|
|
252
|
+
console.warn('Include: JSON files should be loaded with .asJson() instead of .render()');
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
|
|
131
256
|
try {
|
|
132
257
|
this.remove();
|
|
133
258
|
|
|
@@ -161,6 +286,10 @@ export class Include {
|
|
|
161
286
|
}
|
|
162
287
|
|
|
163
288
|
this.element = element;
|
|
289
|
+
|
|
290
|
+
// Log with type indicator
|
|
291
|
+
const typeIndicator = this.explicitType ? '(explicit)' : '(auto-detected)';
|
|
292
|
+
console.log(`✓ Include loaded as ${this.type} ${typeIndicator}: ${this.url}`);
|
|
164
293
|
} catch (error: any) {
|
|
165
294
|
ErrorHandler.captureError({
|
|
166
295
|
component: 'Include',
|
|
@@ -172,6 +301,7 @@ export class Include {
|
|
|
172
301
|
type: this.type,
|
|
173
302
|
url: this.url,
|
|
174
303
|
location: this.options.location,
|
|
304
|
+
explicitType: this.explicitType,
|
|
175
305
|
error: 'runtime_exception'
|
|
176
306
|
}
|
|
177
307
|
});
|
|
@@ -279,14 +409,32 @@ export class Include {
|
|
|
279
409
|
* Factory function - auto-detects type and renders immediately
|
|
280
410
|
*
|
|
281
411
|
* Usage:
|
|
412
|
+
* // Auto-detect (works for most cases)
|
|
282
413
|
* jux.include('styles.css');
|
|
283
414
|
* jux.include('script.js').async();
|
|
415
|
+
*
|
|
416
|
+
* // Explicit type (for extensionless URLs)
|
|
417
|
+
* jux.include('https://cdn.tailwindcss.com').withJs();
|
|
418
|
+
* jux.include('https://cdn.tailwindcss.com').asScript();
|
|
419
|
+
* jux.include('https://unpkg.com/htmx.org').withJs({ defer: true });
|
|
420
|
+
*
|
|
421
|
+
* // CDN with parameters
|
|
422
|
+
* jux.include('https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4').withJs();
|
|
423
|
+
*
|
|
424
|
+
* // Module
|
|
284
425
|
* jux.include('app.mjs').withModule();
|
|
285
|
-
*
|
|
286
|
-
*
|
|
426
|
+
*
|
|
427
|
+
* // For JSON:
|
|
428
|
+
* const data = await jux.include('config.json').asJson();
|
|
429
|
+
* jux.include('data.json').onJson(data => console.log(data));
|
|
287
430
|
*/
|
|
288
431
|
export function include(urlOrFile: string): Include {
|
|
289
432
|
const imp = new Include(urlOrFile);
|
|
290
|
-
|
|
433
|
+
|
|
434
|
+
// Don't auto-render JSON files
|
|
435
|
+
if (imp['type'] !== 'json') {
|
|
436
|
+
imp.render();
|
|
437
|
+
}
|
|
438
|
+
|
|
291
439
|
return imp;
|
|
292
440
|
}
|