@juspay/svelte-ui-components 2.74.0 → 2.76.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 (31) hide show
  1. package/dist/BarChart/BarChart.svelte +163 -48
  2. package/dist/BarChart/properties.d.ts +41 -0
  3. package/dist/DeltaIndicator/DeltaIndicator.svelte +87 -0
  4. package/dist/DeltaIndicator/DeltaIndicator.svelte.d.ts +4 -0
  5. package/dist/DeltaIndicator/properties.d.ts +26 -0
  6. package/dist/DeltaIndicator/properties.js +1 -0
  7. package/dist/DualAxisBarChart/DualAxisBarChart.svelte +521 -0
  8. package/dist/DualAxisBarChart/DualAxisBarChart.svelte.d.ts +4 -0
  9. package/dist/DualAxisBarChart/properties.d.ts +108 -0
  10. package/dist/DualAxisBarChart/properties.js +1 -0
  11. package/dist/FunnelChart/FunnelChart.svelte +335 -0
  12. package/dist/FunnelChart/FunnelChart.svelte.d.ts +4 -0
  13. package/dist/FunnelChart/properties.d.ts +69 -0
  14. package/dist/FunnelChart/properties.js +1 -0
  15. package/dist/IframeViewer/IframeViewer.svelte +65 -0
  16. package/dist/IframeViewer/IframeViewer.svelte.d.ts +4 -0
  17. package/dist/IframeViewer/properties.d.ts +33 -0
  18. package/dist/IframeViewer/properties.js +1 -0
  19. package/dist/LineChart/LineChart.svelte +190 -42
  20. package/dist/LineChart/properties.d.ts +69 -0
  21. package/dist/PieChart/PieChart.svelte +52 -20
  22. package/dist/PieChart/properties.d.ts +26 -0
  23. package/dist/SankeyChart/SankeyChart.svelte +15 -5
  24. package/dist/SankeyChart/properties.d.ts +19 -0
  25. package/dist/_chart/geometry.d.ts +1 -1
  26. package/dist/_chart/geometry.js +5 -4
  27. package/dist/_chart/highlight.d.ts +15 -0
  28. package/dist/_chart/highlight.js +1 -0
  29. package/dist/index.d.ts +9 -0
  30. package/dist/index.js +4 -0
  31. package/package.json +1 -1
@@ -0,0 +1,521 @@
1
+ <script lang="ts">
2
+ import type {
3
+ DualAxisBarChartProperties,
4
+ DualAxisSeries,
5
+ DualAxisTooltipContext
6
+ } from './properties';
7
+ import ChartContainer from '../_chart/ChartContainer.svelte';
8
+ import Axis from '../_chart/Axis.svelte';
9
+ import ChartTooltip from '../_chart/ChartTooltip.svelte';
10
+ import Legend from '../_chart/Legend.svelte';
11
+ import { createBandScale, createLinearScale, niceLinearDomain } from '../_chart/scales';
12
+ import { computeChartDimensions } from '../_chart/geometry';
13
+ import { getColor } from '../_chart/colors';
14
+ import { formatNumber } from '../_chart/format';
15
+ import { roundedRectPath, linePath } from '../_chart/paths';
16
+ import type { LegendItem, TooltipData, LinearScale, BandScale, Point } from '../_chart/types';
17
+
18
+ // ── Per-instance uid for SVG <defs> ids ────────────────────────
19
+ const uid = Math.random().toString(36).slice(2, 9);
20
+
21
+ // ── Props ──────────────────────────────────────────────────────
22
+
23
+ let {
24
+ categories,
25
+ series,
26
+ leftAxis = {},
27
+ rightAxis = {},
28
+ showGridlines = true,
29
+ showLegend = true,
30
+ barRadius = 3,
31
+ barPadding = 0.25,
32
+ aspectRatio = 16 / 9,
33
+ tooltipSnippet,
34
+ onbarclick,
35
+ testId,
36
+ classes
37
+ }: DualAxisBarChartProperties = $props();
38
+
39
+ // ── State ──────────────────────────────────────────────────────
40
+
41
+ let containerEl: HTMLDivElement | null = $state(null);
42
+ let chartWidth = $state(0);
43
+ let chartHeight = $state(0);
44
+ let hoveredCategoryIndex = $state<number | null>(null);
45
+ let mouseX = $state(0);
46
+ let mouseY = $state(0);
47
+
48
+ // ── Formatters ─────────────────────────────────────────────────
49
+
50
+ const leftFormat = $derived(leftAxis.valueFormat ?? formatNumber);
51
+ const rightFormat = $derived(rightAxis.valueFormat ?? formatNumber);
52
+
53
+ // ── Layout — wider right margin to accommodate right-axis labels ─
54
+
55
+ const dims = $derived(
56
+ computeChartDimensions(chartWidth, chartHeight, { top: 24, right: 56, bottom: 40, left: 56 })
57
+ );
58
+
59
+ // ── Scales ─────────────────────────────────────────────────────
60
+
61
+ const catScale: BandScale = $derived(
62
+ createBandScale(categories, [0, dims.innerWidth], barPadding)
63
+ );
64
+
65
+ /**
66
+ * Computes the [min, max] domain for all series mapped to the given axis index,
67
+ * then applies nice rounding. Returns [0, 1] for empty series.
68
+ */
69
+ const axisDomain = (axisIndex: 0 | 1): [number, number] => {
70
+ const axisSeries = series.filter((s) => s.yAxisIndex === axisIndex);
71
+ if (axisSeries.length === 0) {
72
+ return [0, 1];
73
+ }
74
+ const allValues = axisSeries.flatMap((s) => s.data);
75
+ if (allValues.length === 0) {
76
+ return [0, 1];
77
+ }
78
+ return niceLinearDomain(Math.min(0, ...allValues), Math.max(0, ...allValues));
79
+ };
80
+
81
+ const leftDomain: [number, number] = $derived(axisDomain(0));
82
+ const rightDomain: [number, number] = $derived(axisDomain(1));
83
+
84
+ const leftScale: LinearScale = $derived(createLinearScale(leftDomain, [dims.innerHeight, 0]));
85
+ const rightScale: LinearScale = $derived(createLinearScale(rightDomain, [dims.innerHeight, 0]));
86
+
87
+ // ── Series color resolution ────────────────────────────────────
88
+
89
+ const resolvedColor = (s: DualAxisSeries, si: number): string => s.color ?? getColor(si);
90
+
91
+ // ── Column/bar geometry ────────────────────────────────────────
92
+
93
+ /**
94
+ * Groups series by axis so we can compute per-axis sub-band widths.
95
+ * Within an axis group, series are ordered by their original index.
96
+ */
97
+ type AxisSeriesEntry = { series: DualAxisSeries; seriesIndex: number };
98
+
99
+ const leftAxisSeries: AxisSeriesEntry[] = $derived(
100
+ series
101
+ .map((s, si) => ({ series: s, seriesIndex: si }))
102
+ .filter((entry) => entry.series.yAxisIndex === 0 && entry.series.type !== 'line')
103
+ );
104
+
105
+ const rightAxisSeries: AxisSeriesEntry[] = $derived(
106
+ series
107
+ .map((s, si) => ({ series: s, seriesIndex: si }))
108
+ .filter((entry) => entry.series.yAxisIndex === 1 && entry.series.type !== 'line')
109
+ );
110
+
111
+ const columnSeriesEntries: AxisSeriesEntry[] = $derived([...leftAxisSeries, ...rightAxisSeries]);
112
+
113
+ /**
114
+ * Total number of column/bar series that share the category band.
115
+ * Line series float above and are not counted.
116
+ */
117
+ const columnCount: number = $derived(columnSeriesEntries.length);
118
+
119
+ type BarShape = {
120
+ x: number;
121
+ y: number;
122
+ width: number;
123
+ height: number;
124
+ color: string;
125
+ seriesIndex: number;
126
+ categoryIndex: number;
127
+ value: number;
128
+ path: string;
129
+ };
130
+
131
+ const bars: BarShape[] = $derived.by(() => {
132
+ if (dims.innerWidth <= 0 || dims.innerHeight <= 0) {
133
+ return [];
134
+ }
135
+ const result: BarShape[] = [];
136
+ const subBandWidth = columnCount > 0 ? catScale.bandwidth / columnCount : catScale.bandwidth;
137
+ const barW = Math.max(1, subBandWidth * 0.88);
138
+ const barGap = (subBandWidth - barW) / 2;
139
+
140
+ columnSeriesEntries.forEach((entry, subIndex) => {
141
+ const scale = entry.series.yAxisIndex === 0 ? leftScale : rightScale;
142
+ const color = resolvedColor(entry.series, entry.seriesIndex);
143
+ const zeroY = scale(0);
144
+
145
+ entry.series.data.forEach((value, catIdx) => {
146
+ // A series longer than `categories` would index past the band scale and
147
+ // emit NaN geometry; skip the surplus points defensively.
148
+ if (catIdx >= categories.length) {
149
+ return;
150
+ }
151
+ const bandStart = catScale(categories[catIdx]);
152
+ const barX = bandStart + subIndex * subBandWidth + barGap;
153
+ const valueY = scale(value);
154
+ const barY = value >= 0 ? valueY : zeroY;
155
+ const barHeight = Math.max(2, Math.abs(valueY - zeroY));
156
+
157
+ const path = roundedRectPath(barX, barY, barW, barHeight, barRadius, barRadius, 0, 0);
158
+
159
+ result.push({
160
+ x: barX,
161
+ y: barY,
162
+ width: barW,
163
+ height: barHeight,
164
+ color,
165
+ seriesIndex: entry.seriesIndex,
166
+ categoryIndex: catIdx,
167
+ value,
168
+ path
169
+ });
170
+ });
171
+ });
172
+
173
+ return result;
174
+ });
175
+
176
+ // ── Line series geometry ───────────────────────────────────────
177
+
178
+ type LineSeries = {
179
+ points: Point[];
180
+ color: string;
181
+ seriesIndex: number;
182
+ };
183
+
184
+ const lineSeriesData: LineSeries[] = $derived.by(() => {
185
+ if (dims.innerWidth <= 0 || dims.innerHeight <= 0) {
186
+ return [];
187
+ }
188
+ return series
189
+ .map((s, si) => ({ series: s, seriesIndex: si }))
190
+ .filter((entry) => entry.series.type === 'line')
191
+ .map((entry) => {
192
+ const scale = entry.series.yAxisIndex === 0 ? leftScale : rightScale;
193
+ const color = resolvedColor(entry.series, entry.seriesIndex);
194
+ const points: Point[] = [];
195
+ entry.series.data.forEach((value, catIdx) => {
196
+ // Mirror the bar guard: drop points past the category band to avoid NaN geometry.
197
+ if (catIdx >= categories.length) {
198
+ return;
199
+ }
200
+ points.push({
201
+ x: catScale(categories[catIdx]) + catScale.bandwidth / 2,
202
+ y: scale(value)
203
+ });
204
+ });
205
+ return { points, color, seriesIndex: entry.seriesIndex };
206
+ });
207
+ });
208
+
209
+ // ── Legend items ───────────────────────────────────────────────
210
+
211
+ const legendItems: LegendItem[] = $derived(
212
+ series.map((s, si) => ({ label: s.name, color: resolvedColor(s, si) }))
213
+ );
214
+
215
+ // ── Tooltip ────────────────────────────────────────────────────
216
+
217
+ const buildTooltipContext = (catIdx: number): DualAxisTooltipContext => ({
218
+ category: categories[catIdx],
219
+ categoryIndex: catIdx,
220
+ points: series.map((s, si) => ({
221
+ name: s.name,
222
+ value: s.data[catIdx] ?? 0,
223
+ color: resolvedColor(s, si),
224
+ yAxisIndex: s.yAxisIndex,
225
+ type: s.type ?? 'column'
226
+ }))
227
+ });
228
+
229
+ const tooltipData: TooltipData | null = $derived.by(() => {
230
+ if (hoveredCategoryIndex === null) {
231
+ return null;
232
+ }
233
+ const catIdx = hoveredCategoryIndex;
234
+ const category = categories[catIdx];
235
+ const items = series.map((s, si) => {
236
+ const fmt = s.yAxisIndex === 0 ? leftFormat : rightFormat;
237
+ return {
238
+ label: s.name,
239
+ value: fmt(s.data[catIdx] ?? 0),
240
+ color: resolvedColor(s, si)
241
+ };
242
+ });
243
+ return { title: category, items };
244
+ });
245
+
246
+ // ── Axis tick formatters ───────────────────────────────────────
247
+
248
+ const leftTickFormat = (tick: number | string): string =>
249
+ leftFormat(typeof tick === 'string' ? parseFloat(tick) : tick);
250
+
251
+ const rightTickFormat = (tick: number | string): string =>
252
+ rightFormat(typeof tick === 'string' ? parseFloat(tick) : tick);
253
+
254
+ // ── Hover target rectangles ────────────────────────────────────
255
+
256
+ /**
257
+ * Full-height invisible rectangles, one per category, used as hover targets.
258
+ * This is simpler and more reliable than per-bar hit testing and matches the
259
+ * Highcharts shared-tooltip UX where hovering anywhere in a column highlights
260
+ * the entire category.
261
+ */
262
+ type HoverRect = { x: number; width: number; catIdx: number };
263
+
264
+ const hoverRects: HoverRect[] = $derived(
265
+ categories.map((_, catIdx) => ({
266
+ x: catScale(categories[catIdx]),
267
+ width: catScale.bandwidth,
268
+ catIdx
269
+ }))
270
+ );
271
+
272
+ // ── Interactions ───────────────────────────────────────────────
273
+
274
+ const trackMouse = (event: MouseEvent) => {
275
+ if (containerEl === null) {
276
+ return;
277
+ }
278
+ const rect = containerEl.getBoundingClientRect();
279
+ mouseX = event.clientX - rect.left;
280
+ mouseY = event.clientY - rect.top;
281
+ };
282
+
283
+ const handleCategoryEnter = (event: MouseEvent, catIdx: number) => {
284
+ hoveredCategoryIndex = catIdx;
285
+ trackMouse(event);
286
+ };
287
+
288
+ const handleCategoryLeave = () => {
289
+ hoveredCategoryIndex = null;
290
+ };
291
+
292
+ const handleCategoryClick = (catIdx: number) => {
293
+ if (typeof onbarclick !== 'function') {
294
+ return;
295
+ }
296
+ onbarclick({ categoryIndex: catIdx, context: buildTooltipContext(catIdx) });
297
+ };
298
+
299
+ // ── Empty state ────────────────────────────────────────────────
300
+
301
+ const isEmpty = $derived(
302
+ series.length === 0 || categories.length === 0 || series.every((s) => s.data.length === 0)
303
+ );
304
+ </script>
305
+
306
+ <div
307
+ class="dual-axis-bar-chart {classes ?? ''}"
308
+ bind:this={containerEl}
309
+ data-pw={typeof testId === 'string' ? testId : null}
310
+ >
311
+ {#if !isEmpty}
312
+ {#if showLegend}
313
+ <Legend items={legendItems} position="top" />
314
+ {/if}
315
+
316
+ <ChartContainer bind:width={chartWidth} bind:height={chartHeight} {aspectRatio}>
317
+ <g transform="translate({dims.margin.left}, {dims.margin.top})">
318
+ <!-- Left Y-axis (index 0) -->
319
+ <Axis
320
+ orientation="left"
321
+ scale={leftScale}
322
+ {showGridlines}
323
+ gridlineLength={dims.innerWidth}
324
+ tickFormat={leftTickFormat}
325
+ classes={leftAxis.color ? `axis-left-colored` : ''}
326
+ />
327
+
328
+ <!-- Right Y-axis (index 1) — positioned at innerWidth -->
329
+ <g transform="translate({dims.innerWidth}, 0)">
330
+ <Axis
331
+ orientation="right"
332
+ scale={rightScale}
333
+ showGridlines={false}
334
+ tickFormat={rightTickFormat}
335
+ classes={rightAxis.color ? `axis-right-colored` : ''}
336
+ />
337
+ </g>
338
+
339
+ <!-- X-axis at bottom -->
340
+ <g transform="translate(0, {dims.innerHeight})">
341
+ <Axis orientation="bottom" scale={catScale} showGridlines={false} />
342
+ </g>
343
+
344
+ <!-- Axis titles -->
345
+ {#if leftAxis.title}
346
+ <text
347
+ class="axis-title axis-title-left"
348
+ transform="translate({-dims.margin.left + 12}, {dims.innerHeight / 2}) rotate(-90)"
349
+ text-anchor="middle"
350
+ style={leftAxis.color ? `fill: ${leftAxis.color}` : ''}
351
+ >
352
+ {leftAxis.title}
353
+ </text>
354
+ {/if}
355
+ {#if rightAxis.title}
356
+ <text
357
+ class="axis-title axis-title-right"
358
+ transform="translate({dims.innerWidth + dims.margin.right - 12}, {dims.innerHeight /
359
+ 2}) rotate(90)"
360
+ text-anchor="middle"
361
+ style={rightAxis.color ? `fill: ${rightAxis.color}` : ''}
362
+ >
363
+ {rightAxis.title}
364
+ </text>
365
+ {/if}
366
+
367
+ <!-- Column/bar shapes -->
368
+ {#each bars as bar, barIdx (barIdx)}
369
+ <path
370
+ class="bar-shape"
371
+ class:bar-hovered={hoveredCategoryIndex === bar.categoryIndex}
372
+ class:bar-dimmed={hoveredCategoryIndex !== null &&
373
+ hoveredCategoryIndex !== bar.categoryIndex}
374
+ d={bar.path}
375
+ fill={bar.color}
376
+ aria-label="{categories[bar.categoryIndex]}: {bar.value}"
377
+ role="img"
378
+ />
379
+ {/each}
380
+
381
+ <!-- Line series drawn above columns -->
382
+ {#each lineSeriesData as ls, lsi (lsi)}
383
+ {#if ls.points.length >= 2}
384
+ <path
385
+ class="line-series"
386
+ d={linePath(ls.points, 'monotone')}
387
+ stroke={ls.color}
388
+ fill="none"
389
+ />
390
+ {/if}
391
+ <!-- Line dots -->
392
+ {#each ls.points as pt, ptIdx (ptIdx)}
393
+ <circle
394
+ class="line-dot"
395
+ class:dot-hovered={hoveredCategoryIndex === ptIdx}
396
+ class:dot-dimmed={hoveredCategoryIndex !== null && hoveredCategoryIndex !== ptIdx}
397
+ cx={pt.x}
398
+ cy={pt.y}
399
+ r={hoveredCategoryIndex === ptIdx ? 6 : 4}
400
+ fill={ls.color}
401
+ stroke="var(--dual-axis-dot-stroke, #fff)"
402
+ stroke-width="var(--dual-axis-dot-stroke-width, 1.5)"
403
+ aria-label="{categories[ptIdx]}: {series[ls.seriesIndex]?.data[ptIdx] ?? 0}"
404
+ role="img"
405
+ />
406
+ {/each}
407
+ {/each}
408
+
409
+ <!-- Invisible per-category hover targets (full inner height) -->
410
+ {#each hoverRects as hr (hr.catIdx)}
411
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
412
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
413
+ <rect
414
+ class="hover-target"
415
+ x={hr.x}
416
+ y={0}
417
+ width={hr.width}
418
+ height={dims.innerHeight}
419
+ fill="transparent"
420
+ data-category-index={hr.catIdx}
421
+ onmouseenter={(event) => handleCategoryEnter(event, hr.catIdx)}
422
+ onmousemove={trackMouse}
423
+ onmouseleave={handleCategoryLeave}
424
+ onclick={() => handleCategoryClick(hr.catIdx)}
425
+ />
426
+ {/each}
427
+
428
+ <!-- Hover vertical guideline -->
429
+ {#if hoveredCategoryIndex !== null}
430
+ {@const guideX = catScale(categories[hoveredCategoryIndex]) + catScale.bandwidth / 2}
431
+ <line class="hover-guideline" x1={guideX} x2={guideX} y1={0} y2={dims.innerHeight} />
432
+ {/if}
433
+ </g>
434
+
435
+ <!-- SVG defs id namespace anchor (keeps uid live in reactive graph) -->
436
+ <defs>
437
+ <marker id="{uid}-anchor" />
438
+ </defs>
439
+ </ChartContainer>
440
+
441
+ <!-- Tooltip -->
442
+ {#if typeof tooltipSnippet === 'function' && hoveredCategoryIndex !== null}
443
+ <div class="chart-tooltip-slot" style="left: {mouseX + 12}px; top: {mouseY - 12}px;">
444
+ {@render tooltipSnippet(buildTooltipContext(hoveredCategoryIndex))}
445
+ </div>
446
+ {:else}
447
+ <ChartTooltip data={tooltipData} {mouseX} {mouseY} />
448
+ {/if}
449
+ {:else}
450
+ <div class="chart-empty">No data available.</div>
451
+ {/if}
452
+ </div>
453
+
454
+ <style>
455
+ .dual-axis-bar-chart {
456
+ width: 100%;
457
+ position: relative;
458
+ }
459
+
460
+ .bar-shape {
461
+ transition: opacity var(--chart-transition-duration, 0.2s) ease;
462
+ cursor: pointer;
463
+ }
464
+
465
+ .bar-hovered {
466
+ opacity: var(--dual-axis-bar-hover-opacity, 1);
467
+ }
468
+
469
+ .bar-dimmed {
470
+ opacity: var(--dual-axis-bar-dimmed-opacity, 0.3);
471
+ }
472
+
473
+ .line-series {
474
+ stroke-width: var(--dual-axis-line-stroke-width, 2);
475
+ stroke-linecap: round;
476
+ stroke-linejoin: round;
477
+ pointer-events: none;
478
+ }
479
+
480
+ .line-dot {
481
+ transition:
482
+ r var(--chart-transition-duration, 0.2s) ease,
483
+ opacity var(--chart-transition-duration, 0.2s) ease;
484
+ cursor: pointer;
485
+ }
486
+
487
+ .dot-dimmed {
488
+ opacity: var(--dual-axis-bar-dimmed-opacity, 0.3);
489
+ }
490
+
491
+ .hover-target {
492
+ cursor: pointer;
493
+ }
494
+
495
+ .hover-guideline {
496
+ stroke: var(--dual-axis-guideline-color, #aaa);
497
+ stroke-width: var(--dual-axis-guideline-width, 1);
498
+ stroke-dasharray: var(--dual-axis-guideline-dash, 4 3);
499
+ pointer-events: none;
500
+ opacity: 0.6;
501
+ }
502
+
503
+ .axis-title {
504
+ fill: var(--chart-axis-label-color, #333);
505
+ font-size: var(--chart-axis-label-font-size, 11px);
506
+ font-family: var(--chart-font-family, inherit);
507
+ font-weight: 500;
508
+ }
509
+
510
+ .chart-tooltip-slot {
511
+ position: absolute;
512
+ z-index: 10;
513
+ pointer-events: none;
514
+ }
515
+
516
+ .chart-empty {
517
+ padding: var(--chart-empty-padding, 32px 24px);
518
+ color: var(--chart-empty-color, #9ca3af);
519
+ text-align: center;
520
+ }
521
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { DualAxisBarChartProperties } from './properties';
2
+ declare const DualAxisBarChart: import("svelte").Component<DualAxisBarChartProperties, {}, "">;
3
+ type DualAxisBarChart = ReturnType<typeof DualAxisBarChart>;
4
+ export default DualAxisBarChart;
@@ -0,0 +1,108 @@
1
+ import type { Snippet } from 'svelte';
2
+ /**
3
+ * Configuration for one of the two independent Y-axes.
4
+ */
5
+ export type DualAxisConfig = {
6
+ /** Optional label rendered above the axis line. */
7
+ title?: string;
8
+ /**
9
+ * Color applied to axis title text and, by default, to tick labels on this side.
10
+ * Accepts any CSS color string.
11
+ */
12
+ color?: string;
13
+ /** Custom tick formatter. Receives a raw number and returns the display string. */
14
+ valueFormat?: (value: number) => string;
15
+ };
16
+ /**
17
+ * A single data series in the dual-axis chart.
18
+ * Each series is mapped to either the left (0) or right (1) Y-axis.
19
+ */
20
+ export type DualAxisSeries = {
21
+ /** Display name shown in the legend and tooltip. */
22
+ name: string;
23
+ /**
24
+ * Numeric values — one per category. The array length must match `categories`.
25
+ */
26
+ data: number[];
27
+ /**
28
+ * Which Y-axis this series uses.
29
+ * `0` = left axis, `1` = right axis.
30
+ */
31
+ yAxisIndex: 0 | 1;
32
+ /** Optional CSS color for the series bars/line. Falls back to the default palette. */
33
+ color?: string;
34
+ /**
35
+ * Render type for this individual series.
36
+ * `'column'` — vertical bars (default).
37
+ * `'line'` — line with optional dots drawn over the column layer.
38
+ */
39
+ type?: 'column' | 'line';
40
+ };
41
+ /**
42
+ * Data passed to `tooltipSnippet` when the user hovers a category.
43
+ */
44
+ export type DualAxisTooltipContext = {
45
+ /** The hovered category label. */
46
+ category: string;
47
+ /** Zero-based index of the hovered category. */
48
+ categoryIndex: number;
49
+ /**
50
+ * All series values for this category, in series order.
51
+ * Each entry mirrors the corresponding `DualAxisSeries` plus the resolved value.
52
+ */
53
+ points: Array<{
54
+ name: string;
55
+ value: number;
56
+ color: string;
57
+ yAxisIndex: 0 | 1;
58
+ type: 'column' | 'line';
59
+ }>;
60
+ };
61
+ export type DualAxisBarChartProperties = MandatoryDualAxisBarChartProperties & OptionalDualAxisBarChartProperties & DualAxisBarChartEventProperties;
62
+ export type MandatoryDualAxisBarChartProperties = {
63
+ /** Ordered category labels for the shared X-axis (e.g. `['Jan', 'Feb', 'Mar']`). */
64
+ categories: string[];
65
+ /**
66
+ * Array of series descriptors. Each series declares a `yAxisIndex` (0=left, 1=right),
67
+ * the numeric `data` values, and an optional render `type`.
68
+ */
69
+ series: DualAxisSeries[];
70
+ };
71
+ export type OptionalDualAxisBarChartProperties = {
72
+ /** Configuration for the left (primary) Y-axis. */
73
+ leftAxis?: DualAxisConfig;
74
+ /** Configuration for the right (secondary) Y-axis. */
75
+ rightAxis?: DualAxisConfig;
76
+ /** Whether to render dashed gridlines from the left-axis ticks. Default `true`. */
77
+ showGridlines?: boolean;
78
+ /** Whether to render the shared legend below the chart. Default `true`. */
79
+ showLegend?: boolean;
80
+ /** Corner radius on column/bar shapes in pixels. Default `3`. */
81
+ barRadius?: number;
82
+ /** Padding between category bands as a fraction of band width (0–1). Default `0.25`. */
83
+ barPadding?: number;
84
+ /**
85
+ * Width-to-height ratio for the chart area.
86
+ * Passed to `ChartContainer`'s ResizeObserver sizing. Default `16/9`.
87
+ */
88
+ aspectRatio?: number;
89
+ /**
90
+ * Custom tooltip content. Receives a `DualAxisTooltipContext` and replaces the
91
+ * default multi-series tooltip.
92
+ */
93
+ tooltipSnippet?: Snippet<[DualAxisTooltipContext]>;
94
+ /** Value set on `data-pw` for test targeting. */
95
+ testId?: string;
96
+ /** Extra CSS class string on the root `<div>`. */
97
+ classes?: string;
98
+ };
99
+ export type DualAxisBarChartEventProperties = {
100
+ /**
101
+ * Fires when the user clicks a bar or line-dot.
102
+ * Receives the category index and the full tooltip context for that category.
103
+ */
104
+ onbarclick?: (event: {
105
+ categoryIndex: number;
106
+ context: DualAxisTooltipContext;
107
+ }) => void;
108
+ };
@@ -0,0 +1 @@
1
+ export {};