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;
@@ -20,9 +24,11 @@ type Props = {
20
24
  margin?: Margin;
21
25
  color?: Color;
22
26
  colors?: Color[];
23
- showPoints?: boolean;
24
- showGrid?: boolean;
25
- showLegend?: boolean;
27
+ hidePoints?: boolean;
28
+ hideGrid?: boolean;
29
+ hideLegend?: boolean;
30
+ hideXAxis?: boolean;
31
+ hideYAxis?: boolean;
26
32
  curve?: 'linear' | 'smooth';
27
33
  strokeWidth?: number;
28
34
  pointRadius?: number;
@@ -30,6 +36,23 @@ type Props = {
30
36
  empty?: boolean;
31
37
  emptyText?: string;
32
38
  rootClass?: string;
39
+ chartClass?: string;
40
+ size?: Size;
41
+ palette?: Palette;
42
+ legendPosition?: LegendPosition;
43
+ disableAnimation?: boolean;
44
+ animationDuration?: number;
45
+ valueFormatter?: (value: number) => string;
46
+ xFormatter?: (value: number) => string;
47
+ onClick?: (point: DataPoint, seriesName: string, index: number) => void;
48
+ onHover?: (point: DataPoint | null, seriesName: string, index: number) => void;
49
+ tooltipContent?: Snippet<[{
50
+ point: DataPoint;
51
+ seriesName: string;
52
+ color: Color;
53
+ }]>;
54
+ showGradientFill?: boolean;
55
+ showGlow?: boolean;
33
56
  };
34
57
  declare const LineChart: import("svelte").Component<Props, {}, "">;
35
58
  type LineChart = ReturnType<typeof LineChart>;
@@ -1,6 +1,11 @@
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 Slice = DataPoint & {
6
11
  startAngle: number;
@@ -8,57 +13,97 @@
8
13
  midAngle: number;
9
14
  percentage: number;
10
15
  color: Color;
16
+ index: number;
11
17
  };
12
18
 
13
- type Color = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted';
14
-
15
19
  type DataPoint = {
16
20
  label: string;
17
21
  value: number;
18
22
  color?: Color;
23
+ disabled?: boolean;
19
24
  };
20
25
 
21
26
  type Props = {
22
27
  data?: DataPoint[];
23
- colors?: Color[];
24
28
  donut?: boolean;
25
29
  donutWidth?: number;
26
- showLabels?: boolean;
27
- showValues?: boolean;
28
- showLegend?: boolean;
29
- showPercentages?: boolean;
30
+ size?: Size;
31
+ palette?: Palette;
32
+ legendPosition?: LegendPosition;
30
33
  centerLabel?: string;
31
34
  centerValue?: string | number;
32
35
  startAngle?: number;
33
36
  padAngle?: number;
37
+ disableAnimation?: boolean;
38
+ animationDuration?: number;
39
+ showGlow?: boolean;
40
+ showGradient?: boolean;
41
+ hideLabels?: boolean;
42
+ hideValues?: boolean;
43
+ hideLegend?: boolean;
44
+ hidePercentages?: boolean;
34
45
  loading?: boolean;
35
46
  empty?: boolean;
36
47
  emptyText?: string;
37
48
  rootClass?: string;
38
49
  chartClass?: string;
50
+ valueFormatter?: (value: number) => string;
51
+ onClick?: (slice: DataPoint, index: number) => void;
52
+ onHover?: (slice: DataPoint | null, index: number) => void;
53
+ selected?: number[];
54
+ centerContent?: Snippet;
55
+ tooltipContent?: Snippet<[{ slice: Slice; percentage: number }]>;
39
56
  };
40
57
 
41
58
  let {
42
59
  data = [],
43
- colors = ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'muted'] as Color[],
44
60
  donut = false,
45
61
  donutWidth = 60,
46
- showLabels = true,
47
- showValues = true,
48
- showLegend = true,
49
- showPercentages = true,
62
+ size = 'md',
63
+ palette,
64
+ legendPosition = 'right',
50
65
  centerLabel = 'Total',
51
66
  centerValue = undefined,
52
67
  startAngle = -90,
53
68
  padAngle = 0,
69
+ disableAnimation = false,
70
+ animationDuration = 800,
71
+ showGlow = false,
72
+ showGradient = false,
73
+ hideLabels = false,
74
+ hideValues = false,
75
+ hideLegend = false,
76
+ hidePercentages = false,
54
77
  loading = false,
55
78
  empty = false,
56
79
  emptyText = 'No data available',
57
80
  rootClass,
58
- chartClass
81
+ chartClass,
82
+ valueFormatter,
83
+ onClick,
84
+ onHover,
85
+ selected = [],
86
+ centerContent,
87
+ tooltipContent
59
88
  }: Props = $props();
60
89
 
61
- const colorClass = {
90
+ const sizePresets: Record<Size, { height: number; labelSize: number; valueSize: number }> = {
91
+ sm: { height: 150, labelSize: 10, valueSize: 18 },
92
+ md: { height: 224, labelSize: 12, valueSize: 24 },
93
+ lg: { height: 300, labelSize: 14, valueSize: 32 },
94
+ xl: { height: 400, labelSize: 16, valueSize: 40 }
95
+ };
96
+
97
+ const colorPalettes: Record<Palette, Color[]> = {
98
+ default: ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'muted'],
99
+ rainbow: ['danger', 'warning', 'success', 'info', 'primary', 'secondary', 'muted'],
100
+ ocean: ['info', 'primary', 'secondary', 'success', 'muted', 'warning', 'danger'],
101
+ sunset: ['warning', 'danger', 'secondary', 'primary', 'info', 'success', 'muted'],
102
+ forest: ['success', 'primary', 'info', 'secondary', 'muted', 'warning', 'danger'],
103
+ neon: ['secondary', 'primary', 'success', 'warning', 'danger', 'info', 'muted']
104
+ };
105
+
106
+ const colorClass: Record<Color, string> = {
62
107
  primary: 'is-primary',
63
108
  secondary: 'is-secondary',
64
109
  success: 'is-success',
@@ -68,39 +113,71 @@
68
113
  muted: 'is-muted'
69
114
  };
70
115
 
116
+ function getSliceColor(item: DataPoint, index: number): Color {
117
+ if (item.color) return item.color;
118
+ if (palette) {
119
+ const paletteColors = colorPalettes[palette];
120
+ return paletteColors[index % paletteColors.length];
121
+ }
122
+ return colorPalettes.default[index % colorPalettes.default.length];
123
+ }
124
+
125
+ function formatValue(value: number): string {
126
+ if (valueFormatter) return valueFormatter(value);
127
+ return String(value);
128
+ }
129
+
71
130
  let containerEl: HTMLDivElement | undefined = $state();
72
131
  let containerSize = $state({ width: 0, height: 0 });
132
+ let animationFrameId: number | null = null;
73
133
 
74
- let width = $derived(containerSize.width);
75
- let height = $derived(containerSize.height);
134
+ let tooltipData = $state<Slice | null>(null);
135
+ let tooltipPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 });
136
+ let isTooltipActive = $state(false);
137
+ let hoveredIndex = $state<number | null>(null);
138
+
139
+ let sizeConfig = $derived(sizePresets[size]);
140
+ let effectiveHeight = $derived(sizeConfig.height);
141
+
142
+ let width = $derived(containerSize.width || effectiveHeight);
143
+ let height = $derived(containerSize.height || effectiveHeight);
144
+ let viewBoxSize = $derived(Math.min(width, height) || effectiveHeight);
76
145
 
77
146
  let total = $derived(data.reduce((sum, d) => sum + d.value, 0));
78
147
 
148
+ // svelte-ignore state_referenced_locally
149
+ let displayPercentages = $state<number[]>(data.map(() => 0));
150
+
79
151
  let slices = $derived.by((): Slice[] => {
80
152
  const startRad = (startAngle * Math.PI) / 180;
81
153
  let currentAngle = startRad;
82
154
  const padRad = (padAngle * Math.PI) / 180;
83
155
 
84
156
  return data.map((d, i) => {
85
- const percentage = (d.value / total) * 100;
86
- const angle = (d.value / total) * 2 * Math.PI - padRad;
87
- const startAngle = currentAngle;
88
- const endAngle = currentAngle + angle;
89
- const midAngle = startAngle + angle / 2;
157
+ const percentage = total > 0 ? (d.value / total) * 100 : 0;
158
+ const angle = total > 0 ? (d.value / total) * 2 * Math.PI - padRad : 0;
159
+ const sliceStartAngle = currentAngle;
160
+ const sliceEndAngle = currentAngle + angle;
161
+ const midAngle = sliceStartAngle + angle / 2;
90
162
 
91
- currentAngle = endAngle + padRad;
163
+ currentAngle = sliceEndAngle + padRad;
92
164
 
93
165
  return {
94
166
  ...d,
95
- startAngle,
96
- endAngle,
167
+ startAngle: sliceStartAngle,
168
+ endAngle: sliceEndAngle,
97
169
  midAngle,
98
170
  percentage,
99
- color: d.color || colors[i % colors.length]
171
+ color: getSliceColor(d, i),
172
+ index: i
100
173
  };
101
174
  });
102
175
  });
103
176
 
177
+ let gradientIds = $derived(
178
+ data.map((_, i) => `pie-gradient-${i}-${Math.random().toString(36).slice(2, 9)}`)
179
+ );
180
+
104
181
  function createArc(
105
182
  startAngle: number,
106
183
  endAngle: number,
@@ -130,24 +207,102 @@
130
207
  return path;
131
208
  }
132
209
 
133
- let tooltipData = $state<Slice | null>(null);
134
- let tooltipPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 });
135
- let isTooltipActive = $state(false);
210
+ onMount(() => {
211
+ const updateSize = () => {
212
+ if (containerEl) {
213
+ const rect = containerEl.getBoundingClientRect();
214
+ containerSize = { width: rect.width, height: rect.height };
215
+ }
216
+ };
217
+
218
+ const handleScroll = () => {
219
+ if (isTooltipActive) {
220
+ isTooltipActive = false;
221
+ hoveredIndex = null;
222
+ tooltipData = null;
223
+ }
224
+ };
225
+
226
+ updateSize();
227
+ const resizeObserver = new ResizeObserver(updateSize);
228
+ if (containerEl) {
229
+ resizeObserver.observe(containerEl);
230
+ }
231
+
232
+ window.addEventListener('scroll', handleScroll, true);
233
+
234
+ return () => {
235
+ if (animationFrameId !== null) {
236
+ cancelAnimationFrame(animationFrameId);
237
+ }
238
+ resizeObserver.disconnect();
239
+ window.removeEventListener('scroll', handleScroll, true);
240
+ };
241
+ });
242
+
243
+ $effect(() => {
244
+ const targetPercentages = slices.map((s) => s.percentage / 100);
245
+
246
+ if (animationFrameId !== null) {
247
+ cancelAnimationFrame(animationFrameId);
248
+ animationFrameId = null;
249
+ }
250
+
251
+ if (!disableAnimation) {
252
+ const startTime = Date.now();
253
+ const startPercentages = untrack(() => [...displayPercentages]);
254
+
255
+ const animate = () => {
256
+ const now = Date.now();
257
+ const progress = Math.min((now - startTime) / animationDuration, 1);
258
+ const easeProgress = 1 - Math.pow(1 - progress, 3);
259
+
260
+ const newPercentages = startPercentages.map(
261
+ (start, i) => start + ((targetPercentages[i] || 0) - start) * easeProgress
262
+ );
263
+
264
+ displayPercentages = newPercentages;
265
+
266
+ if (progress < 1) {
267
+ animationFrameId = requestAnimationFrame(animate);
268
+ } else {
269
+ animationFrameId = null;
270
+ }
271
+ };
272
+
273
+ animationFrameId = requestAnimationFrame(animate);
274
+ } else {
275
+ displayPercentages = targetPercentages;
276
+ }
277
+ });
136
278
 
137
279
  function handleSliceHover(slice: Slice, event: MouseEvent): void {
280
+ if (slice.disabled) return;
281
+
138
282
  const target = event.target as SVGPathElement;
139
283
  const rect = target.getBoundingClientRect();
140
284
 
141
285
  tooltipData = slice;
142
286
  tooltipPosition = {
143
- x: rect.right + 8,
144
- y: rect.top + rect.height / 2
287
+ x: rect.left + rect.width / 2,
288
+ y: rect.top - 10
145
289
  };
146
290
  isTooltipActive = true;
291
+ hoveredIndex = slice.index;
292
+
293
+ if (onHover) {
294
+ onHover(slice, slice.index);
295
+ }
147
296
  }
148
297
 
149
298
  function handleSliceLeave(): void {
150
299
  isTooltipActive = false;
300
+ hoveredIndex = null;
301
+
302
+ if (onHover) {
303
+ onHover(null, -1);
304
+ }
305
+
151
306
  setTimeout(() => {
152
307
  if (!isTooltipActive) {
153
308
  tooltipData = null;
@@ -155,27 +310,57 @@
155
310
  }, 100);
156
311
  }
157
312
 
158
- onMount(() => {
159
- const updateSize = () => {
160
- if (containerEl) {
161
- const rect = containerEl.getBoundingClientRect();
162
- containerSize = { width: rect.width, height: rect.height };
163
- }
164
- };
313
+ function handleSliceClick(slice: Slice): void {
314
+ if (onClick && !slice.disabled) {
315
+ onClick(slice, slice.index);
316
+ }
317
+ }
165
318
 
166
- updateSize();
167
- const resizeObserver = new ResizeObserver(updateSize);
168
- if (containerEl) {
169
- resizeObserver.observe(containerEl);
319
+ let containerLayoutClass = $derived(() => {
320
+ switch (legendPosition) {
321
+ case 'top':
322
+ return 'flex-col-reverse';
323
+ case 'bottom':
324
+ return 'flex-col';
325
+ case 'left':
326
+ return 'flex-row-reverse';
327
+ case 'right':
328
+ default:
329
+ return 'flex-row';
170
330
  }
331
+ });
171
332
 
172
- return () => {
173
- resizeObserver.disconnect();
174
- };
333
+ let legendLayoutClass = $derived(() => {
334
+ switch (legendPosition) {
335
+ case 'top':
336
+ case 'bottom':
337
+ return 'flex-row flex-wrap';
338
+ default:
339
+ return 'flex-col';
340
+ }
341
+ });
342
+
343
+ let responsiveLabelFontSize = $derived(() => {
344
+ const baseSize = 300;
345
+ const scale = viewBoxSize / baseSize;
346
+ return Math.max(8, Math.min(sizeConfig.labelSize * scale, sizeConfig.labelSize));
347
+ });
348
+
349
+ let responsiveValueFontSize = $derived(() => {
350
+ const baseSize = 300;
351
+ const scale = viewBoxSize / baseSize;
352
+ return Math.max(14, Math.min(sizeConfig.valueSize * scale, sizeConfig.valueSize));
353
+ });
354
+
355
+ let shouldShowCenterText = $derived(viewBoxSize >= 100);
356
+
357
+ let computedCenterValue = $derived(() => {
358
+ const val = centerValue ?? total;
359
+ return valueFormatter && typeof val === 'number' ? valueFormatter(val) : val;
175
360
  });
176
361
  </script>
177
362
 
178
- <div class={cn('pie-chart-container', rootClass)}>
363
+ <div class={cn('pie-chart-container', `is-${size}`, containerLayoutClass(), rootClass)}>
179
364
  {#if loading}
180
365
  <div class="pie-chart-loading">
181
366
  <svg class="pie-chart-loading-spinner" viewBox="0 0 24 24">
@@ -213,64 +398,107 @@
213
398
  <span>{emptyText}</span>
214
399
  </div>
215
400
  {:else}
216
- <div bind:this={containerEl} class={cn('pie-chart', chartClass)}>
217
- <svg class="pie-chart-svg">
401
+ <div bind:this={containerEl} class={cn('pie-chart', `is-${size}`, chartClass)}>
402
+ <svg class="pie-chart-svg" viewBox="0 0 {viewBoxSize} {viewBoxSize}">
403
+ {#if showGradient}
404
+ <defs>
405
+ {#each slices as slice, i}
406
+ <linearGradient id={gradientIds[i]} x1="0%" y1="0%" x2="100%" y2="0%">
407
+ <stop offset="0%" style="stop-color:var(--color-{slice.color})" />
408
+ <stop
409
+ offset="100%"
410
+ style="stop-color:var(--color-{slice.color}-dark, var(--color-{slice.color}))"
411
+ />
412
+ </linearGradient>
413
+ {/each}
414
+ </defs>
415
+ {/if}
416
+
218
417
  {#if slices.length > 0}
219
- {@const centerX = width / 2}
220
- {@const centerY = height / 2}
221
- {@const radius = Math.min(width, height) / 2}
418
+ {@const centerX = viewBoxSize / 2}
419
+ {@const centerY = viewBoxSize / 2}
420
+ {@const radius = viewBoxSize / 2 - 10}
222
421
  {@const innerRadius = donut ? radius - donutWidth : 0}
223
422
 
224
423
  <g transform="translate({centerX}, {centerY})">
225
- {#each slices as slice}
226
- {@const labelRadius = donut ? radius - donutWidth / 2 : radius * 0.7}
424
+ {#each slices as slice, i}
425
+ {@const isSelected = selected.includes(i)}
426
+ {@const isHovered = hoveredIndex === i}
427
+ {@const isDisabled = slice.disabled}
428
+ {@const labelRadius = donut ? radius - donutWidth / 2 : radius * 0.65}
227
429
  {@const labelX = Math.cos(slice.midAngle) * labelRadius}
228
430
  {@const labelY = Math.sin(slice.midAngle) * labelRadius}
229
431
 
230
432
  <!-- svelte-ignore a11y_no_static_element_interactions -->
433
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
231
434
  <path
232
435
  d={createArc(slice.startAngle, slice.endAngle, innerRadius, radius)}
233
- class="pie-chart-slice is-{slice.color}"
436
+ class={cn(
437
+ 'pie-chart-slice',
438
+ colorClass[slice.color],
439
+ isSelected && 'is-selected',
440
+ showGlow && 'has-glow',
441
+ isHovered && 'is-hovered',
442
+ isDisabled && 'is-disabled'
443
+ )}
444
+ style={showGradient ? `fill: url(#${gradientIds[i]});` : ''}
234
445
  onmouseenter={(e) => handleSliceHover(slice, e)}
235
446
  onmouseleave={handleSliceLeave}
447
+ onclick={() => handleSliceClick(slice)}
236
448
  />
237
449
 
238
- {#if showLabels && slice.percentage > 5}
450
+ {#if !hideLabels && slice.percentage > 5}
239
451
  <text
240
452
  x={labelX}
241
453
  y={labelY}
242
454
  class="pie-chart-slice-label"
243
455
  text-anchor="middle"
244
456
  dominant-baseline="middle"
457
+ style="font-size: {responsiveLabelFontSize()}px;"
245
458
  >
246
- {#if showPercentages}
459
+ {#if !hidePercentages}
247
460
  {slice.percentage.toFixed(1)}%
248
- {:else if showValues}
249
- {slice.value}
461
+ {:else if !hideValues}
462
+ {formatValue(slice.value)}
250
463
  {/if}
251
464
  </text>
252
465
  {/if}
253
466
  {/each}
254
467
 
255
- {#if donut}
256
- <text
257
- x="0"
258
- y="-5"
259
- class="pie-chart-center-value"
260
- text-anchor="middle"
261
- dominant-baseline="middle"
262
- >
263
- {centerValue ?? total}
264
- </text>
265
- <text
266
- x="0"
267
- y="15"
268
- class="pie-chart-center-label"
269
- text-anchor="middle"
270
- dominant-baseline="middle"
271
- >
272
- {centerLabel}
273
- </text>
468
+ {#if donut && shouldShowCenterText}
469
+ {#if centerContent}
470
+ <foreignObject
471
+ x={-innerRadius * 0.7}
472
+ y={-innerRadius * 0.4}
473
+ width={innerRadius * 1.4}
474
+ height={innerRadius * 0.8}
475
+ >
476
+ <div class="pie-chart-center-custom">
477
+ {@render centerContent()}
478
+ </div>
479
+ </foreignObject>
480
+ {:else}
481
+ <text
482
+ x="0"
483
+ y="-8"
484
+ class="pie-chart-center-value"
485
+ text-anchor="middle"
486
+ dominant-baseline="middle"
487
+ style="font-size: {responsiveValueFontSize()}px;"
488
+ >
489
+ {computedCenterValue()}
490
+ </text>
491
+ <text
492
+ x="0"
493
+ y="12"
494
+ class="pie-chart-center-label"
495
+ text-anchor="middle"
496
+ dominant-baseline="middle"
497
+ style="font-size: {responsiveLabelFontSize()}px;"
498
+ >
499
+ {centerLabel}
500
+ </text>
501
+ {/if}
274
502
  {/if}
275
503
  </g>
276
504
  {/if}
@@ -280,28 +508,41 @@
280
508
  {#if tooltipData && isTooltipActive}
281
509
  <div
282
510
  class="pie-chart-tooltip"
283
- style="top: {tooltipPosition.y}px; left: {tooltipPosition.x}px;"
511
+ style="left: {tooltipPosition.x}px; top: {tooltipPosition.y}px;"
284
512
  >
285
- <div class="pie-chart-tooltip-content">
286
- <div class="pie-chart-tooltip-title">{tooltipData.label}</div>
287
- <div class="pie-chart-tooltip-row">
288
- <div class="pie-chart-tooltip-color is-{tooltipData.color}"></div>
289
- <span class="pie-chart-tooltip-value">
290
- {tooltipData.value} ({tooltipData.percentage.toFixed(1)}%)
291
- </span>
513
+ {#if tooltipContent}
514
+ {@render tooltipContent({ slice: tooltipData, percentage: tooltipData.percentage })}
515
+ {:else}
516
+ <div class="pie-chart-tooltip-content">
517
+ <div class="pie-chart-tooltip-title">{tooltipData.label}</div>
518
+ <div class="pie-chart-tooltip-row">
519
+ <div class="pie-chart-tooltip-color is-{tooltipData.color}"></div>
520
+ <span class="pie-chart-tooltip-value">
521
+ {formatValue(tooltipData.value)} ({tooltipData.percentage.toFixed(1)}%)
522
+ </span>
523
+ </div>
292
524
  </div>
293
- </div>
525
+ {/if}
294
526
  </div>
295
527
  {/if}
296
528
 
297
- {#if showLegend}
298
- <div class="pie-chart-legend">
299
- {#each slices as slice}
300
- <div class="pie-chart-legend-item">
529
+ {#if !hideLegend && legendPosition !== 'none'}
530
+ <div class={cn('pie-chart-legend', legendLayoutClass())}>
531
+ {#each slices as slice, i}
532
+ {@const isSelected = selected.includes(i)}
533
+ <div
534
+ class={cn('pie-chart-legend-item', isSelected && 'is-selected')}
535
+ onclick={() => handleSliceClick(slice)}
536
+ onkeydown={(e) => e.key === 'Enter' && handleSliceClick(slice)}
537
+ role="button"
538
+ tabindex="0"
539
+ >
301
540
  <div class={cn('pie-chart-legend-color', colorClass[slice.color])}></div>
302
541
  <span>{slice.label}</span>
303
- {#if showValues}
304
- <span class="pie-chart-legend-value">({slice.value})</span>
542
+ {#if !hideValues}
543
+ <span class="pie-chart-legend-value">
544
+ ({formatValue(slice.value)})
545
+ </span>
305
546
  {/if}
306
547
  </div>
307
548
  {/each}
@@ -1,27 +1,55 @@
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
  label: string;
4
8
  value: number;
5
9
  color?: Color;
10
+ disabled?: boolean;
11
+ };
12
+ type Slice = DataPoint & {
13
+ startAngle: number;
14
+ endAngle: number;
15
+ midAngle: number;
16
+ percentage: number;
17
+ color: Color;
18
+ index: number;
6
19
  };
7
20
  type Props = {
8
21
  data?: DataPoint[];
9
- colors?: Color[];
10
22
  donut?: boolean;
11
23
  donutWidth?: number;
12
- showLabels?: boolean;
13
- showValues?: boolean;
14
- showLegend?: boolean;
15
- showPercentages?: boolean;
24
+ size?: Size;
25
+ palette?: Palette;
26
+ legendPosition?: LegendPosition;
16
27
  centerLabel?: string;
17
28
  centerValue?: string | number;
18
29
  startAngle?: number;
19
30
  padAngle?: number;
31
+ disableAnimation?: boolean;
32
+ animationDuration?: number;
33
+ showGlow?: boolean;
34
+ showGradient?: boolean;
35
+ hideLabels?: boolean;
36
+ hideValues?: boolean;
37
+ hideLegend?: boolean;
38
+ hidePercentages?: boolean;
20
39
  loading?: boolean;
21
40
  empty?: boolean;
22
41
  emptyText?: string;
23
42
  rootClass?: string;
24
43
  chartClass?: string;
44
+ valueFormatter?: (value: number) => string;
45
+ onClick?: (slice: DataPoint, index: number) => void;
46
+ onHover?: (slice: DataPoint | null, index: number) => void;
47
+ selected?: number[];
48
+ centerContent?: Snippet;
49
+ tooltipContent?: Snippet<[{
50
+ slice: Slice;
51
+ percentage: number;
52
+ }]>;
25
53
  };
26
54
  declare const PieChart: import("svelte").Component<Props, {}, "">;
27
55
  type PieChart = ReturnType<typeof PieChart>;