@ledgerhq/lumen-ui-rnative-visualization 0.1.14 → 0.1.16
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.
- package/dist/module/lib/Components/Axis/Axis.constants.js +21 -0
- package/dist/module/lib/Components/Axis/Axis.constants.js.map +1 -0
- package/dist/module/lib/Components/Axis/index.js +5 -0
- package/dist/module/lib/Components/Axis/index.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/CartesianChart.js +34 -22
- package/dist/module/lib/Components/CartesianChart/CartesianChart.js.map +1 -1
- package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.js +5 -4
- package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.js.map +1 -1
- package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.test.js +54 -0
- package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.test.js.map +1 -1
- package/dist/module/lib/Components/CartesianChart/utils.js +22 -12
- package/dist/module/lib/Components/CartesianChart/utils.js.map +1 -1
- package/dist/module/lib/Components/Line/utils.js +6 -1
- package/dist/module/lib/Components/Line/utils.js.map +1 -1
- package/dist/module/lib/Components/Line/utils.test.js +88 -0
- package/dist/module/lib/Components/Line/utils.test.js.map +1 -0
- package/dist/module/lib/Components/LineChart/LineChart.js +19 -31
- package/dist/module/lib/Components/LineChart/LineChart.js.map +1 -1
- package/dist/module/lib/Components/LineChart/LineChart.stories.js +38 -0
- package/dist/module/lib/Components/LineChart/LineChart.stories.js.map +1 -1
- package/dist/module/lib/Components/LineChart/LineChart.test.js +192 -0
- package/dist/module/lib/Components/LineChart/LineChart.test.js.map +1 -1
- package/dist/module/lib/Components/Point/Point.js +8 -2
- package/dist/module/lib/Components/Point/Point.js.map +1 -1
- package/dist/module/lib/Components/Point/constants.js +9 -0
- package/dist/module/lib/Components/Point/constants.js.map +1 -0
- package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.js +35 -0
- package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.js.map +1 -0
- package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.test.js +153 -0
- package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.test.js.map +1 -0
- package/dist/module/lib/Components/Point/pointContext/index.js +5 -0
- package/dist/module/lib/Components/Point/pointContext/index.js.map +1 -0
- package/dist/module/lib/Components/Point/pointContext/magneticPointsContext.js +10 -0
- package/dist/module/lib/Components/Point/pointContext/magneticPointsContext.js.map +1 -0
- package/dist/module/lib/Components/Point/utils.js +26 -6
- package/dist/module/lib/Components/Point/utils.js.map +1 -1
- package/dist/module/lib/Components/Point/utils.test.js +84 -2
- package/dist/module/lib/Components/Point/utils.test.js.map +1 -1
- package/dist/module/lib/Components/ReferenceLine/utils.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.js +3 -3
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.js +5 -5
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.test.js +5 -5
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/constants.js +1 -2
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/constants.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js +31 -10
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.js +4 -4
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/ScrubberProvider.js +35 -7
- package/dist/module/lib/Components/Scrubber/ScrubberProvider.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/utils.js +64 -0
- package/dist/module/lib/Components/Scrubber/utils.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/utils.test.js +128 -1
- package/dist/module/lib/Components/Scrubber/utils.test.js.map +1 -1
- package/dist/module/lib/utils/domain/domain.js.map +1 -1
- package/dist/module/lib/utils/scales/scales.js +13 -4
- package/dist/module/lib/utils/scales/scales.js.map +1 -1
- package/dist/module/lib/utils/scales/scales.test.js +29 -0
- package/dist/module/lib/utils/scales/scales.test.js.map +1 -1
- package/dist/module/lib/utils/ticks/ticks.js +13 -3
- package/dist/module/lib/utils/ticks/ticks.js.map +1 -1
- package/dist/module/lib/utils/ticks/ticks.test.js +34 -0
- package/dist/module/lib/utils/ticks/ticks.test.js.map +1 -1
- package/dist/typescript/src/lib/Components/Axis/Axis.constants.d.ts +5 -0
- package/dist/typescript/src/lib/Components/Axis/Axis.constants.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts +39 -0
- package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Axis/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/Axis/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/context/useBuildChartContext.d.ts +5 -4
- package/dist/typescript/src/lib/Components/CartesianChart/context/useBuildChartContext.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts +11 -3
- package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts +18 -8
- package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Line/utils.d.ts +4 -0
- package/dist/typescript/src/lib/Components/Line/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts +1 -1
- package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/LineChart/types.d.ts +10 -3
- package/dist/typescript/src/lib/Components/LineChart/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Point/Point.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Point/Point.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Point/constants.d.ts +7 -0
- package/dist/typescript/src/lib/Components/Point/constants.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/MagneticPointsProvider.d.ts +7 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/MagneticPointsProvider.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/magneticPointsContext.d.ts +13 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/magneticPointsContext.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Point/types.d.ts +7 -0
- package/dist/typescript/src/lib/Components/Point/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Point/utils.d.ts +8 -6
- package/dist/typescript/src/lib/Components/Point/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts +3 -2
- package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.d.ts +1 -2
- package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.d.ts +5 -1
- package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/types.d.ts +13 -0
- package/dist/typescript/src/lib/Components/Scrubber/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts +33 -3
- package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/domain/domain.d.ts +5 -4
- package/dist/typescript/src/lib/utils/domain/domain.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/index.d.ts +1 -1
- package/dist/typescript/src/lib/utils/index.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/scales/scales.d.ts +11 -5
- package/dist/typescript/src/lib/utils/scales/scales.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/ticks/ticks.d.ts +13 -5
- package/dist/typescript/src/lib/utils/ticks/ticks.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/types.d.ts +3 -28
- package/dist/typescript/src/lib/utils/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/lib/Components/Axis/Axis.constants.ts +23 -0
- package/src/lib/Components/Axis/Axis.types.ts +40 -0
- package/src/lib/Components/Axis/index.ts +2 -0
- package/src/lib/Components/CartesianChart/CartesianChart.tsx +39 -23
- package/src/lib/Components/CartesianChart/context/useBuildChartContext.test.ts +56 -0
- package/src/lib/Components/CartesianChart/context/useBuildChartContext.ts +8 -8
- package/src/lib/Components/CartesianChart/types.ts +11 -3
- package/src/lib/Components/CartesianChart/utils.ts +22 -12
- package/src/lib/Components/Line/utils.test.ts +90 -0
- package/src/lib/Components/Line/utils.ts +6 -1
- package/src/lib/Components/LineChart/LineChart.stories.tsx +36 -0
- package/src/lib/Components/LineChart/LineChart.test.tsx +156 -0
- package/src/lib/Components/LineChart/LineChart.tsx +30 -40
- package/src/lib/Components/LineChart/types.ts +10 -3
- package/src/lib/Components/Point/Point.tsx +9 -3
- package/src/lib/Components/Point/constants.ts +6 -0
- package/src/lib/Components/Point/pointContext/MagneticPointsProvider.test.tsx +162 -0
- package/src/lib/Components/Point/pointContext/MagneticPointsProvider.tsx +45 -0
- package/src/lib/Components/Point/pointContext/index.ts +5 -0
- package/src/lib/Components/Point/pointContext/magneticPointsContext.ts +19 -0
- package/src/lib/Components/Point/types.ts +7 -0
- package/src/lib/Components/Point/utils.test.ts +95 -5
- package/src/lib/Components/Point/utils.ts +35 -7
- package/src/lib/Components/ReferenceLine/utils.ts +4 -7
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.tsx +3 -3
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.test.tsx +5 -5
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.tsx +4 -5
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.ts +1 -2
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.ts +4 -4
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.ts +29 -15
- package/src/lib/Components/Scrubber/ScrubberProvider.tsx +41 -5
- package/src/lib/Components/Scrubber/types.ts +13 -0
- package/src/lib/Components/Scrubber/utils.test.ts +150 -0
- package/src/lib/Components/Scrubber/utils.ts +93 -3
- package/src/lib/utils/domain/domain.ts +6 -5
- package/src/lib/utils/index.ts +0 -2
- package/src/lib/utils/scales/scales.test.ts +19 -0
- package/src/lib/utils/scales/scales.ts +15 -9
- package/src/lib/utils/ticks/ticks.test.ts +45 -0
- package/src/lib/utils/ticks/ticks.ts +19 -9
- package/src/lib/utils/types.ts +3 -26
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { renderHook, act } from '@testing-library/react-native';
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
import { useMagneticPointsContext } from './magneticPointsContext';
|
|
6
|
+
import { MagneticPointsProvider } from './MagneticPointsProvider';
|
|
7
|
+
|
|
8
|
+
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
9
|
+
<MagneticPointsProvider>{children}</MagneticPointsProvider>
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
describe('MagneticPointsProvider', () => {
|
|
13
|
+
it('starts with an empty set', () => {
|
|
14
|
+
const { result } = renderHook(() => useMagneticPointsContext(), {
|
|
15
|
+
wrapper,
|
|
16
|
+
});
|
|
17
|
+
expect(result.current.getMagneticPoints().size).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('registers a magnetic point', () => {
|
|
21
|
+
const { result } = renderHook(() => useMagneticPointsContext(), {
|
|
22
|
+
wrapper,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
act(() => {
|
|
26
|
+
result.current.register(3);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(result.current.getMagneticPoints().has(3)).toBe(true);
|
|
30
|
+
expect(result.current.getMagneticPoints().size).toBe(1);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('registers multiple magnetic points', () => {
|
|
34
|
+
const { result } = renderHook(() => useMagneticPointsContext(), {
|
|
35
|
+
wrapper,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
act(() => {
|
|
39
|
+
result.current.register(2);
|
|
40
|
+
result.current.register(7);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const points = result.current.getMagneticPoints();
|
|
44
|
+
expect(points.has(2)).toBe(true);
|
|
45
|
+
expect(points.has(7)).toBe(true);
|
|
46
|
+
expect(points.size).toBe(2);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('unregisters a magnetic point', () => {
|
|
50
|
+
const { result } = renderHook(() => useMagneticPointsContext(), {
|
|
51
|
+
wrapper,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
act(() => {
|
|
55
|
+
result.current.register(5);
|
|
56
|
+
result.current.register(8);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
act(() => {
|
|
60
|
+
result.current.unregister(5);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const points = result.current.getMagneticPoints();
|
|
64
|
+
expect(points.has(5)).toBe(false);
|
|
65
|
+
expect(points.has(8)).toBe(true);
|
|
66
|
+
expect(points.size).toBe(1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('handles duplicate registrations gracefully', () => {
|
|
70
|
+
const { result } = renderHook(() => useMagneticPointsContext(), {
|
|
71
|
+
wrapper,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
act(() => {
|
|
75
|
+
result.current.register(4);
|
|
76
|
+
result.current.register(4);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(result.current.getMagneticPoints().size).toBe(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('handles unregistering a non-existent point gracefully', () => {
|
|
83
|
+
const { result } = renderHook(() => useMagneticPointsContext(), {
|
|
84
|
+
wrapper,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
act(() => {
|
|
88
|
+
result.current.unregister(99);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(result.current.getMagneticPoints().size).toBe(0);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('version counter', () => {
|
|
95
|
+
it('starts at 0', () => {
|
|
96
|
+
const { result } = renderHook(() => useMagneticPointsContext(), {
|
|
97
|
+
wrapper,
|
|
98
|
+
});
|
|
99
|
+
expect(result.current.version).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('increments on register', () => {
|
|
103
|
+
const { result } = renderHook(() => useMagneticPointsContext(), {
|
|
104
|
+
wrapper,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
act(() => {
|
|
108
|
+
result.current.register(1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(result.current.version).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('increments on unregister', () => {
|
|
115
|
+
const { result } = renderHook(() => useMagneticPointsContext(), {
|
|
116
|
+
wrapper,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
act(() => {
|
|
120
|
+
result.current.register(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
act(() => {
|
|
124
|
+
result.current.unregister(1);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(result.current.version).toBe(2);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('does not increment on duplicate register', () => {
|
|
131
|
+
const { result } = renderHook(() => useMagneticPointsContext(), {
|
|
132
|
+
wrapper,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
act(() => {
|
|
136
|
+
result.current.register(5);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const versionAfterFirst = result.current.version;
|
|
140
|
+
|
|
141
|
+
act(() => {
|
|
142
|
+
result.current.register(5);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(result.current.version).toBe(versionAfterFirst);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('does not increment when unregistering a non-existent point', () => {
|
|
149
|
+
const { result } = renderHook(() => useMagneticPointsContext(), {
|
|
150
|
+
wrapper,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const versionBefore = result.current.version;
|
|
154
|
+
|
|
155
|
+
act(() => {
|
|
156
|
+
result.current.unregister(99);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result.current.version).toBe(versionBefore);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
MagneticPointsContextProvider,
|
|
6
|
+
type MagneticPointsContextValue,
|
|
7
|
+
} from './magneticPointsContext';
|
|
8
|
+
|
|
9
|
+
type MagneticPointsProviderProps = {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function MagneticPointsProvider({
|
|
14
|
+
children,
|
|
15
|
+
}: Readonly<MagneticPointsProviderProps>) {
|
|
16
|
+
const pointsRef = useRef(new Set<number>());
|
|
17
|
+
const [version, setVersion] = useState(0);
|
|
18
|
+
|
|
19
|
+
const register = useCallback((dataIndex: number): void => {
|
|
20
|
+
if (pointsRef.current.has(dataIndex)) return;
|
|
21
|
+
pointsRef.current.add(dataIndex);
|
|
22
|
+
setVersion((v) => v + 1);
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
const unregister = useCallback((dataIndex: number): void => {
|
|
26
|
+
if (!pointsRef.current.has(dataIndex)) return;
|
|
27
|
+
pointsRef.current.delete(dataIndex);
|
|
28
|
+
setVersion((v) => v + 1);
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
const getMagneticPoints = useCallback((): ReadonlySet<number> => {
|
|
32
|
+
return pointsRef.current;
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const value: MagneticPointsContextValue = useMemo(
|
|
36
|
+
() => ({ register, unregister, getMagneticPoints, version }),
|
|
37
|
+
[register, unregister, getMagneticPoints, version],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<MagneticPointsContextProvider value={value}>
|
|
42
|
+
{children}
|
|
43
|
+
</MagneticPointsContextProvider>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createSafeContext } from '@ledgerhq/lumen-utils-shared';
|
|
2
|
+
|
|
3
|
+
export type MagneticPointsContextValue = {
|
|
4
|
+
register: (dataIndex: number) => void;
|
|
5
|
+
unregister: (dataIndex: number) => void;
|
|
6
|
+
version: number;
|
|
7
|
+
getMagneticPoints: () => ReadonlySet<number>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const [MagneticPointsContextProvider, _useMagneticPointsSafeContext] =
|
|
11
|
+
createSafeContext<MagneticPointsContextValue>('MagneticPoints');
|
|
12
|
+
|
|
13
|
+
export const useMagneticPointsContext = (): MagneticPointsContextValue =>
|
|
14
|
+
_useMagneticPointsSafeContext({
|
|
15
|
+
consumerName: 'useMagneticPointsContext',
|
|
16
|
+
contextRequired: true,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export { MagneticPointsContextProvider };
|
|
@@ -73,4 +73,11 @@ export type PointProps = {
|
|
|
73
73
|
* Called when the point is pressed.
|
|
74
74
|
*/
|
|
75
75
|
onPress?: (event: GestureResponderEvent) => void;
|
|
76
|
+
/**
|
|
77
|
+
* When `true`, the scrubber magnetically snaps to this point within the
|
|
78
|
+
* chart's `magnetRadius`. Requires `enableScrubbing` to be set on the
|
|
79
|
+
* parent chart.
|
|
80
|
+
* @default false
|
|
81
|
+
*/
|
|
82
|
+
magnetic?: boolean;
|
|
76
83
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { describe, expect, it } from '@jest/globals';
|
|
1
|
+
import { describe, expect, it, jest } from '@jest/globals';
|
|
2
|
+
import { renderHook } from '@testing-library/react-native';
|
|
2
3
|
|
|
4
|
+
import type { BaseAxisProps } from '../Axis';
|
|
5
|
+
import { ARROW_HEIGHT, ARROW_WIDTH, GAP, LABEL_FONT_SIZE } from './constants';
|
|
3
6
|
import {
|
|
4
|
-
ARROW_HEIGHT,
|
|
5
|
-
ARROW_WIDTH,
|
|
6
7
|
buildArrowPoints,
|
|
7
8
|
computeLabelY,
|
|
8
|
-
GAP,
|
|
9
9
|
isWithinBounds,
|
|
10
|
-
LABEL_FONT_SIZE,
|
|
11
10
|
resolveLabel,
|
|
11
|
+
useMagneticRegistration,
|
|
12
12
|
} from './utils';
|
|
13
13
|
|
|
14
14
|
describe('isWithinBounds', () => {
|
|
@@ -116,3 +116,93 @@ describe('computeLabelY', () => {
|
|
|
116
116
|
expect(result).toBe(pixelY + radius + GAP + GAP + LABEL_FONT_SIZE);
|
|
117
117
|
});
|
|
118
118
|
});
|
|
119
|
+
|
|
120
|
+
const makeContext = () => ({
|
|
121
|
+
register: jest.fn(),
|
|
122
|
+
unregister: jest.fn(),
|
|
123
|
+
version: 0,
|
|
124
|
+
getMagneticPoints: () => new Set<number>(),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const noAxisConfig = (): BaseAxisProps | undefined => undefined;
|
|
128
|
+
|
|
129
|
+
describe('useMagneticRegistration', () => {
|
|
130
|
+
it('registers the data index when magnetic is true', () => {
|
|
131
|
+
const ctx = makeContext();
|
|
132
|
+
renderHook(() => useMagneticRegistration(true, 3, noAxisConfig, ctx));
|
|
133
|
+
|
|
134
|
+
expect(ctx.register).toHaveBeenCalledWith(3);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('does not register when magnetic is false', () => {
|
|
138
|
+
const ctx = makeContext();
|
|
139
|
+
renderHook(() => useMagneticRegistration(false, 3, noAxisConfig, ctx));
|
|
140
|
+
|
|
141
|
+
expect(ctx.register).not.toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('unregisters on unmount', () => {
|
|
145
|
+
const ctx = makeContext();
|
|
146
|
+
const { unmount } = renderHook(() =>
|
|
147
|
+
useMagneticRegistration(true, 3, noAxisConfig, ctx),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
unmount();
|
|
151
|
+
expect(ctx.unregister).toHaveBeenCalledWith(3);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('resolves dataX through axis config data when provided', () => {
|
|
155
|
+
const ctx = makeContext();
|
|
156
|
+
const getXAxisConfig = (): BaseAxisProps => ({
|
|
157
|
+
scaleType: 'band',
|
|
158
|
+
data: [100, 200, 300],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
renderHook(() => useMagneticRegistration(true, 200, getXAxisConfig, ctx));
|
|
162
|
+
|
|
163
|
+
expect(ctx.register).toHaveBeenCalledWith(1);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('does not register when axis data exists but does not contain the value', () => {
|
|
167
|
+
const ctx = makeContext();
|
|
168
|
+
const getXAxisConfig = (): BaseAxisProps => ({
|
|
169
|
+
scaleType: 'band',
|
|
170
|
+
data: [100, 200, 300],
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
renderHook(() => useMagneticRegistration(true, 999, getXAxisConfig, ctx));
|
|
174
|
+
|
|
175
|
+
expect(ctx.register).not.toHaveBeenCalled();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('re-registers when dataX changes', () => {
|
|
179
|
+
const ctx = makeContext();
|
|
180
|
+
const { rerender } = renderHook(
|
|
181
|
+
({ dataX }: { dataX: number }) =>
|
|
182
|
+
useMagneticRegistration(true, dataX, noAxisConfig, ctx),
|
|
183
|
+
{ initialProps: { dataX: 2 } },
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
expect(ctx.register).toHaveBeenCalledWith(2);
|
|
187
|
+
|
|
188
|
+
rerender({ dataX: 4 });
|
|
189
|
+
|
|
190
|
+
expect(ctx.unregister).toHaveBeenCalledWith(2);
|
|
191
|
+
expect(ctx.register).toHaveBeenCalledWith(4);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('unregisters and stops when magnetic switches to false', () => {
|
|
195
|
+
const ctx = makeContext();
|
|
196
|
+
const { rerender } = renderHook(
|
|
197
|
+
({ magnetic }: { magnetic: boolean }) =>
|
|
198
|
+
useMagneticRegistration(magnetic, 3, noAxisConfig, ctx),
|
|
199
|
+
{ initialProps: { magnetic: true } },
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
expect(ctx.register).toHaveBeenCalledWith(3);
|
|
203
|
+
|
|
204
|
+
rerender({ magnetic: false });
|
|
205
|
+
|
|
206
|
+
expect(ctx.unregister).toHaveBeenCalledWith(3);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export const ARROW_HEIGHT = 4;
|
|
8
|
-
export const GAP = 4;
|
|
3
|
+
import type { DrawingArea } from '../../utils/types';
|
|
4
|
+
import type { BaseAxisProps } from '../Axis';
|
|
5
|
+
import { LABEL_FONT_SIZE, ARROW_WIDTH, ARROW_HEIGHT, GAP } from './constants';
|
|
6
|
+
import type { MagneticPointsContextValue } from './pointContext/magneticPointsContext';
|
|
9
7
|
|
|
10
8
|
export const isWithinBounds = (
|
|
11
9
|
px: number,
|
|
@@ -53,6 +51,36 @@ export const resolveLabel = (
|
|
|
53
51
|
return resolved === '' ? undefined : resolved;
|
|
54
52
|
};
|
|
55
53
|
|
|
54
|
+
export const resolveDataXToIndex = (
|
|
55
|
+
dataX: number,
|
|
56
|
+
axisConfig: BaseAxisProps | undefined,
|
|
57
|
+
): number | undefined => {
|
|
58
|
+
const axisData = axisConfig?.data;
|
|
59
|
+
if (!axisData || typeof axisData[0] !== 'number') return dataX;
|
|
60
|
+
const index = (axisData as number[]).indexOf(dataX);
|
|
61
|
+
if (index === -1) return undefined;
|
|
62
|
+
return index;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Registers/unregisters a data index as a magnetic snap target
|
|
67
|
+
* when the Point has `magnetic` enabled.
|
|
68
|
+
*/
|
|
69
|
+
export const useMagneticRegistration = (
|
|
70
|
+
magnetic: boolean,
|
|
71
|
+
dataX: number,
|
|
72
|
+
getXAxisConfig: () => BaseAxisProps | undefined,
|
|
73
|
+
{ register, unregister }: MagneticPointsContextValue,
|
|
74
|
+
): void => {
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!magnetic) return;
|
|
77
|
+
const index = resolveDataXToIndex(dataX, getXAxisConfig());
|
|
78
|
+
if (index === undefined) return;
|
|
79
|
+
register(index);
|
|
80
|
+
return () => unregister(index);
|
|
81
|
+
}, [magnetic, dataX, getXAxisConfig, register, unregister]);
|
|
82
|
+
};
|
|
83
|
+
|
|
56
84
|
/**
|
|
57
85
|
* Computes the vertical position of the label text baseline.
|
|
58
86
|
*/
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { getPointOnScale, isCategoricalScale } from '../../utils/scales/scales';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
ChartScaleFunction,
|
|
5
|
-
DrawingArea,
|
|
6
|
-
} from '../../utils/types';
|
|
2
|
+
import type { ChartScaleFunction, DrawingArea } from '../../utils/types';
|
|
3
|
+
import type { BaseAxisProps } from '../Axis';
|
|
7
4
|
|
|
8
5
|
import type { LabelAlignment, LabelPosition } from './types';
|
|
9
6
|
|
|
@@ -28,7 +25,7 @@ type ResolvePixelParams = {
|
|
|
28
25
|
scale: ChartScaleFunction | undefined;
|
|
29
26
|
axis: 'x' | 'y';
|
|
30
27
|
drawingArea: DrawingArea;
|
|
31
|
-
axisConfig?:
|
|
28
|
+
axisConfig?: BaseAxisProps;
|
|
32
29
|
};
|
|
33
30
|
|
|
34
31
|
type LabelParams = {
|
|
@@ -95,7 +92,7 @@ export const isPixelWithinDrawingArea = (
|
|
|
95
92
|
*/
|
|
96
93
|
const resolveDataValue = (
|
|
97
94
|
index: number,
|
|
98
|
-
axisConfig?:
|
|
95
|
+
axisConfig?: BaseAxisProps,
|
|
99
96
|
): number | undefined => {
|
|
100
97
|
const data = axisConfig?.data;
|
|
101
98
|
if (!data || data.length === 0) return index;
|
|
@@ -31,7 +31,7 @@ export const ChartTooltipItem = memo(function ChartTooltipItem({
|
|
|
31
31
|
textAnchor='start'
|
|
32
32
|
alignmentBaseline='central'
|
|
33
33
|
fill={theme.colors.text.muted}
|
|
34
|
-
fontSize={theme.typographies.
|
|
34
|
+
fontSize={theme.typographies.body3.fontSize}
|
|
35
35
|
fontFamily={theme.fontFamilies.sans}
|
|
36
36
|
>
|
|
37
37
|
{String(label)}
|
|
@@ -43,8 +43,8 @@ export const ChartTooltipItem = memo(function ChartTooltipItem({
|
|
|
43
43
|
textAnchor='end'
|
|
44
44
|
alignmentBaseline='central'
|
|
45
45
|
fill={theme.colors.text.base}
|
|
46
|
-
fontSize={theme.typographies.
|
|
47
|
-
fontWeight={theme.typographies.
|
|
46
|
+
fontSize={theme.typographies.body3SemiBold.fontSize}
|
|
47
|
+
fontWeight={theme.typographies.body3SemiBold.fontWeight}
|
|
48
48
|
fontFamily={theme.fontFamilies.sans}
|
|
49
49
|
>
|
|
50
50
|
{String(value)}
|
|
@@ -148,7 +148,7 @@ describe('DefaultScrubberTooltip', () => {
|
|
|
148
148
|
scrubberProps: { tooltip: defaultTooltip },
|
|
149
149
|
scrubberContext: scrubberAt(2),
|
|
150
150
|
});
|
|
151
|
-
expect(getByTestId('chart-tooltip-rect').props.x).toBe(
|
|
151
|
+
expect(getByTestId('chart-tooltip-rect').props.x).toBe(235);
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
it('flips tooltip left when right side would overflow (index 3)', () => {
|
|
@@ -158,7 +158,7 @@ describe('DefaultScrubberTooltip', () => {
|
|
|
158
158
|
},
|
|
159
159
|
scrubberContext: scrubberAt(3),
|
|
160
160
|
});
|
|
161
|
-
expect(getByTestId('chart-tooltip-rect').props.x).toBe(
|
|
161
|
+
expect(getByTestId('chart-tooltip-rect').props.x).toBe(195);
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
it('flips at the rightmost scrubber position (index 4)', () => {
|
|
@@ -168,7 +168,7 @@ describe('DefaultScrubberTooltip', () => {
|
|
|
168
168
|
},
|
|
169
169
|
scrubberContext: scrubberAt(4),
|
|
170
170
|
});
|
|
171
|
-
expect(getByTestId('chart-tooltip-rect').props.x).toBe(
|
|
171
|
+
expect(getByTestId('chart-tooltip-rect').props.x).toBe(295);
|
|
172
172
|
});
|
|
173
173
|
|
|
174
174
|
it('custom offset shifts the no-flip tooltip position', () => {
|
|
@@ -178,7 +178,7 @@ describe('DefaultScrubberTooltip', () => {
|
|
|
178
178
|
},
|
|
179
179
|
scrubberContext: scrubberAt(2),
|
|
180
180
|
});
|
|
181
|
-
expect(getByTestId('chart-tooltip-rect').props.x).toBe(
|
|
181
|
+
expect(getByTestId('chart-tooltip-rect').props.x).toBe(245);
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
it('wider minWidth can trigger a flip that the default width would not', () => {
|
|
@@ -188,7 +188,7 @@ describe('DefaultScrubberTooltip', () => {
|
|
|
188
188
|
},
|
|
189
189
|
scrubberContext: scrubberAt(2),
|
|
190
190
|
});
|
|
191
|
-
expect(getByTestId('chart-tooltip-rect').props.x).toBe(
|
|
191
|
+
expect(getByTestId('chart-tooltip-rect').props.x).toBe(25);
|
|
192
192
|
});
|
|
193
193
|
});
|
|
194
194
|
|
|
@@ -4,7 +4,6 @@ import { G, Rect, Text as SvgText } from 'react-native-svg';
|
|
|
4
4
|
import type { ScrubberTooltipProps } from '../types';
|
|
5
5
|
import { ChartTooltipItem } from './ChartTooltipItem';
|
|
6
6
|
import {
|
|
7
|
-
BORDER_RADIUS,
|
|
8
7
|
DEFAULT_OFFSET,
|
|
9
8
|
DEFAULT_TOOLTIP_MIN_WIDTH,
|
|
10
9
|
PADDING_X,
|
|
@@ -68,7 +67,7 @@ export function DefaultScrubberTooltip({
|
|
|
68
67
|
y={drawingArea.y}
|
|
69
68
|
width={tooltipWidth}
|
|
70
69
|
height={tooltipHeight}
|
|
71
|
-
rx={
|
|
70
|
+
rx={theme.borderRadius.sm}
|
|
72
71
|
fill={theme.colors.bg.muted}
|
|
73
72
|
/>
|
|
74
73
|
{hasTitle && (
|
|
@@ -80,9 +79,9 @@ export function DefaultScrubberTooltip({
|
|
|
80
79
|
x={tooltipX + PADDING_X}
|
|
81
80
|
y={drawingArea.y + PADDING_Y + ROW_HEIGHT / 2}
|
|
82
81
|
alignmentBaseline='central'
|
|
83
|
-
fill={theme.colors.text.
|
|
84
|
-
fontSize={theme.typographies.
|
|
85
|
-
fontWeight={theme.typographies.
|
|
82
|
+
fill={theme.colors.text.muted}
|
|
83
|
+
fontSize={theme.typographies.body3.fontSize}
|
|
84
|
+
fontWeight={theme.typographies.body3.fontWeight}
|
|
86
85
|
fontFamily={theme.fontFamilies.sans}
|
|
87
86
|
>
|
|
88
87
|
{String(title)}
|
|
@@ -3,7 +3,6 @@ export const DEFAULT_TOOLTIP_MIN_WIDTH = 80;
|
|
|
3
3
|
export const PADDING_X = 8;
|
|
4
4
|
export const PADDING_Y = 8;
|
|
5
5
|
export const ROW_HEIGHT = 16;
|
|
6
|
-
export const ROW_GAP =
|
|
6
|
+
export const ROW_GAP = 4;
|
|
7
7
|
export const TITLE_GAP = 6;
|
|
8
|
-
export const BORDER_RADIUS = 4;
|
|
9
8
|
export const LABEL_VALUE_GAP = 12;
|
|
@@ -192,7 +192,7 @@ describe('measureWidths', () => {
|
|
|
192
192
|
titleRef,
|
|
193
193
|
labelRefs,
|
|
194
194
|
valueRefs,
|
|
195
|
-
);
|
|
195
|
+
).promise;
|
|
196
196
|
|
|
197
197
|
expect(result).toEqual({ title: 50, labels: [30], values: [20] });
|
|
198
198
|
raf.mockRestore();
|
|
@@ -212,7 +212,7 @@ describe('measureWidths', () => {
|
|
|
212
212
|
titleRef,
|
|
213
213
|
labelRefs,
|
|
214
214
|
valueRefs,
|
|
215
|
-
);
|
|
215
|
+
).promise;
|
|
216
216
|
|
|
217
217
|
expect(result.title).toBe(0);
|
|
218
218
|
raf.mockRestore();
|
|
@@ -235,7 +235,7 @@ describe('measureWidths', () => {
|
|
|
235
235
|
titleRef,
|
|
236
236
|
labelRefs,
|
|
237
237
|
valueRefs,
|
|
238
|
-
);
|
|
238
|
+
).promise;
|
|
239
239
|
|
|
240
240
|
expect(result.labels).toEqual([10, 20]);
|
|
241
241
|
expect(result.values).toEqual([5, 15]);
|
|
@@ -256,7 +256,7 @@ describe('measureWidths', () => {
|
|
|
256
256
|
titleRef,
|
|
257
257
|
labelRefs,
|
|
258
258
|
valueRefs,
|
|
259
|
-
);
|
|
259
|
+
).promise;
|
|
260
260
|
|
|
261
261
|
expect(result).toEqual({ title: 0, labels: [0], values: [0] });
|
|
262
262
|
raf.mockRestore();
|
|
@@ -107,29 +107,41 @@ export const computeItemsBaseY = (
|
|
|
107
107
|
return drawingAreaY + PADDING_Y + titleBlockHeight;
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
-
const waitForAnimationFrame = ():
|
|
111
|
-
|
|
110
|
+
const waitForAnimationFrame = (): {
|
|
111
|
+
promise: Promise<void>;
|
|
112
|
+
cancel: () => void;
|
|
113
|
+
} => {
|
|
114
|
+
let id: number;
|
|
115
|
+
const promise = new Promise<void>((resolve) => {
|
|
116
|
+
id = requestAnimationFrame(() => resolve());
|
|
117
|
+
});
|
|
118
|
+
return { promise, cancel: () => cancelAnimationFrame(id) };
|
|
119
|
+
};
|
|
112
120
|
|
|
113
121
|
/**
|
|
114
122
|
* Reads the rendered widths of all tooltip text elements via async `getBBox`.
|
|
115
123
|
* Defers until the next animation frame so layout has settled.
|
|
124
|
+
* Returns a cancel function so callers can abort the pending RAF.
|
|
116
125
|
*/
|
|
117
|
-
export const measureWidths =
|
|
126
|
+
export const measureWidths = (
|
|
118
127
|
items: ChartTooltipItemData[],
|
|
119
128
|
hasTitle: boolean,
|
|
120
129
|
titleRef: RefObject<SvgBBoxElement | null>,
|
|
121
130
|
labelRefs: RefObject<(SvgBBoxElement | null)[]>,
|
|
122
131
|
valueRefs: RefObject<(SvgBBoxElement | null)[]>,
|
|
123
|
-
): Promise<Widths
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
): { promise: Promise<Widths>; cancel: () => void } => {
|
|
133
|
+
const raf = waitForAnimationFrame();
|
|
134
|
+
const promise = raf.promise.then(async () => {
|
|
135
|
+
const title = hasTitle ? await safeGetBBoxWidth(titleRef.current) : 0;
|
|
136
|
+
const labels = await Promise.all(
|
|
137
|
+
items.map((_, i) => safeGetBBoxWidth(labelRefs.current[i])),
|
|
138
|
+
);
|
|
139
|
+
const values = await Promise.all(
|
|
140
|
+
items.map((_, i) => safeGetBBoxWidth(valueRefs.current[i])),
|
|
141
|
+
);
|
|
142
|
+
return { title, labels, values };
|
|
143
|
+
});
|
|
144
|
+
return { promise, cancel: raf.cancel };
|
|
133
145
|
};
|
|
134
146
|
|
|
135
147
|
/**
|
|
@@ -186,18 +198,20 @@ export function useTooltipMeasurement(
|
|
|
186
198
|
|
|
187
199
|
let cancelled = false;
|
|
188
200
|
|
|
189
|
-
|
|
201
|
+
const { promise, cancel } = measureWidths(
|
|
190
202
|
itemsRef.current,
|
|
191
203
|
hasTitleRef.current,
|
|
192
204
|
titleRef,
|
|
193
205
|
labelRefs,
|
|
194
206
|
valueRefs,
|
|
195
|
-
)
|
|
207
|
+
);
|
|
208
|
+
void promise.then((result) => {
|
|
196
209
|
if (!cancelled) setWidths(result);
|
|
197
210
|
});
|
|
198
211
|
|
|
199
212
|
return () => {
|
|
200
213
|
cancelled = true;
|
|
214
|
+
cancel();
|
|
201
215
|
};
|
|
202
216
|
}, [shapeKey]);
|
|
203
217
|
|