ui-svelte 0.2.10 → 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 (206) 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 +427 -2
  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 +54 -26
  51. package/dist/display/Avatar.svelte.d.ts +7 -1
  52. package/dist/display/AvatarGroup.svelte +26 -18
  53. package/dist/display/AvatarGroup.svelte.d.ts +9 -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 +31 -18
  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 -44
  83. package/dist/display/css/avatar.css +152 -123
  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/PasswordField.svelte +120 -75
  119. package/dist/form/PasswordField.svelte.d.ts +9 -10
  120. package/dist/form/PhoneField.svelte +34 -16
  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 +11 -1
  135. package/dist/form/Toggle.svelte.d.ts +2 -0
  136. package/dist/form/css/checkbox.css +18 -2
  137. package/dist/form/css/color-field.css +141 -0
  138. package/dist/form/css/control.css +193 -82
  139. package/dist/form/css/csv-field.css +226 -0
  140. package/dist/form/css/date-range.css +122 -0
  141. package/dist/form/css/date.css +24 -2
  142. package/dist/form/css/drag-drop.css +271 -0
  143. package/dist/form/css/dropzone.css +153 -34
  144. package/dist/form/css/editor.css +367 -0
  145. package/dist/form/css/field.css +4 -0
  146. package/dist/form/css/image-cropper.css +223 -22
  147. package/dist/form/css/password.css +1 -1
  148. package/dist/form/css/radio-group.css +1 -1
  149. package/dist/form/css/select.css +2 -2
  150. package/dist/form/css/slider.css +1 -0
  151. package/dist/form/css/textarea.css +178 -75
  152. package/dist/form/css/toggle.css +11 -2
  153. package/dist/hooks/use-table.svelte.d.ts +1 -0
  154. package/dist/hooks/use-table.svelte.js +6 -0
  155. package/dist/icons/index.d.ts +38 -2
  156. package/dist/icons/index.js +40 -4
  157. package/dist/index.css +16 -1
  158. package/dist/index.d.ts +11 -3
  159. package/dist/index.js +10 -2
  160. package/dist/layout/AppBar.svelte +22 -14
  161. package/dist/layout/AppBar.svelte.d.ts +2 -1
  162. package/dist/layout/Footer.svelte +19 -11
  163. package/dist/layout/Footer.svelte.d.ts +2 -1
  164. package/dist/layout/Provider.svelte +27 -4
  165. package/dist/layout/Provider.svelte.d.ts +3 -1
  166. package/dist/layout/css/app-bar.css +63 -66
  167. package/dist/layout/css/footer.css +62 -65
  168. package/dist/navigation/BottomNav.svelte +41 -13
  169. package/dist/navigation/FooterGroup.svelte +1 -1
  170. package/dist/navigation/NavMenu.svelte +47 -23
  171. package/dist/navigation/NavMenu.svelte.d.ts +29 -0
  172. package/dist/navigation/Pagination.svelte +158 -0
  173. package/dist/navigation/Pagination.svelte.d.ts +18 -0
  174. package/dist/navigation/SideNav.svelte +30 -25
  175. package/dist/navigation/SideNav.svelte.d.ts +2 -3
  176. package/dist/navigation/Tabs.svelte +17 -7
  177. package/dist/navigation/Tabs.svelte.d.ts +2 -2
  178. package/dist/navigation/css/bottom-nav.css +279 -257
  179. package/dist/navigation/css/footer-group.css +1 -1
  180. package/dist/navigation/css/footer-nav.css +1 -1
  181. package/dist/navigation/css/nav-menu.css +332 -106
  182. package/dist/navigation/css/pagination.css +74 -0
  183. package/dist/navigation/css/side-nav.css +515 -75
  184. package/dist/navigation/css/tabs.css +246 -52
  185. package/dist/overlay/Command.svelte +340 -0
  186. package/dist/overlay/Command.svelte.d.ts +24 -25
  187. package/dist/overlay/Drawer.svelte +49 -21
  188. package/dist/overlay/Drawer.svelte.d.ts +2 -2
  189. package/dist/overlay/Dropdown.svelte +4 -5
  190. package/dist/overlay/Modal.svelte +51 -16
  191. package/dist/overlay/Modal.svelte.d.ts +3 -3
  192. package/dist/overlay/Toast.svelte +41 -17
  193. package/dist/overlay/Toast.svelte.d.ts +1 -1
  194. package/dist/overlay/Tooltip.svelte +40 -26
  195. package/dist/overlay/Tooltip.svelte.d.ts +2 -2
  196. package/dist/overlay/css/command.css +80 -0
  197. package/dist/overlay/css/drawer.css +63 -24
  198. package/dist/overlay/css/dropdown.css +1 -1
  199. package/dist/overlay/css/hovercard.css +1 -1
  200. package/dist/overlay/css/modal.css +27 -27
  201. package/dist/overlay/css/toast.css +17 -29
  202. package/dist/overlay/css/tooltip.css +83 -66
  203. package/dist/stores/theme.svelte.js +26 -1
  204. package/dist/stores/toast.svelte.d.ts +4 -4
  205. package/dist/stores/toast.svelte.js +2 -2
  206. package/package.json +1 -1
@@ -1,4 +1,8 @@
1
+ import { type Snippet } from 'svelte';
1
2
  type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted';
3
+ type Size = 'sm' | 'md' | 'lg' | 'xl';
4
+ type LegendPosition = 'top' | 'right' | 'bottom' | 'left' | 'none';
5
+ type Palette = 'default' | 'rainbow' | 'ocean' | 'sunset' | 'forest' | 'neon';
2
6
  type DataPoint = {
3
7
  x: number;
4
8
  y: number;
@@ -8,24 +12,49 @@ type Series = {
8
12
  data: DataPoint[];
9
13
  color?: Color;
10
14
  };
15
+ type Margin = {
16
+ top: number;
17
+ right: number;
18
+ bottom: number;
19
+ left: number;
20
+ };
11
21
  type Props = {
12
22
  data?: DataPoint[];
13
23
  series?: Series[];
14
24
  color?: Color;
15
25
  colors?: Color[];
16
- showLine?: boolean;
26
+ margin?: Margin;
27
+ hideLine?: boolean;
17
28
  showPoints?: boolean;
18
- showGrid?: boolean;
19
- showLegend?: boolean;
29
+ hideGrid?: boolean;
30
+ hideLegend?: boolean;
20
31
  curve?: 'linear' | 'smooth';
21
32
  strokeWidth?: number;
22
33
  fillOpacity?: number;
23
34
  stacked?: boolean;
24
- gradient?: boolean;
35
+ hideGradient?: boolean;
25
36
  loading?: boolean;
26
37
  empty?: boolean;
27
38
  emptyText?: string;
28
39
  rootClass?: string;
40
+ chartClass?: string;
41
+ size?: Size;
42
+ palette?: Palette;
43
+ legendPosition?: LegendPosition;
44
+ disableAnimation?: boolean;
45
+ animationDuration?: number;
46
+ valueFormatter?: (value: number) => string;
47
+ xFormatter?: (value: number) => string;
48
+ onClick?: (point: DataPoint, series: Series, index: number) => void;
49
+ onHover?: (point: DataPoint | null, series: Series | null, index: number) => void;
50
+ hideXAxis?: boolean;
51
+ hideYAxis?: boolean;
52
+ showGlow?: boolean;
53
+ tooltipContent?: Snippet<[{
54
+ point: DataPoint;
55
+ series: Series;
56
+ color: Color;
57
+ }]>;
29
58
  };
30
59
  declare const AreaChart: import("svelte").Component<Props, {}, "">;
31
60
  type AreaChart = ReturnType<typeof AreaChart>;
@@ -1,12 +1,18 @@
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
  value: number;
7
12
  label: string;
8
13
  seriesName: string;
9
14
  color: Color;
15
+ index: number;
10
16
  };
11
17
 
12
18
  type LinearScale = {
@@ -23,8 +29,6 @@
23
29
  range: [number, number];
24
30
  };
25
31
 
26
- type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted';
27
-
28
32
  type DataPoint = {
29
33
  label: string;
30
34
  value: number;
@@ -51,7 +55,7 @@
51
55
  colors?: Color[];
52
56
  grouped?: boolean;
53
57
  stacked?: boolean;
54
- showGrid?: boolean;
58
+ hideGrid?: boolean;
55
59
  showLegend?: boolean;
56
60
  showValues?: boolean;
57
61
  barPadding?: number;
@@ -61,6 +65,18 @@
61
65
  empty?: boolean;
62
66
  emptyText?: string;
63
67
  rootClass?: string;
68
+ chartClass?: string;
69
+ size?: Size;
70
+ palette?: Palette;
71
+ legendPosition?: LegendPosition;
72
+ disableAnimation?: boolean;
73
+ animationDuration?: number;
74
+ showGradient?: boolean;
75
+ showGlow?: boolean;
76
+ valueFormatter?: (value: number) => string;
77
+ onClick?: (bar: DataPoint, seriesIndex: number, dataIndex: number) => void;
78
+ onHover?: (bar: DataPoint | null, seriesIndex: number, dataIndex: number) => void;
79
+ tooltipContent?: Snippet<[{ data: TooltipData }]>;
64
80
  };
65
81
 
66
82
  let {
@@ -71,23 +87,84 @@
71
87
  colors = ['primary', 'secondary', 'success', 'info', 'warning', 'danger'] as Color[],
72
88
  grouped = false,
73
89
  stacked = false,
74
- showGrid = true,
90
+ hideGrid = false,
75
91
  showLegend = false,
76
92
  showValues = false,
77
93
  barPadding = 0.2,
78
94
  groupPadding = 0.1,
79
- barRadius = 0,
95
+ barRadius = 4,
80
96
  loading = false,
81
97
  empty = false,
82
98
  emptyText = 'No data available',
83
- rootClass
99
+ rootClass,
100
+ chartClass,
101
+ size = 'md' as Size,
102
+ palette,
103
+ legendPosition = 'bottom' as LegendPosition,
104
+ disableAnimation = false,
105
+ animationDuration = 800,
106
+ showGradient = false,
107
+ showGlow = false,
108
+ valueFormatter,
109
+ onClick,
110
+ onHover,
111
+ tooltipContent
84
112
  }: Props = $props();
85
113
 
114
+ const sizePresets: Record<Size, { height: number }> = {
115
+ sm: { height: 200 },
116
+ md: { height: 300 },
117
+ lg: { height: 400 },
118
+ xl: { height: 500 }
119
+ };
120
+
121
+ const colorPalettes: Record<Palette, Color[]> = {
122
+ default: ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'muted'],
123
+ rainbow: ['danger', 'warning', 'success', 'info', 'primary', 'secondary', 'muted'],
124
+ ocean: ['info', 'primary', 'secondary', 'success', 'muted', 'warning', 'danger'],
125
+ sunset: ['warning', 'danger', 'secondary', 'primary', 'info', 'success', 'muted'],
126
+ forest: ['success', 'primary', 'info', 'secondary', 'muted', 'warning', 'danger'],
127
+ neon: ['secondary', 'primary', 'success', 'warning', 'danger', 'info', 'muted']
128
+ };
129
+
130
+ const colorClass: Record<Color, string> = {
131
+ primary: 'is-primary',
132
+ secondary: 'is-secondary',
133
+ success: 'is-success',
134
+ info: 'is-info',
135
+ warning: 'is-warning',
136
+ danger: 'is-danger',
137
+ muted: 'is-muted'
138
+ };
139
+
140
+ function getSeriesColor(seriesIndex: number, seriesColor?: Color): Color {
141
+ if (seriesColor) return seriesColor;
142
+ if (palette) {
143
+ const paletteColors = colorPalettes[palette];
144
+ return paletteColors[seriesIndex % paletteColors.length];
145
+ }
146
+ return colors[seriesIndex % colors.length];
147
+ }
148
+
149
+ function formatValue(value: number): string {
150
+ if (valueFormatter) return valueFormatter(value);
151
+ if (value === 0) return '0';
152
+ const abs = Math.abs(value);
153
+ if (abs >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
154
+ if (abs >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
155
+ if (abs >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
156
+ if (abs < 1) return value.toFixed(2);
157
+ return value.toFixed(0);
158
+ }
159
+
86
160
  let containerEl: HTMLDivElement | undefined = $state();
87
161
  let containerSize = $state({ width: 0, height: 0 });
162
+ // svelte-ignore state_referenced_locally
163
+ let animationProgress = $state(disableAnimation ? 1 : 0);
164
+ let animationFrameId: number | null = null;
88
165
 
89
166
  let width = $derived(containerSize.width || 600);
90
- let height = $derived(containerSize.height || 400);
167
+ let height = $derived(containerSize.height || sizePresets[size].height);
91
168
 
92
169
  function createLinearScale(domain: [number, number], range: [number, number]): LinearScale {
93
170
  const [d0, d1] = domain;
@@ -130,16 +207,6 @@
130
207
  return scale;
131
208
  }
132
209
 
133
- function formatNumber(value: number): string {
134
- if (value === 0) return '0';
135
- const abs = Math.abs(value);
136
- if (abs >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
137
- if (abs >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
138
- if (abs >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
139
- if (abs < 1) return value.toFixed(2);
140
- return value.toFixed(0);
141
- }
142
-
143
210
  let innerWidth = $derived(width - margin.left - margin.right);
144
211
  let innerHeight = $derived(height - margin.top - margin.bottom);
145
212
 
@@ -148,7 +215,7 @@
148
215
  return series.map((s, i) => ({
149
216
  name: s.name,
150
217
  data: s.data,
151
- color: s.color || colors[i % colors.length]
218
+ color: getSeriesColor(i, s.color)
152
219
  }));
153
220
  }
154
221
 
@@ -234,27 +301,45 @@
234
301
  let tooltipData = $state<TooltipData | null>(null);
235
302
  let tooltipPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 });
236
303
  let isTooltipActive = $state(false);
304
+ let hoveredBar = $state<{ seriesIndex: number; dataIndex: number } | null>(null);
305
+
306
+ let gradientIds = $derived(
307
+ normalizedSeries.map((_, i) => `bar-gradient-${i}-${Math.random().toString(36).slice(2, 9)}`)
308
+ );
237
309
 
238
310
  function handleBarHover(
239
311
  value: number,
240
312
  label: string,
241
313
  seriesName: string,
242
314
  event: MouseEvent,
243
- barColor: Color
315
+ barColor: Color,
316
+ seriesIndex: number,
317
+ dataIndex: number
244
318
  ): void {
245
319
  const target = event.target as SVGRectElement;
246
320
  const rect = target.getBoundingClientRect();
247
321
 
248
- tooltipData = { value, label, seriesName, color: barColor };
322
+ tooltipData = { value, label, seriesName, color: barColor, index: dataIndex };
249
323
  tooltipPosition = {
250
324
  x: rect.right + 8,
251
325
  y: rect.top + rect.height / 2
252
326
  };
253
327
  isTooltipActive = true;
328
+ hoveredBar = { seriesIndex, dataIndex };
329
+
330
+ if (onHover) {
331
+ onHover({ label, value }, seriesIndex, dataIndex);
332
+ }
254
333
  }
255
334
 
256
335
  function handleBarLeave(): void {
257
336
  isTooltipActive = false;
337
+ hoveredBar = null;
338
+
339
+ if (onHover) {
340
+ onHover(null, -1, -1);
341
+ }
342
+
258
343
  setTimeout(() => {
259
344
  if (!isTooltipActive) {
260
345
  tooltipData = null;
@@ -262,6 +347,37 @@
262
347
  }, 100);
263
348
  }
264
349
 
350
+ function handleBarClick(bar: DataPoint, seriesIndex: number, dataIndex: number): void {
351
+ if (onClick) {
352
+ onClick(bar, seriesIndex, dataIndex);
353
+ }
354
+ }
355
+
356
+ let containerLayoutClass = $derived(() => {
357
+ switch (legendPosition) {
358
+ case 'top':
359
+ return 'flex-col-reverse';
360
+ case 'bottom':
361
+ return 'flex-col';
362
+ case 'left':
363
+ return 'flex-row-reverse';
364
+ case 'right':
365
+ return 'flex-row';
366
+ default:
367
+ return 'flex-col';
368
+ }
369
+ });
370
+
371
+ let legendLayoutClass = $derived(() => {
372
+ switch (legendPosition) {
373
+ case 'top':
374
+ case 'bottom':
375
+ return 'flex-row flex-wrap';
376
+ default:
377
+ return 'flex-col';
378
+ }
379
+ });
380
+
265
381
  onMount(() => {
266
382
  const updateSize = () => {
267
383
  if (containerEl) {
@@ -270,19 +386,63 @@
270
386
  }
271
387
  };
272
388
 
389
+ const handleScroll = () => {
390
+ if (isTooltipActive) {
391
+ isTooltipActive = false;
392
+ hoveredBar = null;
393
+ tooltipData = null;
394
+ }
395
+ };
396
+
273
397
  updateSize();
274
398
  const resizeObserver = new ResizeObserver(updateSize);
275
399
  if (containerEl) {
276
400
  resizeObserver.observe(containerEl);
277
401
  }
278
402
 
403
+ window.addEventListener('scroll', handleScroll, true);
404
+
279
405
  return () => {
406
+ if (animationFrameId !== null) {
407
+ cancelAnimationFrame(animationFrameId);
408
+ }
280
409
  resizeObserver.disconnect();
410
+ window.removeEventListener('scroll', handleScroll, true);
281
411
  };
282
412
  });
413
+
414
+ $effect(() => {
415
+ if (animationFrameId !== null) {
416
+ cancelAnimationFrame(animationFrameId);
417
+ animationFrameId = null;
418
+ }
419
+
420
+ if (!disableAnimation && normalizedSeries.length > 0) {
421
+ const startTime = Date.now();
422
+ const startProgress = untrack(() => animationProgress);
423
+
424
+ const animate = () => {
425
+ const now = Date.now();
426
+ const progress = Math.min((now - startTime) / animationDuration, 1);
427
+ const easeProgress = 1 - Math.pow(1 - progress, 3);
428
+
429
+ animationProgress = startProgress + (1 - startProgress) * easeProgress;
430
+
431
+ if (progress < 1) {
432
+ animationFrameId = requestAnimationFrame(animate);
433
+ } else {
434
+ animationFrameId = null;
435
+ }
436
+ };
437
+
438
+ animationFrameId = requestAnimationFrame(animate);
439
+ } else {
440
+ animationProgress = 1;
441
+ }
442
+ });
283
443
  </script>
284
444
 
285
- <div bind:this={containerEl} class={cn('bar-chart-container', rootClass)}>
445
+ <div class={cn('bar-chart-container', `is-${size}`, containerLayoutClass(), rootClass)}>
286
446
  {#if loading}
287
447
  <div class="bar-chart-loading">
288
448
  <svg class="bar-chart-loading-spinner" viewBox="0 0 24 24">
@@ -314,15 +474,30 @@
314
474
  <span>{emptyText}</span>
315
475
  </div>
316
476
  {:else}
317
- <div class="bar-chart">
477
+ <div bind:this={containerEl} class={cn('bar-chart', `is-${size}`, chartClass)}>
318
478
  <svg class="bar-chart-svg" {width} {height}>
479
+ {#if showGradient}
480
+ <defs>
481
+ {#each normalizedSeries as s, i}
482
+ {@const barColor = s.color || 'primary'}
483
+ <linearGradient id={gradientIds[i]} x1="0%" y1="100%" x2="0%" y2="0%">
484
+ <stop offset="0%" style="stop-color:var(--color-{barColor})" />
485
+ <stop
486
+ offset="100%"
487
+ style="stop-color:var(--color-{barColor}-dark, var(--color-{barColor}))"
488
+ />
489
+ </linearGradient>
490
+ {/each}
491
+ </defs>
492
+ {/if}
493
+
319
494
  <g transform="translate({margin.left}, {margin.top})">
320
495
  {#if normalizedSeries.length > 0 && categories.length > 0}
321
496
  {@const grid = createGridLines()}
322
497
  {@const xScale = createBandScale(categories, [0, innerWidth], barPadding)}
323
498
  {@const yScale = createLinearScale(yDomain, [innerHeight, 0])}
324
499
 
325
- {#if showGrid}
500
+ {#if !hideGrid}
326
501
  <g class="bar-chart-grid">
327
502
  {#each grid as line}
328
503
  <line x1={0} y1={line.y} x2={line.x} y2={line.y} class="bar-chart-grid-line" />
@@ -348,7 +523,7 @@
348
523
  text-anchor="end"
349
524
  dominant-baseline="middle"
350
525
  >
351
- {formatNumber(line.value)}
526
+ {formatValue(line.value)}
352
527
  </text>
353
528
  {/each}
354
529
 
@@ -372,98 +547,134 @@
372
547
  )}
373
548
 
374
549
  {#each normalizedSeries as s, seriesIndex}
375
- {#each s.data as d}
550
+ {#each s.data as d, dataIndex}
376
551
  {@const categoryX = xScale(d.label)}
377
552
  {@const barX = categoryX + groupScale(seriesIndex)}
378
553
  {@const barWidth = groupScale.bandwidth()}
379
- {@const barHeight = innerHeight - yScale(d.value)}
380
- {@const barY = yScale(d.value)}
554
+ {@const fullBarHeight = innerHeight - yScale(d.value)}
555
+ {@const barHeight = fullBarHeight * animationProgress}
556
+ {@const barY = innerHeight - barHeight}
557
+ {@const isHovered =
558
+ hoveredBar?.seriesIndex === seriesIndex && hoveredBar?.dataIndex === dataIndex}
381
559
 
382
560
  <!-- svelte-ignore a11y_no_static_element_interactions -->
561
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
383
562
  <rect
384
563
  x={barX}
385
564
  y={barY}
386
565
  width={barWidth}
387
- height={barHeight}
566
+ height={Math.max(0, barHeight)}
388
567
  rx={barRadius}
389
568
  ry={barRadius}
390
- class="bar-chart-bar is-{s.color}"
391
- onmouseenter={(e) => handleBarHover(d.value, d.label, s.name, e, s.color!)}
569
+ class={cn(
570
+ 'bar-chart-bar',
571
+ colorClass[s.color!],
572
+ showGlow && 'has-glow',
573
+ isHovered && 'is-hovered'
574
+ )}
575
+ style={showGradient ? `fill: url(#${gradientIds[seriesIndex]});` : ''}
576
+ onmouseenter={(e) =>
577
+ handleBarHover(d.value, d.label, s.name, e, s.color!, seriesIndex, dataIndex)}
392
578
  onmouseleave={handleBarLeave}
579
+ onclick={() => handleBarClick(d, seriesIndex, dataIndex)}
393
580
  />
394
581
 
395
- {#if showValues}
582
+ {#if showValues && animationProgress === 1}
396
583
  <text
397
584
  x={barX + barWidth / 2}
398
585
  y={barY - 5}
399
- class="bar-chart-axis-label"
586
+ class="bar-chart-value-label"
400
587
  text-anchor="middle"
401
- font-size="10"
402
588
  >
403
- {d.value}
589
+ {formatValue(d.value)}
404
590
  </text>
405
591
  {/if}
406
592
  {/each}
407
593
  {/each}
408
594
  {:else if stacked && normalizedSeries.length > 1}
409
- {#each stackedData as s}
410
- {#each s.data as d}
595
+ {#each stackedData as s, seriesIndex}
596
+ {#each s.data as d, dataIndex}
411
597
  {@const barX = xScale(d.label)}
412
598
  {@const barWidth = xScale.bandwidth()}
413
- {@const barY = yScale((d as StackedDataPoint).y1)}
414
- {@const barHeight =
415
- yScale((d as StackedDataPoint).y0) - yScale((d as StackedDataPoint).y1)}
599
+ {@const fullY0 = yScale((d as StackedDataPoint).y0)}
600
+ {@const fullY1 = yScale((d as StackedDataPoint).y1)}
601
+ {@const fullBarHeight = fullY0 - fullY1}
602
+ {@const barHeight = fullBarHeight * animationProgress}
603
+ {@const barY = innerHeight - (innerHeight - fullY1) * animationProgress}
604
+ {@const isHovered =
605
+ hoveredBar?.seriesIndex === seriesIndex && hoveredBar?.dataIndex === dataIndex}
416
606
 
417
607
  <!-- svelte-ignore a11y_no_static_element_interactions -->
608
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
418
609
  <rect
419
610
  x={barX}
420
611
  y={barY}
421
612
  width={barWidth}
422
- height={barHeight}
613
+ height={Math.max(0, barHeight)}
423
614
  rx={barRadius}
424
615
  ry={barRadius}
425
- class="bar-chart-bar is-{s.color}"
426
- onmouseenter={(e) => handleBarHover(d.value, d.label, s.name, e, s.color!)}
616
+ class={cn(
617
+ 'bar-chart-bar',
618
+ colorClass[s.color!],
619
+ showGlow && 'has-glow',
620
+ isHovered && 'is-hovered'
621
+ )}
622
+ style={showGradient ? `fill: url(#${gradientIds[seriesIndex]});` : ''}
623
+ onmouseenter={(e) =>
624
+ handleBarHover(d.value, d.label, s.name, e, s.color!, seriesIndex, dataIndex)}
427
625
  onmouseleave={handleBarLeave}
626
+ onclick={() => handleBarClick(d, seriesIndex, dataIndex)}
428
627
  />
429
628
  {/each}
430
629
  {/each}
431
630
  {:else}
432
- {#each normalizedSeries[0].data as d}
631
+ {#each normalizedSeries[0].data as d, dataIndex}
433
632
  {@const barX = xScale(d.label)}
434
633
  {@const barWidth = xScale.bandwidth()}
435
- {@const barHeight = innerHeight - yScale(d.value)}
436
- {@const barY = yScale(d.value)}
634
+ {@const fullBarHeight = innerHeight - yScale(d.value)}
635
+ {@const barHeight = fullBarHeight * animationProgress}
636
+ {@const barY = innerHeight - barHeight}
637
+ {@const isHovered =
638
+ hoveredBar?.seriesIndex === 0 && hoveredBar?.dataIndex === dataIndex}
437
639
 
438
640
  <!-- svelte-ignore a11y_no_static_element_interactions -->
641
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
439
642
  <rect
440
643
  x={barX}
441
644
  y={barY}
442
645
  width={barWidth}
443
- height={barHeight}
646
+ height={Math.max(0, barHeight)}
444
647
  rx={barRadius}
445
648
  ry={barRadius}
446
- class="bar-chart-bar is-{normalizedSeries[0].color}"
649
+ class={cn(
650
+ 'bar-chart-bar',
651
+ colorClass[normalizedSeries[0].color!],
652
+ showGlow && 'has-glow',
653
+ isHovered && 'is-hovered'
654
+ )}
655
+ style={showGradient ? `fill: url(#${gradientIds[0]});` : ''}
447
656
  onmouseenter={(e) =>
448
657
  handleBarHover(
449
658
  d.value,
450
659
  d.label,
451
660
  normalizedSeries[0].name,
452
661
  e,
453
- normalizedSeries[0].color!
662
+ normalizedSeries[0].color!,
663
+ 0,
664
+ dataIndex
454
665
  )}
455
666
  onmouseleave={handleBarLeave}
667
+ onclick={() => handleBarClick(d, 0, dataIndex)}
456
668
  />
457
669
 
458
- {#if showValues}
670
+ {#if showValues && animationProgress === 1}
459
671
  <text
460
672
  x={barX + barWidth / 2}
461
673
  y={barY - 5}
462
- class="bar-chart-axis-label"
674
+ class="bar-chart-value-label"
463
675
  text-anchor="middle"
464
- font-size="10"
465
676
  >
466
- {d.value}
677
+ {formatValue(d.value)}
467
678
  </text>
468
679
  {/if}
469
680
  {/each}
@@ -478,23 +689,34 @@
478
689
  class="bar-chart-tooltip"
479
690
  style="top: {tooltipPosition.y}px; left: {tooltipPosition.x}px;"
480
691
  >
481
- <div class="bar-chart-tooltip-content">
482
- {#if tooltipData.seriesName !== 'Data'}
483
- <div class="bar-chart-tooltip-title">{tooltipData.seriesName}</div>
484
- {/if}
485
- <div class="bar-chart-tooltip-row">
486
- <span class="bar-chart-tooltip-label">{tooltipData.label}:</span>
487
- <span class="bar-chart-tooltip-value">{tooltipData.value}</span>
692
+ {#if tooltipContent}
693
+ {@render tooltipContent({ data: tooltipData })}
694
+ {:else}
695
+ <div class="bar-chart-tooltip-content">
696
+ {#if tooltipData.seriesName !== 'Data'}
697
+ <div class="bar-chart-tooltip-title">{tooltipData.seriesName}</div>
698
+ {/if}
699
+ <div class="bar-chart-tooltip-row">
700
+ <div class={cn('bar-chart-tooltip-color', colorClass[tooltipData.color])}></div>
701
+ <span class="bar-chart-tooltip-label">{tooltipData.label}:</span>
702
+ <span class="bar-chart-tooltip-value">{formatValue(tooltipData.value)}</span>
703
+ </div>
488
704
  </div>
489
- </div>
705
+ {/if}
490
706
  </div>
491
707
  {/if}
492
708
 
493
- {#if showLegend && normalizedSeries.length > 1}
494
- <div class="bar-chart-legend">
495
- {#each normalizedSeries as s}
496
- <div class="bar-chart-legend-item">
497
- <div class="bar-chart-legend-color is-{s.color}"></div>
709
+ {#if showLegend && legendPosition !== 'none' && normalizedSeries.length > 1}
710
+ <div class={cn('bar-chart-legend', legendLayoutClass())}>
711
+ {#each normalizedSeries as s, i}
712
+ <div
713
+ class="bar-chart-legend-item"
714
+ onclick={() => onClick && onClick(s.data[0], i, 0)}
715
+ onkeydown={(e) => e.key === 'Enter' && onClick && onClick(s.data[0], i, 0)}
716
+ role="button"
717
+ tabindex="0"
718
+ >
719
+ <div class={cn('bar-chart-legend-color', colorClass[s.color!])}></div>
498
720
  <span>{s.name}</span>
499
721
  </div>
500
722
  {/each}
@@ -1,4 +1,15 @@
1
+ import { type Snippet } from 'svelte';
1
2
  type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted';
3
+ type Size = 'sm' | 'md' | 'lg' | 'xl';
4
+ type LegendPosition = 'top' | 'right' | 'bottom' | 'left' | 'none';
5
+ type Palette = 'default' | 'rainbow' | 'ocean' | 'sunset' | 'forest' | 'neon';
6
+ type TooltipData = {
7
+ value: number;
8
+ label: string;
9
+ seriesName: string;
10
+ color: Color;
11
+ index: number;
12
+ };
2
13
  type DataPoint = {
3
14
  label: string;
4
15
  value: number;
@@ -22,7 +33,7 @@ type Props = {
22
33
  colors?: Color[];
23
34
  grouped?: boolean;
24
35
  stacked?: boolean;
25
- showGrid?: boolean;
36
+ hideGrid?: boolean;
26
37
  showLegend?: boolean;
27
38
  showValues?: boolean;
28
39
  barPadding?: number;
@@ -32,6 +43,20 @@ type Props = {
32
43
  empty?: boolean;
33
44
  emptyText?: string;
34
45
  rootClass?: string;
46
+ chartClass?: string;
47
+ size?: Size;
48
+ palette?: Palette;
49
+ legendPosition?: LegendPosition;
50
+ disableAnimation?: boolean;
51
+ animationDuration?: number;
52
+ showGradient?: boolean;
53
+ showGlow?: boolean;
54
+ valueFormatter?: (value: number) => string;
55
+ onClick?: (bar: DataPoint, seriesIndex: number, dataIndex: number) => void;
56
+ onHover?: (bar: DataPoint | null, seriesIndex: number, dataIndex: number) => void;
57
+ tooltipContent?: Snippet<[{
58
+ data: TooltipData;
59
+ }]>;
35
60
  };
36
61
  declare const BarChart: import("svelte").Component<Props, {}, "">;
37
62
  type BarChart = ReturnType<typeof BarChart>;