juxscript 1.0.3 → 1.0.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 (73) hide show
  1. package/README.md +37 -92
  2. package/bin/cli.js +57 -56
  3. package/lib/components/alert.ts +240 -0
  4. package/lib/components/app.ts +216 -82
  5. package/lib/components/badge.ts +164 -0
  6. package/lib/components/barchart.ts +1248 -0
  7. package/lib/components/button.ts +188 -53
  8. package/lib/components/card.ts +75 -61
  9. package/lib/components/chart.ts +17 -15
  10. package/lib/components/checkbox.ts +199 -0
  11. package/lib/components/code.ts +66 -152
  12. package/lib/components/container.ts +104 -208
  13. package/lib/components/data.ts +1 -3
  14. package/lib/components/datepicker.ts +226 -0
  15. package/lib/components/dialog.ts +258 -0
  16. package/lib/components/docs-data.json +1969 -423
  17. package/lib/components/dropdown.ts +244 -0
  18. package/lib/components/element.ts +271 -0
  19. package/lib/components/fileupload.ts +319 -0
  20. package/lib/components/footer.ts +37 -18
  21. package/lib/components/header.ts +53 -33
  22. package/lib/components/heading.ts +119 -0
  23. package/lib/components/helpers.ts +34 -0
  24. package/lib/components/hero.ts +57 -31
  25. package/lib/components/include.ts +292 -0
  26. package/lib/components/input.ts +508 -77
  27. package/lib/components/layout.ts +144 -18
  28. package/lib/components/list.ts +83 -74
  29. package/lib/components/loading.ts +263 -0
  30. package/lib/components/main.ts +43 -17
  31. package/lib/components/menu.ts +108 -24
  32. package/lib/components/modal.ts +50 -21
  33. package/lib/components/nav.ts +60 -18
  34. package/lib/components/paragraph.ts +111 -0
  35. package/lib/components/progress.ts +276 -0
  36. package/lib/components/radio.ts +236 -0
  37. package/lib/components/req.ts +300 -0
  38. package/lib/components/script.ts +33 -74
  39. package/lib/components/select.ts +280 -0
  40. package/lib/components/sidebar.ts +87 -37
  41. package/lib/components/style.ts +47 -70
  42. package/lib/components/switch.ts +261 -0
  43. package/lib/components/table.ts +47 -24
  44. package/lib/components/tabs.ts +105 -63
  45. package/lib/components/theme-toggle.ts +361 -0
  46. package/lib/components/token-calculator.ts +380 -0
  47. package/lib/components/tooltip.ts +244 -0
  48. package/lib/components/view.ts +36 -20
  49. package/lib/components/write.ts +284 -0
  50. package/lib/globals.d.ts +21 -0
  51. package/lib/jux.ts +178 -68
  52. package/lib/presets/notion.css +521 -0
  53. package/lib/presets/notion.jux +27 -0
  54. package/lib/reactivity/state.ts +364 -0
  55. package/lib/themes/charts.js +126 -0
  56. package/machinery/compiler.js +126 -38
  57. package/machinery/generators/html.js +2 -3
  58. package/machinery/server.js +2 -2
  59. package/package.json +29 -3
  60. package/lib/components/import.ts +0 -430
  61. package/lib/components/node.ts +0 -200
  62. package/lib/components/reactivity.js +0 -104
  63. package/lib/components/theme.ts +0 -97
  64. package/lib/layouts/notion.css +0 -258
  65. package/lib/styles/base-theme.css +0 -186
  66. package/lib/styles/dark-theme.css +0 -144
  67. package/lib/styles/light-theme.css +0 -144
  68. package/lib/styles/tokens/dark.css +0 -86
  69. package/lib/styles/tokens/light.css +0 -86
  70. package/lib/templates/index.juxt +0 -33
  71. package/lib/themes/dark.css +0 -86
  72. package/lib/themes/light.css +0 -86
  73. /package/lib/{styles → presets}/global.css +0 -0
package/README.md CHANGED
@@ -1,4 +1,26 @@
1
- # *JUX* Authoring UI's in pure javascript.
1
+ # The web needs a higher level of abstraction: Meet **JUX**
2
+
3
+ - [X] Layouts (100% done.)
4
+ - [ ] Render Dependency Tree
5
+ > Idea here is, one element may be a predicate for another. Will need promises. **predicting problems with slow-loading components that depend on containers from other components. May to to separate concerns with container "building" vs. content addition OR use async processes (promises).
6
+ - [X] Reactivity (90% done.)
7
+ - [ ] Client Components (99% of what would be needed.)
8
+ - [ ] Charts
9
+ - [ ] Poor Intellisense support? Could be this issue.
10
+ - [ ] Api Wrapper
11
+ - [X] Params/Active State for Menu/Nav matching - built in.
12
+ - [ ] CDN Bundle (import CDN/'state', 'jux' from cdn.)
13
+ - [ ] Icon
14
+ - [ ] Cross Page Store.
15
+ - [ ] Quickstart Boilerplates (20% done,notion.jux)
16
+ - [ ] Mobile Nav
17
+ - [ ] `npx jux present notion|default` etc..
18
+ - [ ] Server side components (api and database)
19
+ - [ ] Quick deploy option
20
+ - [ ] Distributable Bundle (Static Sites)
21
+ - [ ] Tree Shake/Efficiencies.
22
+
23
+ ## *JUX* Authoring UI's in pure javascript.
2
24
 
3
25
  > Build beautiful, reactive UI's using only javascript. **No markup required.**
4
26
  > A clean authorship layer capable of being taught to non-developers, strong enough to be used by real developers.
@@ -59,6 +81,16 @@ npx jux serve
59
81
  > building only (the `examples` project directory)
60
82
  `npm run build:examples`
61
83
 
84
+
85
+ # Authoring in Jux
86
+
87
+ 1. Build a Layout Page you can reuse.
88
+ Check out the presets, they speed this process up. Just modify them with your goals, such as logo, menu options, nav etc.
89
+ 2. Build your Pages
90
+ Just get to work, using the **jux** component library to build most of what you need, filling in any details with *javascript* logically arranged the way you want. Think, authoring SFC but with only the composition layer.
91
+ 3. Test it out!
92
+ Jux comes with a watcher system built in, so you can see your edits to *.jux* files live.
93
+
62
94
  # GOAL: Eliminating the Markup System of building UI's.
63
95
  > HTML markup is the equivalent of still writing in `C` ro `C++` despite the availability of more functional levels of abstraction like `python`.
64
96
  > To remove the requirement for markup, **jux** must address the utility of markup.
@@ -206,99 +238,12 @@ export default {
206
238
 
207
239
 
208
240
  ### LAYOUT NOTES...
209
- # Jux Standard Layout Regions
241
+ ### Jux Standard Layout Regions
210
242
 
211
243
  - [ ] CHORE : REVISIT! I think maybe we ship with our figma, notion, default etc. But then document how to build and reuse a .jux page as a layout using the `jux.layout` class.
212
244
 
213
- ## The Opinionated Structure
214
-
215
- **jux** layout pages provide a **strongly opinionated** layout structure that covers 99% of web applications. Every page includes these regions by default, even if not all are used. This eliminates layout decisions and provides consistent target containers across your entire application.
216
-
217
- These are free to use, but you can roll your own just as easily.
218
- You can find the documentation for each of these layouts here:
219
-
220
- - Notion
221
- - Jux
222
- - Figma
223
- - Other?
224
-
225
-
226
- ### Sample Documenation
227
- ```markdown
228
- #app
229
- ├── #appheader // Top navigation bar - your app's primary navigation
230
- │ ├── #appheader-logo // Brand identity - logo, app name
231
- │ ├── #appheader-nav // Primary navigation menu
232
- │ └── #appheader-actions // User actions - profile, notifications, settings
233
-
234
- ├── #appsubheader // Contextual navigation - changes based on current page
235
- │ ├── #appsubheader-breadcrumbs // Where am I? - navigation path
236
- │ ├── #appsubheader-tabs // Page-level tabs - views within the same context
237
- │ └── #appsubheader-actions // Context-specific actions - filters, search, "New Item"
238
-
239
- ├── #appsidebar // Left sidebar - persistent navigation or filters
240
- │ ├── #appsidebar-header // Sidebar title or search
241
- │ ├── #appsidebar-content // Main sidebar content - nav tree, filters
242
- │ └── #appsidebar-footer // Sidebar actions or status
243
-
244
- ├── #appmain // Your content lives here - the star of the show
245
-
246
- ├── #appaside // Right sidebar - contextual help, metadata
247
- │ ├── #appaside-header // Aside title
248
- │ ├── #appaside-content // Properties panel, help docs, related items
249
- │ └── #appaside-footer // Aside actions
250
-
251
- ├── #appfooter // Bottom of the page
252
- │ ├── #appfooter-content // Footer navigation, social links
253
- │ └── #appfooter-legal // Copyright, terms, privacy
254
-
255
- └── #appmodal // Overlays and dialogs
256
- ├── #appmodal-backdrop // Click-to-close background
257
- └── #appmodal-container
258
- ├── #appmodal-header // Dialog title and close button
259
- ├── #appmodal-content // Dialog body
260
- └── #appmodal-footer // Dialog actions - Cancel, Save, etc.
261
-
262
- ```
263
-
264
- ## Why consider a Jux layout?
265
-
266
- **Predictability**: Every page has the same structure. Developers know exactly where to render components.
267
-
268
- **Flexibility**: Don't need a sidebar? Leave it empty. CSS handles visibility automatically.
269
-
270
- **Composability**: Components can render to any region with `.render('#target-id')`.
271
-
272
- **Convention over Configuration**: No layout decisions needed. Just start building.
273
-
274
- ## Common Patterns
275
-
276
- ```javascript
277
- // Dashboard with sidebar navigation
278
- const sideNav = jux.nav('side-nav', { /* config */ });
279
- await sideNav.render('#appsidebar-content');
280
-
281
- const dashboard = jux.dashboard('main-dashboard', { /* config */ });
282
- await dashboard.render('#appmain');
283
-
284
- // Settings page with tabs
285
- const tabs = jux.tabs('settings-tabs', { /* config */ });
286
- await tabs.render('#appsubheader-tabs');
287
-
288
- const content = jux.container('settings-content', { /* config */ });
289
- await content.render('#appmain');
290
-
291
- // Detail view with metadata sidebar
292
- const detail = jux.detail('item-detail', { /* config */ });
293
- await detail.render('#appmain');
294
-
295
- const metadata = jux.metadata('item-meta', { /* config */ });
296
- await metadata.render('#appaside-content');
245
+ ### Start with a layout page
297
246
 
298
- // Modal dialog
299
- const dialog = jux.dialog('confirm-dialog', { /* config */ });
300
- await dialog.render('#appmodal-content');
247
+ You can build a simple one, or copy and use one of the Jux presets.
301
248
 
302
- // Auto-render to default container (component chooses based on type)
303
- const hero = jux.hero('page-hero', { /* config */ });
304
- await hero.render(); // Automatically renders to #appmain
249
+ `npx jux preset notion|default|figma|slack|other`
package/bin/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { compileJuxFile, copyLibToOutput, copyProjectAssets } from '../machinery/compiler.js';
3
+ import { compileJuxFile, copyLibToOutput, copyProjectAssets, transpileProjectTypeScript } from '../machinery/compiler.js';
4
4
  import { generateDocs } from '../machinery/doc-generator.js';
5
5
  import { start } from '../machinery/server.js';
6
6
  import path from 'path';
@@ -14,20 +14,20 @@ const __dirname = path.dirname(__filename);
14
14
  const PATHS = {
15
15
  // Where jux package is installed (in node_modules/juxscript or local dev)
16
16
  packageRoot: path.resolve(__dirname, '..'),
17
-
17
+
18
18
  // Where the user's project root is (where they run `npx jux`)
19
19
  projectRoot: process.cwd(),
20
-
20
+
21
21
  // Where user's .jux source files live (CONVENTION: ./jux/)
22
22
  get juxSource() {
23
23
  return path.join(this.projectRoot, 'jux');
24
24
  },
25
-
25
+
26
26
  // Where jux lib files are (components, layouts, etc.)
27
27
  get juxLib() {
28
28
  return path.join(this.packageRoot, 'lib');
29
29
  },
30
-
30
+
31
31
  // Where frontend build output goes (CONVENTION: ./jux-dist/)
32
32
  get frontendDist() {
33
33
  return path.join(this.projectRoot, 'jux-dist');
@@ -43,15 +43,22 @@ console.log(` Lib: ${PATHS.juxLib}\n`);
43
43
 
44
44
  const command = process.argv[2];
45
45
 
46
+ /**
47
+ * 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
+ */
46
53
  function findJuxFiles(dir, fileList = []) {
47
54
  if (!fs.existsSync(dir)) return fileList;
48
-
55
+
49
56
  const files = fs.readdirSync(dir);
50
-
57
+
51
58
  files.forEach(file => {
52
59
  const filePath = path.join(dir, file);
53
60
  const stat = fs.statSync(filePath);
54
-
61
+
55
62
  if (stat.isDirectory()) {
56
63
  if (file !== 'node_modules' && file !== 'jux-dist' && file !== '.git' && file !== 'server') {
57
64
  findJuxFiles(filePath, fileList);
@@ -60,29 +67,18 @@ function findJuxFiles(dir, fileList = []) {
60
67
  fileList.push(filePath);
61
68
  }
62
69
  });
63
-
64
- return fileList;
65
- }
66
70
 
67
- async function loadConfig() {
68
- const configPath = path.join(PATHS.projectRoot, 'jux.config.js');
69
-
70
- if (fs.existsSync(configPath)) {
71
- try {
72
- const configModule = await import(configPath);
73
- return configModule.default || {};
74
- } catch (err) {
75
- console.warn('⚠️ Could not load jux.config.js:', err.message);
76
- return {};
77
- }
78
- }
79
-
80
- return {};
71
+ return fileList;
81
72
  }
82
73
 
74
+ /**
75
+ * Build the entire JUX project
76
+ *
77
+ * @param {boolean} isServe - Whether building for dev server with hot reload
78
+ */
83
79
  async function buildProject(isServe = false) {
84
80
  console.log('🔨 Building JUX frontend...\n');
85
-
81
+
86
82
  try {
87
83
  // Verify jux source directory exists
88
84
  if (!fs.existsSync(PATHS.juxSource)) {
@@ -108,13 +104,16 @@ async function buildProject(isServe = false) {
108
104
 
109
105
  // Step 2: Copy jux lib to frontend dist
110
106
  await copyLibToOutput(PATHS.juxLib, PATHS.frontendDist);
111
-
107
+
112
108
  // Step 3: Copy project assets from jux/ (CSS, JS, images)
113
109
  await copyProjectAssets(PATHS.juxSource, PATHS.frontendDist);
114
110
 
115
- // Step 4: Compile .jux files from jux/ directory ONLY
111
+ // Step 4: Transpile TypeScript files from jux/ to jux-dist/
112
+ await transpileProjectTypeScript(PATHS.juxSource, PATHS.frontendDist);
113
+
114
+ // Step 5: Compile .jux files from jux/ directory ONLY
116
115
  const projectJuxFiles = findJuxFiles(PATHS.juxSource);
117
- console.log(`Found ${projectJuxFiles.length} .jux file(s) in /jux\n`);
116
+ console.log(`📝 Found ${projectJuxFiles.length} .jux file(s) in /jux\n`);
118
117
 
119
118
  for (const file of projectJuxFiles) {
120
119
  try {
@@ -128,24 +127,24 @@ async function buildProject(isServe = false) {
128
127
  }
129
128
  }
130
129
 
131
- // Step 5: Compile vendor layouts
130
+ // Step 6: Compile vendor layouts
132
131
  const layoutsDir = path.join(PATHS.juxLib, 'layouts');
133
-
132
+
134
133
  if (fs.existsSync(layoutsDir)) {
135
134
  console.log('\n📐 Compiling vendor layouts...');
136
135
  const vendorJuxFiles = findJuxFiles(layoutsDir);
137
136
  console.log(`Found ${vendorJuxFiles.length} vendor layout(s)\n`);
138
-
137
+
139
138
  for (const file of vendorJuxFiles) {
140
139
  try {
141
140
  const relPath = path.relative(PATHS.juxLib, file);
142
-
143
- await compileJuxFile(file, {
141
+
142
+ await compileJuxFile(file, {
144
143
  distDir: path.join(PATHS.frontendDist, 'lib'),
145
144
  projectRoot: PATHS.juxLib,
146
- isServe
145
+ isServe
147
146
  });
148
-
147
+
149
148
  console.log(` ✓ Compiled: ${relPath}`);
150
149
  } catch (err) {
151
150
  console.error(`Error compiling vendor layout ${file}:`, err.message);
@@ -154,7 +153,7 @@ async function buildProject(isServe = false) {
154
153
  }
155
154
 
156
155
  console.log(`\n✅ Built ${projectJuxFiles.length} file(s) → ${PATHS.frontendDist}\n`);
157
-
156
+
158
157
  // Show backend integration examples
159
158
  console.log('📦 Serve from your backend:');
160
159
  console.log(` Express: app.use(express.static('jux-dist'))`);
@@ -162,7 +161,7 @@ async function buildProject(isServe = false) {
162
161
  console.log(` FastAPI: app.mount("/", StaticFiles(directory="jux-dist"), name="static")`);
163
162
  console.log(` Laravel: Route::view('/', 'jux-dist/index.html')`);
164
163
  console.log('');
165
-
164
+
166
165
  } catch (err) {
167
166
  console.error('❌ Build error:', err.message);
168
167
  console.error(err.stack);
@@ -173,21 +172,21 @@ async function buildProject(isServe = false) {
173
172
  (async () => {
174
173
  if (command === 'init') {
175
174
  console.log('🎨 Initializing JUX project...\n');
176
-
175
+
177
176
  const juxDir = PATHS.juxSource;
178
-
177
+
179
178
  if (fs.existsSync(juxDir)) {
180
179
  console.error('❌ jux/ directory already exists');
181
180
  process.exit(1);
182
181
  }
183
-
182
+
184
183
  // Create structure
185
184
  fs.mkdirSync(juxDir, { recursive: true });
186
-
187
- // Copy template file from lib/templates/index.juxt
188
- const templatePath = path.join(PATHS.packageRoot, 'lib', 'templates', 'index.juxt');
185
+
186
+ // Copy template file from lib/presets/index.juxt
187
+ const templatePath = path.join(PATHS.packageRoot, 'lib', 'presets', 'index.juxt');
189
188
  const targetPath = path.join(juxDir, 'index.jux');
190
-
189
+
191
190
  if (fs.existsSync(templatePath)) {
192
191
  fs.copyFileSync(templatePath, targetPath);
193
192
  console.log('✅ Created jux/index.jux from template');
@@ -212,38 +211,35 @@ jux.hero('hero1', {
212
211
  fs.writeFileSync(targetPath, fallbackContent);
213
212
  console.log('✅ Created jux/index.jux');
214
213
  }
215
-
214
+
216
215
  // Create .gitignore
217
216
  const gitignorePath = path.join(PATHS.projectRoot, '.gitignore');
218
217
  const gitignoreContent = `jux-dist/
219
218
  node_modules/
220
219
  .DS_Store
221
220
  `;
222
-
221
+
223
222
  if (!fs.existsSync(gitignorePath)) {
224
223
  fs.writeFileSync(gitignorePath, gitignoreContent);
225
224
  console.log('✅ Created .gitignore');
226
225
  }
227
-
226
+
228
227
  console.log('✅ Created jux/ directory\n');
229
228
  console.log('Next steps:');
230
229
  console.log(' 1. Edit jux/index.jux');
231
230
  console.log(' 2. Run: npx jux build');
232
231
  console.log(' 3. Serve jux-dist/ from your backend\n');
233
-
232
+
234
233
  } else if (command === 'build') {
235
234
  await buildProject(false);
236
235
  console.log(`✅ Build complete: ${PATHS.frontendDist}`);
237
-
236
+
238
237
  } else if (command === 'serve') {
239
238
  await buildProject(true);
240
-
241
- const config = await loadConfig();
242
- config.distDir = PATHS.frontendDist; // Pass the correct dist directory
243
-
239
+
244
240
  const port = parseInt(process.argv[3]) || 3000;
245
- await start(port, config);
246
-
241
+ await start(port);
242
+
247
243
  } else {
248
244
  console.log(`
249
245
  JUX CLI - A JavaScript UX authorship platform
@@ -257,8 +253,13 @@ Project Structure (Convention):
257
253
  my-project/
258
254
  ├── jux/ # Your .jux source files (REQUIRED)
259
255
  │ ├── index.jux
256
+ │ ├── samples/
257
+ │ │ └── mypage.ts # TypeScript files transpiled to .js
260
258
  │ └── pages/
261
259
  ├── jux-dist/ # Build output (generated, git-ignore this)
260
+ │ ├── samples/
261
+ │ │ └── mypage.js # Transpiled TypeScript
262
+ │ └── ...
262
263
  ├── server/ # Your backend (untouched by jux)
263
264
  └── package.json
264
265
 
@@ -0,0 +1,240 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+
3
+ /**
4
+ * Alert component options
5
+ */
6
+ export interface AlertOptions {
7
+ message?: string;
8
+ variant?: 'info' | 'success' | 'warning' | 'error';
9
+ title?: string;
10
+ dismissible?: boolean;
11
+ onDismiss?: () => void;
12
+ icon?: string;
13
+ style?: string;
14
+ class?: string;
15
+ }
16
+
17
+ /**
18
+ * Alert component state
19
+ */
20
+ type AlertState = {
21
+ message: string;
22
+ variant: string;
23
+ title: string;
24
+ dismissible: boolean;
25
+ icon: string;
26
+ style: string;
27
+ class: string;
28
+ };
29
+
30
+ /**
31
+ * Alert component - Status messages and notifications
32
+ *
33
+ * Usage:
34
+ * jux.alert('my-alert', {
35
+ * variant: 'success',
36
+ * title: 'Success!',
37
+ * message: 'Your changes have been saved.',
38
+ * dismissible: true
39
+ * }).render('#app');
40
+ *
41
+ * Variants: info, success, warning, error
42
+ */
43
+ export class Alert {
44
+ state: AlertState;
45
+ container: HTMLElement | null = null;
46
+ _id: string;
47
+ id: string;
48
+ private _onDismiss?: () => void;
49
+
50
+ constructor(id: string, options: AlertOptions = {}) {
51
+ this._id = id;
52
+ this.id = id;
53
+ this._onDismiss = options.onDismiss;
54
+
55
+ // Default icons per variant
56
+ const defaultIcons = {
57
+ info: 'ℹ️',
58
+ success: '✅',
59
+ warning: '⚠️',
60
+ error: '❌'
61
+ };
62
+
63
+ const variant = options.variant ?? 'info';
64
+
65
+ this.state = {
66
+ message: options.message ?? '',
67
+ variant,
68
+ title: options.title ?? '',
69
+ dismissible: options.dismissible ?? false,
70
+ icon: options.icon ?? defaultIcons[variant as keyof typeof defaultIcons],
71
+ style: options.style ?? '',
72
+ class: options.class ?? ''
73
+ };
74
+ }
75
+
76
+ /* -------------------------
77
+ * Fluent API
78
+ * ------------------------- */
79
+
80
+ message(value: string): this {
81
+ this.state.message = value;
82
+ return this;
83
+ }
84
+
85
+ variant(value: 'info' | 'success' | 'warning' | 'error'): this {
86
+ this.state.variant = value;
87
+ return this;
88
+ }
89
+
90
+ title(value: string): this {
91
+ this.state.title = value;
92
+ return this;
93
+ }
94
+
95
+ dismissible(value: boolean): this {
96
+ this.state.dismissible = value;
97
+ return this;
98
+ }
99
+
100
+ icon(value: string): this {
101
+ this.state.icon = value;
102
+ return this;
103
+ }
104
+
105
+ style(value: string): this {
106
+ this.state.style = value;
107
+ return this;
108
+ }
109
+
110
+ class(value: string): this {
111
+ this.state.class = value;
112
+ return this;
113
+ }
114
+
115
+ /* -------------------------
116
+ * Methods
117
+ * ------------------------- */
118
+
119
+ /**
120
+ * Dismiss/remove the alert
121
+ */
122
+ dismiss(): void {
123
+ const element = document.getElementById(this._id);
124
+ if (element) {
125
+ element.style.opacity = '0';
126
+ element.style.transform = 'translateY(-10px)';
127
+ setTimeout(() => {
128
+ element.remove();
129
+ if (this._onDismiss) {
130
+ this._onDismiss();
131
+ }
132
+ }, 200);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Auto-dismiss after delay
138
+ */
139
+ autoDismiss(delay: number = 5000): this {
140
+ setTimeout(() => this.dismiss(), delay);
141
+ return this;
142
+ }
143
+
144
+ /* -------------------------
145
+ * Render
146
+ * ------------------------- */
147
+
148
+ render(targetId?: string): this {
149
+ let container: HTMLElement;
150
+
151
+ if (targetId) {
152
+ const target = document.querySelector(targetId);
153
+ if (!target || !(target instanceof HTMLElement)) {
154
+ throw new Error(`Alert: Target element "${targetId}" not found`);
155
+ }
156
+ container = target;
157
+ } else {
158
+ container = getOrCreateContainer(this._id);
159
+ }
160
+
161
+ this.container = container;
162
+ const { message, variant, title, dismissible, icon, style, class: className } = this.state;
163
+
164
+ const alert = document.createElement('div');
165
+ alert.className = `jux-alert jux-alert-${variant}`;
166
+ alert.id = this._id;
167
+ alert.setAttribute('role', 'alert');
168
+
169
+ if (className) {
170
+ alert.className += ` ${className}`;
171
+ }
172
+
173
+ if (style) {
174
+ alert.setAttribute('style', style);
175
+ }
176
+
177
+ // Icon
178
+ if (icon) {
179
+ const iconEl = document.createElement('span');
180
+ iconEl.className = 'jux-alert-icon';
181
+ iconEl.textContent = icon;
182
+ alert.appendChild(iconEl);
183
+ }
184
+
185
+ // Content
186
+ const content = document.createElement('div');
187
+ content.className = 'jux-alert-content';
188
+
189
+ if (title) {
190
+ const titleEl = document.createElement('div');
191
+ titleEl.className = 'jux-alert-title';
192
+ titleEl.textContent = title;
193
+ content.appendChild(titleEl);
194
+ }
195
+
196
+ if (message) {
197
+ const messageEl = document.createElement('div');
198
+ messageEl.className = 'jux-alert-message';
199
+ messageEl.textContent = message;
200
+ content.appendChild(messageEl);
201
+ }
202
+
203
+ alert.appendChild(content);
204
+
205
+ // Dismiss button
206
+ if (dismissible) {
207
+ const dismissBtn = document.createElement('button');
208
+ dismissBtn.className = 'jux-alert-dismiss';
209
+ dismissBtn.innerHTML = '×';
210
+ dismissBtn.setAttribute('aria-label', 'Dismiss alert');
211
+ dismissBtn.addEventListener('click', () => this.dismiss());
212
+ alert.appendChild(dismissBtn);
213
+ }
214
+
215
+ container.appendChild(alert);
216
+ return this;
217
+ }
218
+
219
+ /**
220
+ * Render to another Jux component's container
221
+ */
222
+ renderTo(juxComponent: any): this {
223
+ if (!juxComponent || typeof juxComponent !== 'object') {
224
+ throw new Error('Alert.renderTo: Invalid component - not an object');
225
+ }
226
+
227
+ if (!juxComponent._id || typeof juxComponent._id !== 'string') {
228
+ throw new Error('Alert.renderTo: Invalid component - missing _id (not a Jux component)');
229
+ }
230
+
231
+ return this.render(`#${juxComponent._id}`);
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Factory helper
237
+ */
238
+ export function alert(id: string, options: AlertOptions = {}): Alert {
239
+ return new Alert(id, options);
240
+ }