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
@@ -1,1128 +0,0 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
3
- import {
4
- ChartDataPoint,
5
- BarAreaChartOptions,
6
- BarAreaChartState,
7
- ChartTheme,
8
- ChartStyleMode,
9
- ChartOrientation,
10
- ChartDirection,
11
- LegendOrientation,
12
- ChartPropertyMapping,
13
- ChartStateObject
14
- } from './chart-types.js';
15
- import {
16
- lightenColor,
17
- getThemeConfig,
18
- createLegend,
19
- createDataTable,
20
- applyThemeStyles
21
- } from './chart-utils.js';
22
-
23
- // Re-export for convenience
24
- export type { ChartDataPoint as AreaChartDataPoint };
25
- export type { BarAreaChartOptions as AreaChartOptions };
26
-
27
- /**
28
- * Area chart component - Simple SVG-based area chart
29
- */
30
- export class AreaChart {
31
- state: BarAreaChartState;
32
- container: HTMLElement | null = null;
33
- _id: string;
34
- id: string;
35
-
36
- constructor(id: string, options: BarAreaChartOptions = {}) {
37
- this._id = id;
38
- this.id = id;
39
-
40
- let defaultColors = [
41
- '#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
42
- '#ec4899', '#06b6d4', '#f97316', '#84cc16', '#6366f1'
43
- ];
44
- const randomColor = defaultColors[Math.floor(Math.random() * defaultColors.length)];
45
- defaultColors = [randomColor];
46
-
47
- this.state = {
48
- data: options.data ?? [],
49
- title: options.title ?? '',
50
- subtitle: options.subtitle ?? '',
51
- xAxisLabel: options.xAxisLabel ?? '',
52
- yAxisLabel: options.yAxisLabel ?? '',
53
- showTicksX: options.showTicksX ?? true,
54
- showTicksY: options.showTicksY ?? true,
55
- showScaleX: options.showScaleX ?? true,
56
- showScaleY: options.showScaleY ?? true,
57
- scaleXUnit: options.scaleXUnit ?? '',
58
- scaleYUnit: options.scaleYUnit ?? '',
59
- showLegend: options.showLegend ?? false,
60
- legendOrientation: options.legendOrientation ?? 'horizontal',
61
- showDataTable: options.showDataTable ?? false,
62
- showDataLabels: options.showDataLabels ?? true,
63
- animate: options.animate ?? true,
64
- animationDuration: options.animationDuration ?? 800,
65
- chartOrientation: options.chartOrientation ?? 'vertical',
66
- chartDirection: options.chartDirection ?? 'normal',
67
- width: options.width ?? 600,
68
- height: options.height ?? 400,
69
- colors: options.colors ?? defaultColors,
70
- class: options.class ?? '',
71
- style: options.style ?? '',
72
- theme: options.theme,
73
- styleMode: options.styleMode ?? 'default',
74
- borderRadius: options.borderRadius ?? 4
75
- };
76
- }
77
-
78
- /* -------------------------
79
- * Fluent API
80
- * ------------------------- */
81
-
82
- data(value: ChartDataPoint[]): this {
83
- this.state.data = value;
84
- this._updateChart();
85
- return this;
86
- }
87
-
88
- title(value: string): this {
89
- this.state.title = value;
90
- this._updateChart();
91
- return this;
92
- }
93
-
94
- subtitle(value: string): this {
95
- this.state.subtitle = value;
96
- this._updateChart();
97
- return this;
98
- }
99
-
100
- xAxisLabel(value: string): this {
101
- this.state.xAxisLabel = value;
102
- this._updateChart();
103
- return this;
104
- }
105
-
106
- yAxisLabel(value: string): this {
107
- this.state.yAxisLabel = value;
108
- this._updateChart();
109
- return this;
110
- }
111
-
112
- showTicksX(value: boolean): this {
113
- this.state.showTicksX = value;
114
- this._updateChart();
115
- return this;
116
- }
117
-
118
- showTicksY(value: boolean): this {
119
- this.state.showTicksY = value;
120
- this._updateChart();
121
- return this;
122
- }
123
-
124
- showLegend(value: boolean): this {
125
- this.state.showLegend = value;
126
- this._updateChart();
127
- return this;
128
- }
129
-
130
- legendOrientation(value: LegendOrientation): this {
131
- this.state.legendOrientation = value;
132
- this._updateChart();
133
- return this;
134
- }
135
-
136
- showDataTable(value: boolean): this {
137
- this.state.showDataTable = value;
138
- this._updateChart();
139
- return this;
140
- }
141
-
142
- showDataLabels(value: boolean): this {
143
- this.state.showDataLabels = value;
144
- this._updateChart();
145
- return this;
146
- }
147
-
148
- animate(value: boolean): this {
149
- this.state.animate = value;
150
- this._updateChart();
151
- return this;
152
- }
153
-
154
- animationDuration(value: number): this {
155
- this.state.animationDuration = value;
156
- this._updateChart();
157
- return this;
158
- }
159
-
160
- chartOrientation(value: ChartOrientation): this {
161
- this.state.chartOrientation = value;
162
- this._updateChart();
163
- return this;
164
- }
165
-
166
- chartDirection(value: ChartDirection): this {
167
- this.state.chartDirection = value;
168
- this._updateChart();
169
- return this;
170
- }
171
-
172
- width(value: number): this {
173
- this.state.width = value;
174
- this._updateChart();
175
- return this;
176
- }
177
-
178
- height(value: number): this {
179
- this.state.height = value;
180
- this._updateChart();
181
- return this;
182
- }
183
-
184
- theme(value: ChartTheme): this {
185
- this.state.theme = value;
186
- this._applyTheme(value);
187
- this._updateChart();
188
- return this;
189
- }
190
-
191
- styleMode(value: ChartStyleMode): this {
192
- this.state.styleMode = value;
193
- this._updateChart();
194
- return this;
195
- }
196
-
197
- borderRadius(value: number): this {
198
- this.state.borderRadius = value;
199
- this._updateChart();
200
- return this;
201
- }
202
-
203
- /* -------------------------
204
- * Reactivity Support
205
- * ------------------------- */
206
-
207
- /**
208
- * Sync a single property to a state object
209
- */
210
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
211
- const transform = toComponent || ((v: any) => v);
212
-
213
- stateObj.subscribe((val: any) => {
214
- const transformed = transform(val);
215
-
216
- // Map property to correct method
217
- switch (property) {
218
- case 'data': this.data(transformed); break;
219
- case 'title': this.title(transformed); break;
220
- case 'subtitle': this.subtitle(transformed); break;
221
- case 'width': this.width(transformed); break;
222
- case 'height': this.height(transformed); break;
223
- case 'theme': this.theme(transformed); break;
224
- case 'styleMode': this.styleMode(transformed); break;
225
- case 'borderRadius': this.borderRadius(transformed); break;
226
- case 'showTicksX': this.showTicksX(transformed); break;
227
- case 'showTicksY': this.showTicksY(transformed); break;
228
- case 'showLegend': this.showLegend(transformed); break;
229
- case 'showDataLabels': this.showDataLabels(transformed); break;
230
- case 'showDataTable': this.showDataTable(transformed); break;
231
- case 'animate': this.animate(transformed); break;
232
- case 'animationDuration': this.animationDuration(transformed); break;
233
- case 'legendOrientation': this.legendOrientation(transformed); break;
234
- case 'chartOrientation': this.chartOrientation(transformed); break;
235
- case 'chartDirection': this.chartDirection(transformed); break;
236
- }
237
- });
238
-
239
- return this;
240
- }
241
-
242
- /**
243
- * Sync multiple properties from a state object
244
- *
245
- * @param stateObject - Object containing State instances for each property
246
- * @param mapping - Optional mapping of state keys to component method names
247
- *
248
- * @example
249
- * // Auto-mapping (state keys match method names)
250
- * chart.syncState(chartStates);
251
- *
252
- * @example
253
- * // Custom mapping
254
- * chart.syncState(chartStates, {
255
- * chartTheme: 'theme',
256
- * chartTitle: 'title',
257
- * chartWidth: 'width'
258
- * });
259
- *
260
- * @example
261
- * // Function mapping for transforms
262
- * chart.syncState(chartStates, {
263
- * chartWidth: (value) => chart.width(Number(value))
264
- * });
265
- */
266
- syncState(stateObject: ChartStateObject, mapping?: ChartPropertyMapping): this {
267
- // Default mapping: camelCase state names to method names
268
- const defaultMapping: ChartPropertyMapping = {
269
- chartType: 'type', // Not used in area chart, ignored
270
- chartTheme: 'theme',
271
- chartStyleMode: 'styleMode',
272
- borderRadius: 'borderRadius',
273
- chartTitle: 'title',
274
- chartWidth: 'width',
275
- chartHeight: 'height',
276
- showTicksX: 'showTicksX',
277
- showTicksY: 'showTicksY',
278
- showLegend: 'showLegend',
279
- showDataTable: 'showDataTable',
280
- showDataLabels: 'showDataLabels',
281
- animate: 'animate',
282
- animationDuration: 'animationDuration',
283
- legendOrientation: 'legendOrientation',
284
- chartOrientation: 'chartOrientation',
285
- chartDirection: 'chartDirection'
286
- };
287
-
288
- const finalMapping = { ...defaultMapping, ...mapping };
289
-
290
- // Iterate through state object and bind each property
291
- Object.entries(stateObject).forEach(([key, stateObj]) => {
292
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
293
- console.warn(`AreaChart.syncState: "${key}" is not a valid State object`);
294
- return;
295
- }
296
-
297
- const methodOrFunction = finalMapping[key];
298
-
299
- if (!methodOrFunction) {
300
- console.warn(`AreaChart.syncState: No mapping found for "${key}"`);
301
- return;
302
- }
303
-
304
- if (typeof methodOrFunction === 'function') {
305
- // Custom function mapping
306
- stateObj.subscribe(methodOrFunction);
307
- } else {
308
- // String mapping to method name
309
- const methodName = methodOrFunction as keyof this;
310
- const method = this[methodName];
311
-
312
- if (typeof method !== 'function') {
313
- console.warn(`AreaChart.syncState: Method "${String(methodName)}" not found`);
314
- return;
315
- }
316
-
317
- stateObj.subscribe((val: any) => {
318
- (method as Function).call(this, val);
319
- });
320
- }
321
- });
322
-
323
- return this;
324
- }
325
-
326
- /* -------------------------
327
- * Internal Methods
328
- * ------------------------- */
329
-
330
- private _updateChart(): void {
331
- if (!this.container) return;
332
-
333
- const wrapper = this.container.querySelector(`#${this._id}`) as HTMLElement;
334
- if (!wrapper) return;
335
-
336
- wrapper.innerHTML = '';
337
- this._buildChart(wrapper);
338
-
339
- if (this.state.theme) {
340
- this._applyThemeToWrapper(wrapper);
341
- }
342
- }
343
-
344
- private _buildChart(wrapper: HTMLElement): void {
345
- const { title, subtitle, showLegend, showDataTable } = this.state;
346
-
347
- if (title) {
348
- const titleEl = document.createElement('h3');
349
- titleEl.className = 'jux-areachart-title';
350
- titleEl.textContent = title;
351
- wrapper.appendChild(titleEl);
352
- }
353
-
354
- if (subtitle) {
355
- const subtitleEl = document.createElement('p');
356
- subtitleEl.className = 'jux-areachart-subtitle';
357
- subtitleEl.textContent = subtitle;
358
- wrapper.appendChild(subtitleEl);
359
- }
360
-
361
- const svg = this._createSVG();
362
- wrapper.appendChild(svg);
363
-
364
- if (showLegend) {
365
- const legend = createLegend(this.state.data, this.state.colors, 'jux-areachart');
366
- wrapper.appendChild(legend);
367
- }
368
-
369
- if (showDataTable) {
370
- const table = createDataTable(
371
- this.state.data,
372
- this.state.xAxisLabel,
373
- this.state.yAxisLabel,
374
- 'jux-areachart'
375
- );
376
- wrapper.appendChild(table);
377
- }
378
- }
379
-
380
- private _createSVG(): SVGSVGElement {
381
- const {
382
- data, width, height, colors, xAxisLabel, yAxisLabel,
383
- showTicksX, showTicksY, showScaleX, showScaleY, scaleYUnit,
384
- styleMode, borderRadius, showDataLabels, animate, animationDuration,
385
- chartOrientation, chartDirection
386
- } = this.state;
387
-
388
- if (!data.length) {
389
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
390
- svg.setAttribute('width', width.toString());
391
- svg.setAttribute('height', height.toString());
392
- svg.setAttribute('class', 'jux-areachart-svg');
393
- return svg;
394
- }
395
-
396
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
397
- svg.setAttribute('width', width.toString());
398
- svg.setAttribute('height', height.toString());
399
- svg.setAttribute('class', 'jux-areachart-svg');
400
-
401
- // Add animation styles to SVG
402
- if (animate) {
403
- const animationId = `bar-grow-${this._id}`;
404
- const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
405
-
406
- // Different animation based on orientation and direction
407
- let transformOrigin = 'bottom';
408
- let scaleAxis = 'scaleY';
409
-
410
- if (chartOrientation === 'horizontal') {
411
- if (chartDirection === 'normal') {
412
- transformOrigin = 'left';
413
- scaleAxis = 'scaleX';
414
- } else {
415
- transformOrigin = 'right';
416
- scaleAxis = 'scaleX';
417
- }
418
- } else {
419
- if (chartDirection === 'reverse') {
420
- transformOrigin = 'top';
421
- scaleAxis = 'scaleY';
422
- }
423
- }
424
-
425
- style.textContent = `
426
- @keyframes ${animationId} {
427
- from {
428
- transform: ${scaleAxis}(0);
429
- opacity: 0;
430
- }
431
- to {
432
- transform: ${scaleAxis}(1);
433
- opacity: 1;
434
- }
435
- }
436
- .jux-bar-animated {
437
- transform-origin: ${transformOrigin};
438
- animation: ${animationId} ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
439
- }
440
- .jux-label-animated {
441
- opacity: 0;
442
- animation: fadeIn ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
443
- }
444
- @keyframes fadeIn {
445
- from { opacity: 0; }
446
- to { opacity: 1; }
447
- }
448
- `;
449
- svg.appendChild(style);
450
- }
451
-
452
- // Calculate dimensions
453
- const padding = { top: 40, right: 40, bottom: 60, left: 60 };
454
- const chartWidth = width - padding.left - padding.right;
455
- const chartHeight = height - padding.top - padding.bottom;
456
-
457
- const maxValue = Math.max(...data.map(d => d.value));
458
-
459
- // Determine if we're working with vertical or horizontal layout
460
- const isVertical = chartOrientation === 'vertical';
461
- const isReverse = chartDirection === 'reverse';
462
-
463
- if (isVertical) {
464
- // VERTICAL BARS (original logic with direction support)
465
- this._renderVerticalBars(svg, data, colors, padding, chartWidth, chartHeight, maxValue, isReverse);
466
- } else {
467
- // HORIZONTAL BARS (new logic)
468
- this._renderHorizontalBars(svg, data, colors, padding, chartWidth, chartHeight, maxValue, isReverse);
469
- }
470
-
471
- return svg;
472
- }
473
-
474
- private _renderVerticalBars(
475
- svg: SVGSVGElement,
476
- data: ChartDataPoint[],
477
- colors: string[],
478
- padding: { top: number, right: number, bottom: number, left: number },
479
- chartWidth: number,
480
- chartHeight: number,
481
- maxValue: number,
482
- isReverse: boolean
483
- ): void {
484
- const { width, height, xAxisLabel, yAxisLabel, showTicksX, showTicksY, showScaleX, showScaleY, scaleYUnit, styleMode, borderRadius, showDataLabels, animate, animationDuration } = this.state;
485
-
486
- const yScale = chartHeight / maxValue;
487
- const barWidth = chartWidth / data.length;
488
- const barGap = barWidth * 0;
489
- const actualBarWidth = barWidth - barGap;
490
-
491
- // Y-axis
492
- if (showScaleY) {
493
- const yAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
494
- yAxis.setAttribute('x1', padding.left.toString());
495
- yAxis.setAttribute('y1', padding.top.toString());
496
- yAxis.setAttribute('x2', padding.left.toString());
497
- yAxis.setAttribute('y2', (height - padding.bottom).toString());
498
- yAxis.setAttribute('stroke', '#9ca3af');
499
- yAxis.setAttribute('stroke-width', '2');
500
- svg.appendChild(yAxis);
501
-
502
- // Y-axis label
503
- if (yAxisLabel && showTicksY) {
504
- const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
505
- label.setAttribute('x', '20');
506
- label.setAttribute('y', (padding.top + chartHeight / 2).toString());
507
- label.setAttribute('text-anchor', 'middle');
508
- label.setAttribute('transform', `rotate(-90, 20, ${padding.top + chartHeight / 2})`);
509
- label.setAttribute('fill', '#6b7280');
510
- label.setAttribute('font-size', '12');
511
- label.setAttribute('font-weight', '500');
512
- label.setAttribute('font-family', 'inherit');
513
- label.textContent = yAxisLabel;
514
- svg.appendChild(label);
515
- }
516
-
517
- // Y-axis ticks
518
- if (showTicksY) {
519
- const numTicks = 5;
520
- for (let i = 0; i <= numTicks; i++) {
521
- const value = (maxValue / numTicks) * i;
522
- const y = isReverse
523
- ? padding.top + (value * yScale)
524
- : height - padding.bottom - (value * yScale);
525
-
526
- // Grid line
527
- const gridLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
528
- gridLine.setAttribute('x1', padding.left.toString());
529
- gridLine.setAttribute('y1', y.toString());
530
- gridLine.setAttribute('x2', (width - padding.right).toString());
531
- gridLine.setAttribute('y2', y.toString());
532
- gridLine.setAttribute('stroke', '#e5e7eb');
533
- gridLine.setAttribute('stroke-width', '1');
534
- gridLine.setAttribute('stroke-dasharray', '4,4');
535
- svg.appendChild(gridLine);
536
-
537
- // Tick label
538
- const tickLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
539
- tickLabel.setAttribute('x', (padding.left - 10).toString());
540
- tickLabel.setAttribute('y', (y + 4).toString());
541
- tickLabel.setAttribute('text-anchor', 'end');
542
- tickLabel.setAttribute('fill', '#6b7280');
543
- tickLabel.setAttribute('font-size', '11');
544
- tickLabel.setAttribute('font-family', 'inherit');
545
- tickLabel.textContent = Math.round(value).toString() + (scaleYUnit || '');
546
- svg.appendChild(tickLabel);
547
- }
548
- }
549
- }
550
-
551
- // X-axis
552
- if (showScaleX) {
553
- const xAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
554
- const axisY = isReverse ? padding.top : height - padding.bottom;
555
- xAxis.setAttribute('x1', padding.left.toString());
556
- xAxis.setAttribute('y1', axisY.toString());
557
- xAxis.setAttribute('x2', (width - padding.right).toString());
558
- xAxis.setAttribute('y2', axisY.toString());
559
- xAxis.setAttribute('stroke', '#9ca3af');
560
- xAxis.setAttribute('stroke-width', '2');
561
- svg.appendChild(xAxis);
562
-
563
- // X-axis label
564
- if (xAxisLabel && showTicksX) {
565
- const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
566
- label.setAttribute('x', (padding.left + chartWidth / 2).toString());
567
- label.setAttribute('y', (height - 15).toString());
568
- label.setAttribute('text-anchor', 'middle');
569
- label.setAttribute('fill', '#6b7280');
570
- label.setAttribute('font-size', '12');
571
- label.setAttribute('font-weight', '500');
572
- label.setAttribute('font-family', 'inherit');
573
- label.textContent = xAxisLabel;
574
- svg.appendChild(label);
575
- }
576
- }
577
-
578
- // Bars
579
- data.forEach((point, index) => {
580
- const x = padding.left + (index * barWidth) + (barGap / 2);
581
- const barHeight = point.value * yScale;
582
- const y = isReverse
583
- ? padding.top
584
- : height - padding.bottom - barHeight;
585
- const color = point.color || colors[index % colors.length];
586
-
587
- this._renderBar(svg, x, y, actualBarWidth, barHeight, color, index, point, isReverse ? 'bottom' : 'top');
588
- });
589
- }
590
-
591
- private _renderHorizontalBars(
592
- svg: SVGSVGElement,
593
- data: ChartDataPoint[],
594
- colors: string[],
595
- padding: { top: number, right: number, bottom: number, left: number },
596
- chartWidth: number,
597
- chartHeight: number,
598
- maxValue: number,
599
- isReverse: boolean
600
- ): void {
601
- const { width, height, xAxisLabel, yAxisLabel, showTicksX, showTicksY, showScaleX, showScaleY, scaleXUnit, styleMode, borderRadius, showDataLabels, animate, animationDuration } = this.state;
602
-
603
- const xScale = chartWidth / maxValue;
604
- const barHeight = chartHeight / data.length;
605
- const barGap = barHeight * 0.2;
606
- const actualBarHeight = barHeight - barGap;
607
-
608
- // X-axis (now the value axis) - use yAxisLabel since values are horizontal
609
- if (showScaleX) {
610
- const xAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
611
- const axisX = isReverse ? width - padding.right : padding.left;
612
- xAxis.setAttribute('x1', axisX.toString());
613
- xAxis.setAttribute('y1', padding.top.toString());
614
- xAxis.setAttribute('x2', axisX.toString());
615
- xAxis.setAttribute('y2', (height - padding.bottom).toString());
616
- xAxis.setAttribute('stroke', '#9ca3af');
617
- xAxis.setAttribute('stroke-width', '2');
618
- svg.appendChild(xAxis);
619
-
620
- // X-axis label (use yAxisLabel for values in horizontal mode)
621
- if (yAxisLabel && showTicksX) {
622
- const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
623
- label.setAttribute('x', (padding.left + chartWidth / 2).toString());
624
- label.setAttribute('y', (height - 15).toString());
625
- label.setAttribute('text-anchor', 'middle');
626
- label.setAttribute('fill', '#6b7280');
627
- label.setAttribute('font-size', '12');
628
- label.setAttribute('font-weight', '500');
629
- label.setAttribute('font-family', 'inherit');
630
- label.textContent = yAxisLabel;
631
- svg.appendChild(label);
632
- }
633
-
634
- // X-axis ticks
635
- if (showTicksX) {
636
- const numTicks = 5;
637
- for (let i = 0; i <= numTicks; i++) {
638
- const value = (maxValue / numTicks) * i;
639
- const x = isReverse
640
- ? width - padding.right - (value * xScale)
641
- : padding.left + (value * xScale);
642
-
643
- // Grid line
644
- const gridLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
645
- gridLine.setAttribute('x1', x.toString());
646
- gridLine.setAttribute('y1', padding.top.toString());
647
- gridLine.setAttribute('x2', x.toString());
648
- gridLine.setAttribute('y2', (height - padding.bottom).toString());
649
- gridLine.setAttribute('stroke', '#e5e7eb');
650
- gridLine.setAttribute('stroke-width', '1');
651
- gridLine.setAttribute('stroke-dasharray', '4,4');
652
- svg.appendChild(gridLine);
653
-
654
- // Tick label (use scaleXUnit since values are on x-axis now)
655
- const tickLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
656
- tickLabel.setAttribute('x', x.toString());
657
- tickLabel.setAttribute('y', (height - padding.bottom + 20).toString());
658
- tickLabel.setAttribute('text-anchor', 'middle');
659
- tickLabel.setAttribute('fill', '#6b7280');
660
- tickLabel.setAttribute('font-size', '11');
661
- tickLabel.setAttribute('font-family', 'inherit');
662
- tickLabel.textContent = Math.round(value).toString() + (scaleXUnit || '');
663
- svg.appendChild(tickLabel);
664
- }
665
- }
666
- }
667
-
668
- // Y-axis (now the category axis) - use xAxisLabel since categories are vertical
669
- if (showScaleY) {
670
- const yAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
671
- yAxis.setAttribute('x1', padding.left.toString());
672
- yAxis.setAttribute('y1', padding.top.toString());
673
- yAxis.setAttribute('x2', padding.left.toString());
674
- yAxis.setAttribute('y2', (height - padding.bottom).toString());
675
- yAxis.setAttribute('stroke', '#9ca3af');
676
- yAxis.setAttribute('stroke-width', '2');
677
- svg.appendChild(yAxis);
678
-
679
- // Y-axis label (use xAxisLabel for categories in horizontal mode)
680
- if (xAxisLabel && showTicksY) {
681
- const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
682
- label.setAttribute('x', '20');
683
- label.setAttribute('y', (padding.top + chartHeight / 2).toString());
684
- label.setAttribute('text-anchor', 'middle');
685
- label.setAttribute('transform', `rotate(-90, 20, ${padding.top + chartHeight / 2})`);
686
- label.setAttribute('fill', '#6b7280');
687
- label.setAttribute('font-size', '12');
688
- label.setAttribute('font-weight', '500');
689
- label.setAttribute('font-family', 'inherit');
690
- label.textContent = xAxisLabel;
691
- svg.appendChild(label);
692
- }
693
- }
694
-
695
- // Bars
696
- data.forEach((point, index) => {
697
- const y = padding.top + (index * barHeight) + (barGap / 2);
698
- const barWidth = point.value * xScale;
699
- const x = isReverse
700
- ? width - padding.right - barWidth
701
- : padding.left;
702
- const color = point.color || colors[index % colors.length];
703
-
704
- this._renderBar(svg, x, y, barWidth, actualBarHeight, color, index, point, isReverse ? 'right' : 'left', true);
705
-
706
- // Category label
707
- if (showTicksY && showScaleY) {
708
- const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
709
- label.setAttribute('x', (padding.left - 10).toString());
710
- label.setAttribute('y', (y + actualBarHeight / 2 + 4).toString());
711
- label.setAttribute('text-anchor', 'end');
712
- label.setAttribute('fill', '#6b7280');
713
- label.setAttribute('font-size', '11');
714
- label.setAttribute('font-family', 'inherit');
715
- label.textContent = point.label;
716
-
717
- if (animate) {
718
- label.classList.add('jux-label-animated');
719
- label.style.animationDelay = `${index * 100 + 200}ms`;
720
- }
721
-
722
- svg.appendChild(label);
723
- }
724
- });
725
- }
726
-
727
- private _renderBar(
728
- svg: SVGSVGElement,
729
- x: number,
730
- y: number,
731
- width: number,
732
- height: number,
733
- color: string,
734
- index: number,
735
- point: ChartDataPoint,
736
- labelPosition: 'top' | 'bottom' | 'left' | 'right',
737
- isHorizontal: boolean = false
738
- ): void {
739
- const { styleMode, borderRadius, showDataLabels, animate, animationDuration } = this.state;
740
-
741
- const shouldUseGroup = (styleMode === 'glass' || styleMode === 'glow') && animate;
742
- let animationTarget: SVGElement;
743
-
744
- if (shouldUseGroup) {
745
- const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
746
- animationTarget = group;
747
- if (animate) {
748
- group.classList.add('jux-bar-animated');
749
- group.style.animationDelay = `${index * 100}ms`;
750
- }
751
- svg.appendChild(group);
752
- }
753
-
754
- const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
755
- rect.setAttribute('x', x.toString());
756
- rect.setAttribute('y', y.toString());
757
- rect.setAttribute('width', width.toString());
758
- rect.setAttribute('height', height.toString());
759
- // rect.setAttribute('rx', borderRadius.toString());
760
- // rect.setAttribute('ry', borderRadius.toString());
761
-
762
- if (!shouldUseGroup && animate) {
763
- rect.classList.add('jux-bar-animated');
764
- rect.style.animationDelay = `${index * 100}ms`;
765
- }
766
-
767
- // Apply style modes (same as before)
768
- if (styleMode === 'gradient') {
769
- const gradientId = `gradient-${this._id}-${index}`;
770
- const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
771
- gradient.setAttribute('id', gradientId);
772
-
773
- if (isHorizontal) {
774
- gradient.setAttribute('x1', '0%');
775
- gradient.setAttribute('y1', '0%');
776
- gradient.setAttribute('x2', '100%');
777
- gradient.setAttribute('y2', '0%');
778
- } else {
779
- gradient.setAttribute('x1', '0%');
780
- gradient.setAttribute('y1', '0%');
781
- gradient.setAttribute('x2', '0%');
782
- gradient.setAttribute('y2', '100%');
783
- }
784
-
785
- const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
786
- stop1.setAttribute('offset', '0%');
787
- stop1.setAttribute('stop-color', color);
788
- stop1.setAttribute('stop-opacity', '1');
789
-
790
- const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
791
- stop2.setAttribute('offset', '100%');
792
- stop2.setAttribute('stop-color', this._lightenColor(color, 40));
793
- stop2.setAttribute('stop-opacity', '1');
794
-
795
- gradient.appendChild(stop1);
796
- gradient.appendChild(stop2);
797
- svg.appendChild(gradient);
798
-
799
- rect.setAttribute('fill', `url(#${gradientId})`);
800
- } else if (styleMode === 'glow') {
801
- rect.setAttribute('fill', color);
802
- const filterId = `glow-${this._id}-${index}`;
803
- const defs = svg.querySelector('defs') || document.createElementNS('http://www.w3.org/2000/svg', 'defs');
804
- if (!svg.querySelector('defs')) {
805
- svg.insertBefore(defs, svg.firstChild);
806
- }
807
-
808
- const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
809
- filter.setAttribute('id', filterId);
810
- filter.setAttribute('x', '-50%');
811
- filter.setAttribute('y', '-50%');
812
- filter.setAttribute('width', '200%');
813
- filter.setAttribute('height', '200%');
814
-
815
- const feGaussianBlur = document.createElementNS('http://www.w3.org/2000/svg', 'feGaussianBlur');
816
- feGaussianBlur.setAttribute('in', 'SourceGraphic');
817
- feGaussianBlur.setAttribute('stdDeviation', '4');
818
- feGaussianBlur.setAttribute('result', 'blur');
819
-
820
- const feFlood = document.createElementNS('http://www.w3.org/2000/svg', 'feFlood');
821
- feFlood.setAttribute('flood-color', color);
822
- feFlood.setAttribute('result', 'color');
823
-
824
- const feComposite = document.createElementNS('http://www.w3.org/2000/svg', 'feComposite');
825
- feComposite.setAttribute('in', 'color');
826
- feComposite.setAttribute('in2', 'blur');
827
- feComposite.setAttribute('operator', 'in');
828
- feComposite.setAttribute('result', 'glow');
829
-
830
- const feMerge = document.createElementNS('http://www.w3.org/2000/svg', 'feMerge');
831
- const feMergeNode1 = document.createElementNS('http://www.w3.org/2000/svg', 'feMergeNode');
832
- feMergeNode1.setAttribute('in', 'glow');
833
- const feMergeNode2 = document.createElementNS('http://www.w3.org/2000/svg', 'feMergeNode');
834
- feMergeNode2.setAttribute('in', 'SourceGraphic');
835
- feMerge.appendChild(feMergeNode1);
836
- feMerge.appendChild(feMergeNode2);
837
-
838
- filter.appendChild(feGaussianBlur);
839
- filter.appendChild(feFlood);
840
- filter.appendChild(feComposite);
841
- filter.appendChild(feMerge);
842
- defs.appendChild(filter);
843
-
844
- rect.setAttribute('filter', `url(#${filterId})`);
845
- } else if (styleMode === 'glass') {
846
- rect.setAttribute('fill', color);
847
- rect.setAttribute('fill-opacity', '0.6');
848
- rect.setAttribute('stroke', color);
849
- rect.setAttribute('stroke-width', '0');
850
- rect.setAttribute('stroke-opacity', '0.8');
851
- } else {
852
- rect.setAttribute('fill', color);
853
- }
854
-
855
- if (shouldUseGroup) {
856
- animationTarget.appendChild(rect);
857
- } else {
858
- svg.appendChild(rect);
859
- }
860
-
861
- // Value labels
862
- if (showDataLabels) {
863
- const valueLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
864
-
865
- if (isHorizontal) {
866
- // For horizontal bars, place label in the middle of the bar
867
- valueLabel.setAttribute('x', (x + width / 2).toString());
868
- valueLabel.setAttribute('y', (y + height / 2 + 4).toString());
869
- valueLabel.setAttribute('text-anchor', 'middle');
870
- valueLabel.setAttribute('fill', '#ffffff');
871
- valueLabel.setAttribute('font-weight', '700');
872
- } else {
873
- valueLabel.setAttribute('x', (x + width / 2).toString());
874
- if (labelPosition === 'top') {
875
- valueLabel.setAttribute('y', (y - 5).toString());
876
- } else {
877
- valueLabel.setAttribute('y', (y + height + 15).toString());
878
- }
879
- valueLabel.setAttribute('text-anchor', 'middle');
880
- valueLabel.setAttribute('fill', '#374151');
881
- valueLabel.setAttribute('font-weight', '600');
882
- }
883
-
884
- valueLabel.setAttribute('font-size', '11');
885
- valueLabel.setAttribute('font-family', 'inherit');
886
- valueLabel.textContent = point.value.toString();
887
-
888
- if (animate) {
889
- valueLabel.classList.add('jux-label-animated');
890
- valueLabel.style.animationDelay = `${index * 100 + animationDuration - 200}ms`;
891
- }
892
-
893
- svg.appendChild(valueLabel);
894
- }
895
- }
896
-
897
- /* -------------------------
898
- * Legend and Data Table
899
- * ------------------------- */
900
-
901
- private _createLegend(): HTMLElement {
902
- const { data, colors, legendOrientation } = this.state;
903
-
904
- const legend = document.createElement('div');
905
- legend.className = 'jux-areachart-legend';
906
-
907
- data.forEach((point, index) => {
908
- const color = point.color || colors[index % colors.length];
909
-
910
- const item = document.createElement('div');
911
- item.className = 'jux-areachart-legend-item';
912
-
913
- const swatch = document.createElement('div');
914
- swatch.className = 'jux-areachart-legend-swatch';
915
- swatch.style.background = color;
916
-
917
- const label = document.createElement('span');
918
- label.className = 'jux-areachart-legend-label';
919
- label.textContent = point.label;
920
-
921
- item.appendChild(swatch);
922
- item.appendChild(label);
923
- legend.appendChild(item);
924
- });
925
-
926
- return legend;
927
- }
928
-
929
- private _createDataTable(): HTMLElement {
930
- const { data, xAxisLabel, yAxisLabel, chartOrientation } = this.state;
931
-
932
- const table = document.createElement('table');
933
- table.className = 'jux-areachart-table';
934
-
935
- const thead = document.createElement('thead');
936
- const headerRow = document.createElement('tr');
937
-
938
- // Swap headers based on orientation
939
- const columnHeaders = chartOrientation === 'horizontal'
940
- ? [yAxisLabel || 'Label', xAxisLabel || 'Value']
941
- : [xAxisLabel || 'Label', yAxisLabel || 'Value'];
942
-
943
- columnHeaders.forEach(text => {
944
- const th = document.createElement('th');
945
- th.textContent = text;
946
- headerRow.appendChild(th);
947
- });
948
- thead.appendChild(headerRow);
949
- table.appendChild(thead);
950
-
951
- const tbody = document.createElement('tbody');
952
- data.forEach(point => {
953
- const row = document.createElement('tr');
954
-
955
- const labelCell = document.createElement('td');
956
- labelCell.textContent = point.label;
957
-
958
- const valueCell = document.createElement('td');
959
- valueCell.textContent = point.value.toString();
960
-
961
- row.appendChild(labelCell);
962
- row.appendChild(valueCell);
963
- tbody.appendChild(row);
964
- });
965
- table.appendChild(tbody);
966
-
967
- return table;
968
- }
969
-
970
- private _lightenColor(color: string, percent: number): string {
971
- const num = parseInt(color.replace('#', ''), 16);
972
- const r = Math.min(255, Math.floor((num >> 16) + ((255 - (num >> 16)) * percent / 100)));
973
- const g = Math.min(255, Math.floor(((num >> 8) & 0x00FF) + ((255 - ((num >> 8) & 0x00FF)) * percent / 100)));
974
- const b = Math.min(255, Math.floor((num & 0x0000FF) + ((255 - (num & 0x0000FF)) * percent / 100)));
975
- return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
976
- }
977
-
978
- private _applyTheme(themeName: ChartTheme): void {
979
- const theme = getThemeConfig(themeName);
980
- if (!theme) return;
981
-
982
- const randomColor = theme.colors[Math.floor(Math.random() * theme.colors.length)];
983
- this.state.colors = [randomColor];
984
-
985
- applyThemeStyles(themeName, 'jux-areachart', this._getBaseStyles());
986
- }
987
-
988
- private _applyThemeToWrapper(wrapper: HTMLElement): void {
989
- if (!this.state.theme) return;
990
-
991
- wrapper.classList.remove('theme-google', 'theme-seriesa', 'theme-hr', 'theme-figma', 'theme-notion', 'theme-chalk', 'theme-mint');
992
- wrapper.classList.add(`theme-${this.state.theme}`);
993
- }
994
-
995
- private _getBaseStyles(): string {
996
- return `
997
- .jux-areachart {
998
- font-family: var(--chart-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
999
- display: inline-block;
1000
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
1001
- border-radius: 12px;
1002
- background: white;
1003
- padding: 24px;
1004
- }
1005
-
1006
- .jux-areachart-title {
1007
- margin: 0 0 0.5rem 0;
1008
- font-size: 1.25rem;
1009
- font-weight: 600;
1010
- font-family: inherit;
1011
- }
1012
-
1013
- .jux-areachart-subtitle {
1014
- margin: 0 0 1rem 0;
1015
- font-size: 0.875rem;
1016
- color: #6b7280;
1017
- font-family: inherit;
1018
- }
1019
-
1020
- .jux-areachart-legend {
1021
- display: flex;
1022
- flex-wrap: wrap;
1023
- gap: 1rem;
1024
- margin-top: 1rem;
1025
- justify-content: center;
1026
- }
1027
-
1028
- .jux-areachart-legend-item {
1029
- display: flex;
1030
- align-items: center;
1031
- gap: 0.5rem;
1032
- }
1033
-
1034
- .jux-areachart-legend-swatch {
1035
- width: 12px;
1036
- height: 12px;
1037
- border-radius: 2px;
1038
- }
1039
-
1040
- .jux-areachart-legend-label {
1041
- font-size: 0.875rem;
1042
- color: #374151;
1043
- font-family: inherit;
1044
- }
1045
-
1046
- .jux-areachart-table {
1047
- width: 100%;
1048
- margin-top: 1rem;
1049
- border-collapse: collapse;
1050
- font-size: 0.875rem;
1051
- font-family: inherit;
1052
- }
1053
-
1054
- .jux-areachart-table thead th {
1055
- text-align: left;
1056
- padding: 0.5rem;
1057
- border-bottom: 2px solid #e5e7eb;
1058
- font-weight: 600;
1059
- text-align: center;
1060
- }
1061
-
1062
- .jux-areachart-table tbody td {
1063
- padding: 0.5rem;
1064
- border-bottom: 1px solid #f3f4f6;
1065
- text-align: center;
1066
- }
1067
-
1068
- .jux-areachart-svg {
1069
- font-family: inherit;
1070
- }
1071
- `;
1072
- }
1073
-
1074
- render(targetId?: string | HTMLElement): this {
1075
- // Apply theme first if set
1076
- if (this.state.theme) {
1077
- this._applyTheme(this.state.theme);
1078
- }
1079
-
1080
- let container: HTMLElement;
1081
-
1082
- if (targetId) {
1083
- if (targetId instanceof HTMLElement) {
1084
- container = targetId;
1085
- } else {
1086
- const target = document.querySelector(targetId);
1087
- if (!target || !(target instanceof HTMLElement)) {
1088
- throw new Error(`AreaChart: Target element "${targetId}" not found`);
1089
- }
1090
- container = target;
1091
- }
1092
- } else {
1093
- container = getOrCreateContainer(this._id);
1094
- }
1095
-
1096
- this.container = container;
1097
- const { class: className, style } = this.state;
1098
-
1099
- const wrapper = document.createElement('div');
1100
- wrapper.id = this._id;
1101
- wrapper.className = 'jux-areachart';
1102
-
1103
- // Add theme class
1104
- if (this.state.theme) {
1105
- wrapper.classList.add(`theme-${this.state.theme}`);
1106
- }
1107
-
1108
- // Add custom class
1109
- if (className) {
1110
- wrapper.classList.add(...className.split(' '));
1111
- }
1112
-
1113
- if (style) {
1114
- wrapper.setAttribute('style', style);
1115
- }
1116
-
1117
- container.appendChild(wrapper);
1118
-
1119
- // Build chart content
1120
- this._buildChart(wrapper);
1121
-
1122
- return this;
1123
- }
1124
- }
1125
-
1126
- export function areachart(id: string, options: BarAreaChartOptions = {}): AreaChart {
1127
- return new AreaChart(id, options);
1128
- }