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.
- package/dist/charts/ArcChart.svelte +9 -13
- package/dist/charts/ArcChart.svelte.d.ts +3 -3
- package/dist/charts/AreaChart.svelte +347 -118
- package/dist/charts/AreaChart.svelte.d.ts +33 -4
- package/dist/charts/BarChart.svelte +288 -66
- package/dist/charts/BarChart.svelte.d.ts +26 -1
- package/dist/charts/Candlestick.svelte +53 -50
- package/dist/charts/Candlestick.svelte.d.ts +8 -8
- package/dist/charts/LineChart.svelte +391 -91
- package/dist/charts/LineChart.svelte.d.ts +26 -3
- package/dist/charts/PieChart.svelte +333 -92
- package/dist/charts/PieChart.svelte.d.ts +33 -5
- package/dist/charts/css/arc-chart.css +3 -3
- package/dist/charts/css/area-chart.css +127 -29
- package/dist/charts/css/bar-chart.css +114 -8
- package/dist/charts/css/candlestick.css +2 -0
- package/dist/charts/css/line-chart.css +111 -13
- package/dist/charts/css/pie-chart.css +92 -20
- package/dist/control/Audio.svelte +86 -44
- package/dist/control/Audio.svelte.d.ts +4 -1
- package/dist/control/Button.svelte +18 -27
- package/dist/control/Button.svelte.d.ts +3 -2
- package/dist/control/IconButton.svelte +17 -27
- package/dist/control/IconButton.svelte.d.ts +3 -3
- package/dist/control/Image.svelte +123 -0
- package/dist/control/Image.svelte.d.ts +13 -0
- package/dist/control/Record.svelte +144 -98
- package/dist/control/Record.svelte.d.ts +2 -1
- package/dist/control/ToggleGroup.svelte +22 -8
- package/dist/control/ToggleGroup.svelte.d.ts +2 -1
- package/dist/control/ToggleTheme.svelte +13 -11
- package/dist/control/ToggleTheme.svelte.d.ts +3 -2
- package/dist/control/Video.svelte +55 -29
- package/dist/control/Video.svelte.d.ts +1 -0
- package/dist/control/css/btn.css +200 -152
- package/dist/control/css/image.css +56 -0
- package/dist/control/css/media.css +95 -30
- package/dist/control/css/toggle-group.css +269 -84
- package/dist/control/css/video.css +1 -14
- package/dist/css/animations.css +5 -9
- package/dist/css/base.css +13 -347
- package/dist/css/decorations.css +402 -0
- package/dist/css/rich-text.css +485 -0
- package/dist/css/transitions.css +158 -0
- package/dist/css/typography.css +291 -0
- package/dist/display/Accordion.svelte +28 -4
- package/dist/display/Accordion.svelte.d.ts +2 -1
- package/dist/display/Alert.svelte +32 -12
- package/dist/display/Alert.svelte.d.ts +2 -3
- package/dist/display/Avatar.svelte +23 -18
- package/dist/display/Avatar.svelte.d.ts +4 -1
- package/dist/display/AvatarGroup.svelte +20 -18
- package/dist/display/AvatarGroup.svelte.d.ts +6 -3
- package/dist/display/Badge.svelte +11 -4
- package/dist/display/Badge.svelte.d.ts +2 -1
- package/dist/display/Card.svelte +15 -14
- package/dist/display/Card.svelte.d.ts +2 -3
- package/dist/display/Carousel.svelte +130 -99
- package/dist/display/Carousel.svelte.d.ts +6 -4
- package/dist/display/ChatBox.svelte +245 -106
- package/dist/display/ChatBox.svelte.d.ts +32 -5
- package/dist/display/Chip.svelte +31 -17
- package/dist/display/Chip.svelte.d.ts +3 -2
- package/dist/display/Code.svelte +6 -3
- package/dist/display/Code.svelte.d.ts +1 -0
- package/dist/display/Collapsible.svelte +30 -4
- package/dist/display/Collapsible.svelte.d.ts +2 -1
- package/dist/display/Empty.svelte +37 -3
- package/dist/display/Empty.svelte.d.ts +3 -0
- package/dist/display/Item.svelte +30 -11
- package/dist/display/Item.svelte.d.ts +2 -2
- package/dist/display/Map.svelte +488 -0
- package/dist/display/Map.svelte.d.ts +44 -0
- package/dist/display/Section.svelte +14 -12
- package/dist/display/Section.svelte.d.ts +2 -3
- package/dist/display/Skeleton.svelte +32 -0
- package/dist/display/Skeleton.svelte.d.ts +10 -0
- package/dist/display/Table.svelte +94 -132
- package/dist/display/Table.svelte.d.ts +10 -1
- package/dist/display/css/accordion.css +349 -52
- package/dist/display/css/alert.css +18 -25
- package/dist/display/css/avatar-group.css +38 -75
- package/dist/display/css/avatar.css +139 -121
- package/dist/display/css/badge.css +50 -27
- package/dist/display/css/card.css +51 -71
- package/dist/display/css/carousel.css +25 -5
- package/dist/display/css/chat-box.css +158 -26
- package/dist/display/css/chip.css +142 -68
- package/dist/display/css/code.css +2 -6
- package/dist/display/css/collapsible.css +349 -45
- package/dist/display/css/divider.css +8 -6
- package/dist/display/css/empty.css +7 -0
- package/dist/display/css/item.css +311 -89
- package/dist/display/css/map.css +164 -0
- package/dist/display/css/section.css +78 -33
- package/dist/display/css/skeleton.css +58 -0
- package/dist/display/css/table.css +320 -189
- package/dist/form/Checkbox.svelte +11 -5
- package/dist/form/Checkbox.svelte.d.ts +2 -1
- package/dist/form/ColorField.svelte +543 -0
- package/dist/form/ColorField.svelte.d.ts +29 -0
- package/dist/form/ComboBox.svelte +24 -9
- package/dist/form/ComboBox.svelte.d.ts +2 -2
- package/dist/form/CsvField.svelte +62 -136
- package/dist/form/CsvField.svelte.d.ts +2 -2
- package/dist/form/DateField.svelte +33 -15
- package/dist/form/DateField.svelte.d.ts +2 -1
- package/dist/form/DateRange.svelte +436 -0
- package/dist/form/DateRange.svelte.d.ts +24 -0
- package/dist/form/DragDrop.svelte +348 -0
- package/dist/form/DragDrop.svelte.d.ts +32 -0
- package/dist/form/Dropzone.svelte +28 -8
- package/dist/form/Dropzone.svelte.d.ts +2 -2
- package/dist/form/Editor.svelte +626 -0
- package/dist/form/Editor.svelte.d.ts +50 -0
- package/dist/form/ImageCropper.svelte +291 -61
- package/dist/form/ImageCropper.svelte.d.ts +15 -1
- package/dist/form/{PasswordStrength.svelte → PasswordField.svelte} +58 -24
- package/dist/form/{PasswordStrength.svelte.d.ts → PasswordField.svelte.d.ts} +6 -5
- package/dist/form/PhoneField.svelte +26 -14
- package/dist/form/PhoneField.svelte.d.ts +4 -3
- package/dist/form/PinField.svelte +39 -31
- package/dist/form/PinField.svelte.d.ts +3 -3
- package/dist/form/RadioGroup.svelte +4 -4
- package/dist/form/RadioGroup.svelte.d.ts +1 -1
- package/dist/form/Select.svelte +20 -19
- package/dist/form/Select.svelte.d.ts +2 -2
- package/dist/form/Slider.svelte +4 -2
- package/dist/form/Slider.svelte.d.ts +1 -0
- package/dist/form/TextField.svelte +16 -7
- package/dist/form/TextField.svelte.d.ts +2 -2
- package/dist/form/Textarea.svelte +15 -6
- package/dist/form/Textarea.svelte.d.ts +2 -2
- package/dist/form/Toggle.svelte +1 -1
- package/dist/form/css/checkbox.css +18 -2
- package/dist/form/css/color-field.css +141 -0
- package/dist/form/css/control.css +193 -82
- package/dist/form/css/csv-field.css +226 -0
- package/dist/form/css/date-range.css +122 -0
- package/dist/form/css/date.css +24 -2
- package/dist/form/css/drag-drop.css +271 -0
- package/dist/form/css/dropzone.css +153 -34
- package/dist/form/css/editor.css +367 -0
- package/dist/form/css/field.css +4 -0
- package/dist/form/css/image-cropper.css +223 -22
- package/dist/form/css/radio-group.css +1 -1
- package/dist/form/css/select.css +2 -2
- package/dist/form/css/slider.css +1 -0
- package/dist/form/css/textarea.css +178 -75
- package/dist/form/css/toggle.css +3 -3
- package/dist/hooks/use-table.svelte.d.ts +1 -0
- package/dist/hooks/use-table.svelte.js +6 -0
- package/dist/icons/index.d.ts +30 -2
- package/dist/icons/index.js +32 -4
- package/dist/index.css +16 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +11 -3
- package/dist/layout/AppBar.svelte +22 -14
- package/dist/layout/AppBar.svelte.d.ts +2 -1
- package/dist/layout/Footer.svelte +19 -11
- package/dist/layout/Footer.svelte.d.ts +2 -1
- package/dist/layout/Provider.svelte +27 -4
- package/dist/layout/Provider.svelte.d.ts +3 -1
- package/dist/layout/css/app-bar.css +63 -66
- package/dist/layout/css/footer.css +62 -65
- package/dist/navigation/BottomNav.svelte +41 -13
- package/dist/navigation/FooterGroup.svelte +1 -1
- package/dist/navigation/NavMenu.svelte +47 -23
- package/dist/navigation/NavMenu.svelte.d.ts +29 -0
- package/dist/navigation/Pagination.svelte +158 -0
- package/dist/navigation/Pagination.svelte.d.ts +18 -0
- package/dist/navigation/SideNav.svelte +30 -25
- package/dist/navigation/SideNav.svelte.d.ts +2 -3
- package/dist/navigation/Tabs.svelte +17 -7
- package/dist/navigation/Tabs.svelte.d.ts +2 -2
- package/dist/navigation/css/bottom-nav.css +279 -257
- package/dist/navigation/css/footer-group.css +1 -1
- package/dist/navigation/css/footer-nav.css +1 -1
- package/dist/navigation/css/nav-menu.css +332 -106
- package/dist/navigation/css/pagination.css +74 -0
- package/dist/navigation/css/side-nav.css +515 -75
- package/dist/navigation/css/tabs.css +246 -52
- package/dist/overlay/Command.svelte +340 -0
- package/dist/overlay/Command.svelte.d.ts +24 -25
- package/dist/overlay/Drawer.svelte +49 -21
- package/dist/overlay/Drawer.svelte.d.ts +2 -2
- package/dist/overlay/Dropdown.svelte +3 -3
- package/dist/overlay/Modal.svelte +51 -16
- package/dist/overlay/Modal.svelte.d.ts +3 -3
- package/dist/overlay/Toast.svelte +41 -17
- package/dist/overlay/Toast.svelte.d.ts +1 -1
- package/dist/overlay/Tooltip.svelte +40 -26
- package/dist/overlay/Tooltip.svelte.d.ts +2 -2
- package/dist/overlay/css/command.css +80 -0
- package/dist/overlay/css/drawer.css +63 -24
- package/dist/overlay/css/dropdown.css +1 -1
- package/dist/overlay/css/hovercard.css +1 -1
- package/dist/overlay/css/modal.css +27 -27
- package/dist/overlay/css/toast.css +17 -29
- package/dist/overlay/css/tooltip.css +83 -66
- package/dist/stores/theme.svelte.js +26 -1
- package/dist/stores/toast.svelte.d.ts +4 -4
- package/dist/stores/toast.svelte.js +2 -2
- 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
|
-
|
|
26
|
+
margin?: Margin;
|
|
27
|
+
hideLine?: boolean;
|
|
17
28
|
showPoints?: boolean;
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
hideGrid?: boolean;
|
|
30
|
+
hideLegend?: boolean;
|
|
20
31
|
curve?: 'linear' | 'smooth';
|
|
21
32
|
strokeWidth?: number;
|
|
22
33
|
fillOpacity?: number;
|
|
23
34
|
stacked?: boolean;
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
+
hideGrid = false,
|
|
75
91
|
showLegend = false,
|
|
76
92
|
showValues = false,
|
|
77
93
|
barPadding = 0.2,
|
|
78
94
|
groupPadding = 0.1,
|
|
79
|
-
barRadius =
|
|
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 ||
|
|
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
|
|
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
|
|
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=
|
|
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
|
|
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
|
-
{
|
|
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
|
|
380
|
-
{@const
|
|
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=
|
|
391
|
-
|
|
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-
|
|
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
|
|
414
|
-
{@const
|
|
415
|
-
|
|
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=
|
|
426
|
-
|
|
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
|
|
436
|
-
{@const
|
|
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=
|
|
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-
|
|
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
|
-
|
|
482
|
-
{
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
|
|
705
|
+
{/if}
|
|
490
706
|
</div>
|
|
491
707
|
{/if}
|
|
492
708
|
|
|
493
|
-
{#if showLegend && normalizedSeries.length > 1}
|
|
494
|
-
<div class=
|
|
495
|
-
{#each normalizedSeries as s}
|
|
496
|
-
<div
|
|
497
|
-
|
|
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
|
-
|
|
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>;
|