@juspay/svelte-ui-components 2.85.0 → 2.87.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.
@@ -103,6 +103,15 @@ export type OptionalLineChartProperties = {
103
103
  tooltipSnippet?: Snippet<[LineChartTooltipContext]>;
104
104
  /** Content rendered when all series are empty. */
105
105
  empty?: Snippet;
106
+ /** One tooltip listing every series at the hovered x (Highcharts shared tooltip).
107
+ * Defaults to true for multi-series charts; single-series behavior is unchanged. */
108
+ sharedTooltip?: boolean;
109
+ /** Legend items become click/keyboard toggles for series visibility. */
110
+ interactiveLegend?: boolean;
111
+ /** Hide the legend when the measured chart width is below this px value; 0 disables. */
112
+ hideLegendBelow?: number;
113
+ /** Render the tooltip into document.body so scroll/overflow ancestors never clip it. */
114
+ tooltipPortal?: boolean;
106
115
  /** Value for the data-pw attribute on the chart container. */
107
116
  testId?: string;
108
117
  /** CSS class string applied to the top-level element. */
@@ -1,3 +1,24 @@
1
+ <script lang="ts" module>
2
+ // Reference-counted so two Sheets open at once (or a Sheet nested inside
3
+ // another scroll-locking surface) don't have the second one's close clobber
4
+ // the first one's still-needed lock.
5
+ let scrollLockCount = 0;
6
+
7
+ function lockScroll() {
8
+ if (scrollLockCount === 0) {
9
+ document.body.style.overflow = 'hidden';
10
+ }
11
+ scrollLockCount += 1;
12
+ }
13
+
14
+ function unlockScroll() {
15
+ scrollLockCount = Math.max(0, scrollLockCount - 1);
16
+ if (scrollLockCount === 0) {
17
+ document.body.style.overflow = '';
18
+ }
19
+ }
20
+ </script>
21
+
1
22
  <script lang="ts">
2
23
  import type { SheetProperties } from './properties';
3
24
  import { fly, fade } from 'svelte/transition';
@@ -9,6 +30,7 @@
9
30
  side = 'right',
10
31
  title,
11
32
  showOverlay = true,
33
+ dismissOnOutsideClick = showOverlay,
12
34
  showCloseButton = true,
13
35
  testId,
14
36
  content,
@@ -41,7 +63,10 @@
41
63
  }
42
64
 
43
65
  function handleOverlayClick(event: MouseEvent) {
44
- if (event.target === overlayDiv) {
66
+ // Gate dismissal on dismissOnOutsideClick explicitly, not on the overlay's
67
+ // pointer-events alone: a visible-but-non-dismissible overlay still needs to
68
+ // catch (and swallow) the click to block the page underneath, without closing.
69
+ if (event.target === overlayDiv && dismissOnOutsideClick) {
45
70
  close();
46
71
  }
47
72
  }
@@ -71,14 +96,6 @@
71
96
  }
72
97
  }
73
98
 
74
- function lockScroll() {
75
- document.body.style.overflow = 'hidden';
76
- }
77
-
78
- function unlockScroll() {
79
- document.body.style.overflow = '';
80
- }
81
-
82
99
  function scrollLockAction(_node: HTMLElement) {
83
100
  lockScroll();
84
101
  tick().then(() => {
@@ -110,7 +127,10 @@
110
127
  <div
111
128
  bind:this={overlayDiv}
112
129
  use:scrollLockAction
113
- class="sheet-overlay {showOverlay ? 'overlay-active' : 'overlay-inactive'} {classes ?? ''}"
130
+ class="sheet-overlay {showOverlay ? 'overlay-visible' : 'overlay-invisible'} {showOverlay ||
131
+ dismissOnOutsideClick
132
+ ? 'overlay-interactive'
133
+ : 'overlay-noninteractive'} {classes ?? ''}"
114
134
  onclick={handleOverlayClick}
115
135
  onkeydown={handleKeyDown}
116
136
  role="button"
@@ -170,12 +190,19 @@
170
190
  -webkit-tap-highlight-color: transparent;
171
191
  }
172
192
 
173
- .overlay-active {
193
+ .overlay-visible {
174
194
  background-color: var(--sheet-overlay-background, #00000066);
195
+ }
196
+
197
+ .overlay-invisible {
198
+ background-color: transparent;
199
+ }
200
+
201
+ .overlay-interactive {
175
202
  pointer-events: auto;
176
203
  }
177
204
 
178
- .overlay-inactive {
205
+ .overlay-noninteractive {
179
206
  pointer-events: none;
180
207
  }
181
208
 
@@ -190,39 +217,44 @@
190
217
  outline: none;
191
218
  }
192
219
 
220
+ /* --sheet-top/-right/-bottom/-left all default to the previous hardcoded
221
+ edge-to-edge values below, so an unconfigured Sheet renders unchanged.
222
+ Overriding one (e.g. --sheet-top to clear a fixed header, or --sheet-bottom:
223
+ auto to size to content) turns the panel from an edge-to-edge slide-in into
224
+ an anchored floating panel, without needing a different component. */
193
225
  .sheet-panel.left,
194
226
  .sheet-panel.right {
195
- top: 0;
196
- bottom: 0;
227
+ top: var(--sheet-top, 0);
228
+ bottom: var(--sheet-bottom, 0);
197
229
  width: var(--sheet-width, 400px);
198
230
  max-width: var(--sheet-max-width, 100vw);
199
231
  }
200
232
 
201
233
  .sheet-panel.left {
202
- left: 0;
234
+ left: var(--sheet-left, 0);
203
235
  border-right: var(--sheet-border, none);
204
236
  }
205
237
 
206
238
  .sheet-panel.right {
207
- right: 0;
239
+ right: var(--sheet-right, 0);
208
240
  border-left: var(--sheet-border, none);
209
241
  }
210
242
 
211
243
  .sheet-panel.top,
212
244
  .sheet-panel.bottom {
213
- left: 0;
214
- right: 0;
245
+ left: var(--sheet-left, 0);
246
+ right: var(--sheet-right, 0);
215
247
  height: var(--sheet-height, 300px);
216
248
  max-height: var(--sheet-max-height, 100vh);
217
249
  }
218
250
 
219
251
  .sheet-panel.top {
220
- top: 0;
252
+ top: var(--sheet-top, 0);
221
253
  border-bottom: var(--sheet-border, none);
222
254
  }
223
255
 
224
256
  .sheet-panel.bottom {
225
- bottom: 0;
257
+ bottom: var(--sheet-bottom, 0);
226
258
  border-top: var(--sheet-border, none);
227
259
  }
228
260
 
@@ -9,6 +9,14 @@ export type OptionalSheetProperties = {
9
9
  side?: SheetSide;
10
10
  title?: string;
11
11
  showOverlay?: boolean;
12
+ /**
13
+ * Whether clicking outside the panel dismisses the sheet, independent of
14
+ * `showOverlay`'s visual tint. Defaults to mirroring `showOverlay`, so
15
+ * existing behavior is unchanged unless this is set explicitly. Set this to
16
+ * `true` alongside `showOverlay={false}` for a dismissible sheet with no
17
+ * dimming backdrop — e.g. an anchored dropdown-style panel.
18
+ */
19
+ dismissOnOutsideClick?: boolean;
12
20
  showCloseButton?: boolean;
13
21
  testId?: string;
14
22
  footer?: Snippet;
@@ -10,6 +10,10 @@
10
10
  showGridlines = false,
11
11
  gridlineLength = 0,
12
12
  label,
13
+ rotateTicks = false,
14
+ tickEvery = 1,
15
+ labelOffset = 36,
16
+ integerTicks = false,
13
17
  classes
14
18
  }: AxisProperties = $props();
15
19
 
@@ -19,7 +23,7 @@
19
23
 
20
24
  let tickValues = $derived.by(() => {
21
25
  if ('ticks' in scale && typeof scale.ticks === 'function') {
22
- return scale.ticks(tickCount);
26
+ return scale.ticks(tickCount, integerTicks);
23
27
  }
24
28
  if ('domain' in scale && Array.isArray(scale.domain)) {
25
29
  return scale.domain;
@@ -47,14 +51,27 @@
47
51
  {@const x = positionTick(tick)}
48
52
  <g class="tick" transform="translate({x}, 0)">
49
53
  <line class="tick-mark" y2={orientation === 'bottom' ? TICK_SIZE : -TICK_SIZE} />
50
- <text
51
- class="tick-label"
52
- y={orientation === 'bottom' ? TICK_SIZE + 4 : -(TICK_SIZE + 4)}
53
- text-anchor="middle"
54
- dominant-baseline={orientation === 'bottom' ? 'hanging' : 'auto'}
55
- >
56
- {format(tick)}
57
- </text>
54
+ {#if i % tickEvery === 0}
55
+ {#if rotateTicks && orientation === 'bottom'}
56
+ <text
57
+ class="tick-label"
58
+ transform="translate(0, {TICK_SIZE + 4}) rotate(-45)"
59
+ text-anchor="end"
60
+ dominant-baseline="auto"
61
+ >
62
+ {format(tick)}
63
+ </text>
64
+ {:else}
65
+ <text
66
+ class="tick-label"
67
+ y={orientation === 'bottom' ? TICK_SIZE + 4 : -(TICK_SIZE + 4)}
68
+ text-anchor="middle"
69
+ dominant-baseline={orientation === 'bottom' ? 'hanging' : 'auto'}
70
+ >
71
+ {format(tick)}
72
+ </text>
73
+ {/if}
74
+ {/if}
58
75
  {#if showGridlines && gridlineLength > 0}
59
76
  <line
60
77
  class="gridline"
@@ -68,7 +85,7 @@
68
85
  <text
69
86
  class="axis-label"
70
87
  x={(scale.range[0] + scale.range[1]) / 2}
71
- y={orientation === 'bottom' ? 36 : -30}
88
+ y={orientation === 'bottom' ? labelOffset : -30}
72
89
  text-anchor="middle"
73
90
  >
74
91
  {label}
@@ -113,30 +130,30 @@
113
130
 
114
131
  <style>
115
132
  .axis-line {
116
- stroke: var(--chart-axis-color, #666);
133
+ stroke: var(--chart-axis-color, light-dark(#666, #9ca3af));
117
134
  stroke-width: var(--chart-axis-stroke-width, 1);
118
135
  }
119
136
 
120
137
  .tick-mark {
121
- stroke: var(--chart-axis-color, #666);
138
+ stroke: var(--chart-axis-color, light-dark(#666, #9ca3af));
122
139
  stroke-width: var(--chart-axis-stroke-width, 1);
123
140
  }
124
141
 
125
142
  .tick-label {
126
- fill: var(--chart-axis-color, #666);
143
+ fill: var(--chart-axis-color, light-dark(#666, #9ca3af));
127
144
  font-size: var(--chart-axis-font-size, 11px);
128
145
  font-family: var(--chart-axis-font-family, inherit);
129
146
  }
130
147
 
131
148
  .axis-label {
132
- fill: var(--chart-axis-label-color, #333);
149
+ fill: var(--chart-axis-label-color, light-dark(#333, #e5e7eb));
133
150
  font-size: var(--chart-axis-label-font-size, 12px);
134
151
  font-family: var(--chart-axis-font-family, inherit);
135
152
  font-weight: 500;
136
153
  }
137
154
 
138
155
  .gridline {
139
- stroke: var(--chart-gridline-color, #e0e0e0);
156
+ stroke: var(--chart-gridline-color, light-dark(#e0e0e0, #374151));
140
157
  stroke-opacity: var(--chart-gridline-opacity, 0.5);
141
158
  stroke-dasharray: var(--chart-gridline-dash, 4 4);
142
159
  }
@@ -1,83 +1,161 @@
1
1
  <script lang="ts">
2
- import type { ChartTooltipProperties } from './types';
2
+ import type { ChartTooltipProperties, TooltipData } from './types';
3
+ import { computeTooltipPosition } from './tooltipPosition';
3
4
 
4
- let { data, mouseX = 0, mouseY = 0, customSnippet, classes }: ChartTooltipProperties = $props();
5
-
6
- const OFFSET = 12;
5
+ let {
6
+ data,
7
+ mouseX = 0,
8
+ mouseY = 0,
9
+ anchor = null,
10
+ portal = false,
11
+ originEl = null,
12
+ unstyled = false,
13
+ content,
14
+ customSnippet,
15
+ classes
16
+ }: ChartTooltipProperties = $props();
7
17
 
8
18
  let tooltipEl = $state<HTMLDivElement | null>(null);
9
19
  let tooltipWidth = $state(0);
10
20
  let tooltipHeight = $state(0);
21
+ // Portal position depends on untracked DOM reads (originEl rect, viewport
22
+ // size); bump a tick on scroll/resize so the $derived re-runs while open.
23
+ let portalTick = $state(0);
11
24
 
12
- // Clamp against the positioned chart container so the tooltip never spills past
13
- // (and gets clipped by) an overflow:hidden edge. Re-read on each measure/move.
14
- const containerWidth = $derived(tooltipEl?.offsetParent?.clientWidth ?? Number.POSITIVE_INFINITY);
15
- const containerHeight = $derived(
16
- tooltipEl?.offsetParent?.clientHeight ?? Number.POSITIVE_INFINITY
17
- );
18
-
19
- // Horizontal: flip to the left of the cursor when it would overflow the right edge.
20
- const left = $derived.by(() => {
21
- let value = mouseX + OFFSET;
22
- if (value + tooltipWidth > containerWidth) {
23
- value = mouseX - tooltipWidth - OFFSET;
25
+ // eslint-disable-next-line no-restricted-syntax
26
+ $effect(() => {
27
+ if (!portal || data === null || typeof window === 'undefined') {
28
+ return;
24
29
  }
25
- return Math.max(0, value);
30
+ const bump = () => {
31
+ portalTick += 1;
32
+ };
33
+ window.addEventListener('scroll', bump, { capture: true, passive: true });
34
+ window.addEventListener('resize', bump);
35
+ return () => {
36
+ window.removeEventListener('scroll', bump, { capture: true });
37
+ window.removeEventListener('resize', bump);
38
+ };
26
39
  });
27
40
 
28
- // Vertical: keep the tooltip within the container's top and bottom edges.
29
- const top = $derived.by(() => {
30
- let value = mouseY - OFFSET;
31
- if (value + tooltipHeight > containerHeight) {
32
- value = containerHeight - tooltipHeight;
41
+ /**
42
+ * Svelte action: relocates the tooltip to document.body so a position:fixed
43
+ * tooltip is never clipped by an overflow/scroll ancestor. `use:` actions
44
+ * never run during SSR.
45
+ */
46
+ const portalToBody = (node: HTMLElement) => {
47
+ document.body.appendChild(node);
48
+ return { destroy: () => node.remove() };
49
+ };
50
+
51
+ const pos = $derived.by(() => {
52
+ const tooltip = { width: tooltipWidth, height: tooltipHeight };
53
+ if (portal) {
54
+ void portalTick;
55
+ // Convert container coords to viewport coords and clamp to the viewport.
56
+ const rect = originEl?.getBoundingClientRect();
57
+ const dx = rect?.left ?? 0;
58
+ const dy = rect?.top ?? 0;
59
+ const container =
60
+ typeof window === 'undefined'
61
+ ? { width: Number.POSITIVE_INFINITY, height: Number.POSITIVE_INFINITY }
62
+ : { width: window.innerWidth, height: window.innerHeight };
63
+ return computeTooltipPosition({
64
+ mouseX: mouseX + dx,
65
+ mouseY: mouseY + dy,
66
+ anchor: anchor === null ? null : { ...anchor, x: anchor.x + dx, y: anchor.y + dy },
67
+ tooltip,
68
+ container
69
+ });
33
70
  }
34
- return Math.max(0, value);
71
+ const container = {
72
+ width: tooltipEl?.offsetParent?.clientWidth ?? Number.POSITIVE_INFINITY,
73
+ height: tooltipEl?.offsetParent?.clientHeight ?? Number.POSITIVE_INFINITY
74
+ };
75
+ return computeTooltipPosition({ mouseX, mouseY, anchor, tooltip, container });
35
76
  });
36
77
  </script>
37
78
 
38
- {#if data !== null}
39
- <div
40
- bind:this={tooltipEl}
41
- bind:clientWidth={tooltipWidth}
42
- bind:clientHeight={tooltipHeight}
43
- class="chart-tooltip {classes ?? ''}"
44
- style="left: {left}px; top: {top}px;"
45
- >
46
- {#if typeof customSnippet === 'function'}
47
- {@render customSnippet(data)}
48
- {:else}
49
- {#if data.title}
50
- <div class="tooltip-title">{data.title}</div>
51
- {/if}
52
- {#each data.items as item, i (i)}
53
- <div class="tooltip-item">
54
- {#if item.color}
55
- <span class="tooltip-swatch" style="background: {item.color}"></span>
56
- {/if}
57
- <span class="tooltip-label">{item.label}</span>
58
- <span class="tooltip-value">{item.value}</span>
59
- </div>
60
- {/each}
79
+ {#snippet inner(tooltipData: TooltipData)}
80
+ {#if content}
81
+ {@render content()}
82
+ {:else if typeof customSnippet === 'function'}
83
+ {@render customSnippet(tooltipData)}
84
+ {:else}
85
+ {#if tooltipData.title}
86
+ <div class="tooltip-title">{tooltipData.title}</div>
61
87
  {/if}
62
- </div>
88
+ {#each tooltipData.items as item, i (i)}
89
+ <div class="tooltip-item">
90
+ {#if item.color}
91
+ <span class="tooltip-swatch" style="background: {item.color}"></span>
92
+ {/if}
93
+ <span class="tooltip-label">{item.label}</span>
94
+ <span class="tooltip-value">{item.value}</span>
95
+ </div>
96
+ {/each}
97
+ {/if}
98
+ {/snippet}
99
+
100
+ {#if data !== null}
101
+ {#if portal}
102
+ <div
103
+ bind:this={tooltipEl}
104
+ bind:clientWidth={tooltipWidth}
105
+ bind:clientHeight={tooltipHeight}
106
+ class="chart-tooltip portal {unstyled ? 'unstyled' : ''} {classes ?? ''}"
107
+ style="left: {pos.left}px; top: {pos.top}px;"
108
+ use:portalToBody
109
+ >
110
+ {@render inner(data)}
111
+ </div>
112
+ {:else}
113
+ <div
114
+ bind:this={tooltipEl}
115
+ bind:clientWidth={tooltipWidth}
116
+ bind:clientHeight={tooltipHeight}
117
+ class="chart-tooltip {unstyled ? 'unstyled' : ''} {classes ?? ''}"
118
+ style="left: {pos.left}px; top: {pos.top}px;"
119
+ >
120
+ {@render inner(data)}
121
+ </div>
122
+ {/if}
63
123
  {/if}
64
124
 
65
125
  <style>
66
126
  .chart-tooltip {
67
127
  position: absolute;
68
128
  z-index: var(--chart-tooltip-z-index, 10);
69
- background: var(--chart-tooltip-background, rgba(0, 0, 0, 0.85));
70
- color: var(--chart-tooltip-color, #fff);
129
+ background: var(--chart-tooltip-background, light-dark(rgba(0, 0, 0, 0.85), #1f2937));
130
+ color: var(--chart-tooltip-color, light-dark(#fff, #f3f4f6));
71
131
  font-size: var(--chart-tooltip-font-size, 12px);
72
132
  font-family: var(--chart-font-family, inherit);
73
133
  padding: var(--chart-tooltip-padding, 8px 12px);
74
134
  border-radius: var(--chart-tooltip-border-radius, var(--radius, 4px));
75
135
  box-shadow: var(--chart-tooltip-shadow, 0 2px 8px rgba(0, 0, 0, 0.2));
136
+ border: 1px solid
137
+ var(
138
+ --chart-tooltip-border-color,
139
+ light-dark(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.08))
140
+ );
76
141
  pointer-events: none;
77
142
  max-width: var(--chart-tooltip-max-width, 280px);
78
143
  width: fit-content;
79
144
  }
80
145
 
146
+ .chart-tooltip.portal {
147
+ position: fixed;
148
+ }
149
+
150
+ .chart-tooltip.unstyled {
151
+ background: none;
152
+ padding: 0;
153
+ border: none;
154
+ box-shadow: none;
155
+ border-radius: 0;
156
+ max-width: none;
157
+ }
158
+
81
159
  .tooltip-title {
82
160
  font-weight: 600;
83
161
  margin-bottom: 4px;
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { LegendProperties } from './types';
3
3
 
4
- let { items, position = 'bottom', customSnippet, classes }: LegendProperties = $props();
4
+ let { items, position = 'bottom', onToggle, customSnippet, classes }: LegendProperties = $props();
5
5
  </script>
6
6
 
7
7
  {#if items.length > 0}
@@ -10,10 +10,23 @@
10
10
  {@render customSnippet(items)}
11
11
  {:else}
12
12
  {#each items as item, i (i)}
13
- <div class="legend-item">
14
- <span class="legend-swatch" style="background: {item.color}"></span>
15
- <span class="legend-label">{item.label}</span>
16
- </div>
13
+ {#if typeof onToggle === 'function'}
14
+ <button
15
+ type="button"
16
+ class="legend-item legend-toggle"
17
+ class:legend-hidden={item.hidden}
18
+ aria-pressed={!item.hidden}
19
+ onclick={() => onToggle(i)}
20
+ >
21
+ <span class="legend-swatch" style="background: {item.color}"></span>
22
+ <span class="legend-label">{item.label}</span>
23
+ </button>
24
+ {:else}
25
+ <div class="legend-item">
26
+ <span class="legend-swatch" style="background: {item.color}"></span>
27
+ <span class="legend-label">{item.label}</span>
28
+ </div>
29
+ {/if}
17
30
  {/each}
18
31
  {/if}
19
32
  </div>
@@ -54,6 +67,23 @@
54
67
 
55
68
  .legend-label {
56
69
  font-size: var(--chart-legend-font-size, 12px);
57
- color: var(--chart-legend-color, #333);
70
+ color: var(--chart-legend-color, light-dark(#333, #e5e7eb));
71
+ }
72
+
73
+ .legend-toggle {
74
+ background: none;
75
+ border: none;
76
+ padding: 0;
77
+ margin: 0;
78
+ font: inherit;
79
+ cursor: pointer;
80
+ }
81
+
82
+ .legend-hidden .legend-swatch {
83
+ opacity: 0.25;
84
+ }
85
+
86
+ .legend-hidden .legend-label {
87
+ color: var(--chart-legend-hidden-color, light-dark(#bbb, #555));
58
88
  }
59
89
  </style>
@@ -1,25 +1,35 @@
1
1
  import type { Margin, ChartDimensions, PieSliceLayout, ComputedSankeyNode, ComputedSankeyLink, StackedPoint } from './types';
2
+ import { type FontSpec } from './measure';
2
3
  export declare function computeChartDimensions(width: number, height: number, margin?: Partial<Margin>): ChartDimensions;
4
+ export type AutoLayoutInput = {
5
+ width: number;
6
+ height: number;
7
+ yTickLabels: string[];
8
+ xTickLabels: string[];
9
+ y2TickLabels?: string[];
10
+ font?: FontSpec;
11
+ hasXAxisLabel?: boolean;
12
+ hasYAxisLabel?: boolean;
13
+ hasY2AxisLabel?: boolean;
14
+ base?: Partial<Margin>;
15
+ };
16
+ export type AutoLayout = ChartDimensions & {
17
+ xRotate: boolean;
18
+ xEvery: number;
19
+ /**
20
+ * Baseline y for the bottom-axis title, already inside the reserved title
21
+ * band — pass straight to Axis.labelOffset, no extra padding needed.
22
+ */
23
+ xLabelOffset: number;
24
+ };
3
25
  /**
4
- * Measures rendered text width via a shared offscreen canvas context.
5
- * Returns null when measurement is unavailable (SSR, or the environment
6
- * provides no working 2D canvas — e.g. jsdom) so callers can fall back to
7
- * a fixed layout instead of acting on a bogus 0.
8
- */
9
- export declare function measureTextWidth(text: string, font: string): number | null;
10
- /**
11
- * Left margin for a horizontal bar chart's category axis, sized to fit the
12
- * widest category label. Category tick labels render right-aligned 10px left
13
- * of the axis line (tick mark 6px + 4px gap), so any label wider than
14
- * `margin.left - 10` bleeds out of the SVG and gets clipped by the page.
15
- *
16
- * - Never shrinks below `fallback` (the legacy fixed gutter), so charts whose
17
- * labels already fit keep their exact current layout.
18
- * - Caps at 45% of the chart width so one pathological label cannot crush the
19
- * plot area; past the cap the label bleeds as before, but the plot survives.
20
- * - `widestLabelWidth === null` (SSR / unmeasurable) keeps the legacy gutter.
26
+ * Measured, Highcharts-style margins: gutters grow to fit formatted tick labels
27
+ * (instead of clipping) and the bottom axis rotates/thins its labels when the
28
+ * per-category step is too narrow. Order matters to avoid feedback loops:
29
+ * left/right derive from label text only, then innerWidth decides x rotation,
30
+ * then bottom derives from the rotation outcome.
21
31
  */
22
- export declare function computeHorizontalCategoryGutter(widestLabelWidth: number | null, chartWidth: number, fallback?: number): number;
32
+ export declare function computeAutoLayout(input: AutoLayoutInput): AutoLayout;
23
33
  export declare function computePieLayout(data: Array<{
24
34
  label: string;
25
35
  value: number;