@juspay/svelte-ui-components 2.80.1 → 2.80.3

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 (53) hide show
  1. package/dist/Badge/Badge.svelte +1 -1
  2. package/dist/Banner/Banner.svelte +1 -1
  3. package/dist/Book/Book.svelte +1 -1
  4. package/dist/Browser/Browser.svelte +3 -3
  5. package/dist/Button/Button.svelte +150 -34
  6. package/dist/Button/properties.d.ts +40 -1
  7. package/dist/Calendar/Calendar.svelte +2 -2
  8. package/dist/Card/Card.svelte +1 -1
  9. package/dist/Checkbox/Checkbox.svelte +1 -1
  10. package/dist/Choicebox/Choicebox.svelte +1 -1
  11. package/dist/ColorPicker/ColorPicker.svelte +8 -5
  12. package/dist/Combobox/Combobox.svelte +2 -2
  13. package/dist/CommandMenu/CommandMenu.svelte +3 -3
  14. package/dist/ContextMenu/ContextMenu.svelte +1 -1
  15. package/dist/DateRangePicker/DateRangePicker.svelte +9 -9
  16. package/dist/DualAxisBarChart/DualAxisBarChart.svelte +60 -8
  17. package/dist/DualAxisBarChart/properties.d.ts +26 -0
  18. package/dist/FileInput/FileInput.svelte +1 -1
  19. package/dist/GridItem/GridItem.svelte +2 -2
  20. package/dist/Input/Input.svelte +5 -5
  21. package/dist/InputButton/InputButton.svelte +6 -3
  22. package/dist/KeyValue/KeyValue.svelte +129 -0
  23. package/dist/KeyValue/KeyValue.svelte.d.ts +4 -0
  24. package/dist/KeyValue/properties.d.ts +52 -0
  25. package/dist/KeyValue/properties.js +1 -0
  26. package/dist/KeyboardInput/KeyboardInput.svelte +1 -1
  27. package/dist/Menu/Menu.svelte +1 -1
  28. package/dist/Modal/Modal.svelte +3 -3
  29. package/dist/Pagination/Pagination.svelte +1 -1
  30. package/dist/PieChart/PieChart.svelte +1 -1
  31. package/dist/Progress/Progress.svelte +2 -2
  32. package/dist/ProportionBar/ProportionBar.svelte +2 -2
  33. package/dist/SankeyChart/SankeyChart.svelte +27 -3
  34. package/dist/SankeyChart/properties.d.ts +1 -0
  35. package/dist/Select/Select.svelte +3 -3
  36. package/dist/Sheet/Sheet.svelte +1 -1
  37. package/dist/Shimmer/Shimmer.svelte +1 -1
  38. package/dist/Slider/Slider.svelte +2 -2
  39. package/dist/Snippet/Snippet.svelte +2 -2
  40. package/dist/SplitButton/SplitButton.svelte +8 -2
  41. package/dist/StatCard/StatCard.svelte +4 -4
  42. package/dist/Table/Table.svelte +4 -4
  43. package/dist/ThemeSwitcher/ThemeSwitcher.svelte +4 -4
  44. package/dist/Toast/Toast.svelte +1 -1
  45. package/dist/Tooltip/Tooltip.svelte +2 -2
  46. package/dist/Tooltip/tooltip-action.js +1 -1
  47. package/dist/_chart/ChartContainer.svelte +5 -2
  48. package/dist/_chart/ChartTooltip.svelte +1 -1
  49. package/dist/_chart/geometry.js +6 -2
  50. package/dist/_chart/types.d.ts +1 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +1 -0
  53. package/package.json +1 -1
@@ -30,6 +30,9 @@
30
30
  barRadius = 3,
31
31
  barPadding = 0.25,
32
32
  aspectRatio = 16 / 9,
33
+ minBarHeight = 2,
34
+ margin,
35
+ tooltipPortal = false,
33
36
  tooltipSnippet,
34
37
  onbarclick,
35
38
  testId,
@@ -44,6 +47,8 @@
44
47
  let hoveredCategoryIndex = $state<number | null>(null);
45
48
  let mouseX = $state(0);
46
49
  let mouseY = $state(0);
50
+ let mouseClientX = $state(0);
51
+ let mouseClientY = $state(0);
47
52
 
48
53
  // ── Formatters ─────────────────────────────────────────────────
49
54
 
@@ -53,7 +58,13 @@
53
58
  // ── Layout — wider right margin to accommodate right-axis labels ─
54
59
 
55
60
  const dims = $derived(
56
- computeChartDimensions(chartWidth, chartHeight, { top: 24, right: 56, bottom: 40, left: 56 })
61
+ computeChartDimensions(chartWidth, chartHeight, {
62
+ top: 24,
63
+ right: 56,
64
+ bottom: 40,
65
+ left: 56,
66
+ ...margin
67
+ })
57
68
  );
58
69
 
59
70
  // ── Scales ─────────────────────────────────────────────────────
@@ -152,7 +163,7 @@
152
163
  const barX = bandStart + subIndex * subBandWidth + barGap;
153
164
  const valueY = scale(value);
154
165
  const barY = value >= 0 ? valueY : zeroY;
155
- const barHeight = Math.max(2, Math.abs(valueY - zeroY));
166
+ const barHeight = Math.max(minBarHeight, Math.abs(valueY - zeroY));
156
167
 
157
168
  const path = roundedRectPath(barX, barY, barW, barHeight, barRadius, barRadius, 0, 0);
158
169
 
@@ -278,6 +289,22 @@
278
289
  const rect = containerEl.getBoundingClientRect();
279
290
  mouseX = event.clientX - rect.left;
280
291
  mouseY = event.clientY - rect.top;
292
+ mouseClientX = event.clientX;
293
+ mouseClientY = event.clientY;
294
+ };
295
+
296
+ /**
297
+ * Svelte action: relocates the tooltip layer to `document.body` so a `position:fixed`
298
+ * tooltip is never clipped by an `overflow`/scroll ancestor. Used only when
299
+ * `tooltipPortal` is set; `use:` actions never run during SSR.
300
+ */
301
+ const portalToBody = (node: HTMLElement) => {
302
+ document.body.appendChild(node);
303
+ return {
304
+ destroy: () => {
305
+ node.remove();
306
+ }
307
+ };
281
308
  };
282
309
 
283
310
  const handleCategoryEnter = (event: MouseEvent, catIdx: number) => {
@@ -439,12 +466,31 @@
439
466
  </ChartContainer>
440
467
 
441
468
  <!-- 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} />
469
+ {#if hoveredCategoryIndex !== null}
470
+ {#if tooltipPortal}
471
+ <!-- Portaled to <body> with fixed viewport coords so the tooltip is never
472
+ clipped by an overflow/scroll ancestor (e.g. a scrollable report sheet).
473
+ The inner elements keep their own +12/-12 offset relative to this layer. -->
474
+ <div
475
+ class="chart-tooltip-portal"
476
+ style="left: {mouseClientX}px; top: {mouseClientY}px;"
477
+ use:portalToBody
478
+ >
479
+ {#if typeof tooltipSnippet === 'function'}
480
+ <div class="chart-tooltip-slot" style="left: 12px; top: -12px;">
481
+ {@render tooltipSnippet(buildTooltipContext(hoveredCategoryIndex))}
482
+ </div>
483
+ {:else}
484
+ <ChartTooltip data={tooltipData} mouseX={0} mouseY={0} />
485
+ {/if}
486
+ </div>
487
+ {:else if typeof tooltipSnippet === 'function'}
488
+ <div class="chart-tooltip-slot" style="left: {mouseX + 12}px; top: {mouseY - 12}px;">
489
+ {@render tooltipSnippet(buildTooltipContext(hoveredCategoryIndex))}
490
+ </div>
491
+ {:else}
492
+ <ChartTooltip data={tooltipData} {mouseX} {mouseY} />
493
+ {/if}
448
494
  {/if}
449
495
  {:else}
450
496
  <div class="chart-empty">No data available.</div>
@@ -513,6 +559,12 @@
513
559
  pointer-events: none;
514
560
  }
515
561
 
562
+ .chart-tooltip-portal {
563
+ position: fixed;
564
+ z-index: var(--chart-tooltip-z-index, 10);
565
+ pointer-events: none;
566
+ }
567
+
516
568
  .chart-empty {
517
569
  padding: var(--chart-empty-padding, 32px 24px);
518
570
  color: var(--chart-empty-color, #9ca3af);
@@ -95,6 +95,32 @@ export type OptionalDualAxisBarChartProperties = {
95
95
  testId?: string;
96
96
  /** Extra CSS class string on the root `<div>`. */
97
97
  classes?: string;
98
+ /**
99
+ * Minimum rendered bar height in pixels. The default `2` keeps very small non-zero
100
+ * values visible, but it also paints a 2px stub for genuine zeros — set `0` so an
101
+ * all-zero/dormant series renders nothing (letting the consumer detect it and show
102
+ * an empty state instead of a row of indistinguishable baseline stubs). Default `2`.
103
+ */
104
+ minBarHeight?: number;
105
+ /**
106
+ * Override the plot margins (px) reserved for axis titles and tick labels. Merged
107
+ * over the defaults `{ top: 24, right: 56, bottom: 40, left: 56 }`; widen `left`/`right`
108
+ * when long currency tick labels (e.g. `₹1,00,000`) would otherwise overlap the
109
+ * gridlines in a narrow container.
110
+ */
111
+ margin?: {
112
+ top?: number;
113
+ right?: number;
114
+ bottom?: number;
115
+ left?: number;
116
+ };
117
+ /**
118
+ * Render the hover tooltip on `document.body` with `position: fixed` viewport
119
+ * coordinates instead of `position: absolute` inside the chart. Set `true` when the
120
+ * chart sits in an `overflow:auto`/scrollable container that would otherwise clip the
121
+ * tooltip. Default `false` (in-chart positioning, unchanged).
122
+ */
123
+ tooltipPortal?: boolean;
98
124
  };
99
125
  export type DualAxisBarChartEventProperties = {
100
126
  /**
@@ -146,7 +146,7 @@
146
146
  justify-content: var(--file-input-justify-content, center);
147
147
  padding: var(--file-input-padding);
148
148
  border: var(--file-input-border);
149
- border-radius: var(--file-input-radius);
149
+ border-radius: var(--file-input-radius, var(--radius, 4px));
150
150
  background: var(--file-input-background);
151
151
  gap: var(--file-input-gap);
152
152
  text-align: var(--file-input-text-align, center);
@@ -65,7 +65,7 @@
65
65
  width: var(--grid-item-body-width, 64px);
66
66
  background-color: var(--grid-item-background-color, #faf9f9);
67
67
  border: var(--grid-item-border, 1px solid #eaeaea);
68
- border-radius: var(--grid-item-border-radius, 4px);
68
+ border-radius: var(--grid-item-border-radius, var(--radius, 4px));
69
69
  margin: var(--grid-item-margin, 8px 0 0 0);
70
70
  display: flex;
71
71
  justify-content: center;
@@ -101,7 +101,7 @@
101
101
  position: absolute;
102
102
  inset: 0px;
103
103
  margin: var(--grid-item-margin, 8px 0 0 0);
104
- border-radius: var(--grid-item-border-radius, 4px);
104
+ border-radius: var(--grid-item-border-radius, var(--radius, 4px));
105
105
  border: var(--animation-version, 32px solid #cbcccf66);
106
106
  animation: clipperAnimation var(--loader-animation-duration, 3s) infinite linear;
107
107
  }
@@ -331,12 +331,12 @@
331
331
  background-color: var(--input-background, white);
332
332
  font-size: var(--input-font-size, 16px) !important;
333
333
  font-family: var(--input-font-family, inherit);
334
- border-radius: var(--input-radius, 4px);
334
+ border-radius: var(--input-radius, var(--radius, 4px));
335
335
  outline: none;
336
336
  padding: var(--input-padding, 16px);
337
337
  font-weight: var(--input-font-weight, 500);
338
338
  width: var(--input-width, fit-content);
339
- margin: var(--input-margin, 0px 0px 12px 0px);
339
+ margin: var(--input-margin, 0);
340
340
  appearance: none !important;
341
341
  -webkit-appearance: none !important; /* For Safari MWeb */
342
342
  box-shadow: var(--input-box-shadow, 0px 1px 8px #2f537733);
@@ -364,7 +364,7 @@
364
364
  }
365
365
 
366
366
  .action-input {
367
- border-radius: var(--input-radius, 4px 0px 0px 4px);
367
+ border-radius: var(--input-radius, var(--radius, 4px) 0 0 var(--radius, 4px));
368
368
  box-shadow: var(--input-box-shadow, 0px 0px 0px #ffffff);
369
369
  margin-bottom: 0;
370
370
  }
@@ -396,7 +396,7 @@
396
396
  .input-field-wrap {
397
397
  position: relative;
398
398
  display: block;
399
- margin: var(--input-margin, 0px 0px 12px 0px);
399
+ margin: var(--input-margin, 0);
400
400
  }
401
401
 
402
402
  .input-field-wrap > :global(textarea),
@@ -429,7 +429,7 @@
429
429
  .input-icon-button:focus-visible {
430
430
  outline: var(--input-icon-focus-outline, 2px solid var(--input-focus-border-color, #005fcc));
431
431
  outline-offset: var(--input-icon-focus-outline-offset, 2px);
432
- border-radius: var(--input-icon-focus-radius, 2px);
432
+ border-radius: var(--input-icon-focus-radius, var(--radius, 4px));
433
433
  }
434
434
 
435
435
  .input-icon-left {
@@ -136,7 +136,7 @@
136
136
  font-size: var(--input-font-size, 16px) !important;
137
137
  font-weight: var(--input-button-font-weight, 500);
138
138
  margin: var(--input-button-margin);
139
- border-radius: var(--input-button-radius, 4px);
139
+ border-radius: var(--input-button-radius, var(--radius, 4px));
140
140
  border: var(--input-button-container-border);
141
141
  background: var(--input-button-container-background);
142
142
  padding: var(--input-button-container-padding);
@@ -145,7 +145,7 @@
145
145
  .input-button {
146
146
  display: flex;
147
147
  align-items: center;
148
- border-radius: var(--input-button-radius, 4px);
148
+ border-radius: var(--input-button-radius, var(--radius, 4px));
149
149
  border: var(--input-button-border);
150
150
  box-shadow: var(--input-button-box-shadow, 0px 1px 8px #2f537733);
151
151
  background: var(--input-button-background);
@@ -262,7 +262,10 @@
262
262
  --button-font-size: var(--right-button-font-size);
263
263
  --button-height: var(--right-button-height, 54px);
264
264
  --button-padding: var(--right-button-padding);
265
- --button-border-radius: var(--right-button-border-radius, 0px 4px 4px 0px);
265
+ --button-border-radius: var(
266
+ --right-button-border-radius,
267
+ 0 var(--radius, 4px) var(--radius, 4px) 0
268
+ );
266
269
  --button-width: var(--right-button-width, 100%);
267
270
  --cursor: var(--right-button-cursor);
268
271
  --opacity: var(--right-button-opacity);
@@ -0,0 +1,129 @@
1
+ <script lang="ts">
2
+ import type { KeyValueProperties, KeyValueItem } from './properties';
3
+
4
+ let {
5
+ items,
6
+ columns = 2,
7
+ layout = 'vertical',
8
+ size = 'md',
9
+ hideEmpty = true,
10
+ emptyText = '—',
11
+ valueSnippet,
12
+ labelSnippet,
13
+ testId,
14
+ classes = ''
15
+ }: KeyValueProperties = $props();
16
+
17
+ const isEmptyValue = (value: KeyValueItem['value']): boolean =>
18
+ value == null || (typeof value === 'string' && value.trim() === '');
19
+
20
+ let visibleItems = $derived(
21
+ hideEmpty ? items.filter((item) => !isEmptyValue(item.value)) : items
22
+ );
23
+
24
+ const displayValue = (item: KeyValueItem): string =>
25
+ isEmptyValue(item.value) ? emptyText : String(item.value);
26
+ </script>
27
+
28
+ <dl
29
+ class="key-value key-value--{layout} key-value--{size} {classes}"
30
+ style="--keyvalue-columns: {columns};"
31
+ data-pw={testId}
32
+ >
33
+ {#each visibleItems as item, index (item.label + '-' + index)}
34
+ <div class="key-value-item" data-pw={item.testId}>
35
+ <dt class="key-value-label">
36
+ {#if labelSnippet}
37
+ {@render labelSnippet(item, index)}
38
+ {:else}
39
+ {item.label}
40
+ {/if}
41
+ </dt>
42
+ <dd class="key-value-value">
43
+ {#if valueSnippet}
44
+ {@render valueSnippet(item, index)}
45
+ {:else}
46
+ {displayValue(item)}
47
+ {/if}
48
+ </dd>
49
+ </div>
50
+ {/each}
51
+ </dl>
52
+
53
+ <style>
54
+ .key-value {
55
+ display: grid;
56
+ grid-template-columns: repeat(var(--keyvalue-columns, 2), minmax(0, 1fr));
57
+ column-gap: var(--keyvalue-column-gap, 32px);
58
+ row-gap: var(--keyvalue-row-gap, 16px);
59
+ margin: 0;
60
+ width: 100%;
61
+ /* Size-preset defaults (overridable by --keyvalue-label-size / --keyvalue-value-size).
62
+ The value sits 2px below the label, so the key always reads as the heavier, larger element. */
63
+ --_kv-label-size: 16px;
64
+ --_kv-value-size: 14px;
65
+ }
66
+
67
+ .key-value--sm {
68
+ --_kv-label-size: 14px;
69
+ --_kv-value-size: 12px;
70
+ }
71
+
72
+ .key-value--md {
73
+ --_kv-label-size: 16px;
74
+ --_kv-value-size: 14px;
75
+ }
76
+
77
+ .key-value--lg {
78
+ --_kv-label-size: 18px;
79
+ --_kv-value-size: 16px;
80
+ }
81
+
82
+ .key-value-item {
83
+ display: flex;
84
+ min-width: 0;
85
+ }
86
+
87
+ .key-value--vertical .key-value-item {
88
+ flex-direction: column;
89
+ gap: var(--keyvalue-pair-gap, 4px);
90
+ }
91
+
92
+ .key-value--horizontal .key-value-item {
93
+ flex-direction: row;
94
+ align-items: baseline;
95
+ gap: var(--keyvalue-pair-gap, 12px);
96
+ }
97
+
98
+ .key-value-label {
99
+ margin: 0;
100
+ color: var(--keyvalue-label-color, #1a1a1a);
101
+ font-size: var(--keyvalue-label-size, var(--_kv-label-size, 13px));
102
+ font-weight: var(--keyvalue-label-weight, 600);
103
+ line-height: var(--keyvalue-label-line-height, 1.4);
104
+ }
105
+
106
+ .key-value--horizontal .key-value-label {
107
+ flex: 0 0 var(--keyvalue-label-width, 140px);
108
+ }
109
+
110
+ .key-value-value {
111
+ margin: 0;
112
+ color: var(--keyvalue-value-color, #555);
113
+ font-size: var(--keyvalue-value-size, var(--_kv-value-size, 16px));
114
+ font-weight: var(--keyvalue-value-weight, 400);
115
+ line-height: var(--keyvalue-value-line-height, 1.4);
116
+ overflow-wrap: anywhere;
117
+ }
118
+
119
+ .key-value--horizontal .key-value-value {
120
+ flex: 1 1 auto;
121
+ min-width: 0;
122
+ }
123
+
124
+ @media (max-width: 600px) {
125
+ .key-value {
126
+ grid-template-columns: 1fr;
127
+ }
128
+ }
129
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { KeyValueProperties } from './properties';
2
+ declare const KeyValue: import("svelte").Component<KeyValueProperties, {}, "">;
3
+ type KeyValue = ReturnType<typeof KeyValue>;
4
+ export default KeyValue;
@@ -0,0 +1,52 @@
1
+ import type { Snippet } from 'svelte';
2
+ export type KeyValueProperties = MandatoryKeyValueProperties & OptionalKeyValueProperties;
3
+ export type KeyValueItem = {
4
+ /** The field label (the "key"). */
5
+ label: string;
6
+ /**
7
+ * The field value. May be `null`/`undefined`/empty — such items are skipped when
8
+ * `hideEmpty` is `true` (the default), or rendered as `emptyText` otherwise.
9
+ */
10
+ value?: string | number | null;
11
+ /** Optional per-item test id, emitted as `data-pw` on the item wrapper. */
12
+ testId?: string;
13
+ };
14
+ export type KeyValueLayout = 'vertical' | 'horizontal';
15
+ /** Size preset scaling the label/value typography. Mirrors Blend's KeyValuePair sizes. */
16
+ export type KeyValueSize = 'sm' | 'md' | 'lg';
17
+ export type MandatoryKeyValueProperties = {
18
+ items: KeyValueItem[];
19
+ };
20
+ export type OptionalKeyValueProperties = {
21
+ /**
22
+ * Number of columns the pairs flow across (row-major). Defaults to `2`. The grid
23
+ * collapses to a single column below the `--keyvalue-collapse-width` breakpoint.
24
+ */
25
+ columns?: number;
26
+ /**
27
+ * `'vertical'` (default) stacks the value beneath the label; `'horizontal'` places the
28
+ * label and value side-by-side (label width set by `--keyvalue-label-width`).
29
+ */
30
+ layout?: KeyValueLayout;
31
+ /**
32
+ * Typography size preset. `'sm'` (12/14px), `'md'` (13/16px, default), `'lg'` (14/18px) —
33
+ * label/value font sizes respectively. Individual `--keyvalue-label-size` /
34
+ * `--keyvalue-value-size` overrides always take precedence over the preset.
35
+ */
36
+ size?: KeyValueSize;
37
+ /**
38
+ * When `true` (default), items whose value is `null`, `undefined`, or an empty/whitespace
39
+ * string are omitted. Set `false` to render them as `emptyText` instead.
40
+ */
41
+ hideEmpty?: boolean;
42
+ /** Placeholder rendered for empty values when `hideEmpty` is `false`. Defaults to `'—'`. */
43
+ emptyText?: string;
44
+ /** Custom renderer for the value cell, receiving `(item, index)`. Falls back to plain text. */
45
+ valueSnippet?: Snippet<[KeyValueItem, number]>;
46
+ /** Custom renderer for the label cell, receiving `(item, index)`. Falls back to plain text. */
47
+ labelSnippet?: Snippet<[KeyValueItem, number]>;
48
+ /** Test selector applied as the `data-pw` attribute on the root grid. */
49
+ testId?: string;
50
+ /** Additional CSS classes for theming the root grid. */
51
+ classes?: string;
52
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -82,7 +82,7 @@
82
82
  color: var(--keyboard-input-key-color, #333333);
83
83
  background-color: var(--keyboard-input-key-background, #f5f5f5);
84
84
  border: var(--keyboard-input-key-border, 1px solid #d1d1d1);
85
- border-radius: var(--keyboard-input-key-border-radius, 4px);
85
+ border-radius: var(--keyboard-input-key-border-radius, var(--radius, 4px));
86
86
  box-shadow: var(--keyboard-input-key-box-shadow, 0 1px 0 #c4c4c4);
87
87
  min-width: var(--keyboard-input-key-min-width, 1.6em);
88
88
  padding: var(--keyboard-input-key-padding, 2px 6px);
@@ -284,7 +284,7 @@
284
284
  left: var(--menu-dropdown-left, 0);
285
285
  background-color: var(--menu-background-color, #ffffff);
286
286
  border: var(--menu-border, 1px solid #e0e0e0);
287
- border-radius: var(--menu-border-radius, 6px);
287
+ border-radius: var(--menu-border-radius, var(--radius, 4px));
288
288
  box-shadow: var(--menu-box-shadow, 0px 4px 16px rgba(0, 0, 0, 0.12));
289
289
  min-width: var(--menu-min-width, 160px);
290
290
  max-height: var(--menu-max-height, 240px);
@@ -245,7 +245,7 @@
245
245
  cursor: auto;
246
246
  display: flex;
247
247
  flex-direction: column;
248
- border-radius: var(--modal-border-radius, 0px);
248
+ border-radius: var(--modal-border-radius, var(--radius, 4px));
249
249
  overflow: var(--modal-content-overflow, auto);
250
250
  border-top: var(--modal-content-border-top);
251
251
  }
@@ -331,7 +331,7 @@
331
331
  --button-height: var(--modal-footer-secondary-button-height, fit-content);
332
332
  --button-padding: var(--modal-footer-secondary-button-padding, 16px);
333
333
  --button-margin: var(--modal-footer-secondary-button-margin);
334
- --button-border-radius: var(--modal-footer-secondary-button-border-radius, 0px);
334
+ --button-border-radius: var(--modal-footer-secondary-button-border-radius, var(--radius, 4px));
335
335
  --button-width: var(--modal-footer-secondary-button-width, fit-content);
336
336
  --cursor: var(--modal-footer-secondary-button-cursor, pointer);
337
337
  --opacity: var(--modal-footer-secondary-button-opacity, 1);
@@ -372,7 +372,7 @@
372
372
  --button-height: var(--modal-footer-primary-button-height, fit-content);
373
373
  --button-padding: var(--modal-footer-primary-button-padding, 16px);
374
374
  --button-margin: var(--modal-footer-primary-button-margin);
375
- --button-border-radius: var(--modal-footer-primary-button-border-radius, 0px);
375
+ --button-border-radius: var(--modal-footer-primary-button-border-radius, var(--radius, 4px));
376
376
  --button-width: var(--modal-footer-primary-button-width, fit-content);
377
377
  --cursor: var(--modal-footer-primary-button-cursor, pointer);
378
378
  --opacity: var(--modal-footer-primary-button-opacity, 1);
@@ -141,7 +141,7 @@
141
141
  color: var(--pagination-button-color, #3a4550);
142
142
  background: var(--pagination-button-background, transparent);
143
143
  border: var(--pagination-button-border, 1px solid #d1d5db);
144
- border-radius: var(--pagination-button-border-radius, 4px);
144
+ border-radius: var(--pagination-button-border-radius, var(--radius, 4px));
145
145
  cursor: var(--pagination-button-cursor, pointer);
146
146
  min-width: var(--pagination-button-min-width, 36px);
147
147
  height: var(--pagination-button-height, 36px);
@@ -372,7 +372,7 @@
372
372
  display: inline-block;
373
373
  width: var(--chart-legend-swatch-size, 12px);
374
374
  height: var(--chart-legend-swatch-size, 12px);
375
- border-radius: var(--piechart-legend-swatch-radius, 2px);
375
+ border-radius: var(--piechart-legend-swatch-radius, var(--radius, 4px));
376
376
  flex-shrink: 0;
377
377
  }
378
378
  .pie-legend-label {
@@ -33,14 +33,14 @@
33
33
  flex: 1;
34
34
  height: var(--progress-track-height, 8px);
35
35
  background: var(--progress-track-background, #e0e0e0);
36
- border-radius: var(--progress-track-border-radius, 4px);
36
+ border-radius: var(--progress-track-border-radius, var(--radius, 4px));
37
37
  overflow: hidden;
38
38
  }
39
39
 
40
40
  .bar {
41
41
  height: 100%;
42
42
  background: var(--progress-bar-background, #2196f3);
43
- border-radius: var(--progress-bar-border-radius, 4px);
43
+ border-radius: var(--progress-bar-border-radius, var(--radius, 4px));
44
44
  transition: var(--progress-bar-transition, width 0.3s ease);
45
45
  }
46
46
 
@@ -159,7 +159,7 @@
159
159
  .proportion-bar-track {
160
160
  width: 100%;
161
161
  height: var(--proportion-bar-track-height, 10px);
162
- border-radius: var(--proportion-bar-track-border-radius, 4px);
162
+ border-radius: var(--proportion-bar-track-border-radius, var(--radius, 4px));
163
163
  overflow: hidden;
164
164
  background: var(--proportion-bar-track-bg, #f0f0f0);
165
165
  }
@@ -189,7 +189,7 @@
189
189
  display: inline-block;
190
190
  width: var(--proportion-bar-swatch-size, 10px);
191
191
  height: var(--proportion-bar-swatch-size, 10px);
192
- border-radius: var(--proportion-bar-swatch-border-radius, 2px);
192
+ border-radius: var(--proportion-bar-swatch-border-radius, var(--radius, 4px));
193
193
  flex-shrink: 0;
194
194
  }
195
195
 
@@ -18,6 +18,7 @@
18
18
  showValues = false,
19
19
  showLabels = true,
20
20
  aspectRatio = 16 / 9,
21
+ maxHeight = Infinity,
21
22
  valueFormat,
22
23
  tooltipSnippet,
23
24
  empty,
@@ -89,6 +90,26 @@
89
90
  : (Math.max(0, chartWidth - MARGIN * 2) - nodeWidth) / (columnCount - 1)
90
91
  );
91
92
 
93
+ // Node labels render alongside their bar; long labels used to overflow into the
94
+ // next column and collide. Clip each to the horizontal room its column actually
95
+ // has and append an ellipsis (the full text stays available via the node tooltip).
96
+ const LABEL_CHAR_PX = 7.2; // ≈ 0.6em at the 12px default label size
97
+ const truncateLabel = (text: string, column: number): string => {
98
+ const available =
99
+ column === 0
100
+ ? MARGIN + 16
101
+ : column === columnCount - 1
102
+ ? Math.max(colWidth, MARGIN + 24)
103
+ : Math.max(0, colWidth - nodeWidth - 12);
104
+ const maxChars = Math.floor(available / LABEL_CHAR_PX);
105
+ // No usable room — hide the label rather than force text that would overflow;
106
+ // the full text is still reachable via the node's <title> on hover.
107
+ if (maxChars < 3) {
108
+ return '';
109
+ }
110
+ return text.length > maxChars ? text.slice(0, maxChars - 1) + '…' : text;
111
+ };
112
+
92
113
  // ── Helpers ────────────────────────────────────────────────────
93
114
 
94
115
  /** Percentage of source node's total value carried by a link (0–100, 2 dp). */
@@ -276,7 +297,7 @@
276
297
  {#if isEmpty && typeof empty === 'function'}
277
298
  <div class="chart-empty">{@render empty()}</div>
278
299
  {:else}
279
- <ChartContainer bind:width={chartWidth} bind:height={chartHeight} {aspectRatio}>
300
+ <ChartContainer bind:width={chartWidth} bind:height={chartHeight} {aspectRatio} {maxHeight}>
280
301
  <g transform="translate({MARGIN}, {MARGIN})">
281
302
  {#if columnLabels != null && columnLabels.length > 0}
282
303
  {#each columnLabels.slice(0, columnCount) as label, ci (ci)}
@@ -340,8 +361,11 @@
340
361
  y={node.y + node.height / 2}
341
362
  text-anchor={node.column === 0 ? 'end' : 'start'}
342
363
  dominant-baseline="middle"
343
- >{node.label}{#if showValues}
344
- ({format(node.value)}){/if}</text
364
+ >{truncateLabel(
365
+ showValues ? `${node.label} (${format(node.value)})` : node.label,
366
+ node.column
367
+ )}<title>{showValues ? `${node.label} (${format(node.value)})` : node.label}</title
368
+ ></text
345
369
  >
346
370
  {/if}
347
371
  {/each}
@@ -44,6 +44,7 @@ export type OptionalSankeyChartProperties = {
44
44
  showValues?: boolean;
45
45
  showLabels?: boolean;
46
46
  aspectRatio?: number;
47
+ maxHeight?: number;
47
48
  valueFormat?: (value: number) => string;
48
49
  tooltipSnippet?: Snippet<[SankeyTooltipContext]>;
49
50
  empty?: Snippet;
@@ -516,7 +516,7 @@
516
516
  padding: var(--select-trigger-padding, 8px 12px);
517
517
  background: var(--select-trigger-background, #ffffff);
518
518
  border: var(--select-trigger-border, 1px solid #cccccc);
519
- border-radius: var(--select-trigger-border-radius, 6px);
519
+ border-radius: var(--select-trigger-border-radius, var(--radius, 4px));
520
520
  cursor: pointer;
521
521
  outline: none;
522
522
  -webkit-tap-highlight-color: transparent;
@@ -603,7 +603,7 @@
603
603
  margin-top: var(--select-dropdown-gap, 4px);
604
604
  background: var(--select-dropdown-background, #ffffff);
605
605
  border: var(--select-dropdown-border, 1px solid #cccccc);
606
- border-radius: var(--select-dropdown-border-radius, 6px);
606
+ border-radius: var(--select-dropdown-border-radius, var(--radius, 4px));
607
607
  box-shadow: var(--select-dropdown-shadow, 0 4px 12px rgba(0, 0, 0, 0.1));
608
608
  max-height: var(--select-dropdown-max-height, 200px);
609
609
  overflow-y: auto;
@@ -659,7 +659,7 @@
659
659
  width: var(--select-option-indicator-size, 18px);
660
660
  height: var(--select-option-indicator-size, 18px);
661
661
  border: var(--select-option-indicator-border, 2px solid #757575);
662
- border-radius: var(--select-option-indicator-border-radius, 3px);
662
+ border-radius: var(--select-option-indicator-border-radius, var(--radius, 4px));
663
663
  background-color: var(--select-option-indicator-background, transparent);
664
664
  color: var(--select-option-indicator-color, currentColor);
665
665
  transition:
@@ -248,7 +248,7 @@
248
248
  --button-width: var(--sheet-close-button-size, 32px);
249
249
  --button-height: var(--sheet-close-button-size, 32px);
250
250
  --button-border: none;
251
- --button-border-radius: var(--sheet-close-button-border-radius, 4px);
251
+ --button-border-radius: var(--sheet-close-button-border-radius, var(--radius, 4px));
252
252
  --button-color: var(--sheet-close-button-background, transparent);
253
253
  --button-text-color: var(--sheet-close-button-color, #666666);
254
254
  --button-font-size: var(--sheet-close-button-font-size, 16px);
@@ -10,7 +10,7 @@
10
10
  .shimmer {
11
11
  width: var(--shimmer-width, 100%);
12
12
  height: var(--shimmer-height, 16px);
13
- border-radius: var(--shimmer-border-radius, 4px);
13
+ border-radius: var(--shimmer-border-radius, var(--radius, 4px));
14
14
  background-color: var(--shimmer-background, #e0e0e0);
15
15
  opacity: var(--shimmer-opacity, 1);
16
16
  overflow: hidden;