juxscript 1.0.8 → 1.0.10

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.
@@ -242,6 +242,11 @@ export class KPICard {
242
242
  return this;
243
243
  }
244
244
 
245
+ // Remove existing element if it exists
246
+ if (this._element && this._element.parentNode) {
247
+ this._element.parentNode.removeChild(this._element);
248
+ }
249
+
245
250
  this._loadThemeFont();
246
251
  this._buildCard(element as HTMLElement);
247
252
  return this;
@@ -253,8 +258,11 @@ export class KPICard {
253
258
  const element = document.querySelector(this._container);
254
259
  if (!element) return;
255
260
 
256
- // Clear and rebuild
257
- element.innerHTML = '';
261
+ // Remove existing element
262
+ if (this._element && this._element.parentNode) {
263
+ this._element.parentNode.removeChild(this._element);
264
+ }
265
+
258
266
  this._loadThemeFont();
259
267
  this._buildCard(element as HTMLElement);
260
268
  }
@@ -317,6 +325,9 @@ export class KPICard {
317
325
  const baseHeight = 200;
318
326
  const scaleFactor = Math.min(width / baseWidth, height / baseHeight);
319
327
 
328
+ // Determine if we're in dark mode based on style mode
329
+ const isDarkMode = styleMode === 'gradient' || styleMode === 'glass';
330
+
320
331
  const content = document.createElement('div');
321
332
  content.className = 'jux-kpicard-content';
322
333
  content.style.cssText = `
@@ -331,10 +342,19 @@ export class KPICard {
331
342
  const titleEl = document.createElement('div');
332
343
  titleEl.className = 'jux-kpicard-title';
333
344
  titleEl.textContent = title;
345
+
346
+ // Smart color selection based on theme
347
+ let titleColor = '#6b7280'; // default light mode
348
+ if (styleMode === 'gradient') {
349
+ titleColor = 'rgba(255, 255, 255, 0.95)';
350
+ } else if (styleMode === 'glass') {
351
+ titleColor = 'rgba(31, 41, 55, 0.9)'; // dark text for glass effect
352
+ }
353
+
334
354
  titleEl.style.cssText = `
335
355
  font-size: ${16 * scaleFactor}px;
336
356
  font-weight: 500;
337
- color: ${styleMode === 'gradient' ? 'rgba(255, 255, 255, 0.95)' : '#6b7280'};
357
+ color: ${titleColor};
338
358
  margin-bottom: ${16 * scaleFactor}px;
339
359
  font-family: ${themeConfig.variables['--chart-font-family']};
340
360
  `;
@@ -352,10 +372,19 @@ export class KPICard {
352
372
  const valueEl = document.createElement('div');
353
373
  valueEl.className = 'jux-kpicard-value';
354
374
  valueEl.textContent = `${prefix}${value}${suffix}`;
375
+
376
+ // Smart color selection for value
377
+ let valueColor = '#1f2937'; // default dark
378
+ if (styleMode === 'gradient') {
379
+ valueColor = '#ffffff';
380
+ } else if (styleMode === 'glass') {
381
+ valueColor = '#111827'; // very dark for glass
382
+ }
383
+
355
384
  valueEl.style.cssText = `
356
385
  font-size: ${56 * scaleFactor}px;
357
386
  font-weight: 800;
358
- color: ${styleMode === 'gradient' ? '#ffffff' : '#1f2937'};
387
+ color: ${valueColor};
359
388
  line-height: 1;
360
389
  font-family: ${themeConfig.variables['--chart-font-family']};
361
390
  ${styleMode === 'glow' ? `text-shadow: 0 0 ${20 * scaleFactor}px ${themeConfig.colors[0]}40;` : ''}
@@ -391,12 +420,18 @@ export class KPICard {
391
420
  const arrow = this._createArrowSVG(delta > 0, styleMode === 'gradient', scaleFactor);
392
421
  deltaContainer.appendChild(arrow);
393
422
 
394
- // Delta text
423
+ // Delta text with smart coloring
395
424
  const deltaText = document.createElement('span');
396
425
  deltaText.textContent = `${delta > 0 ? '+' : ''}${delta}%`;
397
- const deltaColor = styleMode === 'gradient'
398
- ? (delta > 0 ? 'rgba(255, 255, 255, 0.95)' : 'rgba(255, 200, 200, 0.95)')
399
- : (delta > 0 ? '#10b981' : '#ef4444');
426
+
427
+ let deltaColor;
428
+ if (styleMode === 'gradient') {
429
+ deltaColor = delta > 0 ? 'rgba(255, 255, 255, 0.95)' : 'rgba(255, 200, 200, 0.95)';
430
+ } else if (styleMode === 'glass') {
431
+ deltaColor = delta > 0 ? '#10b981' : '#ef4444';
432
+ } else {
433
+ deltaColor = delta > 0 ? '#10b981' : '#ef4444';
434
+ }
400
435
 
401
436
  deltaText.style.cssText = `
402
437
  font-size: ${18 * scaleFactor}px;
@@ -18,7 +18,7 @@ export interface SidebarOptions {
18
18
  */
19
19
  type SidebarState = {
20
20
  title: string;
21
- width: string;
21
+ width: string | null;
22
22
  position: string;
23
23
  collapsible: boolean;
24
24
  collapsed: boolean;
@@ -49,7 +49,7 @@ export class Sidebar {
49
49
 
50
50
  this.state = {
51
51
  title: options.title ?? '',
52
- width: options.width ?? '300px',
52
+ width: options.width ?? null, // No default width - let CSS handle it
53
53
  position: options.position ?? 'left',
54
54
  collapsible: options.collapsible ?? false,
55
55
  collapsed: options.collapsed ?? false,
@@ -117,10 +117,14 @@ export class Sidebar {
117
117
 
118
118
  if (collapsed) {
119
119
  sidebar.classList.add('jux-sidebar-collapsed');
120
- sidebar.style.width = '0';
120
+ if (width) {
121
+ sidebar.style.width = '0';
122
+ }
121
123
  } else {
122
124
  sidebar.classList.remove('jux-sidebar-collapsed');
123
- sidebar.style.width = width;
125
+ if (width) {
126
+ sidebar.style.width = width;
127
+ }
124
128
  }
125
129
 
126
130
  const toggleBtn = sidebar.querySelector('.jux-sidebar-toggle');
@@ -152,7 +156,11 @@ export class Sidebar {
152
156
  const sidebar = document.createElement('aside');
153
157
  sidebar.className = `jux-sidebar jux-sidebar-${position}`;
154
158
  sidebar.id = this._id;
155
- sidebar.style.width = collapsed ? '0' : width;
159
+
160
+ // Only set width if explicitly provided
161
+ if (width) {
162
+ sidebar.style.width = collapsed ? '0' : width;
163
+ }
156
164
 
157
165
  if (className) {
158
166
  sidebar.className += ` ${className}`;
package/lib/jux.ts CHANGED
@@ -51,9 +51,10 @@ import { heading, Heading, type HeadingOptions } from './components/heading.js';
51
51
  import { paragraph, Paragraph, type ParagraphOptions } from './components/paragraph.js';
52
52
  import { barchart, BarChart, type BarChartOptions, type BarChartDataPoint } from './components/barchart.js';
53
53
  import { areachart, AreaChart, type AreaChartOptions, type AreaChartDataPoint } from './components/areachart.js';
54
- import { areachartsmooth, AreaChartSmooth, type AreaChartSmoothOptions, AreaChartSmoothDataPoint } from './components/areachartsmooth.js';
54
+ import { areachartsmooth, AreaChartSmooth, type AreaChartSmoothOptions, AreaChartSmoothDataPoint } from './components/areachartsmooth.js';
55
55
  import { doughnutchart, DoughnutChart, type DoughnutChartOptions, type DoughnutChartDataPoint } from './components/doughnutchart.js';
56
56
  import { kpicard, KPICard, type KPICardOptions } from './components/kpicard.js';
57
+ import { divider, Divider, type DividerOptions } from './components/divider.js';
57
58
 
58
59
  /* -------------------------
59
60
  * Type Exports
@@ -116,7 +117,8 @@ export type {
116
117
  AreaChartSmoothDataPoint,
117
118
  DoughnutChartOptions,
118
119
  DoughnutChartDataPoint,
119
- KPICardOptions
120
+ KPICardOptions,
121
+ DividerOptions
120
122
  };
121
123
 
122
124
  /* -------------------------
@@ -172,7 +174,8 @@ export {
172
174
  AreaChart,
173
175
  AreaChartSmooth,
174
176
  DoughnutChart,
175
- KPICard
177
+ KPICard,
178
+ Divider
176
179
  };
177
180
 
178
181
  /* -------------------------
@@ -234,6 +237,7 @@ export interface JuxAPI {
234
237
  areachartsmooth: typeof areachartsmooth;
235
238
  doughnutchart: typeof doughnutchart;
236
239
  kpicard: typeof kpicard;
240
+ divider: typeof divider;
237
241
  }
238
242
 
239
243
  /* -------------------------
@@ -311,8 +315,8 @@ class Jux implements JuxAPI {
311
315
  areachartsmooth = areachartsmooth;
312
316
  doughnutchart = doughnutchart;
313
317
  kpicard = kpicard;
318
+ divider = divider;
314
319
  }
315
-
316
320
  /**
317
321
  * Global jux singleton instance
318
322
  */
@@ -2,6 +2,23 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import esbuild from 'esbuild';
4
4
 
5
+ /**
6
+ * Generate import map script tag
7
+ */
8
+ function generateImportMapScript() {
9
+ return `<script type="importmap">
10
+ {
11
+ "imports": {
12
+ "juxscript": "./lib/jux.js",
13
+ "juxscript/": "./lib/",
14
+ "juxscript/reactivity": "./lib/reactivity/state.js",
15
+ "juxscript/presets/": "./lib/presets/",
16
+ "juxscript/components/": "./lib/components/"
17
+ }
18
+ }
19
+ </script>`;
20
+ }
21
+
5
22
  /**
6
23
  * Compile a .jux file to .js and .html
7
24
  *
@@ -36,7 +53,6 @@ export async function compileJuxFile(juxFilePath, options = {}) {
36
53
  // Calculate depth for relative paths
37
54
  const depth = parsedPath.dir.split(path.sep).filter(p => p).length;
38
55
  const libPath = depth === 0 ? './lib/jux.js' : '../'.repeat(depth) + 'lib/jux.js';
39
- const styleBasePath = depth === 0 ? './lib/presets/' : '../'.repeat(depth) + 'lib/presets/';
40
56
 
41
57
  // Transform imports
42
58
  let transformedContent = juxContent;
@@ -68,8 +84,9 @@ export async function compileJuxFile(juxFilePath, options = {}) {
68
84
 
69
85
  console.log(` ✓ JS: ${path.relative(projectRoot, jsOutputPath)}`);
70
86
 
71
- // Generate HTML with correct script path
87
+ // Generate HTML with import map and correct script path
72
88
  const scriptPath = `./${parsedPath.name}.js`;
89
+ const importMapScript = generateImportMapScript();
73
90
 
74
91
  const html = `<!DOCTYPE html>
75
92
  <html lang="en">
@@ -77,12 +94,12 @@ export async function compileJuxFile(juxFilePath, options = {}) {
77
94
  <meta charset="UTF-8">
78
95
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
79
96
  <title>${parsedPath.name}</title>
80
- <!-- JUX Core Styles -->
81
- <link rel="stylesheet" href="${styleBasePath}global.css">
82
97
  </head>
83
98
  <body data-theme="">
84
99
  <!-- App container -->
85
100
  <div id="app" data-jux-page="${parsedPath.name}"></div>
101
+
102
+ ${importMapScript}
86
103
  <script type="module" src="${scriptPath}"></script>
87
104
  ${isServe ? `
88
105
  <!-- Hot reload -->
@@ -12,7 +12,7 @@ const __dirname = path.dirname(__filename);
12
12
 
13
13
  let db = null;
14
14
 
15
- async function serve(port = 3000, distDir = './jux-dist') { // Changed default
15
+ async function serve(port = 3000, distDir = './jux-dist') {
16
16
  const app = express();
17
17
  const absoluteDistDir = path.resolve(distDir);
18
18
  const projectRoot = path.resolve('.');
@@ -29,14 +29,14 @@ async function serve(port = 3000, distDir = './jux-dist') { // Changed default
29
29
  app.post('/api/query', async (req, res) => {
30
30
  try {
31
31
  const { sql, params = [] } = req.body;
32
-
32
+
33
33
  if (!db) {
34
34
  return res.status(500).json({ error: 'Database not initialized' });
35
35
  }
36
36
 
37
37
  const stmt = db.prepare(sql);
38
38
  stmt.bind(params);
39
-
39
+
40
40
  const rows = [];
41
41
  while (stmt.step()) {
42
42
  rows.push(stmt.getAsObject());
@@ -61,16 +61,20 @@ async function serve(port = 3000, distDir = './jux-dist') { // Changed default
61
61
  // Serve HTML files with clean URLs
62
62
  const heyPath = path.join(absoluteDistDir, 'hey.html');
63
63
  const indexPath = path.join(absoluteDistDir, 'index.html');
64
-
64
+
65
65
  app.use((req, res, next) => {
66
- let requestPath = req.path.endsWith('/') && req.path.length > 1
67
- ? req.path.slice(0, -1)
66
+ let requestPath = req.path.endsWith('/') && req.path.length > 1
67
+ ? req.path.slice(0, -1)
68
68
  : req.path;
69
69
 
70
70
  // Root path - serve hey.html or index.html
71
71
  if (requestPath === '/') {
72
- if (fs.existsSync(heyPath)) return res.sendFile(heyPath);
73
- if (fs.existsSync(indexPath)) return res.sendFile(indexPath);
72
+ if (fs.existsSync(heyPath)) {
73
+ return res.sendFile(heyPath);
74
+ }
75
+ if (fs.existsSync(indexPath)) {
76
+ return res.sendFile(indexPath);
77
+ }
74
78
  }
75
79
 
76
80
  // Try to serve as HTML file
@@ -87,7 +91,7 @@ async function serve(port = 3000, distDir = './jux-dist') { // Changed default
87
91
 
88
92
  next();
89
93
  });
90
-
94
+
91
95
  // Serve static files (CSS, JS, images, etc.)
92
96
  app.use(express.static(absoluteDistDir));
93
97
 
@@ -96,25 +100,24 @@ async function serve(port = 3000, distDir = './jux-dist') { // Changed default
96
100
  const notFoundPath = path.join(absoluteDistDir, '404.html');
97
101
  const requestedPath = path.join(absoluteDistDir, req.path);
98
102
  const fileType = path.extname(req.path) || 'directory';
99
-
103
+
100
104
  // Log to console for debugging
101
105
  console.log(`❌ 404: ${req.path}`);
102
106
  console.log(` Looked for: ${requestedPath}`);
103
107
  console.log(` Type: ${fileType}`);
104
108
  console.log(` Referer: ${req.get('referer') || 'direct'}`);
105
-
109
+
106
110
  // If custom 404.html exists and this isn't already /404
107
111
  if (fs.existsSync(notFoundPath) && req.path !== '/404') {
108
- // Add debug info as query params
109
112
  const debugUrl = `/404?path=${encodeURIComponent(req.path)}&type=${fileType}&from=${encodeURIComponent(req.get('referer') || 'direct')}`;
110
113
  return res.redirect(debugUrl);
111
114
  }
112
-
115
+
113
116
  // Serve custom 404 page
114
117
  if (fs.existsSync(notFoundPath)) {
115
118
  return res.status(404).sendFile(notFoundPath);
116
119
  }
117
-
120
+
118
121
  // Fallback: minimal 404 response
119
122
  res.status(404).send('<h1>404 - Not Found</h1>');
120
123
  });
@@ -175,7 +178,7 @@ async function serve(port = 3000, distDir = './jux-dist') { // Changed default
175
178
  async function initDatabase() {
176
179
  const SQL = await initSqlJs();
177
180
  const dbPath = path.join(__dirname, '../db/jux.db');
178
-
181
+
179
182
  if (fs.existsSync(dbPath)) {
180
183
  const buffer = fs.readFileSync(dbPath);
181
184
  db = new SQL.Database(buffer);
@@ -188,5 +191,5 @@ async function initDatabase() {
188
191
 
189
192
  export async function start(port = 3000) {
190
193
  await initDatabase();
191
- return serve(port, './jux-dist'); // Changed default
194
+ return serve(port, './jux-dist');
192
195
  }
package/package.json CHANGED
@@ -1,84 +1,66 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",
7
7
  "types": "lib/jux.d.ts",
8
+ "access": "public",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/jux/juxscript.git"
12
+ },
8
13
  "exports": {
9
14
  ".": {
10
15
  "types": "./lib/jux.d.ts",
11
16
  "import": "./lib/jux.js",
12
17
  "default": "./lib/jux.js"
13
18
  },
14
- "./lib/*": "./lib/*",
15
- "./lib/components/*": "./lib/components/*",
19
+ "./reactivity": {
20
+ "types": "./lib/reactivity/index.d.ts",
21
+ "import": "./lib/reactivity/index.js",
22
+ "default": "./lib/reactivity/index.js"
23
+ },
24
+ "./components/*": "./lib/components/*/index.js",
25
+ "./presets/*": "./presets/*.*",
16
26
  "./package.json": "./package.json"
17
27
  },
18
- "typesVersions": {
19
- "*": {
20
- "*": [
21
- "lib/*"
22
- ],
23
- "lib/*": [
24
- "lib/*"
25
- ],
26
- "lib/components/*": [
27
- "lib/components/*"
28
- ]
29
- }
30
- },
31
- "bin": {
32
- "jux": "./bin/cli.js"
33
- },
34
- "scripts": {
35
- "dev": "cd examples && npx jux serve",
36
- "build:examples": "cd examples && rm -rf dist && npx jux build",
37
- "build": "tsc",
38
- "test": "node test/run-tests.js",
39
- "generate:icons": "node scripts/generate-icon-types.js"
40
- },
41
28
  "files": [
42
29
  "lib",
43
30
  "bin",
44
31
  "machinery",
45
32
  "types",
46
- "lib/**/*.d.ts",
47
33
  "README.md",
48
34
  "LICENSE"
49
35
  ],
50
- "publishConfig": {
51
- "access": "public"
52
- },
53
- "repository": {
54
- "type": "git",
55
- "url": "https://github.com/juxscript/jux.git"
36
+ "bin": {
37
+ "jux": "./bin/cli.js"
56
38
  },
57
39
  "keywords": [
58
- "jux",
59
40
  "ui",
60
- "authoring",
41
+ "ux",
42
+ "components",
43
+ "framework",
44
+ "reactive",
61
45
  "javascript"
62
46
  ],
63
- "author": "Tim Kerr",
64
- "license": "MIT",
47
+ "scripts": {
48
+ "build": "tsc",
49
+ "dev": "tsc --watch",
50
+ "prepublishOnly": "npm run build"
51
+ },
65
52
  "dependencies": {
66
- "acorn": "^8.15.0",
67
- "chokidar": "^5.0.0",
68
- "clean-css": "^5.3.3",
69
- "esbuild": "^0.27.2",
70
- "express": "^5.2.1",
53
+ "chokidar": "^3.5.3",
54
+ "esbuild": "^0.19.0",
55
+ "express": "^4.18.2",
71
56
  "glob": "^13.0.0",
72
- "node": "^24.12.0",
73
- "sql.js": "^1.10.3",
74
- "terser": "^5.44.1",
75
- "ws": "^8.19.0"
76
- },
77
- "optionalDependencies": {
78
- "mysql2": "^3.6.5",
79
- "pg": "^8.11.3"
57
+ "sql.js": "^1.8.0",
58
+ "ws": "^8.13.0"
80
59
  },
81
60
  "devDependencies": {
82
- "typescript": "^5.9.3"
61
+ "@types/express": "^4.17.17",
62
+ "@types/node": "^20.0.0",
63
+ "@types/ws": "^8.5.5",
64
+ "typescript": "^5.0.0"
83
65
  }
84
66
  }