juxscript 1.0.20 → 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.
Files changed (76) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +143 -92
  3. package/lib/components/badge.ts +93 -94
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +40 -131
  7. package/lib/components/card.ts +57 -79
  8. package/lib/components/charts/areachart.ts +315 -0
  9. package/lib/components/charts/barchart.ts +421 -0
  10. package/lib/components/charts/doughnutchart.ts +263 -0
  11. package/lib/components/charts/lib/BaseChart.ts +402 -0
  12. package/lib/components/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
  13. package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
  14. package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
  15. package/lib/components/checkbox.ts +255 -204
  16. package/lib/components/code.ts +31 -78
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +180 -147
  20. package/lib/components/dialog.ts +218 -221
  21. package/lib/components/divider.ts +63 -87
  22. package/lib/components/docs-data.json +498 -2404
  23. package/lib/components/dropdown.ts +191 -236
  24. package/lib/components/element.ts +196 -145
  25. package/lib/components/fileupload.ts +253 -167
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +31 -97
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +51 -114
  30. package/lib/components/icon.ts +33 -120
  31. package/lib/components/icons.ts +2 -1
  32. package/lib/components/include.ts +76 -3
  33. package/lib/components/input.ts +155 -407
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +358 -261
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +63 -152
  38. package/lib/components/modal.ts +42 -129
  39. package/lib/components/nav.ts +79 -101
  40. package/lib/components/paragraph.ts +38 -102
  41. package/lib/components/progress.ts +108 -166
  42. package/lib/components/radio.ts +283 -234
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +189 -199
  45. package/lib/components/sidebar.ts +110 -141
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +254 -183
  48. package/lib/components/table.ts +1078 -208
  49. package/lib/components/tabs.ts +42 -106
  50. package/lib/components/theme-toggle.ts +73 -165
  51. package/lib/components/tooltip.ts +85 -316
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +67 -41
  54. package/machinery/build.js +466 -0
  55. package/machinery/compiler.js +354 -105
  56. package/machinery/server.js +23 -100
  57. package/machinery/watcher.js +153 -130
  58. package/package.json +1 -1
  59. package/presets/base.css +1166 -0
  60. package/presets/notion.css +2 -1975
  61. package/lib/adapters/base-adapter.js +0 -35
  62. package/lib/adapters/index.js +0 -33
  63. package/lib/adapters/mysql-adapter.js +0 -65
  64. package/lib/adapters/postgres-adapter.js +0 -70
  65. package/lib/adapters/sqlite-adapter.js +0 -56
  66. package/lib/components/areachart.ts +0 -1128
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1322
  69. package/lib/components/doughnutchart.ts +0 -1259
  70. package/lib/components/footer.ts +0 -165
  71. package/lib/components/header.ts +0 -187
  72. package/lib/components/layout.ts +0 -239
  73. package/lib/components/main.ts +0 -137
  74. package/lib/layouts/default.jux +0 -8
  75. package/lib/layouts/figma.jux +0 -0
  76. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
package/bin/cli.js CHANGED
@@ -1,6 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { compileJuxFile, copyLibToOutput, copyProjectAssets, transpileProjectTypeScript, copyPresetsToOutput } from '../machinery/compiler.js';
3
+ import {
4
+ copyLibToOutput,
5
+ copyProjectAssets,
6
+ transpileProjectTypeScript,
7
+ copyPresetsToOutput,
8
+ bundleJuxFilesToRouter,
9
+ generateIndexHtml
10
+ } from '../machinery/compiler.js';
4
11
  import { generateDocs } from '../machinery/doc-generator.js';
5
12
  import { start } from '../machinery/server.js';
6
13
  import path from 'path';
@@ -42,13 +49,11 @@ console.log(` Output: ${PATHS.frontendDist}`);
42
49
  console.log(` Lib: ${PATHS.juxLib}\n`);
43
50
 
44
51
  const command = process.argv[2];
52
+ const watchMode = process.argv.includes('--watch');
53
+ const bundleMode = process.argv.includes('--bundle');
45
54
 
46
55
  /**
47
56
  * Recursively find .jux files in a directory
48
- *
49
- * @param {string} dir - Directory to search
50
- * @param {string[]} fileList - Accumulator for found files
51
- * @returns {string[]} Array of .jux file paths
52
57
  */
53
58
  function findJuxFiles(dir, fileList = []) {
54
59
  if (!fs.existsSync(dir)) return fileList;
@@ -72,11 +77,12 @@ function findJuxFiles(dir, fileList = []) {
72
77
  }
73
78
 
74
79
  /**
75
- * Build the entire JUX project
80
+ * Build the entire JUX project (ALWAYS uses router bundle)
76
81
  *
77
- * @param {boolean} isServe - Whether building for dev server with hot reload
82
+ * @param {boolean} isServe - Whether building for dev server
78
83
  */
79
84
  async function buildProject(isServe = false) {
85
+ const buildStartTime = performance.now();
80
86
  console.log('🔨 Building JUX frontend...\n');
81
87
 
82
88
  try {
@@ -93,80 +99,121 @@ async function buildProject(isServe = false) {
93
99
  }
94
100
  fs.mkdirSync(PATHS.frontendDist, { recursive: true });
95
101
 
96
- // Step 1: Generate documentation from jux lib
102
+ // Step 1: Generate documentation
103
+ const docsStartTime = performance.now();
104
+ let docsTime = 0; // ✅ Declare with default value
97
105
  console.log('📚 Generating documentation...');
98
106
  try {
99
107
  await generateDocs(PATHS.juxLib);
100
- console.log('✅ Documentation generated\n');
108
+ docsTime = performance.now() - docsStartTime;
109
+ console.log(`✅ Documentation generated (${docsTime.toFixed(0)}ms)\n`);
101
110
  } catch (error) {
102
- console.warn('⚠️ Failed to generate docs:', error.message);
111
+ docsTime = performance.now() - docsStartTime; // ✅ Still calculate time even on error
112
+ console.warn(`⚠️ Failed to generate docs (${docsTime.toFixed(0)}ms):`, error.message);
103
113
  }
104
114
 
105
115
  // Step 2: Copy jux lib to frontend dist
116
+ const libStartTime = performance.now();
106
117
  await copyLibToOutput(PATHS.juxLib, PATHS.frontendDist);
118
+ const libTime = performance.now() - libStartTime;
119
+ console.log(`⏱️ Lib copy time: ${libTime.toFixed(0)}ms\n`);
107
120
 
108
- // Step 2.5: Copy presets folder
121
+ // Step 3: Copy presets folder
122
+ const presetsStartTime = performance.now();
109
123
  await copyPresetsToOutput(PATHS.packageRoot, PATHS.frontendDist);
124
+ const presetsTime = performance.now() - presetsStartTime;
125
+ console.log(`⏱️ Presets copy time: ${presetsTime.toFixed(0)}ms\n`);
110
126
 
111
- // Step 3: Copy project assets from jux/ (CSS, JS, images)
127
+ // Step 4: Copy project assets (CSS, JS, images)
128
+ const assetsStartTime = performance.now();
112
129
  await copyProjectAssets(PATHS.juxSource, PATHS.frontendDist);
130
+ const assetsTime = performance.now() - assetsStartTime;
131
+ console.log(`⏱️ Assets copy time: ${assetsTime.toFixed(0)}ms\n`);
113
132
 
114
- // Step 4: Transpile TypeScript files from jux/ to jux-dist/
133
+ // Step 5: Transpile TypeScript files
134
+ const tsStartTime = performance.now();
115
135
  await transpileProjectTypeScript(PATHS.juxSource, PATHS.frontendDist);
136
+ const tsTime = performance.now() - tsStartTime;
137
+ console.log(`⏱️ TypeScript transpile time: ${tsTime.toFixed(0)}ms\n`);
116
138
 
117
- // Step 5: Compile .jux files from jux/ directory ONLY
139
+ // Step 6: Bundle all .jux files into router
118
140
  const projectJuxFiles = findJuxFiles(PATHS.juxSource);
119
141
  console.log(`📝 Found ${projectJuxFiles.length} .jux file(s) in /jux\n`);
120
142
 
121
- for (const file of projectJuxFiles) {
122
- try {
123
- await compileJuxFile(file, {
124
- distDir: PATHS.frontendDist,
125
- projectRoot: PATHS.juxSource,
126
- isServe
127
- });
128
- } catch (err) {
129
- console.error(`Error compiling ${file}:`, err.message);
130
- }
143
+ if (projectJuxFiles.length === 0) {
144
+ console.warn('⚠️ No .jux files found to bundle');
145
+ process.exit(1);
131
146
  }
132
147
 
133
- // Step 6: Compile vendor layouts
134
- const layoutsDir = path.join(PATHS.juxLib, 'layouts');
135
-
136
- if (fs.existsSync(layoutsDir)) {
137
- console.log('\n📐 Compiling vendor layouts...');
138
- const vendorJuxFiles = findJuxFiles(layoutsDir);
139
- console.log(`Found ${vendorJuxFiles.length} vendor layout(s)\n`);
140
-
141
- for (const file of vendorJuxFiles) {
142
- try {
143
- const relPath = path.relative(PATHS.juxLib, file);
144
-
145
- await compileJuxFile(file, {
146
- distDir: path.join(PATHS.frontendDist, 'lib'),
147
- projectRoot: PATHS.juxLib,
148
- isServe
149
- });
150
-
151
- console.log(` ✓ Compiled: ${relPath}`);
152
- } catch (err) {
153
- console.error(`Error compiling vendor layout ${file}:`, err.message);
154
- }
155
- }
148
+ // Bundle and get the generated filename
149
+ const bundleStartTime = performance.now();
150
+ const mainJsFilename = await bundleJuxFilesToRouter(PATHS.juxSource, PATHS.frontendDist, {
151
+ routePrefix: ''
152
+ });
153
+ const bundleTime = performance.now() - bundleStartTime;
154
+
155
+ // Generate routes for index.html
156
+ const routes = projectJuxFiles.map(juxFile => {
157
+ const relativePath = path.relative(PATHS.juxSource, juxFile);
158
+ const parsedPath = path.parse(relativePath);
159
+
160
+ const rawFunctionName = parsedPath.dir
161
+ ? `${parsedPath.dir.replace(/\//g, '_')}_${parsedPath.name}`
162
+ : parsedPath.name;
163
+
164
+ const functionName = rawFunctionName
165
+ .replace(/[-_]/g, ' ')
166
+ .split(' ')
167
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
168
+ .join('');
169
+
170
+ const routePath = '/' + (parsedPath.dir ? `${parsedPath.dir}/` : '') + parsedPath.name;
171
+
172
+ return {
173
+ path: routePath.replace(/\/+/g, '/'),
174
+ functionName
175
+ };
176
+ });
177
+
178
+ // ✅ Generate unified index.html
179
+ const indexStartTime = performance.now();
180
+ generateIndexHtml(PATHS.frontendDist, routes, mainJsFilename);
181
+ const indexTime = performance.now() - indexStartTime;
182
+
183
+ const totalBuildTime = performance.now() - buildStartTime;
184
+
185
+ console.log(`\n✅ Bundled ${projectJuxFiles.length} page(s) → ${PATHS.frontendDist}/${mainJsFilename}\n`);
186
+
187
+ // ✅ Build summary with timing breakdown
188
+ console.log(`📊 Build Summary:`);
189
+ console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
190
+ console.log(` Documentation: ${docsTime.toFixed(0)}ms`);
191
+ console.log(` Library copy: ${libTime.toFixed(0)}ms`);
192
+ console.log(` Presets copy: ${presetsTime.toFixed(0)}ms`);
193
+ console.log(` Assets copy: ${assetsTime.toFixed(0)}ms`);
194
+ console.log(` TypeScript: ${tsTime.toFixed(0)}ms`);
195
+ console.log(` Router bundle: ${bundleTime.toFixed(0)}ms`);
196
+ console.log(` Index generation: ${indexTime.toFixed(0)}ms`);
197
+ console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
198
+ console.log(` Total build time: ${totalBuildTime.toFixed(0)}ms\n`);
199
+
200
+ // Show usage
201
+ if (!isServe) {
202
+ console.log('📦 Serve from your backend:');
203
+ console.log(` Express: app.use(express.static('jux-dist'))`);
204
+ console.log(` Flask: app = Flask(__name__, static_folder='jux-dist')`);
205
+ console.log(` FastAPI: app.mount("/", StaticFiles(directory="jux-dist"), name="static")`);
206
+ console.log('');
207
+ console.log('📍 Available routes:');
208
+ routes.forEach(r => {
209
+ console.log(` ${r.path}`);
210
+ });
211
+ console.log('');
156
212
  }
157
213
 
158
- console.log(`\n✅ Built ${projectJuxFiles.length} file(s) → ${PATHS.frontendDist}\n`);
159
-
160
- // Show backend integration examples
161
- console.log('📦 Serve from your backend:');
162
- console.log(` Express: app.use(express.static('jux-dist'))`);
163
- console.log(` Flask: app = Flask(__name__, static_folder='jux-dist')`);
164
- console.log(` FastAPI: app.mount("/", StaticFiles(directory="jux-dist"), name="static")`);
165
- console.log(` Laravel: Route::view('/', 'jux-dist/index.html')`);
166
- console.log('');
167
-
168
214
  } catch (err) {
169
- console.error('❌ Build error:', err.message);
215
+ const failTime = performance.now() - buildStartTime;
216
+ console.error(`❌ Build error after ${failTime.toFixed(0)}ms:`, err.message);
170
217
  console.error(err.stack);
171
218
  process.exit(1);
172
219
  }
@@ -261,10 +308,12 @@ node_modules/
261
308
  console.log(' 4. Serve jux-dist/ from your backend\n');
262
309
 
263
310
  } else if (command === 'build') {
311
+ // ✅ Always builds router bundle
264
312
  await buildProject(false);
265
313
  console.log(`✅ Build complete: ${PATHS.frontendDist}`);
266
314
 
267
315
  } else if (command === 'serve') {
316
+ // ✅ Always serves router bundle
268
317
  await buildProject(true);
269
318
 
270
319
  const port = parseInt(process.argv[3]) || 3000;
@@ -275,17 +324,17 @@ node_modules/
275
324
  JUX CLI - A JavaScript UX authorship platform
276
325
 
277
326
  Usage:
278
- npx jux init Initialize a new JUX project
279
- npx jux build Compile .jux files from ./jux/ to ./jux-dist/
280
- npx jux serve [port] Start dev server with hot reload (default: 3000)
327
+ npx jux init Initialize a new JUX project
328
+ npx jux build Build router bundle to ./jux-dist/
329
+ npx jux serve [port] Start dev server with hot reload (default: 3000)
281
330
 
282
331
  Project Structure:
283
332
  my-project/
284
- ├── jux/ # Your .jux source files
285
- │ ├── index.jux # Entry point (uses 'juxscript' imports)
286
- │ └── pages/ # Additional pages
287
- ├── jux-dist/ # Build output (git-ignore this)
288
- ├── server/ # Your backend
333
+ ├── jux/ # Your .jux source files
334
+ │ ├── index.jux # Entry point
335
+ │ └── pages/ # Additional pages
336
+ ├── jux-dist/ # Build output (git-ignore this)
337
+ ├── server/ # Your backend
289
338
  └── package.json
290
339
 
291
340
  Import Style:
@@ -294,15 +343,15 @@ Import Style:
294
343
  import 'juxscript/presets/notion.js';
295
344
 
296
345
  Getting Started:
297
- 1. npx jux init # Create project structure
298
- 2. npm install # Install dependencies
299
- 3. npx jux build # Build to jux-dist/
346
+ 1. npx jux init # Create project structure
347
+ 2. npm install # Install dependencies
348
+ 3. npx jux serve # Dev server with hot reload
300
349
  4. Serve jux-dist/ from your backend
301
350
 
302
351
  Examples:
303
- npx jux build Build for production
304
- npx jux serve Start dev server
305
- npx jux serve 8080 Start on port 8080
352
+ npx jux build Build production bundle
353
+ npx jux serve Start dev server on port 3000
354
+ npx jux serve 8080 Start dev server on port 8080
306
355
  `);
307
356
  }
308
357
  })();
@@ -1,7 +1,10 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
3
2
  import { renderIcon } from './icons.js';
4
3
 
4
+ // Event definitions
5
+ const TRIGGER_EVENTS = [] as const;
6
+ const CALLBACK_EVENTS = ['dismiss'] as const;
7
+
5
8
  export interface AlertOptions {
6
9
  message?: string;
7
10
  type?: 'info' | 'success' | 'warning' | 'error';
@@ -21,26 +24,11 @@ type AlertState = {
21
24
  class: string;
22
25
  };
23
26
 
24
- export class Alert {
25
- state: AlertState;
26
- container: HTMLElement | null = null;
27
- _id: string;
28
- id: string;
29
-
30
- // CRITICAL: Store bind/sync instructions for deferred wiring
31
- private _bindings: Array<{ event: string, handler: Function }> = [];
32
- private _syncBindings: Array<{
33
- property: string,
34
- stateObj: State<any>,
35
- toState?: Function,
36
- toComponent?: Function
37
- }> = [];
27
+ export class Alert extends BaseComponent<AlertState> {
28
+ private _alert: HTMLElement | null = null;
38
29
 
39
30
  constructor(id: string, options: AlertOptions = {}) {
40
- this._id = id;
41
- this.id = id;
42
-
43
- this.state = {
31
+ super(id, {
44
32
  message: options.message ?? '',
45
33
  type: options.type ?? 'info',
46
34
  dismissible: options.dismissible ?? true,
@@ -48,9 +36,30 @@ export class Alert {
48
36
  visible: true,
49
37
  style: options.style ?? '',
50
38
  class: options.class ?? ''
51
- };
39
+ });
40
+ }
41
+
42
+ protected getTriggerEvents(): readonly string[] {
43
+ return TRIGGER_EVENTS;
44
+ }
45
+
46
+ protected getCallbackEvents(): readonly string[] {
47
+ return CALLBACK_EVENTS;
52
48
  }
53
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
+
54
63
  message(value: string): this {
55
64
  this.state.message = value;
56
65
  return this;
@@ -71,66 +80,17 @@ export class Alert {
71
80
  return this;
72
81
  }
73
82
 
74
- style(value: string): this {
75
- this.state.style = value;
76
- return this;
77
- }
78
-
79
- class(value: string): this {
80
- this.state.class = value;
81
- return this;
82
- }
83
-
84
- bind(event: string, handler: Function): this {
85
- this._bindings.push({ event, handler });
86
- return this;
87
- }
88
-
89
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
90
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
91
- throw new Error(`Alert.sync: Expected a State object for property "${property}"`);
92
- }
93
- this._syncBindings.push({ property, stateObj, toState, toComponent });
94
- return this;
95
- }
96
-
97
- show(): this {
98
- this.state.visible = true;
99
- const alertEl = document.getElementById(this._id);
100
- if (alertEl) {
101
- alertEl.style.display = 'flex';
102
- }
103
- return this;
104
- }
105
-
106
- hide(): this {
107
- this.state.visible = false;
108
- const alertEl = document.getElementById(this._id);
109
- if (alertEl) {
110
- alertEl.style.display = 'none';
111
- }
112
- return this;
113
- }
83
+ /* ═════════════════════════════════════════════════════════════════
84
+ * RENDER
85
+ * ═════════════════════════════════════════════════════════════════ */
114
86
 
115
87
  render(targetId?: string): this {
116
- // === 1. SETUP: Get or create container ===
117
- let container: HTMLElement;
118
- if (targetId) {
119
- const target = document.querySelector(targetId);
120
- if (!target || !(target instanceof HTMLElement)) {
121
- throw new Error(`Alert: Target "${targetId}" not found`);
122
- }
123
- container = target;
124
- } else {
125
- container = getOrCreateContainer(this._id);
126
- }
127
- this.container = container;
88
+ const container = this._setupContainer(targetId);
128
89
 
129
- // === 2. PREPARE: Destructure state and check sync flags ===
130
90
  const { message, type, dismissible, icon, style, class: className } = this.state;
131
91
  const hasVisibleSync = this._syncBindings.some(b => b.property === 'visible');
132
92
 
133
- // === 3. BUILD: Create DOM elements ===
93
+ // Build alert element
134
94
  const alert = document.createElement('div');
135
95
  alert.className = `jux-alert jux-alert-${type}`;
136
96
  alert.id = this._id;
@@ -156,42 +116,50 @@ export class Alert {
156
116
  alert.appendChild(closeBtn);
157
117
  }
158
118
 
159
- // === 4. WIRE: Attach event listeners and sync bindings ===
160
-
161
119
  // Default dismiss behavior (only if NOT using sync)
162
120
  if (!hasVisibleSync && dismissible) {
163
121
  const closeBtn = alert.querySelector('.jux-alert-close');
164
122
  closeBtn?.addEventListener('click', () => {
123
+ // 🎯 Fire the dismiss callback event
124
+ this._triggerCallback('dismiss');
165
125
  alert.remove();
166
126
  });
167
127
  }
168
128
 
169
- // Wire custom bindings from .bind() calls
170
- this._bindings.forEach(({ event, handler }) => {
171
- alert.addEventListener(event, handler as EventListener);
172
- });
129
+ // Wire events using inherited method
130
+ this._wireStandardEvents(alert);
173
131
 
174
- // Wire sync bindings from .sync() calls
132
+ // Wire sync bindings
175
133
  this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
176
134
  if (property === 'message') {
177
- const transformToComponent = toComponent || ((v: any) => String(v));
135
+ const transform = toComponent || ((v: any) => String(v));
178
136
 
179
137
  stateObj.subscribe((val: any) => {
180
- const transformed = transformToComponent(val);
138
+ const transformed = transform(val);
181
139
  content.textContent = transformed;
182
140
  this.state.message = transformed;
183
141
  });
184
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
+ }
185
153
  else if (property === 'visible') {
186
154
  const transformToState = toState || ((v: any) => Boolean(v));
187
- const transformToComponent = toComponent || ((v: any) => Boolean(v));
155
+ const transform = toComponent || ((v: any) => Boolean(v));
188
156
 
189
157
  let isUpdating = false;
190
158
 
191
159
  // State → Component
192
160
  stateObj.subscribe((val: any) => {
193
161
  if (isUpdating) return;
194
- const transformed = transformToComponent(val);
162
+ const transformed = transform(val);
195
163
  alert.style.display = transformed ? 'flex' : 'none';
196
164
  });
197
165
 
@@ -205,14 +173,19 @@ export class Alert {
205
173
  alert.style.display = 'none';
206
174
  stateObj.set(transformToState(false));
207
175
 
176
+ // 🎯 Fire the dismiss callback event
177
+ this._triggerCallback('dismiss');
178
+
208
179
  setTimeout(() => { isUpdating = false; }, 0);
209
180
  });
210
181
  }
211
182
  }
212
183
  });
213
184
 
214
- // === 5. RENDER: Append to DOM and finalize ===
215
185
  container.appendChild(alert);
186
+ this._alert = alert;
187
+
188
+ this._injectAlertStyles();
216
189
 
217
190
  requestAnimationFrame(() => {
218
191
  if ((window as any).lucide) {
@@ -223,11 +196,89 @@ export class Alert {
223
196
  return this;
224
197
  }
225
198
 
226
- renderTo(juxComponent: any): this {
227
- if (!juxComponent?._id) {
228
- throw new Error('Alert.renderTo: Invalid component');
229
- }
230
- return this.render(`#${juxComponent._id}`);
199
+ private _injectAlertStyles(): void {
200
+ const styleId = 'jux-alert-styles';
201
+ if (document.getElementById(styleId)) return;
202
+
203
+ const style = document.createElement('style');
204
+ style.id = styleId;
205
+ style.textContent = `
206
+ .jux-alert {
207
+ display: flex;
208
+ align-items: center;
209
+ gap: 12px;
210
+ padding: 16px;
211
+ border-radius: 8px;
212
+ margin-bottom: 12px;
213
+ position: relative;
214
+ animation: slideIn 0.3s ease-out;
215
+ }
216
+
217
+ @keyframes slideIn {
218
+ from {
219
+ opacity: 0;
220
+ transform: translateX(-20px);
221
+ }
222
+ to {
223
+ opacity: 1;
224
+ transform: translateX(0);
225
+ }
226
+ }
227
+
228
+ .jux-alert-info {
229
+ background: #dbeafe;
230
+ border-left: 4px solid #3b82f6;
231
+ color: #1e40af;
232
+ }
233
+
234
+ .jux-alert-success {
235
+ background: #d1fae5;
236
+ border-left: 4px solid #10b981;
237
+ color: #065f46;
238
+ }
239
+
240
+ .jux-alert-warning {
241
+ background: #fef3c7;
242
+ border-left: 4px solid #f59e0b;
243
+ color: #92400e;
244
+ }
245
+
246
+ .jux-alert-error {
247
+ background: #fee2e2;
248
+ border-left: 4px solid #ef4444;
249
+ color: #991b1b;
250
+ }
251
+
252
+ .jux-alert-icon {
253
+ flex-shrink: 0;
254
+ }
255
+
256
+ .jux-alert-content {
257
+ flex: 1;
258
+ font-size: 14px;
259
+ }
260
+
261
+ .jux-alert-close {
262
+ flex-shrink: 0;
263
+ background: transparent;
264
+ border: none;
265
+ font-size: 24px;
266
+ cursor: pointer;
267
+ opacity: 0.6;
268
+ transition: opacity 0.2s;
269
+ padding: 0;
270
+ width: 24px;
271
+ height: 24px;
272
+ display: flex;
273
+ align-items: center;
274
+ justify-content: center;
275
+ }
276
+
277
+ .jux-alert-close:hover {
278
+ opacity: 1;
279
+ }
280
+ `;
281
+ document.head.appendChild(style);
231
282
  }
232
283
  }
233
284