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.
- package/bin/cli.js +121 -72
- package/lib/components/alert.ts +143 -92
- package/lib/components/badge.ts +93 -94
- package/lib/components/base/BaseComponent.ts +397 -0
- package/lib/components/base/FormInput.ts +322 -0
- package/lib/components/button.ts +40 -131
- package/lib/components/card.ts +57 -79
- package/lib/components/charts/areachart.ts +315 -0
- package/lib/components/charts/barchart.ts +421 -0
- package/lib/components/charts/doughnutchart.ts +263 -0
- package/lib/components/charts/lib/BaseChart.ts +402 -0
- package/lib/components/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
- package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
- package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
- package/lib/components/checkbox.ts +255 -204
- package/lib/components/code.ts +31 -78
- package/lib/components/container.ts +113 -130
- package/lib/components/data.ts +37 -5
- package/lib/components/datepicker.ts +180 -147
- package/lib/components/dialog.ts +218 -221
- package/lib/components/divider.ts +63 -87
- package/lib/components/docs-data.json +498 -2404
- package/lib/components/dropdown.ts +191 -236
- package/lib/components/element.ts +196 -145
- package/lib/components/fileupload.ts +253 -167
- package/lib/components/guard.ts +92 -0
- package/lib/components/heading.ts +31 -97
- package/lib/components/helpers.ts +13 -6
- package/lib/components/hero.ts +51 -114
- package/lib/components/icon.ts +33 -120
- package/lib/components/icons.ts +2 -1
- package/lib/components/include.ts +76 -3
- package/lib/components/input.ts +155 -407
- package/lib/components/kpicard.ts +16 -16
- package/lib/components/list.ts +358 -261
- package/lib/components/loading.ts +142 -211
- package/lib/components/menu.ts +63 -152
- package/lib/components/modal.ts +42 -129
- package/lib/components/nav.ts +79 -101
- package/lib/components/paragraph.ts +38 -102
- package/lib/components/progress.ts +108 -166
- package/lib/components/radio.ts +283 -234
- package/lib/components/script.ts +19 -87
- package/lib/components/select.ts +189 -199
- package/lib/components/sidebar.ts +110 -141
- package/lib/components/style.ts +19 -82
- package/lib/components/switch.ts +254 -183
- package/lib/components/table.ts +1078 -208
- package/lib/components/tabs.ts +42 -106
- package/lib/components/theme-toggle.ts +73 -165
- package/lib/components/tooltip.ts +85 -316
- package/lib/components/write.ts +108 -127
- package/lib/jux.ts +67 -41
- package/machinery/build.js +466 -0
- package/machinery/compiler.js +354 -105
- package/machinery/server.js +23 -100
- package/machinery/watcher.js +153 -130
- package/package.json +1 -1
- package/presets/base.css +1166 -0
- package/presets/notion.css +2 -1975
- package/lib/adapters/base-adapter.js +0 -35
- package/lib/adapters/index.js +0 -33
- package/lib/adapters/mysql-adapter.js +0 -65
- package/lib/adapters/postgres-adapter.js +0 -70
- package/lib/adapters/sqlite-adapter.js +0 -56
- package/lib/components/areachart.ts +0 -1128
- package/lib/components/areachartsmooth.ts +0 -1380
- package/lib/components/barchart.ts +0 -1322
- package/lib/components/doughnutchart.ts +0 -1259
- package/lib/components/footer.ts +0 -165
- package/lib/components/header.ts +0 -187
- package/lib/components/layout.ts +0 -239
- package/lib/components/main.ts +0 -137
- package/lib/layouts/default.jux +0 -8
- package/lib/layouts/figma.jux +0 -0
- /package/lib/{themes → components/charts/lib}/charts.js +0 -0
package/bin/cli.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
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
|
-
|
|
108
|
+
docsTime = performance.now() - docsStartTime;
|
|
109
|
+
console.log(`✅ Documentation generated (${docsTime.toFixed(0)}ms)\n`);
|
|
101
110
|
} catch (error) {
|
|
102
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
//
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
|
279
|
-
npx jux build
|
|
280
|
-
npx jux serve [port]
|
|
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/
|
|
285
|
-
│ ├── index.jux
|
|
286
|
-
│ └── pages/
|
|
287
|
-
├── jux-dist/
|
|
288
|
-
├── server/
|
|
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
|
|
298
|
-
2. npm install
|
|
299
|
-
3. npx jux
|
|
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
|
|
304
|
-
npx jux serve
|
|
305
|
-
npx jux serve 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
|
})();
|
package/lib/components/alert.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
170
|
-
this.
|
|
171
|
-
alert.addEventListener(event, handler as EventListener);
|
|
172
|
-
});
|
|
129
|
+
// Wire events using inherited method
|
|
130
|
+
this._wireStandardEvents(alert);
|
|
173
131
|
|
|
174
|
-
// Wire sync bindings
|
|
132
|
+
// Wire sync bindings
|
|
175
133
|
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
176
134
|
if (property === 'message') {
|
|
177
|
-
const
|
|
135
|
+
const transform = toComponent || ((v: any) => String(v));
|
|
178
136
|
|
|
179
137
|
stateObj.subscribe((val: any) => {
|
|
180
|
-
const transformed =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|