juxscript 1.1.3 → 1.1.5

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.
Files changed (63) hide show
  1. package/machinery/build3.js +7 -91
  2. package/machinery/compiler3.js +3 -131
  3. package/package.json +19 -5
  4. package/lib/components/alert.ts +0 -200
  5. package/lib/components/app.ts +0 -247
  6. package/lib/components/badge.ts +0 -101
  7. package/lib/components/base/BaseComponent.ts +0 -421
  8. package/lib/components/base/FormInput.ts +0 -227
  9. package/lib/components/button.ts +0 -178
  10. package/lib/components/card.ts +0 -173
  11. package/lib/components/chart.ts +0 -231
  12. package/lib/components/checkbox.ts +0 -242
  13. package/lib/components/code.ts +0 -123
  14. package/lib/components/container.ts +0 -140
  15. package/lib/components/data.ts +0 -135
  16. package/lib/components/datepicker.ts +0 -234
  17. package/lib/components/dialog.ts +0 -172
  18. package/lib/components/divider.ts +0 -100
  19. package/lib/components/dropdown.ts +0 -186
  20. package/lib/components/element.ts +0 -267
  21. package/lib/components/fileupload.ts +0 -309
  22. package/lib/components/grid.ts +0 -291
  23. package/lib/components/guard.ts +0 -92
  24. package/lib/components/heading.ts +0 -96
  25. package/lib/components/helpers.ts +0 -41
  26. package/lib/components/hero.ts +0 -224
  27. package/lib/components/icon.ts +0 -178
  28. package/lib/components/icons.ts +0 -464
  29. package/lib/components/include.ts +0 -410
  30. package/lib/components/input.ts +0 -457
  31. package/lib/components/list.ts +0 -419
  32. package/lib/components/loading.ts +0 -100
  33. package/lib/components/menu.ts +0 -275
  34. package/lib/components/modal.ts +0 -284
  35. package/lib/components/nav.ts +0 -257
  36. package/lib/components/paragraph.ts +0 -97
  37. package/lib/components/progress.ts +0 -159
  38. package/lib/components/radio.ts +0 -278
  39. package/lib/components/req.ts +0 -303
  40. package/lib/components/script.ts +0 -41
  41. package/lib/components/select.ts +0 -252
  42. package/lib/components/sidebar.ts +0 -275
  43. package/lib/components/style.ts +0 -41
  44. package/lib/components/switch.ts +0 -246
  45. package/lib/components/table.ts +0 -1249
  46. package/lib/components/tabs.ts +0 -250
  47. package/lib/components/theme-toggle.ts +0 -293
  48. package/lib/components/tooltip.ts +0 -144
  49. package/lib/components/view.ts +0 -190
  50. package/lib/components/write.ts +0 -272
  51. package/lib/layouts/default.css +0 -260
  52. package/lib/layouts/figma.css +0 -334
  53. package/lib/reactivity/state.ts +0 -78
  54. package/lib/utils/fetch.ts +0 -553
  55. package/machinery/ast.js +0 -347
  56. package/machinery/build.js +0 -466
  57. package/machinery/bundleAssets.js +0 -0
  58. package/machinery/bundleJux.js +0 -0
  59. package/machinery/bundleVendors.js +0 -0
  60. package/machinery/doc-generator.js +0 -136
  61. package/machinery/imports.js +0 -155
  62. package/machinery/ts-shim.js +0 -46
  63. package/machinery/validators/file-validator.js +0 -123
@@ -18,31 +18,23 @@ try {
18
18
  }
19
19
 
20
20
  // ═══════════════════════════════════════════════════════════════
21
- // EXPLODE CONFIG INTO NAMED OBJECTS
21
+ // EXPLODE CONFIG
22
22
  // ═══════════════════════════════════════════════════════════════
23
23
  const directories = {
24
24
  source: rawConfig.directories?.source || './jux',
25
- distribution: rawConfig.directories?.distribution || './.jux-dist',
26
- themes: rawConfig.directories?.themes || './themes',
27
- layouts: rawConfig.directories?.layouts || './themes/layouts',
28
- assets: rawConfig.directories?.assets || './themes/assets'
25
+ distribution: rawConfig.directories?.distribution || './.jux-dist'
29
26
  };
30
27
 
31
28
  const defaults = {
32
29
  httpPort: rawConfig.defaults?.httpPort || 3000,
33
30
  wsPort: rawConfig.defaults?.wsPort || 3001,
34
- autoRoute: rawConfig.defaults?.autoRoute ?? true,
35
- layout: rawConfig.defaults?.layout || null,
36
- theme: rawConfig.defaults?.theme || null
31
+ autoRoute: rawConfig.defaults?.autoRoute ?? true
37
32
  };
38
33
 
39
34
  // Resolve absolute paths
40
35
  const paths = {
41
36
  source: path.resolve(PROJECT_ROOT, directories.source),
42
- distribution: path.resolve(PROJECT_ROOT, directories.distribution),
43
- themes: path.resolve(PROJECT_ROOT, directories.source, directories.themes),
44
- layouts: path.resolve(PROJECT_ROOT, directories.source, directories.layouts),
45
- assets: path.resolve(PROJECT_ROOT, directories.source, directories.assets)
37
+ distribution: path.resolve(PROJECT_ROOT, directories.distribution)
46
38
  };
47
39
 
48
40
  // ═══════════════════════════════════════════════════════════════
@@ -50,84 +42,10 @@ const paths = {
50
42
  // ═══════════════════════════════════════════════════════════════
51
43
  console.log(`\n📁 Directory Check:`);
52
44
 
53
- const dirStatus = {};
54
- for (const [name, dirPath] of Object.entries(paths)) {
55
- if (name === 'distribution') continue;
56
-
57
- const exists = fs.existsSync(dirPath);
58
- dirStatus[name] = exists;
59
-
60
- const icon = exists ? '✓' : '✗';
61
- const suffix = exists ? '' : ' (will be skipped)';
62
- console.log(` ${icon} ${name}: ${dirPath}${suffix}`);
63
- }
64
-
65
- // ═══════════════════════════════════════════════════════════════
66
- // RESOLVE DEFAULT LAYOUT & THEME
67
- // ═══════════════════════════════════════════════════════════════
68
- function resolveFile(filename, ...searchPaths) {
69
- if (!filename) return null;
70
-
71
- // First try the filename as-is
72
- if (fs.existsSync(filename)) {
73
- return path.resolve(filename);
74
- }
75
-
76
- // Try each search path
77
- for (const searchPath of searchPaths) {
78
- if (!searchPath || !fs.existsSync(searchPath)) continue;
79
-
80
- const fullPath = path.resolve(searchPath, filename);
81
- if (fs.existsSync(fullPath)) {
82
- return fullPath;
83
- }
84
- }
85
-
86
- return null;
87
- }
88
-
89
- console.log(`\n🎨 Defaults Resolution:`);
90
-
91
- // Resolve layout
92
- const layoutPath = defaults.layout ? resolveFile(
93
- defaults.layout,
94
- paths.layouts,
95
- paths.source,
96
- PROJECT_ROOT
97
- ) : null;
98
-
99
- if (defaults.layout) {
100
- if (layoutPath) {
101
- console.log(` ✓ layout: ${layoutPath}`);
102
- } else {
103
- console.log(` ✗ layout: "${defaults.layout}" not found, will be skipped`);
104
- }
45
+ if (fs.existsSync(paths.source)) {
46
+ console.log(` ✓ source: ${paths.source}`);
105
47
  } else {
106
- console.log(` - layout: not configured`);
107
- }
108
-
109
- // Resolve theme
110
- const themePath = defaults.theme ? resolveFile(
111
- defaults.theme,
112
- paths.themes,
113
- paths.source,
114
- PROJECT_ROOT
115
- ) : null;
116
-
117
- if (defaults.theme) {
118
- if (themePath) {
119
- console.log(` ✓ theme: ${themePath}`);
120
- } else {
121
- console.log(` ✗ theme: "${defaults.theme}" not found, will be skipped`);
122
- }
123
- } else {
124
- console.log(` - theme: not configured`);
125
- }
126
-
127
- // ═══════════════════════════════════════════════════════════════
128
- // VALIDATE SOURCE DIRECTORY EXISTS
129
- // ═══════════════════════════════════════════════════════════════
130
- if (!dirStatus.source) {
48
+ console.log(` source: ${paths.source} (not found)`);
131
49
  console.error(`\n❌ Source directory not found: ${paths.source}`);
132
50
  console.error(` Create the directory or update juxconfig.js`);
133
51
  process.exit(1);
@@ -141,8 +59,6 @@ console.log(`\n`);
141
59
  const compiler = new JuxCompiler({
142
60
  srcDir: paths.source,
143
61
  distDir: paths.distribution,
144
- layoutPath,
145
- themePath,
146
62
  defaults,
147
63
  paths
148
64
  });
@@ -13,8 +13,6 @@ export class JuxCompiler {
13
13
  this.config = config;
14
14
  this.srcDir = config.srcDir || './jux';
15
15
  this.distDir = config.distDir || './.jux-dist';
16
- this.layoutPath = config.layoutPath || null;
17
- this.themePath = config.themePath || null;
18
16
  this.defaults = config.defaults || {};
19
17
  this.paths = config.paths || {};
20
18
  this._juxscriptExports = null;
@@ -93,74 +91,6 @@ export class JuxCompiler {
93
91
  return name.replace(/[^a-zA-Z0-9]/g, '_');
94
92
  }
95
93
 
96
- /**
97
- * Copy all source assets (themes, layouts, assets folders) to dist
98
- */
99
- copySourceAssets() {
100
- const copied = { themes: 0, layouts: 0, assets: 0, other: 0 };
101
-
102
- const copyDir = (srcDir, destDir, category) => {
103
- if (!fs.existsSync(srcDir)) return 0;
104
-
105
- let count = 0;
106
- fs.mkdirSync(destDir, { recursive: true });
107
-
108
- const entries = fs.readdirSync(srcDir, { withFileTypes: true });
109
- for (const entry of entries) {
110
- const srcPath = path.join(srcDir, entry.name);
111
- const destPath = path.join(destDir, entry.name);
112
-
113
- if (entry.isDirectory()) {
114
- count += copyDir(srcPath, destPath, category);
115
- } else {
116
- fs.copyFileSync(srcPath, destPath);
117
- count++;
118
- }
119
- }
120
- return count;
121
- };
122
-
123
- // Copy themes folder
124
- if (this.paths.themes && fs.existsSync(this.paths.themes)) {
125
- copied.themes = copyDir(this.paths.themes, path.join(this.distDir, 'themes'), 'themes');
126
- }
127
-
128
- // Copy layouts folder (if separate from themes)
129
- if (this.paths.layouts && fs.existsSync(this.paths.layouts) && this.paths.layouts !== this.paths.themes) {
130
- copied.layouts = copyDir(this.paths.layouts, path.join(this.distDir, 'layouts'), 'layouts');
131
- }
132
-
133
- // Copy assets folder
134
- if (this.paths.assets && fs.existsSync(this.paths.assets)) {
135
- copied.assets = copyDir(this.paths.assets, path.join(this.distDir, 'assets'), 'assets');
136
- }
137
-
138
- const total = copied.themes + copied.layouts + copied.assets;
139
- if (total > 0) {
140
- console.log(`📂 Copied source assets: ${copied.themes} themes, ${copied.layouts} layouts, ${copied.assets} assets`);
141
- }
142
-
143
- return copied;
144
- }
145
-
146
- /**
147
- * Generate CSS links including theme if configured
148
- */
149
- generateCssLinks() {
150
- const links = [];
151
-
152
- // Note: Component CSS is no longer bundled (handled by Styler)
153
-
154
- // User theme CSS
155
- if (this.themePath) {
156
- const themeName = path.basename(this.themePath);
157
- links.push(` <link rel="stylesheet" href="./themes/${themeName}" id="jux-theme">`);
158
- console.log(`🎨 🎨 🎨 🎨 Included theme: ${themeName} 🎨 🎨 🎨 🎨 `);
159
- }
160
-
161
- return links.join('\n');
162
- }
163
-
164
94
  async loadJuxscriptExports() {
165
95
  if (this._juxscriptExports) return this._juxscriptExports;
166
96
 
@@ -223,7 +153,6 @@ export class JuxCompiler {
223
153
  Identifier(node, parent) {
224
154
  if (parent?.type === 'CallExpression' && parent.callee === node) {
225
155
  const name = node.name;
226
- // Updated: Removed strict Capitalization check to allow facades (lowercase)
227
156
  if (!allImports.has(name) && knownComponents.includes(name)) {
228
157
  issues.push({
229
158
  type: 'warning',
@@ -241,16 +170,8 @@ export class JuxCompiler {
241
170
  }
242
171
 
243
172
  /**
244
- * Extract layout function name from layout file
173
+ * Generate entry point without layout/theme logic
245
174
  */
246
- getLayoutFunctionName() {
247
- if (!this.layoutPath || !fs.existsSync(this.layoutPath)) return null;
248
-
249
- const content = fs.readFileSync(this.layoutPath, 'utf8');
250
- const match = content.match(/export\s+function\s+(\w+)/);
251
- return match ? match[1] : null;
252
- }
253
-
254
175
  generateEntryPoint(views, dataModules, sharedModules) {
255
176
  let entry = `// Auto-generated JUX entry point\n\n`;
256
177
  const allIssues = [];
@@ -265,16 +186,6 @@ export class JuxCompiler {
265
186
  }
266
187
  });
267
188
 
268
- // Check layout file for juxscript imports too
269
- if (this.layoutPath && fs.existsSync(this.layoutPath)) {
270
- const layoutContent = fs.readFileSync(this.layoutPath, 'utf8');
271
- for (const match of layoutContent.matchAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]juxscript['"]/g)) {
272
- match[1].split(',').map(s => s.trim()).forEach(imp => {
273
- if (imp) juxImports.add(imp);
274
- });
275
- }
276
- }
277
-
278
189
  if (juxImports.size > 0) {
279
190
  entry += `import { ${[...juxImports].sort().join(', ')} } from 'juxscript';\n\n`;
280
191
  }
@@ -294,25 +205,6 @@ export class JuxCompiler {
294
205
  entry += `\nObject.assign(window, { ${[...juxImports].join(', ')} });\n`;
295
206
  }
296
207
 
297
- // Add layout function if configured
298
- const layoutFnName = this.getLayoutFunctionName();
299
- if (layoutFnName && this.layoutPath) {
300
- const layoutContent = fs.readFileSync(this.layoutPath, 'utf8');
301
- const layoutCode = this.removeImports(layoutContent);
302
- entry += `\n// --- DEFAULT LAYOUT ---\n`;
303
- entry += layoutCode;
304
- entry += `\n`;
305
-
306
- // Store in source snapshot
307
- const layoutFile = path.basename(this.layoutPath);
308
- sourceSnapshot[layoutFile] = {
309
- name: 'layout',
310
- file: layoutFile,
311
- content: layoutContent,
312
- lines: layoutContent.split('\n')
313
- };
314
- }
315
-
316
208
  entry += `\n// --- VIEW FUNCTIONS ---\n`;
317
209
 
318
210
  views.forEach(v => {
@@ -341,7 +233,6 @@ export class JuxCompiler {
341
233
 
342
234
  this._sourceSnapshot = sourceSnapshot;
343
235
  this._validationIssues = allIssues;
344
- this._layoutFnName = layoutFnName;
345
236
  entry += this._generateRouter(views);
346
237
  return entry;
347
238
  }
@@ -371,11 +262,6 @@ export class JuxCompiler {
371
262
  routeMap += ` '/${v.name.toLowerCase()}': render${cap},\n`;
372
263
  });
373
264
 
374
- // Initialize layout call if configured
375
- const layoutInit = this._layoutFnName
376
- ? `\n// Initialize default layout\nif (typeof ${this._layoutFnName} === 'function') {\n ${this._layoutFnName}();\n}\n`
377
- : '';
378
-
379
265
  return `
380
266
  // --- JUX SOURCE LOADER ---
381
267
  var __juxSources = null;
@@ -400,14 +286,6 @@ function __juxFindSource(stack) {
400
286
  }
401
287
  }
402
288
  }
403
- // Also check for layout
404
- if (stack.indexOf('Layout') > -1) {
405
- for (var file in __juxSources || {}) {
406
- if (file.indexOf('layout') > -1) {
407
- return { file: file, source: __juxSources[file] };
408
- }
409
- }
410
- }
411
289
  return null;
412
290
  }
413
291
 
@@ -508,7 +386,7 @@ window.addEventListener('unhandledrejection', function(e) { __juxErrorOverlay.sh
508
386
 
509
387
  // --- JUX ROUTER ---
510
388
  const routes = {\n${routeMap}};
511
- ${layoutInit}
389
+
512
390
  async function navigate(path) {
513
391
  const view = routes[path];
514
392
  if (!view) {
@@ -555,9 +433,6 @@ navigate(location.pathname);
555
433
  const { views, dataModules, sharedModules } = this.scanFiles();
556
434
  console.log(`📁 Found ${views.length} views, ${sharedModules.length} shared, ${dataModules.length} data`);
557
435
 
558
- // Copy source assets (themes, layouts, assets)
559
- this.copySourceAssets();
560
-
561
436
  // Copy data/shared modules to dist
562
437
  const juxDistDir = path.join(this.distDir, 'jux');
563
438
  fs.mkdirSync(juxDistDir, { recursive: true });
@@ -602,7 +477,6 @@ navigate(location.pathname);
602
477
  return { success: false, errors: [{ message: err.message }], warnings: [] };
603
478
  }
604
479
 
605
- // Generate HTML with layout container sibling to app
606
480
  const html = `<!DOCTYPE html>
607
481
  <html lang="en">
608
482
  <head>
@@ -612,9 +486,7 @@ navigate(location.pathname);
612
486
  <script type="module" src="./bundle.js"></script>
613
487
  </head>
614
488
  <body>
615
- <div id="jux-layout">
616
- <div id="app"></div>
617
- </div>
489
+ <div id="app"></div>
618
490
  </body>
619
491
  </html>`;
620
492
  fs.writeFileSync(path.join(this.distDir, 'index.html'), html);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",
@@ -19,14 +19,25 @@
19
19
  "./package.json": "./package.json"
20
20
  },
21
21
  "files": [
22
- "bin",
23
- "lib",
24
- "machinery",
25
22
  "index.js",
26
23
  "index.d.ts",
24
+ "bin",
25
+ "create/*.jux",
26
+ "lib/**/*.js",
27
+ "lib/**/*.d.ts",
28
+ "machinery/build3.js",
29
+ "machinery/compiler3.js",
30
+ "machinery/config.js",
31
+ "machinery/serve.js",
32
+ "machinery/watcher.js",
33
+ "types",
27
34
  "juxconfig.example.js",
28
35
  "README.md",
29
- "LICENSE"
36
+ "LICENSE",
37
+ "!lib/**/*.test.js",
38
+ "!lib/**/*.spec.js",
39
+ "!lib/previews/**",
40
+ "!**/*.map"
30
41
  ],
31
42
  "bin": {
32
43
  "jux": "./bin/cli.js"
@@ -46,6 +57,7 @@
46
57
  },
47
58
  "dependencies": {
48
59
  "acorn": "^8.15.0",
60
+ "astray": "^1.1.1",
49
61
  "axios": "^1.6.0",
50
62
  "chart.js": "^4.5.1",
51
63
  "esbuild": "^0.19.0",
@@ -54,10 +66,12 @@
54
66
  },
55
67
  "devDependencies": {
56
68
  "@types/express": "^4.17.17",
69
+ "@types/jsdom": "^27.0.0",
57
70
  "@types/node": "^20.0.0",
58
71
  "@types/ws": "^8.5.5",
59
72
  "acorn-walk": "^8.3.4",
60
73
  "jsdom": "^27.4.0",
74
+ "ts-node": "^10.9.2",
61
75
  "typescript": "^5.0.0"
62
76
  }
63
77
  }
@@ -1,200 +0,0 @@
1
- import { BaseComponent } from './base/BaseComponent.js';
2
- import { renderIcon } from './icons.js';
3
-
4
- // Event definitions
5
- const TRIGGER_EVENTS = [] as const;
6
- const CALLBACK_EVENTS = ['dismiss'] as const;
7
-
8
- export interface AlertOptions {
9
- content?: string;
10
- type?: 'info' | 'success' | 'warning' | 'error';
11
- dismissible?: boolean;
12
- icon?: string;
13
- style?: string;
14
- class?: string;
15
- }
16
-
17
- type AlertState = {
18
- content: string;
19
- type: string;
20
- dismissible: boolean;
21
- icon: string;
22
- visible: boolean;
23
- style: string;
24
- class: string;
25
- };
26
-
27
- export class Alert extends BaseComponent<AlertState> {
28
- private _alert: HTMLElement | null = null;
29
-
30
- constructor(id: string, options: AlertOptions = {}) {
31
- super(id, {
32
- content: options.content ?? '',
33
- type: options.type ?? 'info',
34
- dismissible: options.dismissible ?? true,
35
- icon: options.icon ?? '',
36
- visible: true,
37
- style: options.style ?? '',
38
- class: options.class ?? ''
39
- });
40
- }
41
-
42
- protected getTriggerEvents(): readonly string[] {
43
- return TRIGGER_EVENTS;
44
- }
45
-
46
- protected getCallbackEvents(): readonly string[] {
47
- return CALLBACK_EVENTS;
48
- }
49
-
50
- /* ═════════════════════════════════════════════════════════════════
51
- * FLUENT API
52
- * ═════════════════════════════════════════════════════════════════ */
53
-
54
- // ✅ Inherited from BaseComponent:
55
- // - style(), class()
56
- // - bind(), sync(), renderTo()
57
- // - addClass(), removeClass(), toggleClass()
58
- // - visible(), show(), hide(), toggleVisibility()
59
- // - attr(), attrs(), removeAttr()
60
- // - disabled(), enable(), disable()
61
- // - loading(), focus(), blur(), remove()
62
-
63
- content(value: string): this {
64
- this.state.content = value;
65
- return this;
66
- }
67
-
68
- type(value: 'info' | 'success' | 'warning' | 'error'): this {
69
- this.state.type = value;
70
- return this;
71
- }
72
-
73
- dismissible(value: boolean): this {
74
- this.state.dismissible = value;
75
- return this;
76
- }
77
-
78
- icon(value: string): this {
79
- this.state.icon = value;
80
- return this;
81
- }
82
-
83
- /* ═════════════════════════════════════════════════════════════════
84
- * RENDER
85
- * ═════════════════════════════════════════════════════════════════ */
86
-
87
- render(targetId?: string): this {
88
- const container = this._setupContainer(targetId);
89
-
90
- const { content, type, dismissible, icon, style, class: className } = this.state;
91
- const hasVisibleSync = this._syncBindings.some(b => b.property === 'visible');
92
-
93
- // Build alert element
94
- const alert = document.createElement('div');
95
- alert.className = `jux-alert jux-alert-${type}`;
96
- alert.id = this._id;
97
- if (className) alert.className += ` ${className}`;
98
- if (style) alert.setAttribute('style', style);
99
-
100
- if (icon) {
101
- const iconEl = document.createElement('span');
102
- iconEl.className = 'jux-alert-icon';
103
- iconEl.appendChild(renderIcon(icon));
104
- alert.appendChild(iconEl);
105
- }
106
-
107
- const contentEl = document.createElement('div');
108
- contentEl.className = 'jux-alert-content';
109
- contentEl.textContent = content;
110
- alert.appendChild(contentEl);
111
-
112
- if (dismissible) {
113
- const closeBtn = document.createElement('button');
114
- closeBtn.className = 'jux-alert-close';
115
- closeBtn.innerHTML = '×';
116
- alert.appendChild(closeBtn);
117
- }
118
-
119
- // Default dismiss behavior (only if NOT using sync)
120
- if (!hasVisibleSync && dismissible) {
121
- const closeBtn = alert.querySelector('.jux-alert-close');
122
- closeBtn?.addEventListener('click', () => {
123
- // 🎯 Fire the dismiss callback event
124
- this._triggerCallback('dismiss');
125
- alert.remove();
126
- });
127
- }
128
-
129
- // Wire events using inherited method
130
- this._wireStandardEvents(alert);
131
-
132
- // Wire sync bindings
133
- this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
134
- if (property === 'content') {
135
- const transform = toComponent || ((v: any) => String(v));
136
-
137
- stateObj.subscribe((val: any) => {
138
- const transformed = transform(val);
139
- contentEl.textContent = transformed;
140
- this.state.content = transformed;
141
- });
142
- }
143
- else if (property === 'type') {
144
- const transform = toComponent || ((v: any) => String(v));
145
-
146
- stateObj.subscribe((val: any) => {
147
- const transformed = transform(val);
148
- alert.className = `jux-alert jux-alert-${transformed}`;
149
- if (className) alert.className += ` ${className}`;
150
- this.state.type = transformed;
151
- });
152
- }
153
- else if (property === 'visible') {
154
- const transformToState = toState || ((v: any) => Boolean(v));
155
- const transform = toComponent || ((v: any) => Boolean(v));
156
-
157
- let isUpdating = false;
158
-
159
- // State → Component
160
- stateObj.subscribe((val: any) => {
161
- if (isUpdating) return;
162
- const transformed = transform(val);
163
- alert.style.display = transformed ? 'flex' : 'none';
164
- });
165
-
166
- // Component → State (close button)
167
- if (dismissible) {
168
- const closeBtn = alert.querySelector('.jux-alert-close');
169
- closeBtn?.addEventListener('click', () => {
170
- if (isUpdating) return;
171
- isUpdating = true;
172
-
173
- alert.style.display = 'none';
174
- stateObj.set(transformToState(false));
175
-
176
- // 🎯 Fire the dismiss callback event
177
- this._triggerCallback('dismiss');
178
-
179
- setTimeout(() => { isUpdating = false; }, 0);
180
- });
181
- }
182
- }
183
- });
184
-
185
- container.appendChild(alert);
186
- this._alert = alert;
187
-
188
- requestAnimationFrame(() => {
189
- if ((window as any).lucide) {
190
- (window as any).lucide.createIcons();
191
- }
192
- });
193
-
194
- return this;
195
- }
196
- }
197
-
198
- export function alert(id: string, options: AlertOptions = {}): Alert {
199
- return new Alert(id, options);
200
- }