juxscript 1.0.59 → 1.0.61
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/README.md +32 -1
- package/bin/cli.js +13 -33
- package/docs/grid.png +0 -0
- package/juxconfig.example.js +24 -2
- package/lib/components/base/BaseComponent.ts +20 -0
- package/lib/components/chart.ts +231 -0
- package/lib/components/container.ts +76 -118
- package/lib/components/grid.ts +291 -0
- package/lib/components/input.ts +55 -1
- package/lib/jux.ts +10 -29
- package/lib/utils/fetch.ts +553 -0
- package/machinery/ast.js +347 -0
- package/machinery/bundleAssets.js +0 -0
- package/machinery/bundleJux.js +0 -0
- package/machinery/bundleVendors.js +0 -0
- package/machinery/compiler.js +427 -151
- package/machinery/server.js +16 -2
- package/machinery/ts-shim.js +46 -0
- package/package.json +5 -1
- package/presets/default/all.jux +2 -2
- package/presets/default/layout.css +120 -0
- package/lib/components/charts/areachart.ts +0 -315
- package/lib/components/charts/barchart.ts +0 -421
- package/lib/components/charts/doughnutchart.ts +0 -263
- package/lib/components/charts/lib/BaseChart.ts +0 -389
- package/lib/components/charts/lib/chart-types.ts +0 -159
- package/lib/components/charts/lib/chart-utils.ts +0 -160
- package/lib/components/charts/lib/chart.ts +0 -707
- package/lib/components/charts/lib/charts.js +0 -126
- package/lib/components/docs-data.json +0 -2075
- package/lib/components/kpicard.ts +0 -640
package/README.md
CHANGED
|
@@ -1,4 +1,35 @@
|
|
|
1
|
-
#
|
|
1
|
+
# JUX - JavaScript UX Authorship Platform
|
|
2
|
+
|
|
3
|
+
> Say goodbye to markup `</</>>>`. Build reactive UIs with pure JavaScript.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Start
|
|
6
|
+
|
|
7
|
+
### Option 1: Create New Project (Recommended)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx jux create my-app
|
|
11
|
+
cd my-app
|
|
12
|
+
npm run dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Option 2: Add to Existing Project
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Install juxscript first
|
|
19
|
+
npm install juxscript
|
|
20
|
+
|
|
21
|
+
# Then initialize
|
|
22
|
+
npx jux init
|
|
23
|
+
|
|
24
|
+
# Start dev server
|
|
25
|
+
npx jux serve
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Why Install First?
|
|
29
|
+
|
|
30
|
+
JUX uses `esbuild`, `express`, and other tools that must be installed before the CLI can run. When you run `npm install juxscript`, these dependencies are automatically installed.
|
|
31
|
+
|
|
32
|
+
## The web needs a higher level of abstraction: Meet **JUX**
|
|
2
33
|
|
|
3
34
|
|
|
4
35
|
```diff
|
package/bin/cli.js
CHANGED
|
@@ -215,6 +215,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
215
215
|
fs.mkdirSync(PATHS.frontendDist, { recursive: true });
|
|
216
216
|
|
|
217
217
|
// Step 1: Generate documentation
|
|
218
|
+
/*
|
|
218
219
|
const docsStartTime = performance.now();
|
|
219
220
|
let docsTime = 0; // ✅ Declare with default value
|
|
220
221
|
console.log('📚 Generating documentation...');
|
|
@@ -226,6 +227,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
226
227
|
docsTime = performance.now() - docsStartTime; // ✅ Still calculate time even on error
|
|
227
228
|
console.warn(`⚠️ Failed to generate docs (${docsTime.toFixed(0)}ms):`, error.message);
|
|
228
229
|
}
|
|
230
|
+
*/
|
|
229
231
|
|
|
230
232
|
// Step 2: Copy jux lib to frontend dist
|
|
231
233
|
const libStartTime = performance.now();
|
|
@@ -262,47 +264,24 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
262
264
|
|
|
263
265
|
// ✅ Bundle and get the generated filename
|
|
264
266
|
const bundleStartTime = performance.now();
|
|
265
|
-
const
|
|
267
|
+
const bundleResult = await bundleJuxFilesToRouter(PATHS.juxSource, PATHS.frontendDist, {
|
|
266
268
|
routePrefix: ''
|
|
267
269
|
});
|
|
268
270
|
const bundleTime = performance.now() - bundleStartTime;
|
|
269
271
|
|
|
270
|
-
// Generate routes for index.html
|
|
271
|
-
const routes = projectJuxFiles.map(juxFile => {
|
|
272
|
-
const relativePath = path.relative(PATHS.juxSource, juxFile);
|
|
273
|
-
const parsedPath = path.parse(relativePath);
|
|
274
|
-
|
|
275
|
-
const rawFunctionName = parsedPath.dir
|
|
276
|
-
? `${parsedPath.dir.replace(/\//g, '_')}_${parsedPath.name}`
|
|
277
|
-
: parsedPath.name;
|
|
278
|
-
|
|
279
|
-
const functionName = rawFunctionName
|
|
280
|
-
.replace(/[-_]/g, ' ')
|
|
281
|
-
.split(' ')
|
|
282
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
283
|
-
.join('');
|
|
284
|
-
|
|
285
|
-
const routePath = '/' + (parsedPath.dir ? `${parsedPath.dir}/` : '') + parsedPath.name;
|
|
286
|
-
|
|
287
|
-
return {
|
|
288
|
-
path: routePath.replace(/\/+/g, '/'),
|
|
289
|
-
functionName
|
|
290
|
-
};
|
|
291
|
-
});
|
|
292
|
-
|
|
293
272
|
// ✅ Generate unified index.html
|
|
294
273
|
const indexStartTime = performance.now();
|
|
295
|
-
generateIndexHtml(PATHS.frontendDist,
|
|
274
|
+
generateIndexHtml(PATHS.frontendDist, bundleResult);
|
|
296
275
|
const indexTime = performance.now() - indexStartTime;
|
|
297
276
|
|
|
298
277
|
const totalBuildTime = performance.now() - buildStartTime;
|
|
299
278
|
|
|
300
|
-
|
|
279
|
+
// ✅ FIX: Use bundleResult.mainJsFilename instead of bare mainJsFilename
|
|
280
|
+
console.log(`\n✅ Bundled ${projectJuxFiles.length} page(s) → ${PATHS.frontendDist}/${bundleResult.mainJsFilename}\n`);
|
|
301
281
|
|
|
302
282
|
// ✅ Build summary with timing breakdown
|
|
303
283
|
console.log(`📊 Build Summary:`);
|
|
304
284
|
console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
305
|
-
console.log(` Documentation: ${docsTime.toFixed(0)}ms`);
|
|
306
285
|
console.log(` Library copy: ${libTime.toFixed(0)}ms`);
|
|
307
286
|
console.log(` Presets copy: ${presetsTime.toFixed(0)}ms`);
|
|
308
287
|
console.log(` Assets copy: ${assetsTime.toFixed(0)}ms`);
|
|
@@ -320,7 +299,8 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
320
299
|
console.log(` FastAPI: app.mount("/", StaticFiles(directory="jux-dist"), name="static")`);
|
|
321
300
|
console.log('');
|
|
322
301
|
console.log('📍 Available routes:');
|
|
323
|
-
routes
|
|
302
|
+
// ✅ FIX: Use bundleResult.routes instead of local routes variable
|
|
303
|
+
bundleResult.routes.forEach(r => {
|
|
324
304
|
console.log(` ${r.path}`);
|
|
325
305
|
});
|
|
326
306
|
console.log('');
|
|
@@ -350,10 +330,10 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
350
330
|
|
|
351
331
|
// ✅ Copy presets/default/ directly to jux/ (no presets subfolder!)
|
|
352
332
|
const defaultPresetSrc = path.join(PATHS.packageRoot, 'presets', 'default');
|
|
353
|
-
|
|
333
|
+
|
|
354
334
|
if (fs.existsSync(defaultPresetSrc)) {
|
|
355
335
|
console.log('📦 Copying default preset boilerplate...');
|
|
356
|
-
|
|
336
|
+
|
|
357
337
|
const entries = fs.readdirSync(defaultPresetSrc, { withFileTypes: true });
|
|
358
338
|
let copiedCount = 0;
|
|
359
339
|
|
|
@@ -401,7 +381,7 @@ jux.paragraph('counter')
|
|
|
401
381
|
const pkgPath = path.join(PATHS.projectRoot, 'package.json');
|
|
402
382
|
if (!fs.existsSync(pkgPath)) {
|
|
403
383
|
const projectName = path.basename(PATHS.projectRoot).toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
404
|
-
|
|
384
|
+
|
|
405
385
|
const pkgContent = {
|
|
406
386
|
"name": projectName,
|
|
407
387
|
"version": "0.1.0",
|
|
@@ -429,14 +409,14 @@ jux.paragraph('counter')
|
|
|
429
409
|
|
|
430
410
|
// ✅ Copy juxconfig to root
|
|
431
411
|
const configExampleSrc = path.join(PATHS.packageRoot, 'juxconfig.example.js');
|
|
432
|
-
|
|
412
|
+
|
|
433
413
|
if (fs.existsSync(configExampleSrc)) {
|
|
434
414
|
const configDest = path.join(PATHS.projectRoot, 'juxconfig.js');
|
|
435
415
|
if (!fs.existsSync(configDest)) {
|
|
436
416
|
fs.copyFileSync(configExampleSrc, configDest);
|
|
437
417
|
console.log('+ Created juxconfig.js');
|
|
438
418
|
}
|
|
439
|
-
|
|
419
|
+
|
|
440
420
|
// ❌ REMOVE: Don't copy example - it's available in node_modules
|
|
441
421
|
// const configExampleDest = path.join(PATHS.projectRoot, 'juxconfig.example.js');
|
|
442
422
|
// if (!fs.existsSync(configExampleDest)) {
|
package/docs/grid.png
ADDED
|
Binary file
|
package/juxconfig.example.js
CHANGED
|
@@ -16,12 +16,21 @@ export default {
|
|
|
16
16
|
// Application settings
|
|
17
17
|
appName: 'My JUX App',
|
|
18
18
|
autoRoutes: true,
|
|
19
|
-
|
|
20
19
|
// Directories
|
|
21
20
|
sourceDir: 'jux',
|
|
22
21
|
distDir: '.jux-dist',
|
|
23
22
|
|
|
24
|
-
// External services
|
|
23
|
+
// External services - used by jux.fetch and jux.fetch.serviceClient()
|
|
24
|
+
// Each service becomes available as a baseUrl for fetch requests
|
|
25
|
+
//
|
|
26
|
+
// Usage in components:
|
|
27
|
+
// const db = jux.fetch.serviceClient('database');
|
|
28
|
+
// const { data } = await db.fetch('/users').send();
|
|
29
|
+
//
|
|
30
|
+
// // Or directly:
|
|
31
|
+
// const { data } = await jux.fetch('/users')
|
|
32
|
+
// .header('Authorization', 'Bearer token')
|
|
33
|
+
// .send();
|
|
25
34
|
services: {
|
|
26
35
|
database: 'http://localhost:4000/api/db',
|
|
27
36
|
auth: 'http://localhost:4000/api/auth',
|
|
@@ -34,6 +43,10 @@ export default {
|
|
|
34
43
|
ws: 3001
|
|
35
44
|
},
|
|
36
45
|
|
|
46
|
+
// Fetch configuration
|
|
47
|
+
fetchTimeout: 30000, // milliseconds
|
|
48
|
+
fetchLog: false, // true, false, or 'errors' for error-only logging
|
|
49
|
+
|
|
37
50
|
// Build options
|
|
38
51
|
build: {
|
|
39
52
|
minify: false,
|
|
@@ -41,9 +54,18 @@ export default {
|
|
|
41
54
|
},
|
|
42
55
|
|
|
43
56
|
// Bootstrap functions (run on startup)
|
|
57
|
+
// Perfect place to initialize services and auth
|
|
44
58
|
bootstrap: [
|
|
59
|
+
// Example: Setup fetch with service configuration
|
|
60
|
+
// async function initFetch() {
|
|
61
|
+
// await jux.fetch.setupConfig();
|
|
62
|
+
// console.log('✅ Fetch configured');
|
|
63
|
+
// },
|
|
64
|
+
|
|
65
|
+
// Example: Initialize authentication
|
|
45
66
|
// async function initAuth() {
|
|
46
67
|
// console.log('🔐 Initializing auth...');
|
|
47
68
|
// },
|
|
48
69
|
]
|
|
49
70
|
};
|
|
71
|
+
|
|
@@ -394,4 +394,24 @@ export abstract class BaseComponent<TState extends Record<string, any>> {
|
|
|
394
394
|
}
|
|
395
395
|
return this.render(`#${juxComponent._id}`);
|
|
396
396
|
}
|
|
397
|
+
|
|
398
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
399
|
+
* PROPS ACCESSOR - Read-only access to component state
|
|
400
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* ✅ Read-only accessor for component state
|
|
404
|
+
* Provides clear separation between setters (fluent methods) and getters
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* const myCard = card('example')
|
|
408
|
+
* .title('Hello') // ✅ SETTER (fluent)
|
|
409
|
+
* .content('World'); // ✅ SETTER (fluent)
|
|
410
|
+
*
|
|
411
|
+
* console.log(myCard.props.title); // ✅ GETTER: 'Hello'
|
|
412
|
+
* console.log(myCard.props.content); // ✅ GETTER: 'World'
|
|
413
|
+
*/
|
|
414
|
+
get props(): Readonly<TState> {
|
|
415
|
+
return this.state as Readonly<TState>;
|
|
416
|
+
}
|
|
397
417
|
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { BaseComponent } from './base/BaseComponent.js';
|
|
2
|
+
import { Chart, ChartConfiguration, ChartType, ChartOptions, ChartData } from 'chart.js/auto';
|
|
3
|
+
|
|
4
|
+
// filepath: /Users/timkerr/newprojects2025/compile-sqljs/packages/jux/lib/components/chart.ts
|
|
5
|
+
|
|
6
|
+
export interface ChartState {
|
|
7
|
+
type: ChartType;
|
|
8
|
+
data: ChartData;
|
|
9
|
+
options: ChartOptions;
|
|
10
|
+
style?: string;
|
|
11
|
+
class?: string;
|
|
12
|
+
visible?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
loading?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Chart component using Chart.js
|
|
19
|
+
* Provides fluent API for creating and configuring charts
|
|
20
|
+
*/
|
|
21
|
+
export class ChartComponent extends BaseComponent<ChartState> {
|
|
22
|
+
private static readonly TRIGGER_EVENTS: readonly string[] = [];
|
|
23
|
+
private static readonly CALLBACK_EVENTS: readonly string[] = ['render', 'update', 'destroy'];
|
|
24
|
+
private chartInstance: Chart | null = null;
|
|
25
|
+
|
|
26
|
+
constructor(id: string, type: ChartType = 'bar', options?: Partial<ChartState>) {
|
|
27
|
+
super(id, {
|
|
28
|
+
type,
|
|
29
|
+
data: { datasets: [] },
|
|
30
|
+
options: {},
|
|
31
|
+
...options
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected getTriggerEvents(): readonly string[] {
|
|
36
|
+
return ChartComponent.TRIGGER_EVENTS;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protected getCallbackEvents(): readonly string[] {
|
|
40
|
+
return ChartComponent.CALLBACK_EVENTS;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
44
|
+
* CHART TYPE
|
|
45
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
46
|
+
|
|
47
|
+
type(value: ChartType): this {
|
|
48
|
+
this.state.type = value;
|
|
49
|
+
if (this.chartInstance) {
|
|
50
|
+
// Destroy and recreate chart with new type
|
|
51
|
+
const canvas = this.chartInstance.canvas;
|
|
52
|
+
this.chartInstance.destroy();
|
|
53
|
+
const config: ChartConfiguration = {
|
|
54
|
+
type: this.state.type,
|
|
55
|
+
data: this.state.data,
|
|
56
|
+
options: this.state.options
|
|
57
|
+
};
|
|
58
|
+
this.chartInstance = new Chart(canvas, config);
|
|
59
|
+
}
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
64
|
+
* DATA CONFIGURATION
|
|
65
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
66
|
+
|
|
67
|
+
data(value: ChartData): this {
|
|
68
|
+
this.state.data = value;
|
|
69
|
+
if (this.chartInstance) {
|
|
70
|
+
this.chartInstance.data = value;
|
|
71
|
+
this.chartInstance.update();
|
|
72
|
+
}
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
labels(value: string[]): this {
|
|
77
|
+
this.state.data.labels = value;
|
|
78
|
+
if (this.chartInstance) {
|
|
79
|
+
this.chartInstance.data.labels = value;
|
|
80
|
+
this.chartInstance.update();
|
|
81
|
+
}
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
datasets(value: any[]): this {
|
|
86
|
+
this.state.data.datasets = value;
|
|
87
|
+
if (this.chartInstance) {
|
|
88
|
+
this.chartInstance.data.datasets = value;
|
|
89
|
+
this.chartInstance.update();
|
|
90
|
+
}
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
addDataset(dataset: any): this {
|
|
95
|
+
this.state.data.datasets.push(dataset);
|
|
96
|
+
if (this.chartInstance) {
|
|
97
|
+
this.chartInstance.data.datasets.push(dataset);
|
|
98
|
+
this.chartInstance.update();
|
|
99
|
+
}
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
104
|
+
* OPTIONS CONFIGURATION
|
|
105
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
106
|
+
|
|
107
|
+
options(value: ChartOptions): this {
|
|
108
|
+
this.state.options = { ...this.state.options, ...value };
|
|
109
|
+
if (this.chartInstance) {
|
|
110
|
+
this.chartInstance.options = this.state.options;
|
|
111
|
+
this.chartInstance.update();
|
|
112
|
+
}
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
responsive(value: boolean = true): this {
|
|
117
|
+
return this.options({ responsive: value });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
maintainAspectRatio(value: boolean = true): this {
|
|
121
|
+
return this.options({ maintainAspectRatio: value });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
title(text: string, display: boolean = true): this {
|
|
125
|
+
return this.options({
|
|
126
|
+
plugins: {
|
|
127
|
+
...this.state.options.plugins,
|
|
128
|
+
title: { display, text }
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
legend(display: boolean = true, position: 'top' | 'bottom' | 'left' | 'right' = 'top'): this {
|
|
134
|
+
return this.options({
|
|
135
|
+
plugins: {
|
|
136
|
+
...this.state.options.plugins,
|
|
137
|
+
legend: { display, position }
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
tooltip(enabled: boolean = true): this {
|
|
143
|
+
return this.options({
|
|
144
|
+
plugins: {
|
|
145
|
+
...this.state.options.plugins,
|
|
146
|
+
tooltip: { enabled }
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
152
|
+
* CHART OPERATIONS
|
|
153
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
154
|
+
|
|
155
|
+
update(mode?: 'resize' | 'reset' | 'none' | 'hide' | 'show' | 'default'): this {
|
|
156
|
+
if (this.chartInstance) {
|
|
157
|
+
this.chartInstance.update(mode);
|
|
158
|
+
this._triggerCallback('update', this.chartInstance);
|
|
159
|
+
}
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
reset(): this {
|
|
164
|
+
if (this.chartInstance) {
|
|
165
|
+
this.chartInstance.reset();
|
|
166
|
+
}
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
destroy(): this {
|
|
171
|
+
if (this.chartInstance) {
|
|
172
|
+
this.chartInstance.destroy();
|
|
173
|
+
this.chartInstance = null;
|
|
174
|
+
this._triggerCallback('destroy');
|
|
175
|
+
}
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
getChart(): Chart | null {
|
|
180
|
+
return this.chartInstance;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
184
|
+
* RENDER
|
|
185
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
186
|
+
|
|
187
|
+
render(targetId?: string): this {
|
|
188
|
+
const container = this._setupContainer(targetId);
|
|
189
|
+
|
|
190
|
+
// Destroy existing chart
|
|
191
|
+
if (this.chartInstance) {
|
|
192
|
+
this.chartInstance.destroy();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Clear container and create canvas
|
|
196
|
+
container.innerHTML = '';
|
|
197
|
+
const canvas = document.createElement('canvas');
|
|
198
|
+
canvas.id = `${this._id}-canvas`;
|
|
199
|
+
|
|
200
|
+
// Apply styles and classes
|
|
201
|
+
if (this.state.style) container.setAttribute('style', this.state.style);
|
|
202
|
+
if (this.state.class) container.className = this.state.class;
|
|
203
|
+
if (this.state.visible === false) container.style.display = 'none';
|
|
204
|
+
|
|
205
|
+
container.appendChild(canvas);
|
|
206
|
+
|
|
207
|
+
// Create Chart.js instance
|
|
208
|
+
const config: ChartConfiguration = {
|
|
209
|
+
type: this.state.type,
|
|
210
|
+
data: this.state.data,
|
|
211
|
+
options: this.state.options
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
this.chartInstance = new Chart(canvas, config);
|
|
215
|
+
|
|
216
|
+
// Wire events and syncs
|
|
217
|
+
this._wireStandardEvents(container);
|
|
218
|
+
this._wireAllSyncs();
|
|
219
|
+
|
|
220
|
+
this._triggerCallback('render', this.chartInstance);
|
|
221
|
+
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Factory function for creating chart components
|
|
228
|
+
*/
|
|
229
|
+
export function chart(id: string, type: ChartType = 'bar', options?: Partial<ChartState>): ChartComponent {
|
|
230
|
+
return new ChartComponent(id, type, options);
|
|
231
|
+
}
|