juxscript 1.0.19 → 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 (77) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +212 -165
  3. package/lib/components/badge.ts +93 -103
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +63 -122
  7. package/lib/components/card.ts +109 -155
  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/charts/lib/chart-types.ts +159 -0
  13. package/lib/components/charts/lib/chart-utils.ts +160 -0
  14. package/lib/components/charts/lib/chart.ts +707 -0
  15. package/lib/components/checkbox.ts +264 -127
  16. package/lib/components/code.ts +75 -108
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +195 -147
  20. package/lib/components/dialog.ts +187 -157
  21. package/lib/components/divider.ts +85 -191
  22. package/lib/components/docs-data.json +544 -2027
  23. package/lib/components/dropdown.ts +178 -136
  24. package/lib/components/element.ts +227 -171
  25. package/lib/components/fileupload.ts +285 -228
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +46 -69
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +107 -95
  30. package/lib/components/icon.ts +160 -0
  31. package/lib/components/icons.ts +175 -0
  32. package/lib/components/include.ts +153 -5
  33. package/lib/components/input.ts +174 -374
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +378 -240
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +103 -97
  38. package/lib/components/modal.ts +138 -144
  39. package/lib/components/nav.ts +169 -90
  40. package/lib/components/paragraph.ts +49 -150
  41. package/lib/components/progress.ts +118 -200
  42. package/lib/components/radio.ts +297 -149
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +184 -186
  45. package/lib/components/sidebar.ts +152 -140
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +258 -188
  48. package/lib/components/table.ts +1117 -170
  49. package/lib/components/tabs.ts +162 -145
  50. package/lib/components/theme-toggle.ts +108 -169
  51. package/lib/components/tooltip.ts +86 -157
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +86 -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 -2
  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 -1246
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1250
  69. package/lib/components/chart.ts +0 -127
  70. package/lib/components/doughnutchart.ts +0 -1191
  71. package/lib/components/footer.ts +0 -165
  72. package/lib/components/header.ts +0 -187
  73. package/lib/components/layout.ts +0 -239
  74. package/lib/components/main.ts +0 -137
  75. package/lib/layouts/default.jux +0 -8
  76. package/lib/layouts/figma.jux +0 -0
  77. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -1,219 +1,173 @@
1
- import { getOrCreateContainer } from './helpers.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+ import { renderIcon } from './icons.js';
3
+
4
+ // Event definitions - Card is display-only, no interactive events
5
+ const TRIGGER_EVENTS = [] as const;
6
+ const CALLBACK_EVENTS = ['click'] as const; // ✅ Can be clicked for navigation/selection
2
7
 
3
- /**
4
- * Card component options
5
- */
6
8
  export interface CardOptions {
7
9
  title?: string;
8
- subtitle?: string;
9
10
  content?: string;
10
- image?: string;
11
- variant?: 'default' | 'elevated' | 'outlined';
11
+ footer?: string;
12
+ variant?: string;
13
+ hoverable?: boolean;
14
+ icon?: string;
12
15
  style?: string;
13
16
  class?: string;
14
17
  }
15
18
 
16
- /**
17
- * Card component state
18
- */
19
19
  type CardState = {
20
20
  title: string;
21
- subtitle: string;
22
21
  content: string;
23
- image: string;
22
+ footer: string;
24
23
  variant: string;
24
+ hoverable: boolean;
25
+ icon: string;
25
26
  style: string;
26
27
  class: string;
27
- hasActions: boolean;
28
28
  };
29
29
 
30
- /**
31
- * Card component
32
- *
33
- * Usage:
34
- * const card = jux.card('myCard', {
35
- * title: 'Card Title',
36
- * content: 'Card content here'
37
- * });
38
- * card.render();
39
- *
40
- * // Add actions (any components)
41
- * jux.button('view-btn').label('View').render('#myCard-actions');
42
- * jux.button('delete-btn').label('Delete').render('#myCard-actions');
43
- */
44
- export class Card {
45
- state: CardState;
46
- container: HTMLElement | null = null;
47
- _id: string;
48
- id: string;
49
-
30
+ export class Card extends BaseComponent<CardState> {
50
31
  constructor(id: string, options: CardOptions = {}) {
51
- this._id = id;
52
- this.id = id;
53
-
54
- this.state = {
32
+ super(id, {
55
33
  title: options.title ?? '',
56
- subtitle: options.subtitle ?? '',
57
34
  content: options.content ?? '',
58
- image: options.image ?? '',
35
+ footer: options.footer ?? '',
59
36
  variant: options.variant ?? 'default',
37
+ hoverable: options.hoverable ?? false,
38
+ icon: options.icon ?? '',
60
39
  style: options.style ?? '',
61
- class: options.class ?? '',
62
- hasActions: false
63
- };
40
+ class: options.class ?? ''
41
+ });
64
42
  }
65
43
 
66
- /* -------------------------
67
- * Fluent API
68
- * ------------------------- */
69
-
70
- title(value: string): this {
71
- this.state.title = value;
72
- return this;
44
+ protected getTriggerEvents(): readonly string[] {
45
+ return TRIGGER_EVENTS;
73
46
  }
74
47
 
75
- subtitle(value: string): this {
76
- this.state.subtitle = value;
77
- return this;
48
+ protected getCallbackEvents(): readonly string[] {
49
+ return CALLBACK_EVENTS;
78
50
  }
79
51
 
80
- content(value: string): this {
81
- this.state.content = value;
82
- return this;
83
- }
52
+ /* ═════════════════════════════════════════════════════════════════
53
+ * FLUENT API
54
+ * ═════════════════════════════════════════════════════════════════ */
84
55
 
85
- image(value: string): this {
86
- this.state.image = value;
87
- return this;
88
- }
56
+ // Inherited from BaseComponent
89
57
 
90
- variant(value: string): this {
91
- this.state.variant = value;
58
+ title(value: string): this {
59
+ this.state.title = value;
92
60
  return this;
93
61
  }
94
62
 
95
- style(value: string): this {
96
- this.state.style = value;
63
+ content(value: string): this {
64
+ this.state.content = value;
97
65
  return this;
98
66
  }
99
67
 
100
- class(value: string): this {
101
- this.state.class = value;
68
+ footer(value: string): this {
69
+ this.state.footer = value;
102
70
  return this;
103
71
  }
104
72
 
105
- /**
106
- * Enable actions region
107
- * Creates an empty actions container that components can render into
108
- */
109
- withActions(): this {
110
- this.state.hasActions = true;
73
+ icon(value: string): this {
74
+ this.state.icon = value;
111
75
  return this;
112
76
  }
113
77
 
114
- /* -------------------------
115
- * Render
116
- * ------------------------- */
78
+ /* ═════════════════════════════════════════════════════════════════
79
+ * RENDER
80
+ * ═════════════════════════════════════════════════════════════════ */
117
81
 
118
82
  render(targetId?: string): this {
119
- let container: HTMLElement;
120
-
121
- if (targetId) {
122
- const target = document.querySelector(targetId);
123
- if (!target || !(target instanceof HTMLElement)) {
124
- throw new Error(`Card: Target element "${targetId}" not found`);
125
- }
126
- container = target;
127
- } else {
128
- container = getOrCreateContainer(this._id);
129
- }
83
+ const container = this._setupContainer(targetId);
130
84
 
131
- this.container = container;
132
- const { title, subtitle, content, image, variant, style, class: className, hasActions } = this.state;
85
+ const { title, content, footer, variant, hoverable, icon, style, class: className } = this.state;
133
86
 
134
- // Create card element
135
87
  const card = document.createElement('div');
136
88
  card.className = `jux-card jux-card-${variant}`;
137
89
  card.id = this._id;
90
+ if (hoverable) card.classList.add('jux-card-hoverable');
91
+ if (className) card.className += ` ${className}`;
92
+ if (style) card.setAttribute('style', style);
93
+
94
+ if (title || icon) {
95
+ const header = document.createElement('div');
96
+ header.className = 'jux-card-header';
97
+
98
+ if (icon) {
99
+ const iconEl = document.createElement('span');
100
+ iconEl.className = 'jux-card-icon';
101
+ iconEl.appendChild(renderIcon(icon));
102
+ header.appendChild(iconEl);
103
+ }
138
104
 
139
- if (className) {
140
- card.className += ` ${className}`;
141
- }
142
-
143
- if (style) {
144
- card.setAttribute('style', style);
145
- }
146
-
147
- // Image
148
- if (image) {
149
- const img = document.createElement('img');
150
- img.className = 'jux-card-image';
151
- img.src = image;
152
- img.alt = title || 'Card image';
153
- card.appendChild(img);
154
- }
155
-
156
- // Content wrapper
157
- const cardBody = document.createElement('div');
158
- cardBody.className = 'jux-card-body';
105
+ if (title) {
106
+ const titleEl = document.createElement('span');
107
+ titleEl.className = 'jux-card-title';
108
+ titleEl.textContent = title;
109
+ header.appendChild(titleEl);
110
+ }
159
111
 
160
- // Title
161
- if (title) {
162
- const titleEl = document.createElement('h3');
163
- titleEl.className = 'jux-card-title';
164
- titleEl.textContent = title;
165
- cardBody.appendChild(titleEl);
112
+ card.appendChild(header);
166
113
  }
167
114
 
168
- // Subtitle
169
- if (subtitle) {
170
- const subtitleEl = document.createElement('p');
171
- subtitleEl.className = 'jux-card-subtitle';
172
- subtitleEl.textContent = subtitle;
173
- cardBody.appendChild(subtitleEl);
174
- }
115
+ const body = document.createElement('div');
116
+ body.className = 'jux-card-body';
117
+ body.innerHTML = content;
118
+ card.appendChild(body);
175
119
 
176
- // Content
177
- if (content) {
178
- const contentEl = document.createElement('p');
179
- contentEl.className = 'jux-card-content';
180
- contentEl.textContent = content;
181
- cardBody.appendChild(contentEl);
120
+ if (footer) {
121
+ const footerEl = document.createElement('div');
122
+ footerEl.className = 'jux-card-footer';
123
+ footerEl.innerHTML = footer;
124
+ card.appendChild(footerEl);
182
125
  }
183
126
 
184
- card.appendChild(cardBody);
185
-
186
- // Actions region (empty container for user to populate)
187
- if (hasActions) {
188
- const actionsEl = document.createElement('div');
189
- actionsEl.className = 'jux-card-actions';
190
- actionsEl.id = `${this._id}-actions`;
191
- card.appendChild(actionsEl);
192
- }
127
+ this._wireStandardEvents(card);
128
+
129
+ // Wire sync bindings
130
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
131
+ if (property === 'title') {
132
+ const transform = toComponent || ((v: any) => String(v));
133
+
134
+ stateObj.subscribe((val: any) => {
135
+ const transformed = transform(val);
136
+ const header = card.querySelector('.jux-card-header');
137
+ if (header) {
138
+ header.textContent = transformed;
139
+ }
140
+ this.state.title = transformed;
141
+ });
142
+ }
143
+ else if (property === 'content') {
144
+ const transform = toComponent || ((v: any) => String(v));
145
+
146
+ stateObj.subscribe((val: any) => {
147
+ const transformed = transform(val);
148
+ body.innerHTML = transformed;
149
+ this.state.content = transformed;
150
+ });
151
+ }
152
+ else if (property === 'footer') {
153
+ const transform = toComponent || ((v: any) => String(v));
154
+
155
+ stateObj.subscribe((val: any) => {
156
+ const transformed = transform(val);
157
+ const footerEl = card.querySelector('.jux-card-footer');
158
+ if (footerEl) {
159
+ footerEl.innerHTML = transformed;
160
+ }
161
+ this.state.footer = transformed;
162
+ });
163
+ }
164
+ });
193
165
 
194
166
  container.appendChild(card);
195
167
  return this;
196
168
  }
197
-
198
- /**
199
- * Render to another Jux component's container
200
- */
201
- renderTo(juxComponent: any): this {
202
- if (!juxComponent || typeof juxComponent !== 'object') {
203
- throw new Error('Card.renderTo: Invalid component - not an object');
204
- }
205
-
206
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
207
- throw new Error('Card.renderTo: Invalid component - missing _id (not a Jux component)');
208
- }
209
-
210
- return this.render(`#${juxComponent._id}`);
211
- }
212
169
  }
213
170
 
214
- /**
215
- * Factory helper
216
- */
217
171
  export function card(id: string, options: CardOptions = {}): Card {
218
172
  return new Card(id, options);
219
173
  }
@@ -0,0 +1,315 @@
1
+ import { BaseChart, BaseChartState, ChartDataPoint } from './lib/BaseChart.js';
2
+
3
+ export interface AreaChartOptions {
4
+ data?: ChartDataPoint[];
5
+ title?: string;
6
+ subtitle?: string;
7
+ xAxisLabel?: string;
8
+ yAxisLabel?: string;
9
+ showTicksX?: boolean;
10
+ showTicksY?: boolean;
11
+ showScaleX?: boolean;
12
+ showScaleY?: boolean;
13
+ scaleXUnit?: string;
14
+ scaleYUnit?: string;
15
+ showLegend?: boolean;
16
+ legendOrientation?: 'horizontal' | 'vertical';
17
+ showDataTable?: boolean;
18
+ showDataLabels?: boolean;
19
+ animate?: boolean;
20
+ animationDuration?: number;
21
+ width?: number;
22
+ height?: number;
23
+ colors?: string[];
24
+ class?: string;
25
+ style?: string;
26
+ theme?: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint';
27
+ styleMode?: 'default' | 'gradient' | 'outline' | 'dashed' | 'glow' | 'glass';
28
+ borderRadius?: number;
29
+ }
30
+
31
+ interface AreaChartState extends BaseChartState {
32
+ data: ChartDataPoint[];
33
+ }
34
+
35
+ export class AreaChart extends BaseChart<AreaChartState> {
36
+ constructor(id: string, options: AreaChartOptions = {}) {
37
+ const defaultColors = ['#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6'];
38
+ const randomColor = defaultColors[Math.floor(Math.random() * defaultColors.length)];
39
+
40
+ super(id, {
41
+ data: options.data ?? [],
42
+ title: options.title ?? '',
43
+ subtitle: options.subtitle ?? '',
44
+ xAxisLabel: options.xAxisLabel ?? '',
45
+ yAxisLabel: options.yAxisLabel ?? '',
46
+ showTicksX: options.showTicksX ?? true,
47
+ showTicksY: options.showTicksY ?? true,
48
+ showScaleX: options.showScaleX ?? true,
49
+ showScaleY: options.showScaleY ?? true,
50
+ scaleXUnit: options.scaleXUnit ?? '',
51
+ scaleYUnit: options.scaleYUnit ?? '',
52
+ showLegend: options.showLegend ?? false,
53
+ legendOrientation: options.legendOrientation ?? 'horizontal',
54
+ showDataTable: options.showDataTable ?? false,
55
+ showDataLabels: options.showDataLabels ?? true,
56
+ animate: options.animate ?? true,
57
+ animationDuration: options.animationDuration ?? 800,
58
+ width: options.width ?? 600,
59
+ height: options.height ?? 400,
60
+ colors: options.colors ?? [randomColor],
61
+ class: options.class ?? '',
62
+ style: options.style ?? '',
63
+ theme: options.theme,
64
+ styleMode: options.styleMode ?? 'default',
65
+ borderRadius: options.borderRadius ?? 4
66
+ });
67
+ }
68
+
69
+ protected _getChartClassName(): string {
70
+ return 'jux-areachart';
71
+ }
72
+
73
+ protected _createSVG(): SVGSVGElement {
74
+ const { data, width, height, colors, animate, animationDuration } = this.state;
75
+
76
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
77
+ svg.setAttribute('width', width.toString());
78
+ svg.setAttribute('height', height.toString());
79
+ svg.setAttribute('class', 'jux-areachart-svg');
80
+
81
+ if (!data.length) return svg;
82
+
83
+ if (animate) this._addAnimationStyles(svg);
84
+
85
+ const padding = { top: 40, right: 40, bottom: 60, left: 60 };
86
+ const chartWidth = width - padding.left - padding.right;
87
+ const chartHeight = height - padding.top - padding.bottom;
88
+ const maxValue = Math.max(...data.map(d => d.value));
89
+
90
+ this._renderAreaChart(svg, padding, chartWidth, chartHeight, maxValue);
91
+
92
+ return svg;
93
+ }
94
+
95
+ protected _getBaseStyles(): string {
96
+ return `
97
+ .jux-areachart {
98
+ font-family: var(--chart-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
99
+ display: inline-block;
100
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
101
+ border-radius: 12px;
102
+ background: white;
103
+ padding: 24px;
104
+ }
105
+ .jux-areachart-title {
106
+ margin: 0 0 0.5rem 0;
107
+ font-size: 1.25rem;
108
+ font-weight: 600;
109
+ }
110
+ .jux-areachart-subtitle {
111
+ margin: 0 0 1rem 0;
112
+ font-size: 0.875rem;
113
+ color: #6b7280;
114
+ }
115
+ .jux-areachart-legend {
116
+ display: flex;
117
+ flex-wrap: wrap;
118
+ gap: 1rem;
119
+ margin-top: 1rem;
120
+ justify-content: center;
121
+ }
122
+ .jux-areachart-legend-item {
123
+ display: flex;
124
+ align-items: center;
125
+ gap: 0.5rem;
126
+ }
127
+ .jux-areachart-legend-swatch {
128
+ width: 12px;
129
+ height: 12px;
130
+ border-radius: 2px;
131
+ }
132
+ .jux-areachart-legend-label {
133
+ font-size: 0.875rem;
134
+ color: #374151;
135
+ }
136
+ .jux-areachart-table {
137
+ width: 100%;
138
+ margin-top: 1rem;
139
+ border-collapse: collapse;
140
+ font-size: 0.875rem;
141
+ }
142
+ .jux-areachart-table thead th {
143
+ text-align: center;
144
+ padding: 0.5rem;
145
+ border-bottom: 2px solid #e5e7eb;
146
+ font-weight: 600;
147
+ }
148
+ .jux-areachart-table tbody td {
149
+ padding: 0.5rem;
150
+ border-bottom: 1px solid #f3f4f6;
151
+ text-align: center;
152
+ }
153
+ .jux-areachart-svg {
154
+ font-family: inherit;
155
+ }
156
+ `;
157
+ }
158
+
159
+ private _addAnimationStyles(svg: SVGSVGElement): void {
160
+ const { animationDuration } = this.state;
161
+ const animationId = `area-grow-${this._id}`;
162
+ const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
163
+
164
+ style.textContent = `
165
+ @keyframes ${animationId} {
166
+ from { opacity: 0; transform: scaleY(0); }
167
+ to { opacity: 1; transform: scaleY(1); }
168
+ }
169
+ .jux-area-animated {
170
+ transform-origin: bottom;
171
+ animation: ${animationId} ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
172
+ }
173
+ `;
174
+ svg.appendChild(style);
175
+ }
176
+
177
+ private _renderAreaChart(svg: SVGSVGElement, padding: any, chartWidth: number, chartHeight: number, maxValue: number): void {
178
+ const { data, width, height, colors, showDataLabels, animate, xAxisLabel, yAxisLabel, showTicksX, showTicksY, showScaleX, showScaleY, scaleYUnit } = this.state;
179
+
180
+ const yScale = chartHeight / maxValue;
181
+ const xStep = chartWidth / (data.length - 1 || 1);
182
+ const color = colors[0];
183
+
184
+ // Axes
185
+ if (showScaleY) {
186
+ this._renderAxis(svg, padding.left, padding.top, padding.left, height - padding.bottom);
187
+ if (yAxisLabel && showTicksY) {
188
+ this._renderAxisLabel(svg, yAxisLabel, 20, padding.top + chartHeight / 2, -90);
189
+ }
190
+ if (showTicksY) {
191
+ this._renderYTicks(svg, maxValue, yScale, padding, width, height, scaleYUnit, chartHeight);
192
+ }
193
+ }
194
+
195
+ if (showScaleX) {
196
+ this._renderAxis(svg, padding.left, height - padding.bottom, width - padding.right, height - padding.bottom);
197
+ if (xAxisLabel && showTicksX) {
198
+ this._renderAxisLabel(svg, xAxisLabel, padding.left + chartWidth / 2, height - 15, 0);
199
+ }
200
+ }
201
+
202
+ // Build area path
203
+ let pathData = `M ${padding.left} ${height - padding.bottom}`;
204
+ data.forEach((point, i) => {
205
+ const x = padding.left + (i * xStep);
206
+ const y = height - padding.bottom - (point.value * yScale);
207
+ pathData += ` L ${x} ${y}`;
208
+ });
209
+ pathData += ` L ${padding.left + ((data.length - 1) * xStep)} ${height - padding.bottom} Z`;
210
+
211
+ const areaPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
212
+ areaPath.setAttribute('d', pathData);
213
+ areaPath.setAttribute('fill', color);
214
+ areaPath.setAttribute('fill-opacity', '0.3');
215
+ if (animate) areaPath.classList.add('jux-area-animated');
216
+ svg.appendChild(areaPath);
217
+
218
+ // Line path
219
+ let lineData = '';
220
+ data.forEach((point, i) => {
221
+ const x = padding.left + (i * xStep);
222
+ const y = height - padding.bottom - (point.value * yScale);
223
+ lineData += (i === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`);
224
+ });
225
+
226
+ const linePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
227
+ linePath.setAttribute('d', lineData);
228
+ linePath.setAttribute('stroke', color);
229
+ linePath.setAttribute('stroke-width', '3');
230
+ linePath.setAttribute('fill', 'none');
231
+ svg.appendChild(linePath);
232
+
233
+ // Data points
234
+ data.forEach((point, i) => {
235
+ const x = padding.left + (i * xStep);
236
+ const y = height - padding.bottom - (point.value * yScale);
237
+
238
+ const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
239
+ circle.setAttribute('cx', x.toString());
240
+ circle.setAttribute('cy', y.toString());
241
+ circle.setAttribute('r', '4');
242
+ circle.setAttribute('fill', color);
243
+ circle.setAttribute('stroke', 'white');
244
+ circle.setAttribute('stroke-width', '2');
245
+ svg.appendChild(circle);
246
+
247
+ if (showDataLabels) {
248
+ const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
249
+ label.setAttribute('x', x.toString());
250
+ label.setAttribute('y', (y - 10).toString());
251
+ label.setAttribute('text-anchor', 'middle');
252
+ label.setAttribute('fill', '#374151');
253
+ label.setAttribute('font-size', '11');
254
+ label.setAttribute('font-weight', '600');
255
+ label.textContent = point.value.toString();
256
+ svg.appendChild(label);
257
+ }
258
+ });
259
+ }
260
+
261
+ private _renderAxis(svg: SVGSVGElement, x1: number, y1: number, x2: number, y2: number): void {
262
+ const axis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
263
+ axis.setAttribute('x1', x1.toString());
264
+ axis.setAttribute('y1', y1.toString());
265
+ axis.setAttribute('x2', x2.toString());
266
+ axis.setAttribute('y2', y2.toString());
267
+ axis.setAttribute('stroke', '#9ca3af');
268
+ axis.setAttribute('stroke-width', '2');
269
+ svg.appendChild(axis);
270
+ }
271
+
272
+ private _renderAxisLabel(svg: SVGSVGElement, text: string, x: number, y: number, rotate: number): void {
273
+ const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
274
+ label.setAttribute('x', x.toString());
275
+ label.setAttribute('y', y.toString());
276
+ label.setAttribute('text-anchor', 'middle');
277
+ if (rotate !== 0) label.setAttribute('transform', `rotate(${rotate}, ${x}, ${y})`);
278
+ label.setAttribute('fill', '#6b7280');
279
+ label.setAttribute('font-size', '12');
280
+ label.setAttribute('font-weight', '500');
281
+ label.textContent = text;
282
+ svg.appendChild(label);
283
+ }
284
+
285
+ private _renderYTicks(svg: SVGSVGElement, maxValue: number, yScale: number, padding: any, width: number, height: number, unit: string, chartHeight: number): void {
286
+ const numTicks = 5;
287
+ for (let i = 0; i <= numTicks; i++) {
288
+ const value = (maxValue / numTicks) * i;
289
+ const y = height - padding.bottom - (value * yScale);
290
+
291
+ const gridLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
292
+ gridLine.setAttribute('x1', padding.left.toString());
293
+ gridLine.setAttribute('y1', y.toString());
294
+ gridLine.setAttribute('x2', (width - padding.right).toString());
295
+ gridLine.setAttribute('y2', y.toString());
296
+ gridLine.setAttribute('stroke', '#e5e7eb');
297
+ gridLine.setAttribute('stroke-width', '1');
298
+ gridLine.setAttribute('stroke-dasharray', '4,4');
299
+ svg.appendChild(gridLine);
300
+
301
+ const tickLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
302
+ tickLabel.setAttribute('x', (padding.left - 10).toString());
303
+ tickLabel.setAttribute('y', (y + 4).toString());
304
+ tickLabel.setAttribute('text-anchor', 'end');
305
+ tickLabel.setAttribute('fill', '#6b7280');
306
+ tickLabel.setAttribute('font-size', '11');
307
+ tickLabel.textContent = Math.round(value).toString() + (unit || '');
308
+ svg.appendChild(tickLabel);
309
+ }
310
+ }
311
+ }
312
+
313
+ export function areachart(id: string, options: AreaChartOptions = {}): AreaChart {
314
+ return new AreaChart(id, options);
315
+ }