@sybilion/uilib 1.3.65 → 1.3.67

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.
@@ -1,4 +1,20 @@
1
1
  import { ChartConfig } from './Chart.types';
2
+ /** Recharts may reuse/mutate tooltip payload arrays across hovers — copy + dedupe by series key. */
3
+ export type TooltipPayloadEntry = {
4
+ type?: string;
5
+ name?: string;
6
+ dataKey?: string;
7
+ value?: unknown;
8
+ payload?: unknown;
9
+ color?: string;
10
+ };
11
+ export type TooltipItem = TooltipPayloadEntry & {
12
+ type: string;
13
+ name: string;
14
+ value: number | [number, number];
15
+ color: string;
16
+ };
17
+ export declare function normalizeTooltipPayload(payload: readonly TooltipPayloadEntry[] | undefined | null): TooltipPayloadEntry[] | undefined;
2
18
  export declare function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string): {
3
19
  label?: React.ReactNode;
4
20
  icon?: React.ComponentType;
@@ -1,13 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
+ import { type TooltipItem } from '../Chart.helpers';
2
3
  import { ChartConfig } from '../Chart.types';
3
- type TooltipItem = {
4
- type: string;
5
- name: string;
6
- value: number | [number, number];
7
- payload: unknown;
8
- color: string;
9
- dataKey?: string;
10
- };
11
4
  type ChartTooltipItemProps = {
12
5
  item: TooltipItem;
13
6
  index: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.65",
3
+ "version": "1.3.67",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -1,5 +1,47 @@
1
1
  import { ChartConfig } from './Chart.types';
2
2
 
3
+ /** Recharts may reuse/mutate tooltip payload arrays across hovers — copy + dedupe by series key. */
4
+ export type TooltipPayloadEntry = {
5
+ type?: string;
6
+ name?: string;
7
+ dataKey?: string;
8
+ value?: unknown;
9
+ payload?: unknown;
10
+ color?: string;
11
+ };
12
+
13
+ export type TooltipItem = TooltipPayloadEntry & {
14
+ type: string;
15
+ name: string;
16
+ value: number | [number, number];
17
+ color: string;
18
+ };
19
+
20
+ export function normalizeTooltipPayload(
21
+ payload: readonly TooltipPayloadEntry[] | undefined | null,
22
+ ): TooltipPayloadEntry[] | undefined {
23
+ if (!payload?.length) return undefined;
24
+
25
+ const seen = new Set<string>();
26
+ const result: TooltipPayloadEntry[] = [];
27
+
28
+ for (const item of payload) {
29
+ if (
30
+ item.value === null ||
31
+ item.value === undefined ||
32
+ item.type === 'none'
33
+ ) {
34
+ continue;
35
+ }
36
+ const key = String(item.dataKey ?? item.name ?? result.length);
37
+ if (seen.has(key)) continue;
38
+ seen.add(key);
39
+ result.push({ ...item });
40
+ }
41
+
42
+ return result.length ? result : undefined;
43
+ }
44
+
3
45
  export function getPayloadConfigFromPayload(
4
46
  config: ChartConfig,
5
47
  payload: unknown,
@@ -21,6 +21,7 @@ import { chartRenderQueue } from '#uilib/utils/chartRenderQueue';
21
21
  import { Tooltip as ChartTooltip, ComposedChart, LineChart } from 'recharts';
22
22
  import { LegendPayload } from 'recharts/types/component/DefaultLegendContent';
23
23
 
24
+ import { normalizeTooltipPayload } from '../Chart.helpers';
24
25
  import type { ChartConfig } from '../Chart.types';
25
26
  import { getPlotViewBox, resolveChartMargin } from '../tools/chartPlotGeometry';
26
27
  import { formatDate } from '../tools/formatters';
@@ -390,10 +391,7 @@ const BaseChartWrapperContent = forwardRef<
390
391
  const renderTooltipContent = (props: any) => {
391
392
  // Filter payload to exclude items with null/undefined values
392
393
  // This prevents showing stale data when hovering on dates without data points
393
- const filteredPayload = props.payload?.filter(
394
- (item: any) =>
395
- item.value !== null && item.value !== undefined && item.type !== 'none',
396
- );
394
+ const filteredPayload = normalizeTooltipPayload(props.payload);
397
395
 
398
396
  // If no valid payload items, render ChartTooltipContent with active=false and empty payload
399
397
  // This allows ChartTooltipContent to clear its lastTooltipData state
@@ -2,21 +2,17 @@ import cn from 'classnames';
2
2
  import { useEffect, useMemo, useState } from 'react';
3
3
 
4
4
  import { useChart } from '#uilib/components/ui/Chart/Chart.context';
5
- import { getPayloadConfigFromPayload } from '#uilib/components/ui/Chart/Chart.helpers';
5
+ import {
6
+ type TooltipItem,
7
+ type TooltipPayloadEntry,
8
+ getPayloadConfigFromPayload,
9
+ normalizeTooltipPayload,
10
+ } from '#uilib/components/ui/Chart/Chart.helpers';
6
11
  import { ChartTooltipContentProps } from '#uilib/components/ui/Chart/Chart.types';
7
12
 
8
13
  import S from '../Chart.styl';
9
14
  import { ChartTooltipItem } from './ChartTooltipItem';
10
15
 
11
- type TooltipItem = {
12
- type: string;
13
- name: string;
14
- value: number | [number, number];
15
- payload: unknown;
16
- color: string;
17
- dataKey?: string;
18
- };
19
-
20
16
  export function ChartTooltipContent({
21
17
  active,
22
18
  className,
@@ -41,32 +37,41 @@ export function ChartTooltipContent({
41
37
  label: string | number | undefined;
42
38
  } | null>(null);
43
39
 
40
+ const normalizedPayload = useMemo(
41
+ () => normalizeTooltipPayload(payload as TooltipPayloadEntry[] | undefined),
42
+ [payload],
43
+ );
44
+
44
45
  // Update last tooltip data when active
45
46
  useEffect(() => {
46
- // Clear lastTooltipData immediately if label changed (prevents stale data)
47
- // Only clear if label is explicitly provided and different (not undefined)
48
- if (lastTooltipData && label && lastTooltipData.label !== label) {
49
- setLastTooltipData(null);
50
- }
51
-
52
- if (active && payload?.length) {
53
- setLastTooltipData({
54
- active,
55
- payload: payload as TooltipItem[],
56
- label,
57
- });
58
- }
59
- // Don't clear lastTooltipData when mouse moves out - keep it for position maintenance
60
- // It will be cleared when label changes (above) or when new active data arrives
61
- }, [active, payload, label]);
47
+ setLastTooltipData(prev => {
48
+ // Clear when label changed (prevents stale data)
49
+ let next = prev;
50
+ if (prev && label && prev.label !== label) {
51
+ next = null;
52
+ }
53
+
54
+ if (active && normalizedPayload?.length) {
55
+ return {
56
+ active: true,
57
+ payload: normalizedPayload as TooltipItem[],
58
+ label,
59
+ };
60
+ }
61
+
62
+ // Keep frozen snapshot when inactive (position maintenance in BaseChartWrapper)
63
+ if (!active && next) {
64
+ return { ...next, active: false };
65
+ }
66
+
67
+ return next;
68
+ });
69
+ }, [active, normalizedPayload, label]);
62
70
 
63
- // Use last tooltip data if current is inactive, otherwise use current
64
- // lastTooltipData is already cleared in useEffect if label changed, so safe to use here
65
- const displayActive = active || (lastTooltipData?.active ?? false);
66
71
  const displayPayload: TooltipItem[] | undefined =
67
- active && payload?.length
68
- ? (payload as TooltipItem[])
69
- : lastTooltipData?.payload;
72
+ active && normalizedPayload?.length
73
+ ? (normalizedPayload as TooltipItem[])
74
+ : (lastTooltipData?.payload as TooltipItem[] | undefined);
70
75
  const displayLabel = active ? label : lastTooltipData?.label;
71
76
 
72
77
  const tooltipLabel = useMemo(() => {
@@ -1,19 +1,10 @@
1
1
  import cn from 'classnames';
2
2
  import { CSSProperties, ReactNode } from 'react';
3
3
 
4
- import { getPayloadConfigFromPayload } from '../Chart.helpers';
4
+ import { getPayloadConfigFromPayload, type TooltipItem } from '../Chart.helpers';
5
5
  import S from '../Chart.styl';
6
6
  import { ChartConfig } from '../Chart.types';
7
7
 
8
- type TooltipItem = {
9
- type: string;
10
- name: string;
11
- value: number | [number, number];
12
- payload: unknown;
13
- color: string;
14
- dataKey?: string;
15
- };
16
-
17
8
  type ChartTooltipItemProps = {
18
9
  item: TooltipItem;
19
10
  index: number;