juxscript 1.1.69 → 1.1.71

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,120 +1,86 @@
1
1
  /**
2
- * Include - Include external resources (CSS, JS, images, fonts, etc.)
3
- * Auto-detects resource type from URL and provides simple, fluent API
2
+ * Include - Simplified resource injection for bundled and external resources
3
+ * Supports page-specific scoping and cleanup
4
4
  */
5
- type IncludeLocation = 'head' | 'body-start' | 'body-end';
5
+ type ResourceType = 'css' | 'js' | 'module';
6
6
  interface IncludeOptions {
7
- as?: string;
8
- crossOrigin?: 'anonymous' | 'use-credentials';
9
- integrity?: string;
7
+ type?: ResourceType;
8
+ target?: string;
10
9
  async?: boolean;
11
10
  defer?: boolean;
12
- location?: IncludeLocation;
11
+ crossOrigin?: 'anonymous' | 'use-credentials';
12
+ integrity?: string;
13
+ pageScoped?: boolean;
13
14
  }
14
15
  export declare class Include {
15
16
  private url;
16
- private type;
17
17
  private options;
18
18
  private element;
19
- private explicitType;
20
- constructor(urlOrFile: string);
21
- private detectType;
22
- /**
23
- * Force treat as CSS stylesheet
24
- * Use when URL doesn't have .css extension
25
- */
26
- withCss(): this;
19
+ private pageId;
20
+ constructor(url: string, options?: IncludeOptions);
21
+ css(): this;
22
+ js(): this;
23
+ module(): this;
24
+ async(): this;
25
+ defer(): this;
27
26
  /**
28
- * Force treat as JavaScript
29
- * Use when URL doesn't have .js extension (CDN scripts, etc.)
27
+ * Inject into specific container instead of <head>
28
+ * Useful for page-specific styles
30
29
  *
31
30
  * @example
32
- * jux.include('https://cdn.tailwindcss.com').withJs().render();
33
- * jux.include('https://unpkg.com/alpine').withJs({ defer: true }).render();
31
+ * jux.include('/css/page1.css').into('#page1-container');
34
32
  */
35
- withJs(options?: {
36
- async?: boolean;
37
- defer?: boolean;
38
- }): this;
33
+ into(selector: string): this;
39
34
  /**
40
- * Force treat as ES module
41
- * Use for module scripts
42
- */
43
- withModule(): this;
44
- withImage(): this;
45
- withFont(): this;
46
- withPreload(as?: string): this;
47
- withPrefetch(): this;
48
- withJson(): this;
49
- /**
50
- * Shorthand for .withJs()
51
- * @example jux.include(url).asScript()
52
- */
53
- asScript(options?: {
54
- async?: boolean;
55
- defer?: boolean;
56
- }): this;
57
- /**
58
- * Shorthand for .withCss()
59
- * @example jux.include(url).asStylesheet()
60
- */
61
- asStylesheet(): this;
62
- /**
63
- * Fetch and parse JSON file
64
- * Returns a Promise that resolves to the parsed JSON data
35
+ * Mark as page-scoped for automatic cleanup
65
36
  *
66
- * Usage:
67
- * const config = await jux.include('config.json').asJson();
68
- * const data = await jux.include('/api/data').asJson();
69
- */
70
- asJson<T = any>(): Promise<T>;
71
- /**
72
- * Fetch JSON and execute callback with data
73
- *
74
- * Usage:
75
- * jux.include('config.json').onJson(data => {
76
- * console.log('Config:', data);
77
- * });
37
+ * @example
38
+ * jux.include('/css/dashboard.css').forPage('dashboard');
39
+ * // Later: Include.cleanupPage('dashboard');
78
40
  */
79
- onJson<T = any>(callback: (data: T) => void): this;
80
- with(options: IncludeOptions): this;
81
- inHead(): this;
82
- inBody(): this;
83
- async(): this;
84
- defer(): this;
85
- crossOrigin(value?: 'anonymous' | 'use-credentials'): this;
86
- integrity(hash: string): this;
41
+ forPage(pageId: string): this;
87
42
  render(): this;
88
43
  private createStylesheet;
89
44
  private createScript;
90
- private createLink;
91
45
  private getContainer;
92
- private handleError;
46
+ private isAlreadyLoaded;
93
47
  remove(): this;
48
+ /**
49
+ * Remove all resources for a specific page
50
+ *
51
+ * @example
52
+ * Include.cleanupPage('dashboard');
53
+ */
54
+ static cleanupPage(pageId: string): void;
55
+ /**
56
+ * Remove all page-scoped resources
57
+ */
58
+ static cleanupAll(): void;
94
59
  }
95
60
  /**
96
- * Factory function - auto-detects type and renders immediately
61
+ * Factory function - simplified usage
97
62
  *
98
63
  * Usage:
99
- * // Auto-detect (works for most cases)
100
- * jux.include('styles.css');
101
- * jux.include('script.js').async();
64
+ * // Basic (auto-detects from extension)
65
+ * jux.include('/css/styles.css');
66
+ * jux.include('/js/app.js');
102
67
  *
103
- * // Explicit type (for extensionless URLs)
104
- * jux.include('https://cdn.tailwindcss.com').withJs();
105
- * jux.include('https://cdn.tailwindcss.com').asScript();
106
- * jux.include('https://unpkg.com/htmx.org').withJs({ defer: true });
68
+ * // Page-specific with cleanup
69
+ * jux.include('/css/dashboard.css').forPage('dashboard');
70
+ * jux.include('/js/dashboard.js').forPage('dashboard');
107
71
  *
108
- * // CDN with parameters
109
- * jux.include('https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4').withJs();
72
+ * // Later cleanup:
73
+ * Include.cleanupPage('dashboard');
110
74
  *
111
- * // Module
112
- * jux.include('app.mjs').withModule();
75
+ * // Inject into specific container
76
+ * jux.include('/css/widget.css').into('#widget-container');
77
+ *
78
+ * // External CDN
79
+ * jux.include('https://cdn.tailwindcss.com').js();
113
80
  *
114
- * // For JSON:
115
- * const data = await jux.include('config.json').asJson();
116
- * jux.include('data.json').onJson(data => console.log(data));
81
+ * // Module
82
+ * jux.include('/js/app.mjs').module();
117
83
  */
118
- export declare function include(urlOrFile: string): Include;
84
+ export declare function include(url: string, options?: IncludeOptions): Include;
119
85
  export {};
120
86
  //# sourceMappingURL=include.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"include.d.ts","sourceRoot":"","sources":["include.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,KAAK,eAAe,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC;AAE1D,UAAU,cAAc;IACtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,WAAW,GAAG,iBAAiB,CAAC;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,qBAAa,OAAO;IAClB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,YAAY,CAAkB;gBAE1B,SAAS,EAAE,MAAM;IAS7B,OAAO,CAAC,UAAU;IA2BlB;;;OAGG;IACH,OAAO,IAAI,IAAI;IAMf;;;;;;;OAOG;IACH,MAAM,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAQ5D;;;OAGG;IACH,UAAU,IAAI,IAAI;IAMlB,SAAS,IAAI,IAAI;IAMjB,QAAQ,IAAI,IAAI;IAMhB,WAAW,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAO9B,YAAY,IAAI,IAAI;IAMpB,QAAQ,IAAI,IAAI;IAUhB;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAI9D;;;OAGG;IACH,YAAY,IAAI,IAAI;IAQpB;;;;;;;OAOG;IACG,MAAM,CAAC,CAAC,GAAG,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC;IAgBnC;;;;;;;OAOG;IACH,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,GAAG,IAAI;IAWlD,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAKnC,MAAM,IAAI,IAAI;IAKd,MAAM,IAAI,IAAI;IAKd,KAAK,IAAI,IAAI;IAKb,KAAK,IAAI,IAAI;IAKb,WAAW,CAAC,KAAK,GAAE,WAAW,GAAG,iBAA+B,GAAG,IAAI;IAKvE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAS7B,MAAM,IAAI,IAAI;IAyDd,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,UAAU;IAoClB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,WAAW;IAMnB,MAAM,IAAI,IAAI;CAOf;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CASlD"}
1
+ {"version":3,"file":"include.d.ts","sourceRoot":"","sources":["include.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,KAAK,YAAY,GAAG,KAAK,GAAG,IAAI,GAAG,QAAQ,CAAC;AAE5C,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,WAAW,GAAG,iBAAiB,CAAC;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAKD,qBAAa,OAAO;IAClB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,MAAM,CAAuB;gBAEzB,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB;IAoBrD,GAAG,IAAI,IAAI;IAKX,EAAE,IAAI,IAAI;IAKV,MAAM,IAAI,IAAI;IAKd,KAAK,IAAI,IAAI;IAKb,KAAK,IAAI,IAAI;IAKb;;;;;;OAMG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK5B;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAU7B,MAAM,IAAI,IAAI;IAoDd,OAAO,CAAC,gBAAgB;IAiBxB,OAAO,CAAC,YAAY;IAuBpB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,eAAe;IAKvB,MAAM,IAAI,IAAI;IAYd;;;;;OAKG;IACH,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAYxC;;OAEG;IACH,MAAM,CAAC,UAAU,IAAI,IAAI;CAS1B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAItE"}
@@ -1,175 +1,41 @@
1
1
  /**
2
- * Include - Include external resources (CSS, JS, images, fonts, etc.)
3
- * Auto-detects resource type from URL and provides simple, fluent API
2
+ * Include - Simplified resource injection for bundled and external resources
3
+ * Supports page-specific scoping and cleanup
4
4
  */
5
+ // Global registry for page-scoped resources
6
+ const scopedResources = new Map();
5
7
  export class Include {
6
- constructor(urlOrFile) {
7
- this.options = {};
8
+ constructor(url, options = {}) {
8
9
  this.element = null;
9
- this.explicitType = false; // NEW: Track if type was explicitly set
10
- this.url = urlOrFile;
11
- this.type = this.detectType(urlOrFile);
12
- }
13
- /* -------------------------
14
- * Type Detection
15
- * ------------------------- */
16
- detectType(url) {
17
- // Check for common script patterns in URLs (CDN, etc.)
18
- if (url.includes('tailwindcss') ||
19
- url.includes('jsdelivr.net/npm') ||
20
- url.includes('unpkg.com') ||
21
- url.includes('cdn.') ||
22
- url.match(/\.(js|mjs)($|\?)/)) {
23
- return 'script';
24
- }
25
- if (url.endsWith('.css'))
26
- return 'stylesheet';
27
- if (url.endsWith('.json'))
28
- return 'json';
29
- if (url.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i))
30
- return 'image';
31
- if (url.match(/\.(woff|woff2|ttf|otf|eot)$/i))
32
- return 'font';
33
- // Default to script for extensionless URLs from CDN domains
34
- if (!url.includes('.') || url.match(/^https?:\/\/cdn/)) {
35
- return 'script';
36
- }
37
- return 'preload';
38
- }
39
- /* -------------------------
40
- * Fluent Type Setters
41
- * ------------------------- */
42
- /**
43
- * Force treat as CSS stylesheet
44
- * Use when URL doesn't have .css extension
45
- */
46
- withCss() {
47
- this.type = 'stylesheet';
48
- this.explicitType = true; // Mark as explicit
49
- return this;
50
- }
51
- /**
52
- * Force treat as JavaScript
53
- * Use when URL doesn't have .js extension (CDN scripts, etc.)
54
- *
55
- * @example
56
- * jux.include('https://cdn.tailwindcss.com').withJs().render();
57
- * jux.include('https://unpkg.com/alpine').withJs({ defer: true }).render();
58
- */
59
- withJs(options) {
60
- this.type = 'script';
61
- this.explicitType = true; // Mark as explicit
62
- if (options?.async)
63
- this.options.async = true;
64
- if (options?.defer)
65
- this.options.defer = true;
66
- return this;
67
- }
68
- /**
69
- * Force treat as ES module
70
- * Use for module scripts
71
- */
72
- withModule() {
73
- this.type = 'module';
74
- this.explicitType = true; // Mark as explicit
75
- return this;
76
- }
77
- withImage() {
78
- this.type = 'image';
79
- this.explicitType = true;
80
- return this;
81
- }
82
- withFont() {
83
- this.type = 'font';
84
- this.explicitType = true;
85
- return this;
86
- }
87
- withPreload(as) {
88
- this.type = 'preload';
89
- this.explicitType = true;
90
- if (as)
91
- this.options.as = as;
92
- return this;
93
- }
94
- withPrefetch() {
95
- this.type = 'prefetch';
96
- this.explicitType = true;
97
- return this;
98
- }
99
- withJson() {
100
- this.type = 'json';
101
- this.explicitType = true;
102
- return this;
103
- }
104
- /* -------------------------
105
- * Convenience aliases for common patterns
106
- * ------------------------- */
107
- /**
108
- * Shorthand for .withJs()
109
- * @example jux.include(url).asScript()
110
- */
111
- asScript(options) {
112
- return this.withJs(options);
113
- }
114
- /**
115
- * Shorthand for .withCss()
116
- * @example jux.include(url).asStylesheet()
117
- */
118
- asStylesheet() {
119
- return this.withCss();
120
- }
121
- /* -------------------------
122
- * JSON Fetching
123
- * ------------------------- */
124
- /**
125
- * Fetch and parse JSON file
126
- * Returns a Promise that resolves to the parsed JSON data
127
- *
128
- * Usage:
129
- * const config = await jux.include('config.json').asJson();
130
- * const data = await jux.include('/api/data').asJson();
131
- */
132
- async asJson() {
133
- try {
134
- const response = await fetch(this.url);
135
- if (!response.ok) {
136
- throw new Error(`HTTP error! status: ${response.status}`);
10
+ this.pageId = null;
11
+ this.url = url;
12
+ this.options = options;
13
+ // Auto-detect type from extension if not provided
14
+ if (!options.type) {
15
+ if (url.endsWith('.css')) {
16
+ this.options.type = 'css';
17
+ }
18
+ else if (url.endsWith('.mjs') || url.endsWith('.module.js')) {
19
+ this.options.type = 'module';
20
+ }
21
+ else {
22
+ this.options.type = 'js';
137
23
  }
138
- const data = await response.json();
139
- console.log(`✓ JSON loaded: ${this.url}`);
140
- return data;
141
- }
142
- catch (error) {
143
- throw error;
144
24
  }
145
25
  }
146
- /**
147
- * Fetch JSON and execute callback with data
148
- *
149
- * Usage:
150
- * jux.include('config.json').onJson(data => {
151
- * console.log('Config:', data);
152
- * });
153
- */
154
- onJson(callback) {
155
- this.asJson().then(callback).catch(error => {
156
- console.error('Failed to load JSON:', error);
157
- });
158
- return this;
159
- }
160
26
  /* -------------------------
161
- * Options
27
+ * Fluent API
162
28
  * ------------------------- */
163
- with(options) {
164
- this.options = { ...this.options, ...options };
29
+ css() {
30
+ this.options.type = 'css';
165
31
  return this;
166
32
  }
167
- inHead() {
168
- this.options.location = 'head';
33
+ js() {
34
+ this.options.type = 'js';
169
35
  return this;
170
36
  }
171
- inBody() {
172
- this.options.location = 'body-end';
37
+ module() {
38
+ this.options.type = 'module';
173
39
  return this;
174
40
  }
175
41
  async() {
@@ -180,12 +46,27 @@ export class Include {
180
46
  this.options.defer = true;
181
47
  return this;
182
48
  }
183
- crossOrigin(value = 'anonymous') {
184
- this.options.crossOrigin = value;
49
+ /**
50
+ * Inject into specific container instead of <head>
51
+ * Useful for page-specific styles
52
+ *
53
+ * @example
54
+ * jux.include('/css/page1.css').into('#page1-container');
55
+ */
56
+ into(selector) {
57
+ this.options.target = selector;
185
58
  return this;
186
59
  }
187
- integrity(hash) {
188
- this.options.integrity = hash;
60
+ /**
61
+ * Mark as page-scoped for automatic cleanup
62
+ *
63
+ * @example
64
+ * jux.include('/css/dashboard.css').forPage('dashboard');
65
+ * // Later: Include.cleanupPage('dashboard');
66
+ */
67
+ forPage(pageId) {
68
+ this.pageId = pageId;
69
+ this.options.pageScoped = true;
189
70
  return this;
190
71
  }
191
72
  /* -------------------------
@@ -194,45 +75,40 @@ export class Include {
194
75
  render() {
195
76
  if (typeof document === 'undefined')
196
77
  return this;
197
- // Don't render JSON type (it's fetched via asJson() instead)
198
- if (this.type === 'json') {
199
- console.warn('Include: JSON files should be loaded with .asJson() instead of .render()');
200
- return this;
201
- }
202
78
  try {
203
- this.remove();
79
+ // Check if already loaded
80
+ if (this.isAlreadyLoaded()) {
81
+ console.log(`⚠️ Resource already loaded: ${this.url}`);
82
+ return this;
83
+ }
84
+ // Create element based on type
204
85
  let element;
205
- switch (this.type) {
206
- case 'stylesheet':
86
+ switch (this.options.type) {
87
+ case 'css':
207
88
  element = this.createStylesheet();
208
89
  break;
209
- case 'script':
90
+ case 'js':
210
91
  case 'module':
211
92
  element = this.createScript();
212
93
  break;
213
- case 'image':
214
- case 'font':
215
- case 'preload':
216
- case 'prefetch':
217
- element = this.createLink();
218
- break;
219
94
  default:
220
- throw new Error(`Unknown include type: ${this.type}`);
95
+ throw new Error(`Unknown resource type: ${this.options.type}`);
221
96
  }
97
+ // Get target container
222
98
  const container = this.getContainer();
223
- const location = this.options.location || 'head';
224
- if (location === 'body-end') {
225
- container.appendChild(element);
226
- }
227
- else {
228
- container.insertBefore(element, container.firstChild);
229
- }
99
+ container.appendChild(element);
230
100
  this.element = element;
231
- // Log with type indicator
232
- const typeIndicator = this.explicitType ? '(explicit)' : '(auto-detected)';
233
- console.log(`✓ Include loaded as ${this.type} ${typeIndicator}: ${this.url}`);
101
+ // Register for page cleanup if needed
102
+ if (this.options.pageScoped && this.pageId) {
103
+ if (!scopedResources.has(this.pageId)) {
104
+ scopedResources.set(this.pageId, new Set());
105
+ }
106
+ scopedResources.get(this.pageId).add(element);
107
+ }
108
+ console.log(`✓ Loaded ${this.options.type}: ${this.url}`);
234
109
  }
235
110
  catch (error) {
111
+ console.error(`✗ Failed to load ${this.options.type}: ${this.url}`, error);
236
112
  throw error;
237
113
  }
238
114
  return this;
@@ -244,18 +120,22 @@ export class Include {
244
120
  const link = document.createElement('link');
245
121
  link.rel = 'stylesheet';
246
122
  link.href = this.url;
123
+ link.dataset.juxInclude = this.url;
247
124
  if (this.options.crossOrigin)
248
125
  link.crossOrigin = this.options.crossOrigin;
249
126
  if (this.options.integrity)
250
127
  link.integrity = this.options.integrity;
251
128
  link.onload = () => console.log(`✓ Stylesheet loaded: ${this.url}`);
252
- link.onerror = () => this.handleError('stylesheet');
129
+ link.onerror = () => {
130
+ throw new Error(`Failed to load stylesheet: ${this.url}`);
131
+ };
253
132
  return link;
254
133
  }
255
134
  createScript() {
256
135
  const script = document.createElement('script');
257
136
  script.src = this.url;
258
- if (this.type === 'module')
137
+ script.dataset.juxInclude = this.url;
138
+ if (this.options.type === 'module')
259
139
  script.type = 'module';
260
140
  if (this.options.async)
261
141
  script.async = true;
@@ -266,48 +146,27 @@ export class Include {
266
146
  if (this.options.integrity)
267
147
  script.integrity = this.options.integrity;
268
148
  script.onload = () => console.log(`✓ Script loaded: ${this.url}`);
269
- script.onerror = () => this.handleError('script');
149
+ script.onerror = () => {
150
+ throw new Error(`Failed to load script: ${this.url}`);
151
+ };
270
152
  return script;
271
153
  }
272
- createLink() {
273
- const link = document.createElement('link');
274
- // Set rel based on type
275
- if (this.type === 'prefetch') {
276
- link.rel = 'prefetch';
277
- }
278
- else {
279
- link.rel = 'preload';
280
- }
281
- link.href = this.url;
282
- // Set 'as' attribute
283
- if (this.type === 'image') {
284
- link.as = 'image';
285
- }
286
- else if (this.type === 'font') {
287
- link.as = 'font';
288
- link.crossOrigin = this.options.crossOrigin || 'anonymous';
289
- }
290
- else if (this.options.as) {
291
- link.as = this.options.as;
292
- }
293
- if (this.options.crossOrigin && this.type !== 'font') {
294
- link.crossOrigin = this.options.crossOrigin;
295
- }
296
- if (this.options.integrity) {
297
- link.integrity = this.options.integrity;
298
- }
299
- return link;
300
- }
301
154
  /* -------------------------
302
155
  * Helpers
303
156
  * ------------------------- */
304
157
  getContainer() {
305
- const location = this.options.location || 'head';
306
- return location === 'head' ? document.head : document.body;
158
+ if (this.options.target) {
159
+ const container = document.querySelector(this.options.target);
160
+ if (!container) {
161
+ throw new Error(`Target container not found: ${this.options.target}`);
162
+ }
163
+ return container;
164
+ }
165
+ return document.head;
307
166
  }
308
- handleError(type) {
309
- const error = new Error(`Failed to load ${type}: ${this.url}`);
310
- throw error;
167
+ isAlreadyLoaded() {
168
+ const selector = `[data-jux-include="${this.url}"]`;
169
+ return document.querySelector(selector) !== null;
311
170
  }
312
171
  remove() {
313
172
  if (this.element?.parentNode) {
@@ -316,35 +175,64 @@ export class Include {
316
175
  }
317
176
  return this;
318
177
  }
178
+ /* -------------------------
179
+ * Static Page Cleanup
180
+ * ------------------------- */
181
+ /**
182
+ * Remove all resources for a specific page
183
+ *
184
+ * @example
185
+ * Include.cleanupPage('dashboard');
186
+ */
187
+ static cleanupPage(pageId) {
188
+ const resources = scopedResources.get(pageId);
189
+ if (!resources)
190
+ return;
191
+ resources.forEach(element => {
192
+ element.parentNode?.removeChild(element);
193
+ });
194
+ scopedResources.delete(pageId);
195
+ console.log(`✓ Cleaned up page resources: ${pageId}`);
196
+ }
197
+ /**
198
+ * Remove all page-scoped resources
199
+ */
200
+ static cleanupAll() {
201
+ scopedResources.forEach((resources, pageId) => {
202
+ resources.forEach(element => {
203
+ element.parentNode?.removeChild(element);
204
+ });
205
+ });
206
+ scopedResources.clear();
207
+ console.log('✓ Cleaned up all page-scoped resources');
208
+ }
319
209
  }
320
210
  /**
321
- * Factory function - auto-detects type and renders immediately
211
+ * Factory function - simplified usage
322
212
  *
323
213
  * Usage:
324
- * // Auto-detect (works for most cases)
325
- * jux.include('styles.css');
326
- * jux.include('script.js').async();
214
+ * // Basic (auto-detects from extension)
215
+ * jux.include('/css/styles.css');
216
+ * jux.include('/js/app.js');
327
217
  *
328
- * // Explicit type (for extensionless URLs)
329
- * jux.include('https://cdn.tailwindcss.com').withJs();
330
- * jux.include('https://cdn.tailwindcss.com').asScript();
331
- * jux.include('https://unpkg.com/htmx.org').withJs({ defer: true });
218
+ * // Page-specific with cleanup
219
+ * jux.include('/css/dashboard.css').forPage('dashboard');
220
+ * jux.include('/js/dashboard.js').forPage('dashboard');
332
221
  *
333
- * // CDN with parameters
334
- * jux.include('https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4').withJs();
222
+ * // Later cleanup:
223
+ * Include.cleanupPage('dashboard');
335
224
  *
336
- * // Module
337
- * jux.include('app.mjs').withModule();
225
+ * // Inject into specific container
226
+ * jux.include('/css/widget.css').into('#widget-container');
338
227
  *
339
- * // For JSON:
340
- * const data = await jux.include('config.json').asJson();
341
- * jux.include('data.json').onJson(data => console.log(data));
228
+ * // External CDN
229
+ * jux.include('https://cdn.tailwindcss.com').js();
230
+ *
231
+ * // Module
232
+ * jux.include('/js/app.mjs').module();
342
233
  */
343
- export function include(urlOrFile) {
344
- const imp = new Include(urlOrFile);
345
- // Don't auto-render JSON files
346
- if (imp['type'] !== 'json') {
347
- imp.render();
348
- }
349
- return imp;
234
+ export function include(url, options) {
235
+ const inc = new Include(url, options);
236
+ inc.render();
237
+ return inc;
350
238
  }