juxscript 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +37 -92
  2. package/bin/cli.js +57 -56
  3. package/lib/components/alert.ts +240 -0
  4. package/lib/components/app.ts +216 -82
  5. package/lib/components/badge.ts +164 -0
  6. package/lib/components/barchart.ts +1248 -0
  7. package/lib/components/button.ts +188 -53
  8. package/lib/components/card.ts +75 -61
  9. package/lib/components/chart.ts +17 -15
  10. package/lib/components/checkbox.ts +199 -0
  11. package/lib/components/code.ts +66 -152
  12. package/lib/components/container.ts +104 -208
  13. package/lib/components/data.ts +1 -3
  14. package/lib/components/datepicker.ts +226 -0
  15. package/lib/components/dialog.ts +258 -0
  16. package/lib/components/docs-data.json +1969 -423
  17. package/lib/components/dropdown.ts +244 -0
  18. package/lib/components/element.ts +271 -0
  19. package/lib/components/fileupload.ts +319 -0
  20. package/lib/components/footer.ts +37 -18
  21. package/lib/components/header.ts +53 -33
  22. package/lib/components/heading.ts +119 -0
  23. package/lib/components/helpers.ts +34 -0
  24. package/lib/components/hero.ts +57 -31
  25. package/lib/components/include.ts +292 -0
  26. package/lib/components/input.ts +508 -77
  27. package/lib/components/layout.ts +144 -18
  28. package/lib/components/list.ts +83 -74
  29. package/lib/components/loading.ts +263 -0
  30. package/lib/components/main.ts +43 -17
  31. package/lib/components/menu.ts +108 -24
  32. package/lib/components/modal.ts +50 -21
  33. package/lib/components/nav.ts +60 -18
  34. package/lib/components/paragraph.ts +111 -0
  35. package/lib/components/progress.ts +276 -0
  36. package/lib/components/radio.ts +236 -0
  37. package/lib/components/req.ts +300 -0
  38. package/lib/components/script.ts +33 -74
  39. package/lib/components/select.ts +280 -0
  40. package/lib/components/sidebar.ts +87 -37
  41. package/lib/components/style.ts +47 -70
  42. package/lib/components/switch.ts +261 -0
  43. package/lib/components/table.ts +47 -24
  44. package/lib/components/tabs.ts +105 -63
  45. package/lib/components/theme-toggle.ts +361 -0
  46. package/lib/components/token-calculator.ts +380 -0
  47. package/lib/components/tooltip.ts +244 -0
  48. package/lib/components/view.ts +36 -20
  49. package/lib/components/write.ts +284 -0
  50. package/lib/globals.d.ts +21 -0
  51. package/lib/jux.ts +178 -68
  52. package/lib/presets/notion.css +521 -0
  53. package/lib/presets/notion.jux +27 -0
  54. package/lib/reactivity/state.ts +364 -0
  55. package/lib/themes/charts.js +126 -0
  56. package/machinery/compiler.js +126 -38
  57. package/machinery/generators/html.js +2 -3
  58. package/machinery/server.js +2 -2
  59. package/package.json +29 -3
  60. package/lib/components/import.ts +0 -430
  61. package/lib/components/node.ts +0 -200
  62. package/lib/components/reactivity.js +0 -104
  63. package/lib/components/theme.ts +0 -97
  64. package/lib/layouts/notion.css +0 -258
  65. package/lib/styles/base-theme.css +0 -186
  66. package/lib/styles/dark-theme.css +0 -144
  67. package/lib/styles/light-theme.css +0 -144
  68. package/lib/styles/tokens/dark.css +0 -86
  69. package/lib/styles/tokens/light.css +0 -86
  70. package/lib/templates/index.juxt +0 -33
  71. package/lib/themes/dark.css +0 -86
  72. package/lib/themes/light.css +0 -86
  73. /package/lib/{styles → presets}/global.css +0 -0
@@ -0,0 +1,1248 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
3
+ import {
4
+ googleTheme,
5
+ seriesaTheme,
6
+ hrTheme,
7
+ figmaTheme,
8
+ notionTheme,
9
+ chalkTheme,
10
+ mintTheme
11
+ } from '../themes/charts.js';
12
+
13
+ /**
14
+ * Bar chart data point
15
+ */
16
+ export interface BarChartDataPoint {
17
+ label: string;
18
+ value: number;
19
+ color?: string;
20
+ }
21
+
22
+ /**
23
+ * Bar chart options
24
+ */
25
+ export interface BarChartOptions {
26
+ data?: BarChartDataPoint[];
27
+ title?: string;
28
+ subtitle?: string;
29
+ xAxisLabel?: string;
30
+ yAxisLabel?: string;
31
+ showTicksX?: boolean;
32
+ showTicksY?: boolean;
33
+ showScaleX?: boolean;
34
+ showScaleY?: boolean;
35
+ scaleXUnit?: string;
36
+ scaleYUnit?: string;
37
+ showLegend?: boolean;
38
+ legendOrientation?: 'horizontal' | 'vertical';
39
+ showDataTable?: boolean;
40
+ showDataLabels?: boolean;
41
+ animate?: boolean;
42
+ animationDuration?: number;
43
+ chartOrientation?: 'vertical' | 'horizontal'; // NEW
44
+ chartDirection?: 'normal' | 'reverse'; // NEW: normal = bottom-to-top or left-to-right, reverse = opposite
45
+ width?: number;
46
+ height?: number;
47
+ colors?: string[];
48
+ class?: string;
49
+ style?: string;
50
+ theme?: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint';
51
+ styleMode?: 'default' | 'gradient' | 'outline' | 'dashed' | 'glow' | 'glass';
52
+ borderRadius?: number;
53
+ }
54
+
55
+ /**
56
+ * Bar chart state
57
+ */
58
+ type BarChartState = {
59
+ data: BarChartDataPoint[];
60
+ title: string;
61
+ subtitle: string;
62
+ xAxisLabel: string;
63
+ yAxisLabel: string;
64
+ showTicksX: boolean;
65
+ showTicksY: boolean;
66
+ showScaleX: boolean;
67
+ showScaleY: boolean;
68
+ scaleXUnit: string;
69
+ scaleYUnit: string;
70
+ showLegend: boolean;
71
+ legendOrientation: 'horizontal' | 'vertical';
72
+ showDataTable: boolean;
73
+ showDataLabels: boolean;
74
+ animate: boolean;
75
+ animationDuration: number;
76
+ chartOrientation: 'vertical' | 'horizontal'; // NEW
77
+ chartDirection: 'normal' | 'reverse'; // NEW
78
+ width: number;
79
+ height: number;
80
+ colors: string[];
81
+ class: string;
82
+ style: string;
83
+ theme?: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint';
84
+ styleMode: 'default' | 'gradient' | 'outline' | 'dashed' | 'glow' | 'glass';
85
+ borderRadius: number;
86
+ };
87
+
88
+ /**
89
+ * Bar chart component - Simple SVG-based bar chart
90
+ *
91
+ * Usage:
92
+ * jux.barchart('sales-chart')
93
+ * .data([
94
+ * { label: 'Jan', value: 100 },
95
+ * { label: 'Feb', value: 150 },
96
+ * { label: 'Mar', value: 200 }
97
+ * ])
98
+ * .title('Monthly Sales')
99
+ * .showLegend(true)
100
+ * .render('#app');
101
+ */
102
+ export class BarChart {
103
+ state: BarChartState;
104
+ container: HTMLElement | null = null;
105
+ _id: string;
106
+ id: string;
107
+
108
+ // State bindings
109
+ private _boundTheme?: State<string>;
110
+ private _boundStyleMode?: State<string>;
111
+ private _boundBorderRadius?: State<number>;
112
+
113
+ constructor(id: string, options: BarChartOptions = {}) {
114
+ this._id = id;
115
+ this.id = id;
116
+
117
+ const defaultColors = [
118
+ '#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
119
+ '#ec4899', '#06b6d4', '#f97316', '#84cc16', '#6366f1'
120
+ ];
121
+
122
+ this.state = {
123
+ data: options.data ?? [],
124
+ title: options.title ?? '',
125
+ subtitle: options.subtitle ?? '',
126
+ xAxisLabel: options.xAxisLabel ?? '',
127
+ yAxisLabel: options.yAxisLabel ?? '',
128
+ showTicksX: options.showTicksX ?? true,
129
+ showTicksY: options.showTicksY ?? true,
130
+ showScaleX: options.showScaleX ?? true,
131
+ showScaleY: options.showScaleY ?? true,
132
+ scaleXUnit: options.scaleXUnit ?? '',
133
+ scaleYUnit: options.scaleYUnit ?? '',
134
+ showLegend: options.showLegend ?? false,
135
+ legendOrientation: options.legendOrientation ?? 'horizontal',
136
+ showDataTable: options.showDataTable ?? false,
137
+ showDataLabels: options.showDataLabels ?? true,
138
+ animate: options.animate ?? true,
139
+ animationDuration: options.animationDuration ?? 800,
140
+ chartOrientation: options.chartOrientation ?? 'vertical', // NEW
141
+ chartDirection: options.chartDirection ?? 'normal', // NEW
142
+ width: options.width ?? 600,
143
+ height: options.height ?? 400,
144
+ colors: options.colors ?? defaultColors,
145
+ class: options.class ?? '',
146
+ style: options.style ?? '',
147
+ theme: options.theme,
148
+ styleMode: options.styleMode ?? 'default',
149
+ borderRadius: options.borderRadius ?? 4
150
+ };
151
+ }
152
+
153
+ /* -------------------------
154
+ * State Binding Methods
155
+ * ------------------------- */
156
+
157
+ /**
158
+ * Bind theme to reactive state
159
+ */
160
+ bindTheme(stateObj: State<string>): this {
161
+ this._boundTheme = stateObj;
162
+
163
+ stateObj.subscribe((val) => {
164
+ this.theme(val as any);
165
+ });
166
+
167
+ return this;
168
+ }
169
+
170
+ /**
171
+ * Bind styleMode to reactive state
172
+ */
173
+ bindStyleMode(stateObj: State<string>): this {
174
+ this._boundStyleMode = stateObj;
175
+
176
+ stateObj.subscribe((val) => {
177
+ this.styleMode(val as any);
178
+ });
179
+
180
+ return this;
181
+ }
182
+
183
+ /**
184
+ * Bind borderRadius to reactive state
185
+ */
186
+ bindBorderRadius(stateObj: State<number>): this {
187
+ this._boundBorderRadius = stateObj;
188
+
189
+ stateObj.subscribe((val) => {
190
+ this.borderRadius(val);
191
+ });
192
+
193
+ return this;
194
+ }
195
+
196
+ /* -------------------------
197
+ * Fluent API
198
+ * ------------------------- */
199
+
200
+ data(value: BarChartDataPoint[]): this {
201
+ this.state.data = value;
202
+ this._updateChart();
203
+ return this;
204
+ }
205
+
206
+ title(value: string): this {
207
+ this.state.title = value;
208
+ this._updateChart();
209
+ return this;
210
+ }
211
+
212
+ subtitle(value: string): this {
213
+ this.state.subtitle = value;
214
+ this._updateChart();
215
+ return this;
216
+ }
217
+
218
+ xAxisLabel(value: string): this {
219
+ this.state.xAxisLabel = value;
220
+ this._updateChart();
221
+ return this;
222
+ }
223
+
224
+ yAxisLabel(value: string): this {
225
+ this.state.yAxisLabel = value;
226
+ this._updateChart();
227
+ return this;
228
+ }
229
+
230
+ showTicksX(value: boolean): this {
231
+ this.state.showTicksX = value;
232
+ this._updateChart();
233
+ return this;
234
+ }
235
+
236
+ showTicksY(value: boolean): this {
237
+ this.state.showTicksY = value;
238
+ this._updateChart();
239
+ return this;
240
+ }
241
+
242
+ showScaleX(value: boolean): this {
243
+ this.state.showScaleX = value;
244
+ this._updateChart();
245
+ return this;
246
+ }
247
+
248
+ showScaleY(value: boolean): this {
249
+ this.state.showScaleY = value;
250
+ this._updateChart();
251
+ return this;
252
+ }
253
+
254
+ scaleXUnit(value: string): this {
255
+ this.state.scaleXUnit = value;
256
+ this._updateChart();
257
+ return this;
258
+ }
259
+
260
+ scaleYUnit(value: string): this {
261
+ this.state.scaleYUnit = value;
262
+ this._updateChart();
263
+ return this;
264
+ }
265
+
266
+ showLegend(value: boolean): this {
267
+ this.state.showLegend = value;
268
+ this._updateChart();
269
+ return this;
270
+ }
271
+
272
+ legendOrientation(value: 'horizontal' | 'vertical'): this {
273
+ this.state.legendOrientation = value;
274
+ this._updateChart();
275
+ return this;
276
+ }
277
+
278
+ showDataTable(value: boolean): this {
279
+ this.state.showDataTable = value;
280
+ this._updateChart();
281
+ return this;
282
+ }
283
+
284
+ /**
285
+ * Show/hide value labels on bars
286
+ */
287
+ showDataLabels(value: boolean): this {
288
+ this.state.showDataLabels = value;
289
+ this._updateChart();
290
+ return this;
291
+ }
292
+
293
+ /**
294
+ * Enable/disable bar grow animation
295
+ */
296
+ animate(value: boolean): this {
297
+ this.state.animate = value;
298
+ this._updateChart();
299
+ return this;
300
+ }
301
+
302
+ /**
303
+ * Set animation duration in milliseconds
304
+ */
305
+ animationDuration(value: number): this {
306
+ this.state.animationDuration = value;
307
+ this._updateChart();
308
+ return this;
309
+ }
310
+
311
+ /**
312
+ * Set chart orientation (vertical bars or horizontal bars)
313
+ */
314
+ chartOrientation(value: 'vertical' | 'horizontal'): this {
315
+ this.state.chartOrientation = value;
316
+ this._updateChart();
317
+ return this;
318
+ }
319
+
320
+ /**
321
+ * Set chart direction (normal or reverse)
322
+ * For vertical: normal = bottom-to-top, reverse = top-to-bottom
323
+ * For horizontal: normal = left-to-right, reverse = right-to-left
324
+ */
325
+ chartDirection(value: 'normal' | 'reverse'): this {
326
+ this.state.chartDirection = value;
327
+ this._updateChart();
328
+ return this;
329
+ }
330
+
331
+ width(value: number): this {
332
+ this.state.width = value;
333
+ this._updateChart();
334
+ return this;
335
+ }
336
+
337
+ height(value: number): this {
338
+ this.state.height = value;
339
+ this._updateChart();
340
+ return this;
341
+ }
342
+
343
+ colors(value: string[]): this {
344
+ this.state.colors = value;
345
+ this._updateChart();
346
+ return this;
347
+ }
348
+
349
+ class(value: string): this {
350
+ this.state.class = value;
351
+ return this;
352
+ }
353
+
354
+ style(value: string): this {
355
+ this.state.style = value;
356
+ return this;
357
+ }
358
+
359
+ /**
360
+ * Set chart theme
361
+ */
362
+ theme(value: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint'): this {
363
+ this.state.theme = value;
364
+ this._applyTheme(value);
365
+ this._updateChart();
366
+ return this;
367
+ }
368
+
369
+ /**
370
+ * Set bar style mode
371
+ */
372
+ styleMode(value: 'default' | 'gradient' | 'outline' | 'dashed' | 'glow' | 'glass'): this {
373
+ this.state.styleMode = value;
374
+ this._updateChart();
375
+ return this;
376
+ }
377
+
378
+ /**
379
+ * Set border radius for bars (0 = sharp corners, higher = rounder)
380
+ */
381
+ borderRadius(value: number): this {
382
+ this.state.borderRadius = value;
383
+ this._updateChart();
384
+ return this;
385
+ }
386
+
387
+ /* -------------------------
388
+ * Update chart
389
+ * ------------------------- */
390
+
391
+ private _updateChart(): void {
392
+ if (!this.container) return;
393
+
394
+ // Find the wrapper div
395
+ const wrapper = this.container.querySelector(`#${this._id}`) as HTMLElement;
396
+ if (!wrapper) return;
397
+
398
+ // Clear and rebuild
399
+ wrapper.innerHTML = '';
400
+ this._buildChart(wrapper);
401
+
402
+ // Reapply theme after rebuild
403
+ if (this.state.theme) {
404
+ this._applyThemeToWrapper(wrapper);
405
+ }
406
+ }
407
+
408
+ private _buildChart(wrapper: HTMLElement): void {
409
+ const { data, title, subtitle, width, height, showLegend, showDataTable } = this.state;
410
+
411
+ // Title
412
+ if (title) {
413
+ const titleEl = document.createElement('h3');
414
+ titleEl.className = 'jux-barchart-title';
415
+ titleEl.textContent = title;
416
+ wrapper.appendChild(titleEl);
417
+ }
418
+
419
+ // Subtitle
420
+ if (subtitle) {
421
+ const subtitleEl = document.createElement('p');
422
+ subtitleEl.className = 'jux-barchart-subtitle';
423
+ subtitleEl.textContent = subtitle;
424
+ wrapper.appendChild(subtitleEl);
425
+ }
426
+
427
+ // SVG Chart
428
+ const svg = this._createSVG();
429
+ wrapper.appendChild(svg);
430
+
431
+ // Legend
432
+ if (showLegend) {
433
+ const legend = this._createLegend();
434
+ wrapper.appendChild(legend);
435
+ }
436
+
437
+ // Data Table
438
+ if (showDataTable) {
439
+ const table = this._createDataTable();
440
+ wrapper.appendChild(table);
441
+ }
442
+ }
443
+
444
+ private _createSVG(): SVGSVGElement {
445
+ const {
446
+ data, width, height, colors, xAxisLabel, yAxisLabel,
447
+ showTicksX, showTicksY, showScaleX, showScaleY, scaleYUnit,
448
+ styleMode, borderRadius, showDataLabels, animate, animationDuration,
449
+ chartOrientation, chartDirection
450
+ } = this.state;
451
+
452
+ if (!data.length) {
453
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
454
+ svg.setAttribute('width', width.toString());
455
+ svg.setAttribute('height', height.toString());
456
+ svg.setAttribute('class', 'jux-barchart-svg');
457
+ return svg;
458
+ }
459
+
460
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
461
+ svg.setAttribute('width', width.toString());
462
+ svg.setAttribute('height', height.toString());
463
+ svg.setAttribute('class', 'jux-barchart-svg');
464
+
465
+ // Add animation styles to SVG
466
+ if (animate) {
467
+ const animationId = `bar-grow-${this._id}`;
468
+ const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
469
+
470
+ // Different animation based on orientation and direction
471
+ let transformOrigin = 'bottom';
472
+ let scaleAxis = 'scaleY';
473
+
474
+ if (chartOrientation === 'horizontal') {
475
+ if (chartDirection === 'normal') {
476
+ transformOrigin = 'left';
477
+ scaleAxis = 'scaleX';
478
+ } else {
479
+ transformOrigin = 'right';
480
+ scaleAxis = 'scaleX';
481
+ }
482
+ } else {
483
+ if (chartDirection === 'reverse') {
484
+ transformOrigin = 'top';
485
+ scaleAxis = 'scaleY';
486
+ }
487
+ }
488
+
489
+ style.textContent = `
490
+ @keyframes ${animationId} {
491
+ from {
492
+ transform: ${scaleAxis}(0);
493
+ opacity: 0;
494
+ }
495
+ to {
496
+ transform: ${scaleAxis}(1);
497
+ opacity: 1;
498
+ }
499
+ }
500
+ .jux-bar-animated {
501
+ transform-origin: ${transformOrigin};
502
+ animation: ${animationId} ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
503
+ }
504
+ .jux-label-animated {
505
+ opacity: 0;
506
+ animation: fadeIn ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
507
+ }
508
+ @keyframes fadeIn {
509
+ from { opacity: 0; }
510
+ to { opacity: 1; }
511
+ }
512
+ `;
513
+ svg.appendChild(style);
514
+ }
515
+
516
+ // Calculate dimensions
517
+ const padding = { top: 40, right: 40, bottom: 60, left: 60 };
518
+ const chartWidth = width - padding.left - padding.right;
519
+ const chartHeight = height - padding.top - padding.bottom;
520
+
521
+ const maxValue = Math.max(...data.map(d => d.value));
522
+
523
+ // Determine if we're working with vertical or horizontal layout
524
+ const isVertical = chartOrientation === 'vertical';
525
+ const isReverse = chartDirection === 'reverse';
526
+
527
+ if (isVertical) {
528
+ // VERTICAL BARS (original logic with direction support)
529
+ this._renderVerticalBars(svg, data, colors, padding, chartWidth, chartHeight, maxValue, isReverse);
530
+ } else {
531
+ // HORIZONTAL BARS (new logic)
532
+ this._renderHorizontalBars(svg, data, colors, padding, chartWidth, chartHeight, maxValue, isReverse);
533
+ }
534
+
535
+ return svg;
536
+ }
537
+
538
+ private _renderVerticalBars(
539
+ svg: SVGSVGElement,
540
+ data: BarChartDataPoint[],
541
+ colors: string[],
542
+ padding: { top: number, right: number, bottom: number, left: number },
543
+ chartWidth: number,
544
+ chartHeight: number,
545
+ maxValue: number,
546
+ isReverse: boolean
547
+ ): void {
548
+ const { width, height, xAxisLabel, yAxisLabel, showTicksX, showTicksY, showScaleX, showScaleY, scaleYUnit, styleMode, borderRadius, showDataLabels, animate, animationDuration } = this.state;
549
+
550
+ const yScale = chartHeight / maxValue;
551
+ const barWidth = chartWidth / data.length;
552
+ const barGap = barWidth * 0.2;
553
+ const actualBarWidth = barWidth - barGap;
554
+
555
+ // Y-axis
556
+ if (showScaleY) {
557
+ const yAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
558
+ yAxis.setAttribute('x1', padding.left.toString());
559
+ yAxis.setAttribute('y1', padding.top.toString());
560
+ yAxis.setAttribute('x2', padding.left.toString());
561
+ yAxis.setAttribute('y2', (height - padding.bottom).toString());
562
+ yAxis.setAttribute('stroke', '#9ca3af');
563
+ yAxis.setAttribute('stroke-width', '2');
564
+ svg.appendChild(yAxis);
565
+
566
+ // Y-axis label
567
+ if (yAxisLabel && showTicksY) {
568
+ const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
569
+ label.setAttribute('x', '20');
570
+ label.setAttribute('y', (padding.top + chartHeight / 2).toString());
571
+ label.setAttribute('text-anchor', 'middle');
572
+ label.setAttribute('transform', `rotate(-90, 20, ${padding.top + chartHeight / 2})`);
573
+ label.setAttribute('fill', '#6b7280');
574
+ label.setAttribute('font-size', '12');
575
+ label.setAttribute('font-weight', '500');
576
+ label.setAttribute('font-family', 'inherit');
577
+ label.textContent = yAxisLabel;
578
+ svg.appendChild(label);
579
+ }
580
+
581
+ // Y-axis ticks
582
+ if (showTicksY) {
583
+ const numTicks = 5;
584
+ for (let i = 0; i <= numTicks; i++) {
585
+ const value = (maxValue / numTicks) * i;
586
+ const y = isReverse
587
+ ? padding.top + (value * yScale)
588
+ : height - padding.bottom - (value * yScale);
589
+
590
+ // Grid line
591
+ const gridLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
592
+ gridLine.setAttribute('x1', padding.left.toString());
593
+ gridLine.setAttribute('y1', y.toString());
594
+ gridLine.setAttribute('x2', (width - padding.right).toString());
595
+ gridLine.setAttribute('y2', y.toString());
596
+ gridLine.setAttribute('stroke', '#e5e7eb');
597
+ gridLine.setAttribute('stroke-width', '1');
598
+ gridLine.setAttribute('stroke-dasharray', '4,4');
599
+ svg.appendChild(gridLine);
600
+
601
+ // Tick label
602
+ const tickLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
603
+ tickLabel.setAttribute('x', (padding.left - 10).toString());
604
+ tickLabel.setAttribute('y', (y + 4).toString());
605
+ tickLabel.setAttribute('text-anchor', 'end');
606
+ tickLabel.setAttribute('fill', '#6b7280');
607
+ tickLabel.setAttribute('font-size', '11');
608
+ tickLabel.setAttribute('font-family', 'inherit');
609
+ tickLabel.textContent = Math.round(value).toString() + (scaleYUnit || '');
610
+ svg.appendChild(tickLabel);
611
+ }
612
+ }
613
+ }
614
+
615
+ // X-axis
616
+ if (showScaleX) {
617
+ const xAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
618
+ const axisY = isReverse ? padding.top : height - padding.bottom;
619
+ xAxis.setAttribute('x1', padding.left.toString());
620
+ xAxis.setAttribute('y1', axisY.toString());
621
+ xAxis.setAttribute('x2', (width - padding.right).toString());
622
+ xAxis.setAttribute('y2', axisY.toString());
623
+ xAxis.setAttribute('stroke', '#9ca3af');
624
+ xAxis.setAttribute('stroke-width', '2');
625
+ svg.appendChild(xAxis);
626
+
627
+ // X-axis label
628
+ if (xAxisLabel && showTicksX) {
629
+ const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
630
+ label.setAttribute('x', (padding.left + chartWidth / 2).toString());
631
+ label.setAttribute('y', (height - 15).toString());
632
+ label.setAttribute('text-anchor', 'middle');
633
+ label.setAttribute('fill', '#6b7280');
634
+ label.setAttribute('font-size', '12');
635
+ label.setAttribute('font-weight', '500');
636
+ label.setAttribute('font-family', 'inherit');
637
+ label.textContent = xAxisLabel;
638
+ svg.appendChild(label);
639
+ }
640
+ }
641
+
642
+ // Bars
643
+ data.forEach((point, index) => {
644
+ const x = padding.left + (index * barWidth) + (barGap / 2);
645
+ const barHeight = point.value * yScale;
646
+ const y = isReverse
647
+ ? padding.top
648
+ : height - padding.bottom - barHeight;
649
+ const color = point.color || colors[index % colors.length];
650
+
651
+ this._renderBar(svg, x, y, actualBarWidth, barHeight, color, index, point, isReverse ? 'bottom' : 'top');
652
+ });
653
+ }
654
+
655
+ private _renderHorizontalBars(
656
+ svg: SVGSVGElement,
657
+ data: BarChartDataPoint[],
658
+ colors: string[],
659
+ padding: { top: number, right: number, bottom: number, left: number },
660
+ chartWidth: number,
661
+ chartHeight: number,
662
+ maxValue: number,
663
+ isReverse: boolean
664
+ ): void {
665
+ const { width, height, xAxisLabel, yAxisLabel, showTicksX, showTicksY, showScaleX, showScaleY, scaleXUnit, styleMode, borderRadius, showDataLabels, animate, animationDuration } = this.state;
666
+
667
+ const xScale = chartWidth / maxValue;
668
+ const barHeight = chartHeight / data.length;
669
+ const barGap = barHeight * 0.2;
670
+ const actualBarHeight = barHeight - barGap;
671
+
672
+ // X-axis (now the value axis)
673
+ if (showScaleX) {
674
+ const xAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
675
+ const axisX = isReverse ? width - padding.right : padding.left;
676
+ xAxis.setAttribute('x1', axisX.toString());
677
+ xAxis.setAttribute('y1', padding.top.toString());
678
+ xAxis.setAttribute('x2', axisX.toString());
679
+ xAxis.setAttribute('y2', (height - padding.bottom).toString());
680
+ xAxis.setAttribute('stroke', '#9ca3af');
681
+ xAxis.setAttribute('stroke-width', '2');
682
+ svg.appendChild(xAxis);
683
+
684
+ // X-axis label (now the value label)
685
+ if (xAxisLabel && showTicksX) {
686
+ const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
687
+ label.setAttribute('x', (padding.left + chartWidth / 2).toString());
688
+ label.setAttribute('y', (height - 15).toString());
689
+ label.setAttribute('text-anchor', 'middle');
690
+ label.setAttribute('fill', '#6b7280');
691
+ label.setAttribute('font-size', '12');
692
+ label.setAttribute('font-weight', '500');
693
+ label.setAttribute('font-family', 'inherit');
694
+ label.textContent = xAxisLabel;
695
+ svg.appendChild(label);
696
+ }
697
+
698
+ // X-axis ticks
699
+ if (showTicksX) {
700
+ const numTicks = 5;
701
+ for (let i = 0; i <= numTicks; i++) {
702
+ const value = (maxValue / numTicks) * i;
703
+ const x = isReverse
704
+ ? width - padding.right - (value * xScale)
705
+ : padding.left + (value * xScale);
706
+
707
+ // Grid line
708
+ const gridLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
709
+ gridLine.setAttribute('x1', x.toString());
710
+ gridLine.setAttribute('y1', padding.top.toString());
711
+ gridLine.setAttribute('x2', x.toString());
712
+ gridLine.setAttribute('y2', (height - padding.bottom).toString());
713
+ gridLine.setAttribute('stroke', '#e5e7eb');
714
+ gridLine.setAttribute('stroke-width', '1');
715
+ gridLine.setAttribute('stroke-dasharray', '4,4');
716
+ svg.appendChild(gridLine);
717
+
718
+ // Tick label
719
+ const tickLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
720
+ tickLabel.setAttribute('x', x.toString());
721
+ tickLabel.setAttribute('y', (height - padding.bottom + 20).toString());
722
+ tickLabel.setAttribute('text-anchor', 'middle');
723
+ tickLabel.setAttribute('fill', '#6b7280');
724
+ tickLabel.setAttribute('font-size', '11');
725
+ tickLabel.setAttribute('font-family', 'inherit');
726
+ tickLabel.textContent = Math.round(value).toString() + (scaleXUnit || '');
727
+ svg.appendChild(tickLabel);
728
+ }
729
+ }
730
+ }
731
+
732
+ // Y-axis (now the category axis)
733
+ if (showScaleY) {
734
+ const yAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
735
+ yAxis.setAttribute('x1', padding.left.toString());
736
+ yAxis.setAttribute('y1', padding.top.toString());
737
+ yAxis.setAttribute('x2', padding.left.toString());
738
+ yAxis.setAttribute('y2', (height - padding.bottom).toString());
739
+ yAxis.setAttribute('stroke', '#9ca3af');
740
+ yAxis.setAttribute('stroke-width', '2');
741
+ svg.appendChild(yAxis);
742
+
743
+ // Y-axis label (now the category label)
744
+ if (yAxisLabel && showTicksY) {
745
+ const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
746
+ label.setAttribute('x', '20');
747
+ label.setAttribute('y', (padding.top + chartHeight / 2).toString());
748
+ label.setAttribute('text-anchor', 'middle');
749
+ label.setAttribute('transform', `rotate(-90, 20, ${padding.top + chartHeight / 2})`);
750
+ label.setAttribute('fill', '#6b7280');
751
+ label.setAttribute('font-size', '12');
752
+ label.setAttribute('font-weight', '500');
753
+ label.setAttribute('font-family', 'inherit');
754
+ label.textContent = yAxisLabel;
755
+ svg.appendChild(label);
756
+ }
757
+ }
758
+
759
+ // Bars
760
+ data.forEach((point, index) => {
761
+ const y = padding.top + (index * barHeight) + (barGap / 2);
762
+ const barWidth = point.value * xScale;
763
+ const x = isReverse
764
+ ? width - padding.right - barWidth
765
+ : padding.left;
766
+ const color = point.color || colors[index % colors.length];
767
+
768
+ this._renderBar(svg, x, y, barWidth, actualBarHeight, color, index, point, isReverse ? 'right' : 'left', true);
769
+
770
+ // Category label
771
+ if (showTicksY && showScaleY) {
772
+ const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
773
+ label.setAttribute('x', (padding.left - 10).toString());
774
+ label.setAttribute('y', (y + actualBarHeight / 2 + 4).toString());
775
+ label.setAttribute('text-anchor', 'end');
776
+ label.setAttribute('fill', '#6b7280');
777
+ label.setAttribute('font-size', '11');
778
+ label.setAttribute('font-family', 'inherit');
779
+ label.textContent = point.label;
780
+
781
+ if (animate) {
782
+ label.classList.add('jux-label-animated');
783
+ label.style.animationDelay = `${index * 100 + 200}ms`;
784
+ }
785
+
786
+ svg.appendChild(label);
787
+ }
788
+ });
789
+ }
790
+
791
+ private _renderBar(
792
+ svg: SVGSVGElement,
793
+ x: number,
794
+ y: number,
795
+ width: number,
796
+ height: number,
797
+ color: string,
798
+ index: number,
799
+ point: BarChartDataPoint,
800
+ labelPosition: 'top' | 'bottom' | 'left' | 'right',
801
+ isHorizontal: boolean = false
802
+ ): void {
803
+ const { styleMode, borderRadius, showDataLabels, animate, animationDuration } = this.state;
804
+
805
+ const shouldUseGroup = (styleMode === 'glass' || styleMode === 'glow') && animate;
806
+ let animationTarget: SVGElement;
807
+
808
+ if (shouldUseGroup) {
809
+ const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
810
+ animationTarget = group;
811
+ if (animate) {
812
+ group.classList.add('jux-bar-animated');
813
+ group.style.animationDelay = `${index * 100}ms`;
814
+ }
815
+ svg.appendChild(group);
816
+ }
817
+
818
+ const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
819
+ rect.setAttribute('x', x.toString());
820
+ rect.setAttribute('y', y.toString());
821
+ rect.setAttribute('width', width.toString());
822
+ rect.setAttribute('height', height.toString());
823
+ rect.setAttribute('rx', borderRadius.toString());
824
+ rect.setAttribute('ry', borderRadius.toString());
825
+
826
+ if (!shouldUseGroup && animate) {
827
+ rect.classList.add('jux-bar-animated');
828
+ rect.style.animationDelay = `${index * 100}ms`;
829
+ }
830
+
831
+ // Apply style modes (same as before)
832
+ if (styleMode === 'gradient') {
833
+ const gradientId = `gradient-${this._id}-${index}`;
834
+ const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
835
+ gradient.setAttribute('id', gradientId);
836
+
837
+ if (isHorizontal) {
838
+ gradient.setAttribute('x1', '0%');
839
+ gradient.setAttribute('y1', '0%');
840
+ gradient.setAttribute('x2', '100%');
841
+ gradient.setAttribute('y2', '0%');
842
+ } else {
843
+ gradient.setAttribute('x1', '0%');
844
+ gradient.setAttribute('y1', '0%');
845
+ gradient.setAttribute('x2', '0%');
846
+ gradient.setAttribute('y2', '100%');
847
+ }
848
+
849
+ const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
850
+ stop1.setAttribute('offset', '0%');
851
+ stop1.setAttribute('stop-color', color);
852
+ stop1.setAttribute('stop-opacity', '1');
853
+
854
+ const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
855
+ stop2.setAttribute('offset', '100%');
856
+ stop2.setAttribute('stop-color', this._lightenColor(color, 40));
857
+ stop2.setAttribute('stop-opacity', '1');
858
+
859
+ gradient.appendChild(stop1);
860
+ gradient.appendChild(stop2);
861
+ svg.appendChild(gradient);
862
+
863
+ rect.setAttribute('fill', `url(#${gradientId})`);
864
+ } else if (styleMode === 'outline') {
865
+ rect.setAttribute('fill', 'transparent');
866
+ rect.setAttribute('stroke', color);
867
+ rect.setAttribute('stroke-width', '3');
868
+ } else if (styleMode === 'dashed') {
869
+ rect.setAttribute('fill', this._lightenColor(color, 60));
870
+ rect.setAttribute('stroke', color);
871
+ rect.setAttribute('stroke-width', '2');
872
+ rect.setAttribute('stroke-dasharray', '8,4');
873
+ } else if (styleMode === 'glow') {
874
+ rect.setAttribute('fill', color);
875
+ const filterId = `glow-${this._id}-${index}`;
876
+ const defs = svg.querySelector('defs') || document.createElementNS('http://www.w3.org/2000/svg', 'defs');
877
+ if (!svg.querySelector('defs')) {
878
+ svg.insertBefore(defs, svg.firstChild);
879
+ }
880
+
881
+ const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
882
+ filter.setAttribute('id', filterId);
883
+ filter.setAttribute('x', '-50%');
884
+ filter.setAttribute('y', '-50%');
885
+ filter.setAttribute('width', '200%');
886
+ filter.setAttribute('height', '200%');
887
+
888
+ const feGaussianBlur = document.createElementNS('http://www.w3.org/2000/svg', 'feGaussianBlur');
889
+ feGaussianBlur.setAttribute('in', 'SourceGraphic');
890
+ feGaussianBlur.setAttribute('stdDeviation', '4');
891
+ feGaussianBlur.setAttribute('result', 'blur');
892
+
893
+ const feFlood = document.createElementNS('http://www.w3.org/2000/svg', 'feFlood');
894
+ feFlood.setAttribute('flood-color', color);
895
+ feFlood.setAttribute('result', 'color');
896
+
897
+ const feComposite = document.createElementNS('http://www.w3.org/2000/svg', 'feComposite');
898
+ feComposite.setAttribute('in', 'color');
899
+ feComposite.setAttribute('in2', 'blur');
900
+ feComposite.setAttribute('operator', 'in');
901
+ feComposite.setAttribute('result', 'glow');
902
+
903
+ const feMerge = document.createElementNS('http://www.w3.org/2000/svg', 'feMerge');
904
+ const feMergeNode1 = document.createElementNS('http://www.w3.org/2000/svg', 'feMergeNode');
905
+ feMergeNode1.setAttribute('in', 'glow');
906
+ const feMergeNode2 = document.createElementNS('http://www.w3.org/2000/svg', 'feMergeNode');
907
+ feMergeNode2.setAttribute('in', 'SourceGraphic');
908
+ feMerge.appendChild(feMergeNode1);
909
+ feMerge.appendChild(feMergeNode2);
910
+
911
+ filter.appendChild(feGaussianBlur);
912
+ filter.appendChild(feFlood);
913
+ filter.appendChild(feComposite);
914
+ filter.appendChild(feMerge);
915
+ defs.appendChild(filter);
916
+
917
+ rect.setAttribute('filter', `url(#${filterId})`);
918
+ } else if (styleMode === 'glass') {
919
+ rect.setAttribute('fill', color);
920
+ rect.setAttribute('fill-opacity', '0.6');
921
+ rect.setAttribute('stroke', color);
922
+ rect.setAttribute('stroke-width', '2');
923
+ rect.setAttribute('stroke-opacity', '0.8');
924
+ } else {
925
+ rect.setAttribute('fill', color);
926
+ }
927
+
928
+ if (shouldUseGroup) {
929
+ animationTarget.appendChild(rect);
930
+ } else {
931
+ svg.appendChild(rect);
932
+ }
933
+
934
+ // Value labels
935
+ if (showDataLabels) {
936
+ const valueLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
937
+
938
+ if (isHorizontal) {
939
+ // For horizontal bars, place label in the middle of the bar
940
+ valueLabel.setAttribute('x', (x + width / 2).toString());
941
+ valueLabel.setAttribute('y', (y + height / 2 + 4).toString());
942
+ valueLabel.setAttribute('text-anchor', 'middle');
943
+ valueLabel.setAttribute('fill', '#ffffff');
944
+ valueLabel.setAttribute('font-weight', '700');
945
+ } else {
946
+ valueLabel.setAttribute('x', (x + width / 2).toString());
947
+ if (labelPosition === 'top') {
948
+ valueLabel.setAttribute('y', (y - 5).toString());
949
+ } else {
950
+ valueLabel.setAttribute('y', (y + height + 15).toString());
951
+ }
952
+ valueLabel.setAttribute('text-anchor', 'middle');
953
+ valueLabel.setAttribute('fill', '#374151');
954
+ valueLabel.setAttribute('font-weight', '600');
955
+ }
956
+
957
+ valueLabel.setAttribute('font-size', '11');
958
+ valueLabel.setAttribute('font-family', 'inherit');
959
+ valueLabel.textContent = point.value.toString();
960
+
961
+ if (animate) {
962
+ valueLabel.classList.add('jux-label-animated');
963
+ valueLabel.style.animationDelay = `${index * 100 + animationDuration - 200}ms`;
964
+ }
965
+
966
+ svg.appendChild(valueLabel);
967
+ }
968
+ }
969
+
970
+ /* -------------------------
971
+ * Legend and Data Table
972
+ * ------------------------- */
973
+
974
+ private _createLegend(): HTMLElement {
975
+ const { data, colors, legendOrientation } = this.state;
976
+
977
+ const legend = document.createElement('div');
978
+ legend.className = 'jux-barchart-legend';
979
+
980
+ data.forEach((point, index) => {
981
+ const color = point.color || colors[index % colors.length];
982
+
983
+ const item = document.createElement('div');
984
+ item.className = 'jux-barchart-legend-item';
985
+
986
+ const swatch = document.createElement('div');
987
+ swatch.className = 'jux-barchart-legend-swatch';
988
+ swatch.style.background = color;
989
+
990
+ const label = document.createElement('span');
991
+ label.className = 'jux-barchart-legend-label';
992
+ label.textContent = point.label;
993
+
994
+ item.appendChild(swatch);
995
+ item.appendChild(label);
996
+ legend.appendChild(item);
997
+ });
998
+
999
+ return legend;
1000
+ }
1001
+
1002
+ private _createDataTable(): HTMLElement {
1003
+ const { data, xAxisLabel, yAxisLabel } = this.state;
1004
+
1005
+ const table = document.createElement('table');
1006
+ table.className = 'jux-barchart-table';
1007
+
1008
+ const thead = document.createElement('thead');
1009
+ const headerRow = document.createElement('tr');
1010
+
1011
+ const columnHeaders = [
1012
+ xAxisLabel || 'Label',
1013
+ yAxisLabel || 'Value'
1014
+ ];
1015
+
1016
+ columnHeaders.forEach(text => {
1017
+ const th = document.createElement('th');
1018
+ th.textContent = text;
1019
+ headerRow.appendChild(th);
1020
+ });
1021
+ thead.appendChild(headerRow);
1022
+ table.appendChild(thead);
1023
+
1024
+ const tbody = document.createElement('tbody');
1025
+ data.forEach(point => {
1026
+ const row = document.createElement('tr');
1027
+
1028
+ const labelCell = document.createElement('td');
1029
+ labelCell.textContent = point.label;
1030
+
1031
+ const valueCell = document.createElement('td');
1032
+ valueCell.textContent = point.value.toString();
1033
+
1034
+ row.appendChild(labelCell);
1035
+ row.appendChild(valueCell);
1036
+ tbody.appendChild(row);
1037
+ });
1038
+ table.appendChild(tbody);
1039
+
1040
+ return table;
1041
+ }
1042
+
1043
+ private _lightenColor(color: string, percent: number): string {
1044
+ const num = parseInt(color.replace('#', ''), 16);
1045
+ const r = Math.min(255, Math.floor((num >> 16) + ((255 - (num >> 16)) * percent / 100)));
1046
+ const g = Math.min(255, Math.floor(((num >> 8) & 0x00FF) + ((255 - ((num >> 8) & 0x00FF)) * percent / 100)));
1047
+ const b = Math.min(255, Math.floor((num & 0x0000FF) + ((255 - (num & 0x0000FF)) * percent / 100)));
1048
+ return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
1049
+ }
1050
+
1051
+ private _applyTheme(themeName: string): void {
1052
+ const themes: Record<string, any> = {
1053
+ google: googleTheme,
1054
+ seriesa: seriesaTheme,
1055
+ hr: hrTheme,
1056
+ figma: figmaTheme,
1057
+ notion: notionTheme,
1058
+ chalk: chalkTheme,
1059
+ mint: mintTheme
1060
+ };
1061
+
1062
+ const theme = themes[themeName];
1063
+ if (!theme) return;
1064
+
1065
+ // Apply colors
1066
+ this.state.colors = theme.colors;
1067
+
1068
+ // Inject base styles (once)
1069
+ const baseStyleId = 'jux-barchart-base-styles';
1070
+ if (!document.getElementById(baseStyleId)) {
1071
+ const style = document.createElement('style');
1072
+ style.id = baseStyleId;
1073
+ style.textContent = this._getBaseStyles();
1074
+ document.head.appendChild(style);
1075
+ }
1076
+
1077
+ // Inject font (once per theme)
1078
+ if (theme.font && !document.querySelector(`link[href="${theme.font}"]`)) {
1079
+ const link = document.createElement('link');
1080
+ link.rel = 'stylesheet';
1081
+ link.href = theme.font;
1082
+ document.head.appendChild(link);
1083
+ }
1084
+
1085
+ // Apply theme-specific styles
1086
+ const styleId = `jux-barchart-theme-${themeName}`;
1087
+ let styleElement = document.getElementById(styleId) as HTMLStyleElement;
1088
+
1089
+ if (!styleElement) {
1090
+ styleElement = document.createElement('style');
1091
+ styleElement.id = styleId;
1092
+ document.head.appendChild(styleElement);
1093
+ }
1094
+
1095
+ // Generate CSS with theme variables
1096
+ const variablesCSS = Object.entries(theme.variables)
1097
+ .map(([key, value]) => ` ${key}: ${value};`)
1098
+ .join('\n');
1099
+
1100
+ styleElement.textContent = `
1101
+ .jux-barchart.theme-${themeName} {
1102
+ ${variablesCSS}
1103
+ }
1104
+ `;
1105
+ }
1106
+
1107
+ private _applyThemeToWrapper(wrapper: HTMLElement): void {
1108
+ if (!this.state.theme) return;
1109
+
1110
+ // Remove old theme classes
1111
+ wrapper.classList.remove('theme-google', 'theme-seriesa', 'theme-hr', 'theme-figma', 'theme-notion', 'theme-chalk', 'theme-mint');
1112
+
1113
+ // Add new theme class
1114
+ wrapper.classList.add(`theme-${this.state.theme}`);
1115
+ }
1116
+
1117
+ private _getBaseStyles(): string {
1118
+ return `
1119
+ .jux-barchart {
1120
+ font-family: var(--chart-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
1121
+ display: inline-block;
1122
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
1123
+ border-radius: 12px;
1124
+ background: white;
1125
+ padding: 24px;
1126
+ }
1127
+
1128
+ .jux-barchart-title {
1129
+ margin: 0 0 0.5rem 0;
1130
+ font-size: 1.25rem;
1131
+ font-weight: 600;
1132
+ font-family: inherit;
1133
+ }
1134
+
1135
+ .jux-barchart-subtitle {
1136
+ margin: 0 0 1rem 0;
1137
+ font-size: 0.875rem;
1138
+ color: #6b7280;
1139
+ font-family: inherit;
1140
+ }
1141
+
1142
+ .jux-barchart-legend {
1143
+ display: flex;
1144
+ flex-wrap: wrap;
1145
+ gap: 1rem;
1146
+ margin-top: 1rem;
1147
+ justify-content: center;
1148
+ }
1149
+
1150
+ .jux-barchart-legend-item {
1151
+ display: flex;
1152
+ align-items: center;
1153
+ gap: 0.5rem;
1154
+ }
1155
+
1156
+ .jux-barchart-legend-swatch {
1157
+ width: 12px;
1158
+ height: 12px;
1159
+ border-radius: 2px;
1160
+ }
1161
+
1162
+ .jux-barchart-legend-label {
1163
+ font-size: 0.875rem;
1164
+ color: #374151;
1165
+ font-family: inherit;
1166
+ }
1167
+
1168
+ .jux-barchart-table {
1169
+ width: 100%;
1170
+ margin-top: 1rem;
1171
+ border-collapse: collapse;
1172
+ font-size: 0.875rem;
1173
+ font-family: inherit;
1174
+ }
1175
+
1176
+ .jux-barchart-table thead th {
1177
+ text-align: left;
1178
+ padding: 0.5rem;
1179
+ border-bottom: 2px solid #e5e7eb;
1180
+ font-weight: 600;
1181
+ }
1182
+
1183
+ .jux-barchart-table tbody td {
1184
+ padding: 0.5rem;
1185
+ border-bottom: 1px solid #f3f4f6;
1186
+ }
1187
+
1188
+ .jux-barchart-svg {
1189
+ font-family: inherit;
1190
+ }
1191
+ `;
1192
+ }
1193
+
1194
+ render(targetId?: string | HTMLElement): this {
1195
+ // Apply theme first if set
1196
+ if (this.state.theme) {
1197
+ this._applyTheme(this.state.theme);
1198
+ }
1199
+
1200
+ let container: HTMLElement;
1201
+
1202
+ if (targetId) {
1203
+ if (targetId instanceof HTMLElement) {
1204
+ container = targetId;
1205
+ } else {
1206
+ const target = document.querySelector(targetId);
1207
+ if (!target || !(target instanceof HTMLElement)) {
1208
+ throw new Error(`BarChart: Target element "${targetId}" not found`);
1209
+ }
1210
+ container = target;
1211
+ }
1212
+ } else {
1213
+ container = getOrCreateContainer(this._id);
1214
+ }
1215
+
1216
+ this.container = container;
1217
+ const { class: className, style } = this.state;
1218
+
1219
+ const wrapper = document.createElement('div');
1220
+ wrapper.id = this._id;
1221
+ wrapper.className = 'jux-barchart';
1222
+
1223
+ // Add theme class
1224
+ if (this.state.theme) {
1225
+ wrapper.classList.add(`theme-${this.state.theme}`);
1226
+ }
1227
+
1228
+ // Add custom class
1229
+ if (className) {
1230
+ wrapper.classList.add(...className.split(' '));
1231
+ }
1232
+
1233
+ if (style) {
1234
+ wrapper.setAttribute('style', style);
1235
+ }
1236
+
1237
+ container.appendChild(wrapper);
1238
+
1239
+ // Build chart content
1240
+ this._buildChart(wrapper);
1241
+
1242
+ return this;
1243
+ }
1244
+ }
1245
+
1246
+ export function barchart(id: string, options: BarChartOptions = {}): BarChart {
1247
+ return new BarChart(id, options);
1248
+ }