ui-svelte 0.2.11 → 0.2.12

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 (204) hide show
  1. package/dist/charts/ArcChart.svelte +9 -13
  2. package/dist/charts/ArcChart.svelte.d.ts +3 -3
  3. package/dist/charts/AreaChart.svelte +347 -118
  4. package/dist/charts/AreaChart.svelte.d.ts +33 -4
  5. package/dist/charts/BarChart.svelte +288 -66
  6. package/dist/charts/BarChart.svelte.d.ts +26 -1
  7. package/dist/charts/Candlestick.svelte +53 -50
  8. package/dist/charts/Candlestick.svelte.d.ts +8 -8
  9. package/dist/charts/LineChart.svelte +391 -91
  10. package/dist/charts/LineChart.svelte.d.ts +26 -3
  11. package/dist/charts/PieChart.svelte +333 -92
  12. package/dist/charts/PieChart.svelte.d.ts +33 -5
  13. package/dist/charts/css/arc-chart.css +3 -3
  14. package/dist/charts/css/area-chart.css +127 -29
  15. package/dist/charts/css/bar-chart.css +114 -8
  16. package/dist/charts/css/candlestick.css +2 -0
  17. package/dist/charts/css/line-chart.css +111 -13
  18. package/dist/charts/css/pie-chart.css +92 -20
  19. package/dist/control/Audio.svelte +86 -44
  20. package/dist/control/Audio.svelte.d.ts +4 -1
  21. package/dist/control/Button.svelte +18 -27
  22. package/dist/control/Button.svelte.d.ts +3 -2
  23. package/dist/control/IconButton.svelte +17 -27
  24. package/dist/control/IconButton.svelte.d.ts +3 -3
  25. package/dist/control/Image.svelte +123 -0
  26. package/dist/control/Image.svelte.d.ts +13 -0
  27. package/dist/control/Record.svelte +144 -98
  28. package/dist/control/Record.svelte.d.ts +2 -1
  29. package/dist/control/ToggleGroup.svelte +22 -8
  30. package/dist/control/ToggleGroup.svelte.d.ts +2 -1
  31. package/dist/control/ToggleTheme.svelte +13 -11
  32. package/dist/control/ToggleTheme.svelte.d.ts +3 -2
  33. package/dist/control/Video.svelte +55 -29
  34. package/dist/control/Video.svelte.d.ts +1 -0
  35. package/dist/control/css/btn.css +200 -152
  36. package/dist/control/css/image.css +56 -0
  37. package/dist/control/css/media.css +95 -30
  38. package/dist/control/css/toggle-group.css +269 -84
  39. package/dist/control/css/video.css +1 -14
  40. package/dist/css/animations.css +5 -9
  41. package/dist/css/base.css +13 -347
  42. package/dist/css/decorations.css +402 -0
  43. package/dist/css/rich-text.css +485 -0
  44. package/dist/css/transitions.css +158 -0
  45. package/dist/css/typography.css +291 -0
  46. package/dist/display/Accordion.svelte +28 -4
  47. package/dist/display/Accordion.svelte.d.ts +2 -1
  48. package/dist/display/Alert.svelte +32 -12
  49. package/dist/display/Alert.svelte.d.ts +2 -3
  50. package/dist/display/Avatar.svelte +23 -18
  51. package/dist/display/Avatar.svelte.d.ts +4 -1
  52. package/dist/display/AvatarGroup.svelte +20 -18
  53. package/dist/display/AvatarGroup.svelte.d.ts +6 -3
  54. package/dist/display/Badge.svelte +11 -4
  55. package/dist/display/Badge.svelte.d.ts +2 -1
  56. package/dist/display/Card.svelte +15 -14
  57. package/dist/display/Card.svelte.d.ts +2 -3
  58. package/dist/display/Carousel.svelte +130 -99
  59. package/dist/display/Carousel.svelte.d.ts +6 -4
  60. package/dist/display/ChatBox.svelte +245 -106
  61. package/dist/display/ChatBox.svelte.d.ts +32 -5
  62. package/dist/display/Chip.svelte +31 -17
  63. package/dist/display/Chip.svelte.d.ts +3 -2
  64. package/dist/display/Code.svelte +6 -3
  65. package/dist/display/Code.svelte.d.ts +1 -0
  66. package/dist/display/Collapsible.svelte +30 -4
  67. package/dist/display/Collapsible.svelte.d.ts +2 -1
  68. package/dist/display/Empty.svelte +37 -3
  69. package/dist/display/Empty.svelte.d.ts +3 -0
  70. package/dist/display/Item.svelte +30 -11
  71. package/dist/display/Item.svelte.d.ts +2 -2
  72. package/dist/display/Map.svelte +488 -0
  73. package/dist/display/Map.svelte.d.ts +44 -0
  74. package/dist/display/Section.svelte +14 -12
  75. package/dist/display/Section.svelte.d.ts +2 -3
  76. package/dist/display/Skeleton.svelte +32 -0
  77. package/dist/display/Skeleton.svelte.d.ts +10 -0
  78. package/dist/display/Table.svelte +94 -132
  79. package/dist/display/Table.svelte.d.ts +10 -1
  80. package/dist/display/css/accordion.css +349 -52
  81. package/dist/display/css/alert.css +18 -25
  82. package/dist/display/css/avatar-group.css +38 -75
  83. package/dist/display/css/avatar.css +139 -121
  84. package/dist/display/css/badge.css +50 -27
  85. package/dist/display/css/card.css +51 -71
  86. package/dist/display/css/carousel.css +25 -5
  87. package/dist/display/css/chat-box.css +158 -26
  88. package/dist/display/css/chip.css +142 -68
  89. package/dist/display/css/code.css +2 -6
  90. package/dist/display/css/collapsible.css +349 -45
  91. package/dist/display/css/divider.css +8 -6
  92. package/dist/display/css/empty.css +7 -0
  93. package/dist/display/css/item.css +311 -89
  94. package/dist/display/css/map.css +164 -0
  95. package/dist/display/css/section.css +78 -33
  96. package/dist/display/css/skeleton.css +58 -0
  97. package/dist/display/css/table.css +320 -189
  98. package/dist/form/Checkbox.svelte +11 -5
  99. package/dist/form/Checkbox.svelte.d.ts +2 -1
  100. package/dist/form/ColorField.svelte +543 -0
  101. package/dist/form/ColorField.svelte.d.ts +29 -0
  102. package/dist/form/ComboBox.svelte +24 -9
  103. package/dist/form/ComboBox.svelte.d.ts +2 -2
  104. package/dist/form/CsvField.svelte +62 -136
  105. package/dist/form/CsvField.svelte.d.ts +2 -2
  106. package/dist/form/DateField.svelte +33 -15
  107. package/dist/form/DateField.svelte.d.ts +2 -1
  108. package/dist/form/DateRange.svelte +436 -0
  109. package/dist/form/DateRange.svelte.d.ts +24 -0
  110. package/dist/form/DragDrop.svelte +348 -0
  111. package/dist/form/DragDrop.svelte.d.ts +32 -0
  112. package/dist/form/Dropzone.svelte +28 -8
  113. package/dist/form/Dropzone.svelte.d.ts +2 -2
  114. package/dist/form/Editor.svelte +626 -0
  115. package/dist/form/Editor.svelte.d.ts +50 -0
  116. package/dist/form/ImageCropper.svelte +291 -61
  117. package/dist/form/ImageCropper.svelte.d.ts +15 -1
  118. package/dist/form/{PasswordStrength.svelte → PasswordField.svelte} +58 -24
  119. package/dist/form/{PasswordStrength.svelte.d.ts → PasswordField.svelte.d.ts} +6 -5
  120. package/dist/form/PhoneField.svelte +26 -14
  121. package/dist/form/PhoneField.svelte.d.ts +4 -3
  122. package/dist/form/PinField.svelte +39 -31
  123. package/dist/form/PinField.svelte.d.ts +3 -3
  124. package/dist/form/RadioGroup.svelte +4 -4
  125. package/dist/form/RadioGroup.svelte.d.ts +1 -1
  126. package/dist/form/Select.svelte +20 -19
  127. package/dist/form/Select.svelte.d.ts +2 -2
  128. package/dist/form/Slider.svelte +4 -2
  129. package/dist/form/Slider.svelte.d.ts +1 -0
  130. package/dist/form/TextField.svelte +16 -7
  131. package/dist/form/TextField.svelte.d.ts +2 -2
  132. package/dist/form/Textarea.svelte +15 -6
  133. package/dist/form/Textarea.svelte.d.ts +2 -2
  134. package/dist/form/Toggle.svelte +1 -1
  135. package/dist/form/css/checkbox.css +18 -2
  136. package/dist/form/css/color-field.css +141 -0
  137. package/dist/form/css/control.css +193 -82
  138. package/dist/form/css/csv-field.css +226 -0
  139. package/dist/form/css/date-range.css +122 -0
  140. package/dist/form/css/date.css +24 -2
  141. package/dist/form/css/drag-drop.css +271 -0
  142. package/dist/form/css/dropzone.css +153 -34
  143. package/dist/form/css/editor.css +367 -0
  144. package/dist/form/css/field.css +4 -0
  145. package/dist/form/css/image-cropper.css +223 -22
  146. package/dist/form/css/radio-group.css +1 -1
  147. package/dist/form/css/select.css +2 -2
  148. package/dist/form/css/slider.css +1 -0
  149. package/dist/form/css/textarea.css +178 -75
  150. package/dist/form/css/toggle.css +3 -3
  151. package/dist/hooks/use-table.svelte.d.ts +1 -0
  152. package/dist/hooks/use-table.svelte.js +6 -0
  153. package/dist/icons/index.d.ts +30 -2
  154. package/dist/icons/index.js +32 -4
  155. package/dist/index.css +16 -1
  156. package/dist/index.d.ts +12 -4
  157. package/dist/index.js +11 -3
  158. package/dist/layout/AppBar.svelte +22 -14
  159. package/dist/layout/AppBar.svelte.d.ts +2 -1
  160. package/dist/layout/Footer.svelte +19 -11
  161. package/dist/layout/Footer.svelte.d.ts +2 -1
  162. package/dist/layout/Provider.svelte +27 -4
  163. package/dist/layout/Provider.svelte.d.ts +3 -1
  164. package/dist/layout/css/app-bar.css +63 -66
  165. package/dist/layout/css/footer.css +62 -65
  166. package/dist/navigation/BottomNav.svelte +41 -13
  167. package/dist/navigation/FooterGroup.svelte +1 -1
  168. package/dist/navigation/NavMenu.svelte +47 -23
  169. package/dist/navigation/NavMenu.svelte.d.ts +29 -0
  170. package/dist/navigation/Pagination.svelte +158 -0
  171. package/dist/navigation/Pagination.svelte.d.ts +18 -0
  172. package/dist/navigation/SideNav.svelte +30 -25
  173. package/dist/navigation/SideNav.svelte.d.ts +2 -3
  174. package/dist/navigation/Tabs.svelte +17 -7
  175. package/dist/navigation/Tabs.svelte.d.ts +2 -2
  176. package/dist/navigation/css/bottom-nav.css +279 -257
  177. package/dist/navigation/css/footer-group.css +1 -1
  178. package/dist/navigation/css/footer-nav.css +1 -1
  179. package/dist/navigation/css/nav-menu.css +332 -106
  180. package/dist/navigation/css/pagination.css +74 -0
  181. package/dist/navigation/css/side-nav.css +515 -75
  182. package/dist/navigation/css/tabs.css +246 -52
  183. package/dist/overlay/Command.svelte +340 -0
  184. package/dist/overlay/Command.svelte.d.ts +24 -25
  185. package/dist/overlay/Drawer.svelte +49 -21
  186. package/dist/overlay/Drawer.svelte.d.ts +2 -2
  187. package/dist/overlay/Dropdown.svelte +3 -3
  188. package/dist/overlay/Modal.svelte +51 -16
  189. package/dist/overlay/Modal.svelte.d.ts +3 -3
  190. package/dist/overlay/Toast.svelte +41 -17
  191. package/dist/overlay/Toast.svelte.d.ts +1 -1
  192. package/dist/overlay/Tooltip.svelte +40 -26
  193. package/dist/overlay/Tooltip.svelte.d.ts +2 -2
  194. package/dist/overlay/css/command.css +80 -0
  195. package/dist/overlay/css/drawer.css +63 -24
  196. package/dist/overlay/css/dropdown.css +1 -1
  197. package/dist/overlay/css/hovercard.css +1 -1
  198. package/dist/overlay/css/modal.css +27 -27
  199. package/dist/overlay/css/toast.css +17 -29
  200. package/dist/overlay/css/tooltip.css +83 -66
  201. package/dist/stores/theme.svelte.js +26 -1
  202. package/dist/stores/toast.svelte.d.ts +4 -4
  203. package/dist/stores/toast.svelte.js +2 -2
  204. package/package.json +1 -1
@@ -1,11 +1,17 @@
1
1
  <script lang="ts">
2
2
  import { cn } from '../utils/class-names.js';
3
- import { onMount } from 'svelte';
3
+ import { onMount, untrack, type Snippet } from 'svelte';
4
+
5
+ type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted';
6
+ type Size = 'sm' | 'md' | 'lg' | 'xl';
7
+ type LegendPosition = 'top' | 'right' | 'bottom' | 'left' | 'none';
8
+ type Palette = 'default' | 'rainbow' | 'ocean' | 'sunset' | 'forest' | 'neon';
4
9
 
5
10
  type TooltipData = {
6
11
  point: DataPoint;
7
12
  seriesName: string;
8
13
  color: Color;
14
+ seriesIndex: number;
9
15
  };
10
16
 
11
17
  type LinearScale = {
@@ -15,8 +21,6 @@
15
21
  range: [number, number];
16
22
  };
17
23
 
18
- type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted';
19
-
20
24
  type DataPoint = {
21
25
  x: number;
22
26
  y: number;
@@ -28,24 +32,46 @@
28
32
  color?: Color;
29
33
  };
30
34
 
35
+ type Margin = {
36
+ top: number;
37
+ right: number;
38
+ bottom: number;
39
+ left: number;
40
+ };
41
+
31
42
  type Props = {
32
43
  data?: DataPoint[];
33
44
  series?: Series[];
34
45
  color?: Color;
35
46
  colors?: Color[];
36
- showLine?: boolean;
47
+ margin?: Margin;
48
+ hideLine?: boolean;
37
49
  showPoints?: boolean;
38
- showGrid?: boolean;
39
- showLegend?: boolean;
50
+ hideGrid?: boolean;
51
+ hideLegend?: boolean;
40
52
  curve?: 'linear' | 'smooth';
41
53
  strokeWidth?: number;
42
54
  fillOpacity?: number;
43
55
  stacked?: boolean;
44
- gradient?: boolean;
56
+ hideGradient?: boolean;
45
57
  loading?: boolean;
46
58
  empty?: boolean;
47
59
  emptyText?: string;
48
60
  rootClass?: string;
61
+ chartClass?: string;
62
+ size?: Size;
63
+ palette?: Palette;
64
+ legendPosition?: LegendPosition;
65
+ disableAnimation?: boolean;
66
+ animationDuration?: number;
67
+ valueFormatter?: (value: number) => string;
68
+ xFormatter?: (value: number) => string;
69
+ onClick?: (point: DataPoint, series: Series, index: number) => void;
70
+ onHover?: (point: DataPoint | null, series: Series | null, index: number) => void;
71
+ hideXAxis?: boolean;
72
+ hideYAxis?: boolean;
73
+ showGlow?: boolean;
74
+ tooltipContent?: Snippet<[{ point: DataPoint; series: Series; color: Color }]>;
49
75
  };
50
76
 
51
77
  let {
@@ -53,28 +79,53 @@
53
79
  series = [],
54
80
  color = 'primary' as Color,
55
81
  colors = ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'muted'] as Color[],
56
- showLine = true,
82
+ margin = { top: 20, right: 20, bottom: 40, left: 50 },
83
+ hideLine = false,
57
84
  showPoints = false,
58
- showGrid = true,
59
- showLegend = false,
85
+ hideGrid = false,
86
+ hideLegend = false,
60
87
  curve = 'linear' as 'linear' | 'smooth',
61
88
  strokeWidth = 2,
62
89
  fillOpacity = 0.3,
63
90
  stacked = false,
64
- gradient = true,
91
+ hideGradient = false,
65
92
  loading = false,
66
93
  empty = false,
67
94
  emptyText = 'No data available',
68
- rootClass
95
+ rootClass,
96
+ chartClass,
97
+ size = 'md',
98
+ palette,
99
+ legendPosition = 'bottom',
100
+ disableAnimation = false,
101
+ animationDuration = 800,
102
+ valueFormatter,
103
+ xFormatter,
104
+ onClick,
105
+ onHover,
106
+ hideXAxis = false,
107
+ hideYAxis = false,
108
+ showGlow = false,
109
+ tooltipContent
69
110
  }: Props = $props();
70
111
 
71
- let containerEl: HTMLDivElement | undefined = $state();
72
- let containerSize = $state({ width: 0, height: 0 });
112
+ const sizePresets: Record<Size, { height: number; pointRadius: number; fontSize: number }> = {
113
+ sm: { height: 200, pointRadius: 2, fontSize: 10 },
114
+ md: { height: 300, pointRadius: 3, fontSize: 12 },
115
+ lg: { height: 400, pointRadius: 4, fontSize: 14 },
116
+ xl: { height: 500, pointRadius: 5, fontSize: 16 }
117
+ };
73
118
 
74
- let width = $derived(containerSize.width);
75
- let height = $derived(containerSize.height);
119
+ const colorPalettes: Record<Palette, Color[]> = {
120
+ default: ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'muted'],
121
+ rainbow: ['danger', 'warning', 'success', 'info', 'primary', 'secondary', 'muted'],
122
+ ocean: ['info', 'primary', 'secondary', 'success', 'muted', 'warning', 'danger'],
123
+ sunset: ['warning', 'danger', 'secondary', 'primary', 'info', 'success', 'muted'],
124
+ forest: ['success', 'primary', 'info', 'secondary', 'muted', 'warning', 'danger'],
125
+ neon: ['secondary', 'primary', 'success', 'warning', 'danger', 'info', 'muted']
126
+ };
76
127
 
77
- const colorClass = {
128
+ const colorClass: Record<Color, string> = {
78
129
  primary: 'is-primary',
79
130
  secondary: 'is-secondary',
80
131
  success: 'is-success',
@@ -84,6 +135,27 @@
84
135
  muted: 'is-muted'
85
136
  };
86
137
 
138
+ function getSeriesColor(seriesItem: Series, index: number): Color {
139
+ if (seriesItem.color) return seriesItem.color;
140
+ if (palette) {
141
+ const paletteColors = colorPalettes[palette];
142
+ return paletteColors[index % paletteColors.length];
143
+ }
144
+ return colors[index % colors.length];
145
+ }
146
+
147
+ let containerEl: HTMLDivElement | undefined = $state();
148
+ let containerSize = $state({ width: 0, height: 0 });
149
+ let animationProgress = $state(0);
150
+ let animationFrameId: number | null = null;
151
+
152
+ let sizeConfig = $derived(sizePresets[size]);
153
+ let width = $derived(containerSize.width || 600);
154
+ let height = $derived(containerSize.height || sizeConfig.height);
155
+
156
+ let innerWidth = $derived(width - margin.left - margin.right);
157
+ let innerHeight = $derived(height - margin.top - margin.bottom);
158
+
87
159
  function createLinearScale(domain: [number, number], range: [number, number]): LinearScale {
88
160
  const [d0, d1] = domain;
89
161
  const [r0, r1] = range;
@@ -103,6 +175,7 @@
103
175
  }
104
176
 
105
177
  function formatNumber(value: number): string {
178
+ if (valueFormatter) return valueFormatter(value);
106
179
  if (value === 0) return '0';
107
180
  const abs = Math.abs(value);
108
181
  if (abs >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
@@ -112,12 +185,17 @@
112
185
  return value.toFixed(0);
113
186
  }
114
187
 
188
+ function formatX(value: number): string {
189
+ if (xFormatter) return xFormatter(value);
190
+ return formatNumber(value);
191
+ }
192
+
115
193
  let normalizedSeries = $derived.by((): Series[] => {
116
194
  if (series.length > 0) {
117
195
  return series.map((s, i) => ({
118
196
  name: s.name,
119
197
  data: s.data,
120
- color: s.color || colors[i % colors.length]
198
+ color: getSeriesColor(s, i)
121
199
  }));
122
200
  }
123
201
 
@@ -163,28 +241,33 @@
163
241
  function createAreaPath(points: DataPoint[], baselineY: number, smooth: boolean = false): string {
164
242
  if (points.length === 0) return '';
165
243
 
166
- const xScale = createLinearScale(xDomain, [0, width]);
167
- const yScale = createLinearScale(yDomain, [height, 0]);
244
+ const xScale = createLinearScale(xDomain, [0, innerWidth]);
245
+ const yScale = createLinearScale(yDomain, [innerHeight, 0]);
168
246
 
169
- let path = `M ${xScale(points[0].x)} ${baselineY}`;
170
- path += ` L ${xScale(points[0].x)} ${yScale(points[0].y)}`;
247
+ const animatedPoints = points.map((p) => ({
248
+ x: p.x,
249
+ y: p.y * animationProgress
250
+ }));
171
251
 
172
- if (smooth && points.length > 1) {
173
- for (let i = 1; i < points.length; i++) {
174
- const x0 = xScale(points[i - 1].x);
175
- const y0 = yScale(points[i - 1].y);
176
- const x1 = xScale(points[i].x);
177
- const y1 = yScale(points[i].y);
252
+ let path = `M ${xScale(animatedPoints[0].x)} ${baselineY}`;
253
+ path += ` L ${xScale(animatedPoints[0].x)} ${yScale(animatedPoints[0].y)}`;
254
+
255
+ if (smooth && animatedPoints.length > 1) {
256
+ for (let i = 1; i < animatedPoints.length; i++) {
257
+ const x0 = xScale(animatedPoints[i - 1].x);
258
+ const y0 = yScale(animatedPoints[i - 1].y);
259
+ const x1 = xScale(animatedPoints[i].x);
260
+ const y1 = yScale(animatedPoints[i].y);
178
261
  const cpx = (x0 + x1) / 2;
179
262
  path += ` C ${cpx} ${y0}, ${cpx} ${y1}, ${x1} ${y1}`;
180
263
  }
181
264
  } else {
182
- for (let i = 1; i < points.length; i++) {
183
- path += ` L ${xScale(points[i].x)} ${yScale(points[i].y)}`;
265
+ for (let i = 1; i < animatedPoints.length; i++) {
266
+ path += ` L ${xScale(animatedPoints[i].x)} ${yScale(animatedPoints[i].y)}`;
184
267
  }
185
268
  }
186
269
 
187
- path += ` L ${xScale(points[points.length - 1].x)} ${baselineY}`;
270
+ path += ` L ${xScale(animatedPoints[animatedPoints.length - 1].x)} ${baselineY}`;
188
271
  path += ' Z';
189
272
 
190
273
  return path;
@@ -193,23 +276,28 @@
193
276
  function createLinePath(points: DataPoint[], smooth: boolean = false): string {
194
277
  if (points.length === 0) return '';
195
278
 
196
- const xScale = createLinearScale(xDomain, [0, width]);
197
- const yScale = createLinearScale(yDomain, [height, 0]);
279
+ const xScale = createLinearScale(xDomain, [0, innerWidth]);
280
+ const yScale = createLinearScale(yDomain, [innerHeight, 0]);
281
+
282
+ const animatedPoints = points.map((p) => ({
283
+ x: p.x,
284
+ y: p.y * animationProgress
285
+ }));
198
286
 
199
- let path = `M ${xScale(points[0].x)} ${yScale(points[0].y)}`;
287
+ let path = `M ${xScale(animatedPoints[0].x)} ${yScale(animatedPoints[0].y)}`;
200
288
 
201
- if (smooth && points.length > 1) {
202
- for (let i = 1; i < points.length; i++) {
203
- const x0 = xScale(points[i - 1].x);
204
- const y0 = yScale(points[i - 1].y);
205
- const x1 = xScale(points[i].x);
206
- const y1 = yScale(points[i].y);
289
+ if (smooth && animatedPoints.length > 1) {
290
+ for (let i = 1; i < animatedPoints.length; i++) {
291
+ const x0 = xScale(animatedPoints[i - 1].x);
292
+ const y0 = yScale(animatedPoints[i - 1].y);
293
+ const x1 = xScale(animatedPoints[i].x);
294
+ const y1 = yScale(animatedPoints[i].y);
207
295
  const cpx = (x0 + x1) / 2;
208
296
  path += ` C ${cpx} ${y0}, ${cpx} ${y1}, ${x1} ${y1}`;
209
297
  }
210
298
  } else {
211
- for (let i = 1; i < points.length; i++) {
212
- path += ` L ${xScale(points[i].x)} ${yScale(points[i].y)}`;
299
+ for (let i = 1; i < animatedPoints.length; i++) {
300
+ path += ` L ${xScale(animatedPoints[i].x)} ${yScale(animatedPoints[i].y)}`;
213
301
  }
214
302
  }
215
303
 
@@ -217,19 +305,18 @@
217
305
  }
218
306
 
219
307
  function createGridLines(): Array<{ x: number; y: number; value: number }> {
220
- const yScale = createLinearScale(yDomain, [height, 0]);
308
+ const yScale = createLinearScale(yDomain, [innerHeight, 0]);
221
309
  const yTicks = 5;
222
310
 
223
311
  return Array.from({ length: yTicks + 1 }, (_, i) => {
224
312
  const value = yDomain[0] + (yDomain[1] - yDomain[0]) * (i / yTicks);
225
- return { x: width, y: yScale(value), value };
313
+ return { x: innerWidth, y: yScale(value), value };
226
314
  });
227
315
  }
228
316
 
229
- // NUEVA FUNCIÓN: Crear ticks para el eje X
230
317
  function createXAxisTicks(): Array<{ x: number; value: number }> {
231
- const xScale = createLinearScale(xDomain, [0, width]);
232
- const xTicks = 5;
318
+ const xScale = createLinearScale(xDomain, [0, innerWidth]);
319
+ const xTicks = Math.min(6, Math.max(2, Math.floor(innerWidth / 80)));
233
320
 
234
321
  return Array.from({ length: xTicks + 1 }, (_, i) => {
235
322
  const value = xDomain[0] + (xDomain[1] - xDomain[0]) * (i / xTicks);
@@ -274,26 +361,43 @@
274
361
  let tooltipData = $state<TooltipData | null>(null);
275
362
  let tooltipPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 });
276
363
  let isTooltipActive = $state(false);
364
+ let hoveredSeriesIndex = $state<number | null>(null);
277
365
 
278
366
  function handlePointHover(
279
367
  point: DataPoint,
280
- seriesName: string,
281
- event: MouseEvent,
282
- pointColor: Color
368
+ seriesItem: Series,
369
+ seriesIndex: number,
370
+ event: MouseEvent
283
371
  ): void {
284
372
  const target = event.target as SVGCircleElement;
285
373
  const rect = target.getBoundingClientRect();
286
374
 
287
- tooltipData = { point, seriesName, color: pointColor };
375
+ tooltipData = {
376
+ point,
377
+ seriesName: seriesItem.name,
378
+ color: seriesItem.color as Color,
379
+ seriesIndex
380
+ };
288
381
  tooltipPosition = {
289
- x: rect.right + 8,
290
- y: rect.top + rect.height / 2
382
+ x: rect.left + rect.width / 2,
383
+ y: rect.top - 10
291
384
  };
292
385
  isTooltipActive = true;
386
+ hoveredSeriesIndex = seriesIndex;
387
+
388
+ if (onHover) {
389
+ onHover(point, seriesItem, seriesIndex);
390
+ }
293
391
  }
294
392
 
295
393
  function handlePointLeave(): void {
296
394
  isTooltipActive = false;
395
+ hoveredSeriesIndex = null;
396
+
397
+ if (onHover) {
398
+ onHover(null, null, -1);
399
+ }
400
+
297
401
  setTimeout(() => {
298
402
  if (!isTooltipActive) {
299
403
  tooltipData = null;
@@ -301,6 +405,36 @@
301
405
  }, 100);
302
406
  }
303
407
 
408
+ function handlePointClick(point: DataPoint, seriesItem: Series, index: number): void {
409
+ if (onClick) {
410
+ onClick(point, seriesItem, index);
411
+ }
412
+ }
413
+
414
+ let containerLayoutClass = $derived(() => {
415
+ switch (legendPosition) {
416
+ case 'top':
417
+ return 'flex-col-reverse';
418
+ case 'bottom':
419
+ return 'flex-col';
420
+ case 'left':
421
+ return 'flex-row-reverse';
422
+ case 'right':
423
+ default:
424
+ return 'flex-row';
425
+ }
426
+ });
427
+
428
+ let legendLayoutClass = $derived(() => {
429
+ switch (legendPosition) {
430
+ case 'top':
431
+ case 'bottom':
432
+ return 'flex-row flex-wrap';
433
+ default:
434
+ return 'flex-col';
435
+ }
436
+ });
437
+
304
438
  onMount(() => {
305
439
  const updateSize = () => {
306
440
  if (containerEl) {
@@ -309,19 +443,65 @@
309
443
  }
310
444
  };
311
445
 
446
+ const handleScroll = () => {
447
+ if (isTooltipActive) {
448
+ isTooltipActive = false;
449
+ hoveredSeriesIndex = null;
450
+ tooltipData = null;
451
+ }
452
+ };
453
+
312
454
  updateSize();
313
455
  const resizeObserver = new ResizeObserver(updateSize);
314
456
  if (containerEl) {
315
457
  resizeObserver.observe(containerEl);
316
458
  }
317
459
 
460
+ window.addEventListener('scroll', handleScroll, true);
461
+
318
462
  return () => {
463
+ if (animationFrameId !== null) {
464
+ cancelAnimationFrame(animationFrameId);
465
+ }
319
466
  resizeObserver.disconnect();
467
+ window.removeEventListener('scroll', handleScroll, true);
320
468
  };
321
469
  });
470
+
471
+ $effect(() => {
472
+ const _ = normalizedSeries;
473
+
474
+ if (animationFrameId !== null) {
475
+ cancelAnimationFrame(animationFrameId);
476
+ animationFrameId = null;
477
+ }
478
+
479
+ if (!disableAnimation) {
480
+ const startTime = Date.now();
481
+ const startProgress = untrack(() => animationProgress);
482
+
483
+ const animate = () => {
484
+ const now = Date.now();
485
+ const progress = Math.min((now - startTime) / animationDuration, 1);
486
+ const easeProgress = 1 - Math.pow(1 - progress, 3);
487
+
488
+ animationProgress = startProgress + (1 - startProgress) * easeProgress;
489
+
490
+ if (progress < 1) {
491
+ animationFrameId = requestAnimationFrame(animate);
492
+ } else {
493
+ animationFrameId = null;
494
+ }
495
+ };
496
+
497
+ animationFrameId = requestAnimationFrame(animate);
498
+ } else {
499
+ animationProgress = 1;
500
+ }
501
+ });
322
502
  </script>
323
503
 
324
- <div class={cn('area-chart-container', rootClass)}>
504
+ <div class={cn('area-chart-container', `is-${size}`, containerLayoutClass(), rootClass)}>
325
505
  {#if loading}
326
506
  <div class="area-chart-loading">
327
507
  <svg class="area-chart-loading-spinner" viewBox="0 0 24 24">
@@ -353,11 +533,11 @@
353
533
  <span>{emptyText}</span>
354
534
  </div>
355
535
  {:else}
356
- <div bind:this={containerEl} class="area-chart">
357
- <svg class="area-chart-svg">
536
+ <div bind:this={containerEl} class={cn('area-chart', `is-${size}`, chartClass)}>
537
+ <svg class="area-chart-svg" {width} {height}>
358
538
  <defs>
359
539
  {#each normalizedSeries as s, i}
360
- {#if gradient}
540
+ {#if !hideGradient}
361
541
  <linearGradient id="area-gradient-{i}" x1="0%" y1="0%" x2="0%" y2="100%">
362
542
  <stop
363
543
  offset="0%"
@@ -372,16 +552,28 @@
372
552
  </linearGradient>
373
553
  {/if}
374
554
  {/each}
555
+
556
+ {#if showGlow}
557
+ {#each normalizedSeries as s, i}
558
+ <filter id="area-glow-{i}" x="-50%" y="-50%" width="200%" height="200%">
559
+ <feGaussianBlur stdDeviation="3" result="coloredBlur" />
560
+ <feMerge>
561
+ <feMergeNode in="coloredBlur" />
562
+ <feMergeNode in="SourceGraphic" />
563
+ </feMerge>
564
+ </filter>
565
+ {/each}
566
+ {/if}
375
567
  </defs>
376
568
 
377
- <g>
569
+ <g transform="translate({margin.left}, {margin.top})">
378
570
  {#if normalizedSeries.length > 0}
379
571
  {@const grid = createGridLines()}
380
572
  {@const xTicks = createXAxisTicks()}
381
- {@const xScale = createLinearScale(xDomain, [0, width])}
382
- {@const yScale = createLinearScale(yDomain, [height, 0])}
573
+ {@const xScale = createLinearScale(xDomain, [0, innerWidth])}
574
+ {@const yScale = createLinearScale(yDomain, [innerHeight, 0])}
383
575
 
384
- {#if showGrid}
576
+ {#if !hideGrid}
385
577
  <g class="area-chart-grid">
386
578
  {#each grid as line}
387
579
  <line x1={0} y1={line.y} x2={line.x} y2={line.y} class="area-chart-grid-line" />
@@ -390,67 +582,90 @@
390
582
  {/if}
391
583
 
392
584
  <g class="area-chart-axis">
393
- <line x1={0} y1={height} x2={width} y2={height} class="area-chart-axis-line" />
394
- <line x1={0} y1={0} x2={0} y2={height} class="area-chart-axis-line" />
395
-
396
- <!-- Etiquetas del eje Y -->
397
- {#each grid as line}
398
- <text
399
- x={-10}
400
- y={line.y}
401
- class="area-chart-axis-label"
402
- text-anchor="end"
403
- dominant-baseline="middle"
404
- >
405
- {formatNumber(line.value)}
406
- </text>
407
- {/each}
408
-
409
- <!-- NUEVO: Etiquetas del eje X -->
410
- {#each xTicks as tick}
411
- <text
412
- x={tick.x}
413
- y={height + 15}
414
- class="area-chart-axis-label"
415
- text-anchor="middle"
416
- dominant-baseline="hanging"
417
- >
418
- {formatNumber(tick.value)}
419
- </text>
420
- {/each}
585
+ {#if !hideXAxis}
586
+ <line
587
+ x1={0}
588
+ y1={innerHeight}
589
+ x2={innerWidth}
590
+ y2={innerHeight}
591
+ class="area-chart-axis-line"
592
+ />
593
+ {#each xTicks as tick}
594
+ <text
595
+ x={tick.x}
596
+ y={innerHeight + 20}
597
+ class="area-chart-axis-label"
598
+ text-anchor="middle"
599
+ style="font-size: {sizeConfig.fontSize}px;"
600
+ >
601
+ {formatX(tick.value)}
602
+ </text>
603
+ {/each}
604
+ {/if}
605
+
606
+ {#if !hideYAxis}
607
+ <line x1={0} y1={0} x2={0} y2={innerHeight} class="area-chart-axis-line" />
608
+ {#each grid as line}
609
+ <text
610
+ x={-10}
611
+ y={line.y}
612
+ class="area-chart-axis-label"
613
+ text-anchor="end"
614
+ dominant-baseline="middle"
615
+ style="font-size: {sizeConfig.fontSize}px;"
616
+ >
617
+ {formatNumber(line.value)}
618
+ </text>
619
+ {/each}
620
+ {/if}
421
621
  </g>
422
622
 
423
623
  {#each stacked ? stackedData : normalizedSeries as s, i}
424
624
  {@const baselineY =
425
625
  stacked && s.data[0]?.y !== undefined
426
626
  ? yScale((s.data[0] as StackedDataPoint).y0)
427
- : height}
627
+ : innerHeight}
628
+ {@const isHovered = hoveredSeriesIndex === i}
428
629
 
429
630
  <path
430
631
  d={createAreaPath(s.data, baselineY, curve === 'smooth')}
431
- fill={gradient ? `url(#area-gradient-${i})` : undefined}
432
- class="area-chart-area is-{s.color}"
433
- style={!gradient ? `opacity: ${fillOpacity}` : ''}
632
+ fill={!hideGradient ? `url(#area-gradient-${i})` : undefined}
633
+ class={cn(
634
+ 'area-chart-area',
635
+ colorClass[s.color!],
636
+ isHovered && 'is-hovered',
637
+ showGlow && 'has-glow'
638
+ )}
639
+ style={hideGradient ? `opacity: ${fillOpacity}` : ''}
640
+ filter={showGlow ? `url(#area-glow-${i})` : undefined}
434
641
  />
435
642
 
436
- {#if showLine}
643
+ {#if !hideLine}
437
644
  <path
438
645
  d={createLinePath(s.data, curve === 'smooth')}
439
- class="area-chart-line is-{s.color}"
646
+ class={cn(
647
+ 'area-chart-line',
648
+ colorClass[s.color!],
649
+ isHovered && 'is-hovered',
650
+ showGlow && 'has-glow'
651
+ )}
440
652
  style="stroke-width: {strokeWidth};"
653
+ filter={showGlow ? `url(#area-glow-${i})` : undefined}
441
654
  />
442
655
  {/if}
443
656
 
444
657
  {#if showPoints}
445
- {#each s.data as point}
658
+ {#each s.data as point, pointIndex}
446
659
  <!-- svelte-ignore a11y_no_static_element_interactions -->
660
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
447
661
  <circle
448
662
  cx={xScale(point.x)}
449
- cy={yScale(point.y)}
450
- r={3}
451
- class="area-chart-point is-{s.color}"
452
- onmouseenter={(e) => handlePointHover(point, s.name, e, s.color!)}
663
+ cy={yScale(point.y * animationProgress)}
664
+ r={sizeConfig.pointRadius}
665
+ class={cn('area-chart-point', colorClass[s.color!], showGlow && 'has-glow')}
666
+ onmouseenter={(e) => handlePointHover(point, s, i, e)}
453
667
  onmouseleave={handlePointLeave}
668
+ onclick={() => handlePointClick(point, s, pointIndex)}
454
669
  />
455
670
  {/each}
456
671
  {/if}
@@ -463,29 +678,43 @@
463
678
  {#if tooltipData && isTooltipActive}
464
679
  <div
465
680
  class="area-chart-tooltip"
466
- style="top: {tooltipPosition.y}px; left: {tooltipPosition.x}px;"
681
+ style="left: {tooltipPosition.x}px; top: {tooltipPosition.y}px;"
467
682
  >
468
- <div class="area-chart-tooltip-content">
469
- {#if tooltipData.seriesName !== 'Data'}
470
- <div class="area-chart-tooltip-title">{tooltipData.seriesName}</div>
471
- {/if}
472
- <div class="area-chart-tooltip-row">
473
- <span class="area-chart-tooltip-label">x:</span>
474
- <span class="area-chart-tooltip-value">{tooltipData.point.x}</span>
475
- </div>
476
- <div class="area-chart-tooltip-row">
477
- <span class="area-chart-tooltip-label">y:</span>
478
- <span class="area-chart-tooltip-value">{tooltipData.point.y}</span>
683
+ {#if tooltipContent}
684
+ {@render tooltipContent({
685
+ point: tooltipData.point,
686
+ series: normalizedSeries[tooltipData.seriesIndex],
687
+ color: tooltipData.color
688
+ })}
689
+ {:else}
690
+ <div class="area-chart-tooltip-content">
691
+ {#if tooltipData.seriesName !== 'Data'}
692
+ <div class="area-chart-tooltip-title">{tooltipData.seriesName}</div>
693
+ {/if}
694
+ <div class="area-chart-tooltip-row">
695
+ <div class="area-chart-tooltip-color {colorClass[tooltipData.color]}"></div>
696
+ <span class="area-chart-tooltip-value">
697
+ {formatX(tooltipData.point.x)}: {formatNumber(tooltipData.point.y)}
698
+ </span>
699
+ </div>
479
700
  </div>
480
- </div>
701
+ {/if}
481
702
  </div>
482
703
  {/if}
483
704
 
484
- {#if showLegend && normalizedSeries.length > 1}
485
- <div class="area-chart-legend">
486
- {#each normalizedSeries as s}
487
- <div class="area-chart-legend-item">
488
- <div class="area-chart-legend-color is-{s.color}"></div>
705
+ {#if !hideLegend && normalizedSeries.length > 1 && legendPosition !== 'none'}
706
+ <div class={cn('area-chart-legend', legendLayoutClass())}>
707
+ {#each normalizedSeries as s, i}
708
+ {@const isHovered = hoveredSeriesIndex === i}
709
+ <div
710
+ class={cn('area-chart-legend-item', isHovered && 'is-selected')}
711
+ onmouseenter={() => (hoveredSeriesIndex = i)}
712
+ onmouseleave={() => (hoveredSeriesIndex = null)}
713
+ role="button"
714
+ tabindex="0"
715
+ onkeydown={(e) => e.key === 'Enter' && (hoveredSeriesIndex = i)}
716
+ >
717
+ <div class={cn('area-chart-legend-color', colorClass[s.color!])}></div>
489
718
  <span>{s.name}</span>
490
719
  </div>
491
720
  {/each}