react-native-livechart 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +2 -0
  2. package/dist/components/LoadingOverlay.d.ts.map +1 -1
  3. package/dist/components/MarkerOverlay.d.ts.map +1 -1
  4. package/dist/components/MultiSeriesValueLines.d.ts.map +1 -1
  5. package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
  6. package/dist/components/ValueLineOverlay.d.ts.map +1 -1
  7. package/dist/components/XAxisOverlay.d.ts.map +1 -1
  8. package/dist/components/YAxisOverlay.d.ts.map +1 -1
  9. package/dist/hooks/useBadge.d.ts +2 -2
  10. package/dist/hooks/useBadge.d.ts.map +1 -1
  11. package/dist/hooks/useCandlePaths.d.ts +8 -10
  12. package/dist/hooks/useCandlePaths.d.ts.map +1 -1
  13. package/dist/hooks/useChartPaths.d.ts +8 -7
  14. package/dist/hooks/useChartPaths.d.ts.map +1 -1
  15. package/dist/hooks/useMultiSeriesLinePaths.d.ts +9 -8
  16. package/dist/hooks/useMultiSeriesLinePaths.d.ts.map +1 -1
  17. package/dist/hooks/usePathBuilder.d.ts +27 -0
  18. package/dist/hooks/usePathBuilder.d.ts.map +1 -0
  19. package/dist/math/spline.d.ts +10 -2
  20. package/dist/math/spline.d.ts.map +1 -1
  21. package/package.json +2 -2
  22. package/src/components/LoadingOverlay.tsx +15 -29
  23. package/src/components/MarkerOverlay.tsx +23 -28
  24. package/src/components/MultiSeriesValueLines.tsx +20 -40
  25. package/src/components/ReferenceLineOverlay.tsx +65 -107
  26. package/src/components/ValueLineOverlay.tsx +9 -28
  27. package/src/components/XAxisOverlay.tsx +9 -29
  28. package/src/components/YAxisOverlay.tsx +6 -22
  29. package/src/draw/markerAtlas.ts +9 -9
  30. package/src/hooks/useBadge.ts +17 -32
  31. package/src/hooks/useCandlePaths.ts +24 -63
  32. package/src/hooks/useChartPaths.ts +27 -39
  33. package/src/hooks/useMultiSeriesLinePaths.ts +26 -28
  34. package/src/hooks/usePathBuilder.ts +42 -0
  35. package/src/math/spline.ts +22 -2
@@ -3,16 +3,14 @@ import {
3
3
  Group,
4
4
  Path,
5
5
  RoundedRect,
6
- Skia,
7
6
  Text as SkiaText,
8
7
  type SkFont,
9
- type SkPath,
10
8
  } from "@shopify/react-native-skia";
11
- import { useRef } from "react";
12
9
  import { useDerivedValue } from "react-native-reanimated";
13
10
 
14
11
  import type { ChartEngineLayout } from "../core/useLiveChartEngine";
15
12
  import type { ChartPadding } from "../draw/line";
13
+ import { usePathBuilder } from "../hooks/usePathBuilder";
16
14
  import { useReferenceLine } from "../hooks/useReferenceLine";
17
15
  import { measureFontTextWidth } from "../lib/measureFontTextWidth";
18
16
  import { referenceLineForm } from "../math/referenceLines";
@@ -65,131 +63,91 @@ export function ReferenceLineOverlay({
65
63
  const badgeBorderColor = line.badgeBorderColor ?? color;
66
64
  const badgeRadius = line.badgeRadius ?? OFF_AXIS_PILL_RADIUS;
67
65
 
68
- const cacheRef = useRef<{
69
- lineA: SkPath;
70
- lineB: SkPath;
71
- lineT: boolean;
72
- bandA: SkPath;
73
- bandB: SkPath;
74
- bandT: boolean;
75
- borderA: SkPath;
76
- borderB: SkPath;
77
- borderT: boolean;
78
- offA: SkPath;
79
- offB: SkPath;
80
- offT: boolean;
81
- chevA: SkPath;
82
- chevB: SkPath;
83
- chevT: boolean;
84
- } | null>(null);
85
- if (cacheRef.current === null) {
86
- cacheRef.current = {
87
- lineA: Skia.Path.Make(),
88
- lineB: Skia.Path.Make(),
89
- lineT: false,
90
- bandA: Skia.Path.Make(),
91
- bandB: Skia.Path.Make(),
92
- bandT: false,
93
- borderA: Skia.Path.Make(),
94
- borderB: Skia.Path.Make(),
95
- borderT: false,
96
- offA: Skia.Path.Make(),
97
- offB: Skia.Path.Make(),
98
- offT: false,
99
- chevA: Skia.Path.Make(),
100
- chevB: Skia.Path.Make(),
101
- chevT: false,
102
- };
103
- }
66
+ const lineBuilder = usePathBuilder();
67
+ const bandBuilder = usePathBuilder();
68
+ const borderBuilder = usePathBuilder();
69
+ const offBuilder = usePathBuilder();
70
+ const chevBuilder = usePathBuilder();
104
71
 
105
72
  const linePath = useDerivedValue(() => {
106
- const cache = cacheRef.current!;
107
- cache.lineT = !cache.lineT;
108
- const p = cache.lineT ? cache.lineA : cache.lineB;
109
- p.reset();
73
+ const b = lineBuilder.value;
110
74
  const l = layout.get();
111
- if (!l.visible || l.offAxis || isBand) return p;
112
- p.moveTo(l.x1, l.y);
113
- p.lineTo(l.x2, l.y);
114
- return p;
75
+ if (l.visible && !l.offAxis && !isBand) {
76
+ b.moveTo(l.x1, l.y);
77
+ b.lineTo(l.x2, l.y);
78
+ }
79
+ return b.detach();
115
80
  });
116
81
 
117
82
  const bandPath = useDerivedValue(() => {
118
- const cache = cacheRef.current!;
119
- cache.bandT = !cache.bandT;
120
- const p = cache.bandT ? cache.bandA : cache.bandB;
121
- p.reset();
83
+ const b = bandBuilder.value;
122
84
  const l = layout.get();
123
- if (!l.visible || !isBand) return p;
124
- p.moveTo(l.x1, l.y);
125
- p.lineTo(l.x2, l.y);
126
- p.lineTo(l.x2, l.yBottom);
127
- p.lineTo(l.x1, l.yBottom);
128
- p.close();
129
- return p;
85
+ if (l.visible && isBand) {
86
+ b.moveTo(l.x1, l.y);
87
+ b.lineTo(l.x2, l.y);
88
+ b.lineTo(l.x2, l.yBottom);
89
+ b.lineTo(l.x1, l.yBottom);
90
+ b.close();
91
+ }
92
+ return b.detach();
130
93
  });
131
94
 
132
95
  const bandBorderPath = useDerivedValue(() => {
133
- const cache = cacheRef.current!;
134
- cache.borderT = !cache.borderT;
135
- const p = cache.borderT ? cache.borderA : cache.borderB;
136
- p.reset();
96
+ const b = borderBuilder.value;
137
97
  const l = layout.get();
138
- if (!l.visible || !hasBandBorder) return p;
139
- if (form === "time-band") {
140
- // Vertical edges at the band's left / right.
141
- p.moveTo(l.x1, l.y);
142
- p.lineTo(l.x1, l.yBottom);
143
- p.moveTo(l.x2, l.y);
144
- p.lineTo(l.x2, l.yBottom);
145
- } else {
146
- // Horizontal edges at the band's top / bottom.
147
- p.moveTo(l.x1, l.y);
148
- p.lineTo(l.x2, l.y);
149
- p.moveTo(l.x1, l.yBottom);
150
- p.lineTo(l.x2, l.yBottom);
98
+ if (l.visible && hasBandBorder) {
99
+ if (form === "time-band") {
100
+ // Vertical edges at the band's left / right.
101
+ b.moveTo(l.x1, l.y);
102
+ b.lineTo(l.x1, l.yBottom);
103
+ b.moveTo(l.x2, l.y);
104
+ b.lineTo(l.x2, l.yBottom);
105
+ } else {
106
+ // Horizontal edges at the band's top / bottom.
107
+ b.moveTo(l.x1, l.y);
108
+ b.lineTo(l.x2, l.y);
109
+ b.moveTo(l.x1, l.yBottom);
110
+ b.lineTo(l.x2, l.yBottom);
111
+ }
151
112
  }
152
- return p;
113
+ return b.detach();
153
114
  });
154
115
 
155
116
  const offLinePath = useDerivedValue(() => {
156
- const cache = cacheRef.current!;
157
- cache.offT = !cache.offT;
158
- const p = cache.offT ? cache.offA : cache.offB;
159
- p.reset();
117
+ const b = offBuilder.value;
160
118
  const l = layout.get();
161
- if (!l.visible || !l.offAxis) return p;
162
- // Start the connector just past the badge pill's right edge so the dashed
163
- // line runs out to the chart edge rather than behind the badge.
164
- const pillRight =
165
- l.labelX + measureFontTextWidth(font, l.label) + OFF_AXIS_PILL_PAD_X;
166
- const start = pillRight + 4;
167
- if (start >= l.x2) return p;
168
- p.moveTo(start, l.y);
169
- p.lineTo(l.x2, l.y);
170
- return p;
119
+ if (l.visible && l.offAxis) {
120
+ // Start the connector just past the badge pill's right edge so the dashed
121
+ // line runs out to the chart edge rather than behind the badge.
122
+ const pillRight =
123
+ l.labelX + measureFontTextWidth(font, l.label) + OFF_AXIS_PILL_PAD_X;
124
+ const start = pillRight + 4;
125
+ if (start < l.x2) {
126
+ b.moveTo(start, l.y);
127
+ b.lineTo(l.x2, l.y);
128
+ }
129
+ }
130
+ return b.detach();
171
131
  });
172
132
 
173
133
  const chevronPath = useDerivedValue(() => {
174
- const cache = cacheRef.current!;
175
- cache.chevT = !cache.chevT;
176
- const p = cache.chevT ? cache.chevA : cache.chevB;
177
- p.reset();
134
+ const b = chevBuilder.value;
178
135
  const l = layout.get();
179
- if (!l.visible || !l.offAxis) return p;
180
- const cx = l.x1 + 6;
181
- const cy = l.y;
182
- const s = 4;
183
- if (l.chevronUp) {
184
- p.moveTo(cx - s, cy + s);
185
- p.lineTo(cx, cy - s);
186
- p.lineTo(cx + s, cy + s);
187
- } else {
188
- p.moveTo(cx - s, cy - s);
189
- p.lineTo(cx, cy + s);
190
- p.lineTo(cx + s, cy - s);
136
+ if (l.visible && l.offAxis) {
137
+ const cx = l.x1 + 6;
138
+ const cy = l.y;
139
+ const s = 4;
140
+ if (l.chevronUp) {
141
+ b.moveTo(cx - s, cy + s);
142
+ b.lineTo(cx, cy - s);
143
+ b.lineTo(cx + s, cy + s);
144
+ } else {
145
+ b.moveTo(cx - s, cy - s);
146
+ b.lineTo(cx, cy + s);
147
+ b.lineTo(cx + s, cy - s);
148
+ }
191
149
  }
192
- return p;
150
+ return b.detach();
193
151
  });
194
152
 
195
153
  const lineOpacity = useDerivedValue(() =>
@@ -1,14 +1,9 @@
1
- import {
2
- DashPathEffect,
3
- Path,
4
- Skia,
5
- type SkPath,
6
- } from "@shopify/react-native-skia";
7
- import { useRef } from "react";
1
+ import { DashPathEffect, Path } from "@shopify/react-native-skia";
8
2
  import { useDerivedValue, type SharedValue } from "react-native-reanimated";
9
3
 
10
4
  import type { ChartPadding } from "../draw/line";
11
5
  import type { ChartEngineLayout } from "../core/useLiveChartEngine";
6
+ import { usePathBuilder } from "../hooks/usePathBuilder";
12
7
 
13
8
  /**
14
9
  * A dashed horizontal line that tracks the live display value — sitting at
@@ -29,30 +24,16 @@ export function ValueLineOverlay({
29
24
  intervals: [number, number];
30
25
  color: string;
31
26
  }) {
32
- // Ping-pong persistent paths — avoid allocating a JSI-backed SkPath per frame.
33
- const cacheRef = useRef<{
34
- a: SkPath;
35
- b: SkPath;
36
- tick: boolean;
37
- } | null>(null);
38
- if (cacheRef.current === null) {
39
- cacheRef.current = {
40
- a: Skia.Path.Make(),
41
- b: Skia.Path.Make(),
42
- tick: false,
43
- };
44
- }
27
+ const builder = usePathBuilder();
45
28
 
46
29
  const path = useDerivedValue(() => {
47
- const cache = cacheRef.current!;
48
- cache.tick = !cache.tick;
49
- const p = cache.tick ? cache.a : cache.b;
50
- p.reset();
30
+ const b = builder.value;
51
31
  const y = dotY.get();
52
- if (y < 0) return p;
53
- p.moveTo(padding.left, y);
54
- p.lineTo(engine.canvasWidth.get() - padding.right, y);
55
- return p;
32
+ if (y >= 0) {
33
+ b.moveTo(padding.left, y);
34
+ b.lineTo(engine.canvasWidth.get() - padding.right, y);
35
+ }
36
+ return b.detach();
56
37
  });
57
38
 
58
39
  return (
@@ -1,15 +1,9 @@
1
- import {
2
- Group,
3
- Path,
4
- Skia,
5
- type SkFont,
6
- type SkPath,
7
- } from "@shopify/react-native-skia";
8
- import { useRef } from "react";
1
+ import { Group, Path, type SkFont } from "@shopify/react-native-skia";
9
2
  import { useDerivedValue, type SharedValue } from "react-native-reanimated";
10
3
  import type { ChartEngineLayout } from "../core/useLiveChartEngine";
11
4
  import type { ChartPadding } from "../draw/line";
12
5
  import type { XAxisEntry } from "../hooks/useXAxis";
6
+ import { usePathBuilder } from "../hooks/usePathBuilder";
13
7
  import { measureFontTextWidth } from "../lib/measureFontTextWidth";
14
8
  import type { LiveChartPalette } from "../types";
15
9
  import { AnimatedLabel } from "./AnimatedLabel";
@@ -31,40 +25,26 @@ export function XAxisOverlay({
31
25
  palette: LiveChartPalette;
32
26
  font: SkFont;
33
27
  }) {
34
- const axisCacheRef = useRef<{
35
- a: SkPath;
36
- b: SkPath;
37
- tick: boolean;
38
- } | null>(null);
39
- if (axisCacheRef.current === null) {
40
- axisCacheRef.current = {
41
- a: Skia.Path.Make(),
42
- b: Skia.Path.Make(),
43
- tick: false,
44
- };
45
- }
28
+ const axisBuilder = usePathBuilder();
46
29
 
47
30
  const axisPath = useDerivedValue(() => {
48
31
  "worklet";
49
- const axisCache = axisCacheRef.current!;
50
- axisCache.tick = !axisCache.tick;
51
- const path = axisCache.tick ? axisCache.a : axisCache.b;
52
- path.reset();
32
+ const b = axisBuilder.value;
53
33
  const w = engine.canvasWidth.get();
54
34
  const h = engine.canvasHeight.get();
55
35
  const lineY = h - padding.bottom;
56
36
 
57
37
  // Bottom axis line
58
- path.moveTo(padding.left, lineY);
59
- path.lineTo(w - padding.right, lineY);
38
+ b.moveTo(padding.left, lineY);
39
+ b.lineTo(w - padding.right, lineY);
60
40
 
61
41
  // Tick marks
62
42
  const items = entries.get();
63
43
  for (let i = 0; i < items.length; i++) {
64
- path.moveTo(items[i].x, lineY);
65
- path.lineTo(items[i].x, lineY + TICK_HEIGHT);
44
+ b.moveTo(items[i].x, lineY);
45
+ b.lineTo(items[i].x, lineY + TICK_HEIGHT);
66
46
  }
67
- return path;
47
+ return b.detach();
68
48
  });
69
49
 
70
50
  // Transform XAxisEntry[] into { x, y, label, alpha } for AnimatedLabel
@@ -2,11 +2,8 @@ import {
2
2
  DashPathEffect,
3
3
  Group,
4
4
  Path,
5
- Skia,
6
5
  type SkFont,
7
- type SkPath,
8
6
  } from "@shopify/react-native-skia";
9
- import { useRef } from "react";
10
7
  import { useDerivedValue, type SharedValue } from "react-native-reanimated";
11
8
  import { BADGE_METRICS_DEFAULTS } from "../constants";
12
9
  import type { ResolvedGridStyleConfig } from "../core/resolveConfig";
@@ -18,6 +15,7 @@ import {
18
15
  pillTextLeftX,
19
16
  type ChartPadding,
20
17
  } from "../draw/line";
18
+ import { usePathBuilder } from "../hooks/usePathBuilder";
21
19
  import { measureFontTextWidth } from "../lib/measureFontTextWidth";
22
20
  import type { BadgeMetrics, LiveChartPalette } from "../types";
23
21
  import type { ChartEngineLayout } from "../core/useLiveChartEngine";
@@ -57,31 +55,17 @@ export function YAxisOverlay({
57
55
  const gridWidth = gridStyle?.strokeWidth ?? 1;
58
56
  const gridIntervals = gridStyle?.intervals ?? [];
59
57
  const gridOpacity = gridStyle?.opacity ?? 1;
60
- const gridCacheRef = useRef<{
61
- a: SkPath;
62
- b: SkPath;
63
- tick: boolean;
64
- } | null>(null);
65
- if (gridCacheRef.current === null) {
66
- gridCacheRef.current = {
67
- a: Skia.Path.Make(),
68
- b: Skia.Path.Make(),
69
- tick: false,
70
- };
71
- }
58
+ const gridBuilder = usePathBuilder();
72
59
 
73
60
  const gridLinesPath = useDerivedValue(() => {
74
- const gridCache = gridCacheRef.current!;
75
- gridCache.tick = !gridCache.tick;
76
- const path = gridCache.tick ? gridCache.a : gridCache.b;
77
- path.reset();
61
+ const b = gridBuilder.value;
78
62
  const items = entries.get();
79
63
  const w = engine.canvasWidth.get();
80
64
  for (let i = 0; i < items.length; i++) {
81
- path.moveTo(padding.left, items[i].y);
82
- path.lineTo(w - padding.right, items[i].y);
65
+ b.moveTo(padding.left, items[i].y);
66
+ b.lineTo(w - padding.right, items[i].y);
83
67
  }
84
- return path;
68
+ return b.detach();
85
69
  });
86
70
 
87
71
  const leftInset =
@@ -207,17 +207,17 @@ function cellSpec(m: Marker, palette: LiveChartPalette, font: SkFont): CellSpec
207
207
  w: box,
208
208
  h: box,
209
209
  draw: (canvas, cx, cy) => {
210
- const p = Skia.Path.Make();
210
+ const pb = Skia.PathBuilder.Make();
211
211
  for (let k = 0; k < 10; k++) {
212
212
  const ang = -Math.PI / 2 + (k * Math.PI) / 5;
213
213
  const rad = k % 2 === 0 ? outer : inner;
214
214
  const px = cx + rad * Math.cos(ang);
215
215
  const py = cy + rad * Math.sin(ang);
216
- if (k === 0) p.moveTo(px, py);
217
- else p.lineTo(px, py);
216
+ if (k === 0) pb.moveTo(px, py);
217
+ else pb.lineTo(px, py);
218
218
  }
219
- p.close();
220
- canvas.drawPath(p, fillPaint(color));
219
+ pb.close();
220
+ canvas.drawPath(pb.detach(), fillPaint(color));
221
221
  },
222
222
  };
223
223
  }
@@ -229,15 +229,15 @@ function cellSpec(m: Marker, palette: LiveChartPalette, font: SkFont): CellSpec
229
229
  w: box,
230
230
  h: box,
231
231
  draw: (canvas, cx, cy) => {
232
- const p = Skia.Path.Make();
232
+ const pb = Skia.PathBuilder.Make();
233
233
  for (let k = 0; k < 4; k++) {
234
234
  const ang = (k * Math.PI) / 4;
235
235
  const dx = L * Math.cos(ang);
236
236
  const dy = L * Math.sin(ang);
237
- p.moveTo(cx - dx, cy - dy);
238
- p.lineTo(cx + dx, cy + dy);
237
+ pb.moveTo(cx - dx, cy - dy);
238
+ pb.lineTo(cx + dx, cy + dy);
239
239
  }
240
- canvas.drawPath(p, strokePaint(color, 1.5, true));
240
+ canvas.drawPath(pb.detach(), strokePaint(color, 1.5, true));
241
241
  },
242
242
  };
243
243
  }
@@ -1,5 +1,4 @@
1
- import { Skia, type SkFont, type SkPath } from "@shopify/react-native-skia";
2
- import { useRef } from "react";
1
+ import { type SkFont } from "@shopify/react-native-skia";
3
2
  import {
4
3
  useDerivedValue,
5
4
  useSharedValue,
@@ -25,6 +24,7 @@ import type {
25
24
  LiveChartPalette,
26
25
  Momentum,
27
26
  } from "../types";
27
+ import { usePathBuilder } from "./usePathBuilder";
28
28
 
29
29
  export function useBadge(
30
30
  engine: ChartEngineWithLiveValue,
@@ -48,32 +48,17 @@ export function useBadge(
48
48
  const downRgb = hexToRgb(palette.dotDown);
49
49
  const accentRgb = hexToRgb(palette.badgeBg);
50
50
 
51
- // Ping-pong between two persistent badge paths so the derived value always
52
- // returns a freshly-mutated SkPath without allocating a new one every frame.
53
- // See useChartPaths for the full rationale on per-frame Skia.Path.Make().
54
- const cacheRef = useRef<{
55
- a: SkPath;
56
- b: SkPath;
57
- tick: boolean;
58
- } | null>(null);
59
- if (cacheRef.current === null) {
60
- cacheRef.current = {
61
- a: Skia.Path.Make(),
62
- b: Skia.Path.Make(),
63
- tick: false,
64
- };
65
- }
51
+ // Pill path is built into a reused PathBuilder and finalized with detach()
52
+ // each frame a fresh immutable SkPath, no per-frame Skia.Path.Make().
53
+ const badgeBuilder = usePathBuilder();
66
54
 
67
55
  const badge = useDerivedValue(() => {
68
- const cache = cacheRef.current!;
56
+ const b = badgeBuilder.value;
69
57
  const w = engine.canvasWidth.get();
70
58
  const h = engine.canvasHeight.get();
71
- cache.tick = !cache.tick;
72
- const path = cache.tick ? cache.a : cache.b;
73
- path.reset();
74
59
  if (w === 0 || h === 0) {
75
60
  return {
76
- path,
61
+ path: b.detach(),
77
62
  textX: 0,
78
63
  textY: 0,
79
64
  text: "",
@@ -108,7 +93,7 @@ export function useBadge(
108
93
  const bodyLeft = Math.max(badgeMetrics.marginEdge, bodyRight - pillW);
109
94
  const pillBodyW = bodyRight - bodyLeft;
110
95
  textX = (bodyLeft + bodyRight - textW) / 2;
111
- path.addRRect({
96
+ b.addRRect({
112
97
  rect: { x: bodyLeft, y: badgeY, width: pillBodyW, height: pillH },
113
98
  rx: r,
114
99
  ry: r,
@@ -132,16 +117,16 @@ export function useBadge(
132
117
  const badgeX = w - padding.right + badgeMetrics.dotGap;
133
118
  const cx = tl + pillW - r;
134
119
 
135
- path.moveTo(badgeX + tl, badgeY);
136
- path.lineTo(badgeX + cx, badgeY);
137
- path.arcToOval(
120
+ b.moveTo(badgeX + tl, badgeY);
121
+ b.lineTo(badgeX + cx, badgeY);
122
+ b.arcToOval(
138
123
  { x: badgeX + cx - r, y: badgeY, width: r * 2, height: pillH },
139
124
  -90,
140
125
  180,
141
126
  false,
142
127
  );
143
- path.lineTo(badgeX + tl, badgeY + pillH);
144
- path.cubicTo(
128
+ b.lineTo(badgeX + tl, badgeY + pillH);
129
+ b.cubicTo(
145
130
  badgeX + badgeMetrics.tailLength + 2,
146
131
  badgeY + pillH,
147
132
  badgeX + 3,
@@ -149,7 +134,7 @@ export function useBadge(
149
134
  badgeX,
150
135
  badgeY + r,
151
136
  );
152
- path.cubicTo(
137
+ b.cubicTo(
153
138
  badgeX + 3,
154
139
  badgeY + r - badgeMetrics.tailSpread,
155
140
  badgeX + badgeMetrics.tailLength + 2,
@@ -157,9 +142,9 @@ export function useBadge(
157
142
  badgeX + tl,
158
143
  badgeY,
159
144
  );
160
- path.close();
145
+ b.close();
161
146
  } else {
162
- path.addRRect({
147
+ b.addRRect({
163
148
  rect: { x: bodyLeft, y: badgeY, width: pillW, height: pillH },
164
149
  rx: r,
165
150
  ry: r,
@@ -199,7 +184,7 @@ export function useBadge(
199
184
  const textColor =
200
185
  variant === "minimal" ? "rgba(100,100,100,1)" : palette.badgeText;
201
186
 
202
- return { path, textX, textY, text, bgColor, textColor };
187
+ return { path: b.detach(), textX, textY, text, bgColor, textColor };
203
188
  });
204
189
 
205
190
  return badge;