@juspay/svelte-ui-components 2.30.0 → 2.32.0

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 (45) hide show
  1. package/dist/AreaChart/AreaChart.svelte +421 -0
  2. package/dist/AreaChart/AreaChart.svelte.d.ts +4 -0
  3. package/dist/AreaChart/properties.d.ts +62 -0
  4. package/dist/AreaChart/properties.js +1 -0
  5. package/dist/BarChart/BarChart.svelte +372 -0
  6. package/dist/BarChart/BarChart.svelte.d.ts +4 -0
  7. package/dist/BarChart/properties.d.ts +44 -0
  8. package/dist/BarChart/properties.js +1 -0
  9. package/dist/LineChart/LineChart.svelte +361 -0
  10. package/dist/LineChart/LineChart.svelte.d.ts +4 -0
  11. package/dist/LineChart/properties.d.ts +59 -0
  12. package/dist/LineChart/properties.js +1 -0
  13. package/dist/PieChart/PieChart.svelte +236 -0
  14. package/dist/PieChart/PieChart.svelte.d.ts +4 -0
  15. package/dist/PieChart/properties.d.ts +36 -0
  16. package/dist/PieChart/properties.js +1 -0
  17. package/dist/SankeyChart/SankeyChart.svelte +312 -0
  18. package/dist/SankeyChart/SankeyChart.svelte.d.ts +4 -0
  19. package/dist/SankeyChart/properties.d.ts +52 -0
  20. package/dist/SankeyChart/properties.js +1 -0
  21. package/dist/Select/Select.svelte +43 -24
  22. package/dist/Select/properties.d.ts +5 -0
  23. package/dist/_chart/Axis.svelte +143 -0
  24. package/dist/_chart/Axis.svelte.d.ts +4 -0
  25. package/dist/_chart/ChartContainer.svelte +81 -0
  26. package/dist/_chart/ChartContainer.svelte.d.ts +4 -0
  27. package/dist/_chart/ChartTooltip.svelte +81 -0
  28. package/dist/_chart/ChartTooltip.svelte.d.ts +4 -0
  29. package/dist/_chart/Legend.svelte +59 -0
  30. package/dist/_chart/Legend.svelte.d.ts +4 -0
  31. package/dist/_chart/colors.d.ts +4 -0
  32. package/dist/_chart/colors.js +36 -0
  33. package/dist/_chart/format.d.ts +3 -0
  34. package/dist/_chart/format.js +28 -0
  35. package/dist/_chart/geometry.d.ts +24 -0
  36. package/dist/_chart/geometry.js +204 -0
  37. package/dist/_chart/paths.d.ts +4 -0
  38. package/dist/_chart/paths.js +138 -0
  39. package/dist/_chart/scales.d.ts +5 -0
  40. package/dist/_chart/scales.js +77 -0
  41. package/dist/_chart/types.d.ts +128 -0
  42. package/dist/_chart/types.js +1 -0
  43. package/dist/index.d.ts +10 -0
  44. package/dist/index.js +5 -0
  45. package/package.json +1 -1
@@ -0,0 +1,421 @@
1
+ <script lang="ts">
2
+ import type { AreaChartProperties, AreaChartTooltipContext } from './properties';
3
+ import ChartContainer from '../_chart/ChartContainer.svelte';
4
+ import Axis from '../_chart/Axis.svelte';
5
+ import ChartTooltip from '../_chart/ChartTooltip.svelte';
6
+ import Legend from '../_chart/Legend.svelte';
7
+ import { createLinearScale, niceLinearDomain } from '../_chart/scales';
8
+ import { computeChartDimensions, computeStackedValues } from '../_chart/geometry';
9
+ import { linePath, areaPath } from '../_chart/paths';
10
+ import { getColor } from '../_chart/colors';
11
+ import { formatNumber } from '../_chart/format';
12
+ import type { LegendItem, Point } from '../_chart/types';
13
+
14
+ // ── Props ──────────────────────────────────────────────────────
15
+
16
+ let {
17
+ series,
18
+ curve = 'monotone',
19
+ stacked = false,
20
+ stackNormalize = false,
21
+ fillOpacity = 0.3,
22
+ showDots = false,
23
+ showLine = true,
24
+ showValues = false,
25
+ strokeWidth = 2,
26
+ showGridlines = true,
27
+ showXAxis = true,
28
+ showYAxis = true,
29
+ showLegend = false,
30
+ xDomain,
31
+ yDomain,
32
+ xAxisLabel,
33
+ yAxisLabel,
34
+ xTickFormat,
35
+ yTickFormat,
36
+ aspectRatio = 16 / 9,
37
+ tooltipSnippet,
38
+ empty,
39
+ onpointhover,
40
+ onpointclick,
41
+ testId,
42
+ classes
43
+ }: AreaChartProperties = $props();
44
+
45
+ // ── State ──────────────────────────────────────────────────────
46
+
47
+ let containerEl: HTMLDivElement | null = $state(null);
48
+ let chartWidth = $state(0);
49
+ let chartHeight = $state(0);
50
+ let hovered = $state<{ si: number; pi: number } | null>(null);
51
+ let mouseX = $state(0);
52
+ let mouseY = $state(0);
53
+
54
+ // ── Layout ─────────────────────────────────────────────────────
55
+
56
+ let dims = $derived(computeChartDimensions(chartWidth, chartHeight));
57
+ let isStacked = $derived(stacked || stackNormalize);
58
+ let isEmpty = $derived(series.length === 0 || series.every((s) => s.data.length === 0));
59
+
60
+ let normalizedSeries = $derived.by(() => {
61
+ if (!stackNormalize) {
62
+ return series.map((s) => s.data);
63
+ }
64
+ const totals = series[0]?.data.map((_, i) =>
65
+ series.reduce((sum, s) => sum + Math.max(0, s.data[i]?.y ?? 0), 0)
66
+ );
67
+ return series.map((s) =>
68
+ s.data.map((d, i) => ({
69
+ x: d.x,
70
+ y: totals && totals[i] > 0 ? (Math.max(0, d.y) / totals[i]) * 100 : 0,
71
+ label: d.label
72
+ }))
73
+ );
74
+ });
75
+
76
+ let xExtent = $derived.by<[number, number]>(() => {
77
+ if (xDomain) {
78
+ return xDomain;
79
+ }
80
+ const allX = series.flatMap((s) => s.data.map((d) => d.x));
81
+ if (allX.length === 0) {
82
+ return [0, 1];
83
+ }
84
+ return niceLinearDomain(Math.min(...allX), Math.max(...allX));
85
+ });
86
+ let yExtent = $derived.by<[number, number]>(() => {
87
+ if (yDomain) {
88
+ return yDomain;
89
+ }
90
+ if (stackNormalize) {
91
+ return [0, 100];
92
+ }
93
+ if (isStacked) {
94
+ const topValues = computeStackedValues(normalizedSeries).flatMap((s) => s.map((p) => p.y1));
95
+ if (topValues.length === 0) {
96
+ return [0, 1];
97
+ }
98
+ return niceLinearDomain(0, Math.max(...topValues));
99
+ }
100
+ const allY = series.flatMap((s) => s.data.map((d) => d.y));
101
+ if (allY.length === 0) {
102
+ return [0, 1];
103
+ }
104
+ return niceLinearDomain(Math.min(0, ...allY), Math.max(...allY));
105
+ });
106
+
107
+ let xScale = $derived(createLinearScale(xExtent, [0, dims.innerWidth]));
108
+ let yScale = $derived(createLinearScale(yExtent, [dims.innerHeight, 0]));
109
+
110
+ let areas = $derived.by(() => {
111
+ if (isStacked) {
112
+ const stackedVals = computeStackedValues(normalizedSeries);
113
+ return stackedVals.map((seg, si) => {
114
+ const color = series[si].color ?? getColor(si);
115
+ const top: Point[] = seg.map((p) => ({ x: xScale(p.x), y: yScale(p.y1) }));
116
+ const bot: Point[] = seg.map((p) => ({ x: xScale(p.x), y: yScale(p.y0) })).reverse();
117
+ const topD = linePath(top, curve);
118
+ const botD = linePath(bot, curve);
119
+ const areaD =
120
+ topD + ` L ${bot[0].x} ${bot[0].y}` + botD.replace(/^M [^ ]+ [^ ]+/, '') + ' Z';
121
+ return { color, points: top, areaD, lineD: topD };
122
+ });
123
+ }
124
+ return series.map((s, si) => {
125
+ const color = s.color ?? getColor(si);
126
+ const points: Point[] = s.data.map((d) => ({ x: xScale(d.x), y: yScale(d.y) }));
127
+ return {
128
+ color,
129
+ points,
130
+ areaD: areaPath(points, dims.innerHeight, curve),
131
+ lineD: linePath(points, curve)
132
+ };
133
+ });
134
+ });
135
+
136
+ let legendItems = $derived<LegendItem[]>(
137
+ series.map((s, i) => ({ label: s.name, color: s.color ?? getColor(i) }))
138
+ );
139
+
140
+ // ── Tooltip ────────────────────────────────────────────────────
141
+
142
+ let hoverLineX = $derived(
143
+ hovered === null ? null : (areas[hovered.si]?.points[hovered.pi]?.x ?? null)
144
+ );
145
+
146
+ let tooltipContext = $derived.by<AreaChartTooltipContext | null>(() => {
147
+ if (hovered === null) {
148
+ return null;
149
+ }
150
+ const hp = series[hovered.si]?.data[hovered.pi];
151
+ if (!hp) {
152
+ return null;
153
+ }
154
+ return {
155
+ x: hp.x,
156
+ points: series.map((s, si) => {
157
+ const p = s.data[hovered!.pi];
158
+ return {
159
+ name: s.name,
160
+ y: p?.y ?? 0,
161
+ color: s.color ?? getColor(si),
162
+ label: p?.label
163
+ };
164
+ })
165
+ };
166
+ });
167
+
168
+ let tooltipData = $derived.by(() => {
169
+ if (tooltipContext === null) {
170
+ return null;
171
+ }
172
+ return {
173
+ title: `x: ${formatNumber(tooltipContext.x)}`,
174
+ items: tooltipContext.points.map((p) => ({
175
+ label: p.name,
176
+ value: formatNumber(p.y),
177
+ color: p.color
178
+ }))
179
+ };
180
+ });
181
+
182
+ // ── Interactions ───────────────────────────────────────────────
183
+
184
+ function trackMouse(e: MouseEvent) {
185
+ if (containerEl === null) {
186
+ return;
187
+ }
188
+ const rect = containerEl.getBoundingClientRect();
189
+ mouseX = e.clientX - rect.left;
190
+ mouseY = e.clientY - rect.top;
191
+ }
192
+
193
+ function findNearest(plotX: number, plotY: number): { si: number; pi: number } | null {
194
+ if (series.length === 0) {
195
+ return null;
196
+ }
197
+ // Use the longest series as the index reference so hover still works when
198
+ // the first series is empty or shorter than the others.
199
+ const reference = series.reduce((a, b) => (b.data.length > a.data.length ? b : a)).data;
200
+ if (reference.length === 0) {
201
+ return null;
202
+ }
203
+ let nearestPi = 0;
204
+ let nearestXDist = Infinity;
205
+ for (let i = 0; i < reference.length; i++) {
206
+ const px = xScale(reference[i].x);
207
+ const dist = Math.abs(px - plotX);
208
+ if (dist < nearestXDist) {
209
+ nearestXDist = dist;
210
+ nearestPi = i;
211
+ }
212
+ }
213
+ let nearestSi = 0;
214
+ let nearestYDist = Infinity;
215
+ for (let si = 0; si < areas.length; si++) {
216
+ const p = areas[si].points[nearestPi];
217
+ if (!p) {
218
+ continue;
219
+ }
220
+ const dist = Math.abs(p.y - plotY);
221
+ if (dist < nearestYDist) {
222
+ nearestYDist = dist;
223
+ nearestSi = si;
224
+ }
225
+ }
226
+ return { si: nearestSi, pi: nearestPi };
227
+ }
228
+
229
+ function handleOverlayMove(e: MouseEvent) {
230
+ trackMouse(e);
231
+ const plotX = mouseX - dims.margin.left;
232
+ const plotY = mouseY - dims.margin.top;
233
+ const next = findNearest(plotX, plotY);
234
+ if (next === null) {
235
+ hovered = null;
236
+ return;
237
+ }
238
+ if (hovered === null || hovered.si !== next.si || hovered.pi !== next.pi) {
239
+ hovered = next;
240
+ const point = series[next.si].data[next.pi];
241
+ onpointhover?.({ seriesIndex: next.si, pointIndex: next.pi, point });
242
+ }
243
+ }
244
+
245
+ function handleLeave() {
246
+ if (hovered !== null) {
247
+ hovered = null;
248
+ onpointhover?.(null);
249
+ }
250
+ }
251
+
252
+ function handleClick() {
253
+ if (hovered === null) {
254
+ return;
255
+ }
256
+ const point = series[hovered.si]?.data[hovered.pi];
257
+ if (point) {
258
+ onpointclick?.({ seriesIndex: hovered.si, pointIndex: hovered.pi, point });
259
+ }
260
+ }
261
+ </script>
262
+
263
+ <div
264
+ class="area-chart {classes ?? ''}"
265
+ bind:this={containerEl}
266
+ data-pw={typeof testId === 'string' ? testId : null}
267
+ >
268
+ {#if isEmpty && typeof empty === 'function'}
269
+ <div class="chart-empty">{@render empty()}</div>
270
+ {:else}
271
+ {#if showLegend && series.length > 1}
272
+ <Legend items={legendItems} position="top" />
273
+ {/if}
274
+
275
+ <ChartContainer bind:width={chartWidth} bind:height={chartHeight} {aspectRatio}>
276
+ <g transform="translate({dims.margin.left}, {dims.margin.top})">
277
+ {#if showYAxis}
278
+ <Axis
279
+ orientation="left"
280
+ scale={yScale}
281
+ {showGridlines}
282
+ gridlineLength={dims.innerWidth}
283
+ label={yAxisLabel}
284
+ tickFormat={yTickFormat}
285
+ />
286
+ {/if}
287
+ {#if showXAxis}
288
+ <g transform="translate(0, {dims.innerHeight})">
289
+ <Axis orientation="bottom" scale={xScale} label={xAxisLabel} tickFormat={xTickFormat} />
290
+ </g>
291
+ {/if}
292
+
293
+ {#each areas as area, si (si)}
294
+ <path
295
+ class="area-fill"
296
+ class:dimmed={hovered !== null && hovered.si !== si}
297
+ d={area.areaD}
298
+ fill={area.color}
299
+ fill-opacity={hovered?.si === si ? fillOpacity + 0.2 : fillOpacity}
300
+ />
301
+ {#if showLine}
302
+ <path
303
+ class="area-line"
304
+ class:dimmed={hovered !== null && hovered.si !== si}
305
+ d={area.lineD}
306
+ stroke={area.color}
307
+ stroke-width={strokeWidth}
308
+ fill="none"
309
+ />
310
+ {/if}
311
+ {#if showDots}
312
+ {#each area.points as point, pi (pi)}
313
+ <circle
314
+ class="dot"
315
+ cx={point.x}
316
+ cy={point.y}
317
+ r={hovered?.si === si && hovered?.pi === pi ? 6 : 3}
318
+ fill={area.color}
319
+ />
320
+ {/each}
321
+ {/if}
322
+ {#if showValues}
323
+ {#each area.points as point, pi (pi)}
324
+ <text
325
+ class="point-value"
326
+ x={point.x}
327
+ y={point.y - 8}
328
+ text-anchor="middle"
329
+ dominant-baseline="auto">{formatNumber(series[si].data[pi].y)}</text
330
+ >
331
+ {/each}
332
+ {/if}
333
+ {/each}
334
+
335
+ {#if hoverLineX !== null}
336
+ <line class="hover-line" x1={hoverLineX} x2={hoverLineX} y1={0} y2={dims.innerHeight} />
337
+ {/if}
338
+
339
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
340
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
341
+ <rect
342
+ class="hover-overlay"
343
+ x={0}
344
+ y={0}
345
+ width={dims.innerWidth}
346
+ height={dims.innerHeight}
347
+ fill="transparent"
348
+ onmousemove={handleOverlayMove}
349
+ onmouseleave={handleLeave}
350
+ onclick={handleClick}
351
+ />
352
+ </g>
353
+ </ChartContainer>
354
+
355
+ {#if typeof tooltipSnippet === 'function' && tooltipContext !== null}
356
+ <div class="chart-tooltip-slot" style="left: {mouseX + 12}px; top: {mouseY - 12}px;">
357
+ {@render tooltipSnippet(tooltipContext)}
358
+ </div>
359
+ {:else}
360
+ <ChartTooltip data={tooltipData} {mouseX} {mouseY} />
361
+ {/if}
362
+ {/if}
363
+ </div>
364
+
365
+ <style>
366
+ .area-chart {
367
+ width: 100%;
368
+ position: relative;
369
+ }
370
+ .area-fill {
371
+ transition:
372
+ fill-opacity var(--chart-transition-duration, 0.2s) ease,
373
+ opacity var(--chart-transition-duration, 0.2s) ease;
374
+ pointer-events: none;
375
+ }
376
+ .area-fill.dimmed {
377
+ opacity: var(--areachart-dimmed-opacity, 0.1);
378
+ }
379
+ .area-line {
380
+ transition: opacity var(--chart-transition-duration, 0.2s) ease;
381
+ stroke-linecap: round;
382
+ stroke-linejoin: round;
383
+ pointer-events: none;
384
+ }
385
+ .area-line.dimmed {
386
+ opacity: var(--areachart-dimmed-opacity, 0.1);
387
+ }
388
+ .dot {
389
+ transition:
390
+ r var(--chart-transition-duration, 0.2s) ease,
391
+ opacity var(--chart-transition-duration, 0.2s) ease;
392
+ stroke: var(--chart-background, #fff);
393
+ stroke-width: 2;
394
+ pointer-events: none;
395
+ }
396
+ .point-value {
397
+ fill: var(--areachart-value-color, #333);
398
+ font-size: var(--areachart-value-font-size, 11px);
399
+ font-family: var(--chart-font-family, inherit);
400
+ pointer-events: none;
401
+ }
402
+ .hover-line {
403
+ stroke: var(--linechart-hover-line-color, #ccc);
404
+ stroke-width: 1;
405
+ stroke-dasharray: 4 4;
406
+ pointer-events: none;
407
+ }
408
+ .hover-overlay {
409
+ cursor: crosshair;
410
+ }
411
+ .chart-tooltip-slot {
412
+ position: absolute;
413
+ z-index: 10;
414
+ pointer-events: none;
415
+ }
416
+ .chart-empty {
417
+ padding: var(--chart-empty-padding, 32px 24px);
418
+ color: var(--chart-empty-color, #9ca3af);
419
+ text-align: center;
420
+ }
421
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { AreaChartProperties } from './properties';
2
+ declare const AreaChart: import("svelte").Component<AreaChartProperties, {}, "">;
3
+ type AreaChart = ReturnType<typeof AreaChart>;
4
+ export default AreaChart;
@@ -0,0 +1,62 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { CurveType } from '../_chart/types';
3
+ export type AreaChartDataPoint = {
4
+ x: number;
5
+ y: number;
6
+ label?: string;
7
+ };
8
+ export type AreaChartSeries = {
9
+ name: string;
10
+ data: AreaChartDataPoint[];
11
+ color?: string;
12
+ };
13
+ export type AreaChartTooltipContext = {
14
+ x: number;
15
+ points: Array<{
16
+ name: string;
17
+ y: number;
18
+ color: string;
19
+ label?: string;
20
+ }>;
21
+ };
22
+ export type AreaChartProperties = MandatoryAreaChartProperties & OptionalAreaChartProperties & AreaChartEventProperties;
23
+ export type MandatoryAreaChartProperties = {
24
+ series: AreaChartSeries[];
25
+ };
26
+ export type OptionalAreaChartProperties = {
27
+ curve?: CurveType;
28
+ stacked?: boolean;
29
+ stackNormalize?: boolean;
30
+ fillOpacity?: number;
31
+ showDots?: boolean;
32
+ showLine?: boolean;
33
+ showValues?: boolean;
34
+ strokeWidth?: number;
35
+ showGridlines?: boolean;
36
+ showXAxis?: boolean;
37
+ showYAxis?: boolean;
38
+ showLegend?: boolean;
39
+ xDomain?: [number, number];
40
+ yDomain?: [number, number];
41
+ xAxisLabel?: string;
42
+ yAxisLabel?: string;
43
+ xTickFormat?: (value: number | string) => string;
44
+ yTickFormat?: (value: number | string) => string;
45
+ aspectRatio?: number;
46
+ tooltipSnippet?: Snippet<[AreaChartTooltipContext]>;
47
+ empty?: Snippet;
48
+ testId?: string;
49
+ classes?: string;
50
+ };
51
+ export type AreaChartEventProperties = {
52
+ onpointhover?: (event: {
53
+ seriesIndex: number;
54
+ pointIndex: number;
55
+ point: AreaChartDataPoint;
56
+ } | null) => void;
57
+ onpointclick?: (event: {
58
+ seriesIndex: number;
59
+ pointIndex: number;
60
+ point: AreaChartDataPoint;
61
+ }) => void;
62
+ };
@@ -0,0 +1 @@
1
+ export {};