@cdx-ui/components 0.3.4 → 0.3.6

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 (287) hide show
  1. package/README.md +1 -1
  2. package/coverage.svg +1 -1
  3. package/docs/components/bar-chart/content.md +703 -0
  4. package/docs/components/content.md +9 -1
  5. package/docs/components/line-chart/content.md +567 -0
  6. package/docs/components/pie-chart/content.md +493 -0
  7. package/docs/components/progress-segmented/content.md +1 -1
  8. package/docs/components/select/content.md +144 -2
  9. package/docs/components/stepper/content.md +599 -0
  10. package/docs/components/system-alert/content.md +533 -0
  11. package/docs/foundations/expressions/icons/content.md +1 -1
  12. package/docs/foundations/styles/tokens/all-tokens/content.md +2 -1
  13. package/docs/llms.txt +10 -0
  14. package/docs/patterns/content.md +1 -0
  15. package/docs/patterns/dashboard/content.md +423 -19
  16. package/docs/patterns/insights/content.md +590 -0
  17. package/docs/resources/release-notes/0.3.4/content.md +27 -0
  18. package/docs/resources/release-notes/0.3.5/content.md +7 -0
  19. package/docs/resources/release-notes/content.md +2 -0
  20. package/lib/commonjs/components/BarChart/geometry.js +1130 -0
  21. package/lib/commonjs/components/BarChart/geometry.js.map +1 -0
  22. package/lib/commonjs/components/BarChart/index.js +942 -0
  23. package/lib/commonjs/components/BarChart/index.js.map +1 -0
  24. package/lib/commonjs/components/BarChart/styles.js +26 -0
  25. package/lib/commonjs/components/BarChart/styles.js.map +1 -0
  26. package/lib/commonjs/components/Button/index.js +2 -2
  27. package/lib/commonjs/components/Button/index.js.map +1 -1
  28. package/lib/commonjs/components/Chip/styles.js +3 -1
  29. package/lib/commonjs/components/Chip/styles.js.map +1 -1
  30. package/lib/commonjs/components/LineChart/geometry.js +698 -0
  31. package/lib/commonjs/components/LineChart/geometry.js.map +1 -0
  32. package/lib/commonjs/components/LineChart/index.js +747 -0
  33. package/lib/commonjs/components/LineChart/index.js.map +1 -0
  34. package/lib/commonjs/components/LineChart/styles.js +25 -0
  35. package/lib/commonjs/components/LineChart/styles.js.map +1 -0
  36. package/lib/commonjs/components/PieChart/geometry.js +216 -0
  37. package/lib/commonjs/components/PieChart/geometry.js.map +1 -0
  38. package/lib/commonjs/components/PieChart/index.js +476 -0
  39. package/lib/commonjs/components/PieChart/index.js.map +1 -0
  40. package/lib/commonjs/components/PieChart/styles.js +88 -0
  41. package/lib/commonjs/components/PieChart/styles.js.map +1 -0
  42. package/lib/commonjs/components/ProgressCircle/ProgressCircleRing.js +9 -31
  43. package/lib/commonjs/components/ProgressCircle/ProgressCircleRing.js.map +1 -1
  44. package/lib/commonjs/components/ProgressCircle/ProgressRing.js +52 -0
  45. package/lib/commonjs/components/ProgressCircle/ProgressRing.js.map +1 -0
  46. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js +8 -7
  47. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js.map +1 -1
  48. package/lib/commonjs/components/Stepper/index.js +369 -0
  49. package/lib/commonjs/components/Stepper/index.js.map +1 -0
  50. package/lib/commonjs/components/Stepper/styles.js +211 -0
  51. package/lib/commonjs/components/Stepper/styles.js.map +1 -0
  52. package/lib/commonjs/components/Stepper/useStepperLabelFit.js +22 -0
  53. package/lib/commonjs/components/Stepper/useStepperLabelFit.js.map +1 -0
  54. package/lib/commonjs/components/Stepper/useStepperLabelFit.web.js +52 -0
  55. package/lib/commonjs/components/Stepper/useStepperLabelFit.web.js.map +1 -0
  56. package/lib/commonjs/components/Stepper/useStepperRingColors.js +31 -0
  57. package/lib/commonjs/components/Stepper/useStepperRingColors.js.map +1 -0
  58. package/lib/commonjs/components/SystemAlert/animation.js +218 -0
  59. package/lib/commonjs/components/SystemAlert/animation.js.map +1 -0
  60. package/lib/commonjs/components/SystemAlert/index.js +525 -0
  61. package/lib/commonjs/components/SystemAlert/index.js.map +1 -0
  62. package/lib/commonjs/components/SystemAlert/styles.js +178 -0
  63. package/lib/commonjs/components/SystemAlert/styles.js.map +1 -0
  64. package/lib/commonjs/components/SystemAlert/useSystemAlertStatusBarStyle.js +53 -0
  65. package/lib/commonjs/components/SystemAlert/useSystemAlertStatusBarStyle.js.map +1 -0
  66. package/lib/commonjs/components/Tabs/index.js +4 -0
  67. package/lib/commonjs/components/Tabs/index.js.map +1 -1
  68. package/lib/commonjs/components/Tabs/styles.js +1 -1
  69. package/lib/commonjs/components/Tabs/styles.js.map +1 -1
  70. package/lib/commonjs/components/chart/ChartInspector.js +610 -0
  71. package/lib/commonjs/components/chart/ChartInspector.js.map +1 -0
  72. package/lib/commonjs/components/chart/format.js +93 -0
  73. package/lib/commonjs/components/chart/format.js.map +1 -0
  74. package/lib/commonjs/components/chart/index.js +2 -0
  75. package/lib/commonjs/components/chart/index.js.map +1 -0
  76. package/lib/commonjs/components/chart/motion.js +97 -0
  77. package/lib/commonjs/components/chart/motion.js.map +1 -0
  78. package/lib/commonjs/components/chart/palette.js +178 -0
  79. package/lib/commonjs/components/chart/palette.js.map +1 -0
  80. package/lib/commonjs/components/chart/size.js +34 -0
  81. package/lib/commonjs/components/chart/size.js.map +1 -0
  82. package/lib/commonjs/components/chart/ticks.js +59 -0
  83. package/lib/commonjs/components/chart/ticks.js.map +1 -0
  84. package/lib/commonjs/components/chart/types.js +6 -0
  85. package/lib/commonjs/components/chart/types.js.map +1 -0
  86. package/lib/commonjs/components/chart/useChartHostWidth.js +27 -0
  87. package/lib/commonjs/components/chart/useChartHostWidth.js.map +1 -0
  88. package/lib/commonjs/components/index.js +60 -0
  89. package/lib/commonjs/components/index.js.map +1 -1
  90. package/lib/commonjs/figma/SystemAlert.Button.figma.js +28 -0
  91. package/lib/commonjs/figma/SystemAlert.Button.figma.js.map +1 -0
  92. package/lib/commonjs/figma/SystemAlert.Close.figma.js +22 -0
  93. package/lib/commonjs/figma/SystemAlert.Close.figma.js.map +1 -0
  94. package/lib/commonjs/figma/SystemAlert.Description.figma.js +20 -0
  95. package/lib/commonjs/figma/SystemAlert.Description.figma.js.map +1 -0
  96. package/lib/commonjs/figma/SystemAlert.IconButton.figma.js +31 -0
  97. package/lib/commonjs/figma/SystemAlert.IconButton.figma.js.map +1 -0
  98. package/lib/commonjs/figma/SystemAlert.figma.js +48 -0
  99. package/lib/commonjs/figma/SystemAlert.figma.js.map +1 -0
  100. package/lib/module/components/BarChart/geometry.js +1110 -0
  101. package/lib/module/components/BarChart/geometry.js.map +1 -0
  102. package/lib/module/components/BarChart/index.js +936 -0
  103. package/lib/module/components/BarChart/index.js.map +1 -0
  104. package/lib/module/components/BarChart/styles.js +22 -0
  105. package/lib/module/components/BarChart/styles.js.map +1 -0
  106. package/lib/module/components/Button/index.js +2 -2
  107. package/lib/module/components/Button/index.js.map +1 -1
  108. package/lib/module/components/Chip/styles.js +3 -1
  109. package/lib/module/components/Chip/styles.js.map +1 -1
  110. package/lib/module/components/LineChart/geometry.js +685 -0
  111. package/lib/module/components/LineChart/geometry.js.map +1 -0
  112. package/lib/module/components/LineChart/index.js +741 -0
  113. package/lib/module/components/LineChart/index.js.map +1 -0
  114. package/lib/module/components/LineChart/styles.js +21 -0
  115. package/lib/module/components/LineChart/styles.js.map +1 -0
  116. package/lib/module/components/PieChart/geometry.js +209 -0
  117. package/lib/module/components/PieChart/geometry.js.map +1 -0
  118. package/lib/module/components/PieChart/index.js +471 -0
  119. package/lib/module/components/PieChart/index.js.map +1 -0
  120. package/lib/module/components/PieChart/styles.js +84 -0
  121. package/lib/module/components/PieChart/styles.js.map +1 -0
  122. package/lib/module/components/ProgressCircle/ProgressCircleRing.js +10 -32
  123. package/lib/module/components/ProgressCircle/ProgressCircleRing.js.map +1 -1
  124. package/lib/module/components/ProgressCircle/ProgressRing.js +48 -0
  125. package/lib/module/components/ProgressCircle/ProgressRing.js.map +1 -0
  126. package/lib/module/components/ScrollShadow/useScrollShadow.js +8 -7
  127. package/lib/module/components/ScrollShadow/useScrollShadow.js.map +1 -1
  128. package/lib/module/components/Stepper/index.js +360 -0
  129. package/lib/module/components/Stepper/index.js.map +1 -0
  130. package/lib/module/components/Stepper/styles.js +206 -0
  131. package/lib/module/components/Stepper/styles.js.map +1 -0
  132. package/lib/module/components/Stepper/useStepperLabelFit.js +18 -0
  133. package/lib/module/components/Stepper/useStepperLabelFit.js.map +1 -0
  134. package/lib/module/components/Stepper/useStepperLabelFit.web.js +48 -0
  135. package/lib/module/components/Stepper/useStepperLabelFit.web.js.map +1 -0
  136. package/lib/module/components/Stepper/useStepperRingColors.js +28 -0
  137. package/lib/module/components/Stepper/useStepperRingColors.js.map +1 -0
  138. package/lib/module/components/SystemAlert/animation.js +213 -0
  139. package/lib/module/components/SystemAlert/animation.js.map +1 -0
  140. package/lib/module/components/SystemAlert/index.js +515 -0
  141. package/lib/module/components/SystemAlert/index.js.map +1 -0
  142. package/lib/module/components/SystemAlert/styles.js +174 -0
  143. package/lib/module/components/SystemAlert/styles.js.map +1 -0
  144. package/lib/module/components/SystemAlert/useSystemAlertStatusBarStyle.js +49 -0
  145. package/lib/module/components/SystemAlert/useSystemAlertStatusBarStyle.js.map +1 -0
  146. package/lib/module/components/Tabs/index.js +4 -0
  147. package/lib/module/components/Tabs/index.js.map +1 -1
  148. package/lib/module/components/Tabs/styles.js +1 -1
  149. package/lib/module/components/Tabs/styles.js.map +1 -1
  150. package/lib/module/components/chart/ChartInspector.js +600 -0
  151. package/lib/module/components/chart/ChartInspector.js.map +1 -0
  152. package/lib/module/components/chart/format.js +84 -0
  153. package/lib/module/components/chart/format.js.map +1 -0
  154. package/lib/module/components/chart/index.js +2 -0
  155. package/lib/module/components/chart/index.js.map +1 -0
  156. package/lib/module/components/chart/motion.js +89 -0
  157. package/lib/module/components/chart/motion.js.map +1 -0
  158. package/lib/module/components/chart/palette.js +168 -0
  159. package/lib/module/components/chart/palette.js.map +1 -0
  160. package/lib/module/components/chart/size.js +29 -0
  161. package/lib/module/components/chart/size.js.map +1 -0
  162. package/lib/module/components/chart/ticks.js +55 -0
  163. package/lib/module/components/chart/ticks.js.map +1 -0
  164. package/lib/module/components/chart/types.js +4 -0
  165. package/lib/module/components/chart/types.js.map +1 -0
  166. package/lib/module/components/chart/useChartHostWidth.js +23 -0
  167. package/lib/module/components/chart/useChartHostWidth.js.map +1 -0
  168. package/lib/module/components/index.js +5 -0
  169. package/lib/module/components/index.js.map +1 -1
  170. package/lib/module/figma/SystemAlert.Button.figma.js +22 -0
  171. package/lib/module/figma/SystemAlert.Button.figma.js.map +1 -0
  172. package/lib/module/figma/SystemAlert.Close.figma.js +18 -0
  173. package/lib/module/figma/SystemAlert.Close.figma.js.map +1 -0
  174. package/lib/module/figma/SystemAlert.Description.figma.js +14 -0
  175. package/lib/module/figma/SystemAlert.Description.figma.js.map +1 -0
  176. package/lib/module/figma/SystemAlert.IconButton.figma.js +25 -0
  177. package/lib/module/figma/SystemAlert.IconButton.figma.js.map +1 -0
  178. package/lib/module/figma/SystemAlert.figma.js +42 -0
  179. package/lib/module/figma/SystemAlert.figma.js.map +1 -0
  180. package/lib/typescript/components/BarChart/geometry.d.ts +236 -0
  181. package/lib/typescript/components/BarChart/geometry.d.ts.map +1 -0
  182. package/lib/typescript/components/BarChart/index.d.ts +193 -0
  183. package/lib/typescript/components/BarChart/index.d.ts.map +1 -0
  184. package/lib/typescript/components/BarChart/styles.d.ts +32 -0
  185. package/lib/typescript/components/BarChart/styles.d.ts.map +1 -0
  186. package/lib/typescript/components/Chip/styles.d.ts.map +1 -1
  187. package/lib/typescript/components/LineChart/geometry.d.ts +248 -0
  188. package/lib/typescript/components/LineChart/geometry.d.ts.map +1 -0
  189. package/lib/typescript/components/LineChart/index.d.ts +105 -0
  190. package/lib/typescript/components/LineChart/index.d.ts.map +1 -0
  191. package/lib/typescript/components/LineChart/styles.d.ts +12 -0
  192. package/lib/typescript/components/LineChart/styles.d.ts.map +1 -0
  193. package/lib/typescript/components/PieChart/geometry.d.ts +43 -0
  194. package/lib/typescript/components/PieChart/geometry.d.ts.map +1 -0
  195. package/lib/typescript/components/PieChart/index.d.ts +147 -0
  196. package/lib/typescript/components/PieChart/index.d.ts.map +1 -0
  197. package/lib/typescript/components/PieChart/styles.d.ts +27 -0
  198. package/lib/typescript/components/PieChart/styles.d.ts.map +1 -0
  199. package/lib/typescript/components/ProgressCircle/ProgressCircleRing.d.ts.map +1 -1
  200. package/lib/typescript/components/ProgressCircle/ProgressRing.d.ts +17 -0
  201. package/lib/typescript/components/ProgressCircle/ProgressRing.d.ts.map +1 -0
  202. package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts.map +1 -1
  203. package/lib/typescript/components/Stepper/index.d.ts +68 -0
  204. package/lib/typescript/components/Stepper/index.d.ts.map +1 -0
  205. package/lib/typescript/components/Stepper/styles.d.ts +46 -0
  206. package/lib/typescript/components/Stepper/styles.d.ts.map +1 -0
  207. package/lib/typescript/components/Stepper/useStepperLabelFit.d.ts +15 -0
  208. package/lib/typescript/components/Stepper/useStepperLabelFit.d.ts.map +1 -0
  209. package/lib/typescript/components/Stepper/useStepperLabelFit.web.d.ts +10 -0
  210. package/lib/typescript/components/Stepper/useStepperLabelFit.web.d.ts.map +1 -0
  211. package/lib/typescript/components/Stepper/useStepperRingColors.d.ts +6 -0
  212. package/lib/typescript/components/Stepper/useStepperRingColors.d.ts.map +1 -0
  213. package/lib/typescript/components/SystemAlert/animation.d.ts +56 -0
  214. package/lib/typescript/components/SystemAlert/animation.d.ts.map +1 -0
  215. package/lib/typescript/components/SystemAlert/index.d.ts +110 -0
  216. package/lib/typescript/components/SystemAlert/index.d.ts.map +1 -0
  217. package/lib/typescript/components/SystemAlert/styles.d.ts +38 -0
  218. package/lib/typescript/components/SystemAlert/styles.d.ts.map +1 -0
  219. package/lib/typescript/components/SystemAlert/useSystemAlertStatusBarStyle.d.ts +14 -0
  220. package/lib/typescript/components/SystemAlert/useSystemAlertStatusBarStyle.d.ts.map +1 -0
  221. package/lib/typescript/components/Tabs/index.d.ts.map +1 -1
  222. package/lib/typescript/components/chart/ChartInspector.d.ts +101 -0
  223. package/lib/typescript/components/chart/ChartInspector.d.ts.map +1 -0
  224. package/lib/typescript/components/chart/format.d.ts +16 -0
  225. package/lib/typescript/components/chart/format.d.ts.map +1 -0
  226. package/lib/typescript/components/chart/index.d.ts +3 -0
  227. package/lib/typescript/components/chart/index.d.ts.map +1 -0
  228. package/lib/typescript/components/chart/motion.d.ts +27 -0
  229. package/lib/typescript/components/chart/motion.d.ts.map +1 -0
  230. package/lib/typescript/components/chart/palette.d.ts +44 -0
  231. package/lib/typescript/components/chart/palette.d.ts.map +1 -0
  232. package/lib/typescript/components/chart/size.d.ts +25 -0
  233. package/lib/typescript/components/chart/size.d.ts.map +1 -0
  234. package/lib/typescript/components/chart/ticks.d.ts +3 -0
  235. package/lib/typescript/components/chart/ticks.d.ts.map +1 -0
  236. package/lib/typescript/components/chart/types.d.ts +163 -0
  237. package/lib/typescript/components/chart/types.d.ts.map +1 -0
  238. package/lib/typescript/components/chart/useChartHostWidth.d.ts +9 -0
  239. package/lib/typescript/components/chart/useChartHostWidth.d.ts.map +1 -0
  240. package/lib/typescript/components/index.d.ts +5 -0
  241. package/lib/typescript/components/index.d.ts.map +1 -1
  242. package/lib/typescript/figma/SystemAlert.Button.figma.d.ts +11 -0
  243. package/lib/typescript/figma/SystemAlert.Button.figma.d.ts.map +1 -0
  244. package/lib/typescript/figma/SystemAlert.Close.figma.d.ts +11 -0
  245. package/lib/typescript/figma/SystemAlert.Close.figma.d.ts.map +1 -0
  246. package/lib/typescript/figma/SystemAlert.Description.figma.d.ts +8 -0
  247. package/lib/typescript/figma/SystemAlert.Description.figma.d.ts.map +1 -0
  248. package/lib/typescript/figma/SystemAlert.IconButton.figma.d.ts +11 -0
  249. package/lib/typescript/figma/SystemAlert.IconButton.figma.d.ts.map +1 -0
  250. package/lib/typescript/figma/SystemAlert.figma.d.ts +8 -0
  251. package/lib/typescript/figma/SystemAlert.figma.d.ts.map +1 -0
  252. package/package.json +5 -5
  253. package/src/components/BarChart/geometry.ts +1469 -0
  254. package/src/components/BarChart/index.tsx +1424 -0
  255. package/src/components/BarChart/styles.ts +31 -0
  256. package/src/components/Button/index.tsx +1 -1
  257. package/src/components/Chip/styles.ts +3 -1
  258. package/src/components/LineChart/geometry.ts +957 -0
  259. package/src/components/LineChart/index.tsx +1109 -0
  260. package/src/components/LineChart/styles.ts +26 -0
  261. package/src/components/PieChart/geometry.ts +295 -0
  262. package/src/components/PieChart/index.tsx +715 -0
  263. package/src/components/PieChart/styles.ts +101 -0
  264. package/src/components/ProgressCircle/ProgressCircleRing.tsx +10 -39
  265. package/src/components/ProgressCircle/ProgressRing.tsx +60 -0
  266. package/src/components/ScrollShadow/useScrollShadow.ts +12 -11
  267. package/src/components/Stepper/index.tsx +452 -0
  268. package/src/components/Stepper/styles.ts +234 -0
  269. package/src/components/Stepper/useStepperLabelFit.ts +21 -0
  270. package/src/components/Stepper/useStepperLabelFit.web.ts +53 -0
  271. package/src/components/Stepper/useStepperRingColors.ts +29 -0
  272. package/src/components/SystemAlert/animation.ts +252 -0
  273. package/src/components/SystemAlert/index.tsx +684 -0
  274. package/src/components/SystemAlert/styles.ts +212 -0
  275. package/src/components/SystemAlert/useSystemAlertStatusBarStyle.ts +57 -0
  276. package/src/components/Tabs/index.tsx +4 -0
  277. package/src/components/Tabs/styles.ts +1 -1
  278. package/src/components/chart/ChartInspector.tsx +726 -0
  279. package/src/components/chart/format.ts +95 -0
  280. package/src/components/chart/index.ts +24 -0
  281. package/src/components/chart/motion.ts +98 -0
  282. package/src/components/chart/palette.tsx +247 -0
  283. package/src/components/chart/size.ts +21 -0
  284. package/src/components/chart/ticks.ts +53 -0
  285. package/src/components/chart/types.ts +183 -0
  286. package/src/components/chart/useChartHostWidth.ts +26 -0
  287. package/src/components/index.ts +5 -0
@@ -0,0 +1,726 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { PanResponder, Platform, Pressable, Text, View } from 'react-native';
3
+ import { dataAttributes } from '@cdx-ui/primitives';
4
+ import type { ChartTooltipContent, ChartTooltipItem } from './types';
5
+
6
+ export interface ChartInspectorTarget {
7
+ readonly key: string;
8
+ /** Hit box in overlay coordinates. */
9
+ readonly left: number;
10
+ readonly top: number;
11
+ readonly width: number;
12
+ readonly height: number;
13
+ /** Point the tooltip sits beside. */
14
+ readonly anchorX: number;
15
+ readonly anchorY: number;
16
+ /** Half-width of the mark at the anchor, so the tooltip clears it. */
17
+ readonly anchorRadius?: number;
18
+ readonly tooltip: ChartTooltipContent;
19
+ readonly accessibilityLabel: string;
20
+ readonly selected: boolean;
21
+ /** Keyboard focus ring. Defaults to the hit box. */
22
+ readonly outline?: {
23
+ readonly left: number;
24
+ readonly top: number;
25
+ readonly width: number;
26
+ readonly height: number;
27
+ };
28
+ }
29
+
30
+ interface InspectorKeyEvent {
31
+ readonly nativeEvent: { readonly key: string; readonly shiftKey?: boolean };
32
+ readonly key?: string;
33
+ readonly shiftKey?: boolean;
34
+ readonly preventDefault?: () => void;
35
+ }
36
+
37
+ interface InspectorBlurEvent {
38
+ nativeEvent: { relatedTarget?: unknown };
39
+ relatedTarget?: unknown;
40
+ }
41
+
42
+ const TOOLTIP_GAP = 8;
43
+ const TOOLTIP_MAX_WIDTH = 192;
44
+ /** WCAG 2.5.8 (AA) is 24px; 44px matches Apple HIG / WCAG 2.5.5 for touch. */
45
+ export const MIN_POINTER_TARGET_PX = 44;
46
+ /** Hold before scrubbing a scrollable plot, so a flick can still scroll (Apple Health). */
47
+ export const CHART_SCRUB_HOLD_MS = 250;
48
+ const SCRUB_MOVE_SLOP_PX = 10;
49
+
50
+ interface InspectorTouchEvent {
51
+ nativeEvent: {
52
+ locationX?: number;
53
+ locationY?: number;
54
+ pageX?: number;
55
+ pageY?: number;
56
+ touches?: readonly {
57
+ locationX?: number;
58
+ locationY?: number;
59
+ pageX?: number;
60
+ pageY?: number;
61
+ }[];
62
+ };
63
+ }
64
+
65
+ function touchPoint(event: InspectorTouchEvent): {
66
+ locationX: number;
67
+ locationY: number;
68
+ pageX: number;
69
+ pageY: number;
70
+ } {
71
+ const touch = event.nativeEvent.touches?.[0];
72
+ const locationX = touch?.locationX ?? event.nativeEvent.locationX ?? 0;
73
+ const locationY = touch?.locationY ?? event.nativeEvent.locationY ?? 0;
74
+ return {
75
+ locationX,
76
+ locationY,
77
+ pageX: touch?.pageX ?? event.nativeEvent.pageX ?? locationX,
78
+ pageY: touch?.pageY ?? event.nativeEvent.pageY ?? locationY,
79
+ };
80
+ }
81
+
82
+ export function expandHitBox(
83
+ left: number,
84
+ top: number,
85
+ width: number,
86
+ height: number,
87
+ minSize: number = MIN_POINTER_TARGET_PX,
88
+ ): { left: number; top: number; width: number; height: number } {
89
+ const nextWidth = Math.max(width, minSize);
90
+ const nextHeight = Math.max(height, minSize);
91
+ return {
92
+ left: left - (nextWidth - width) / 2,
93
+ top: top - (nextHeight - height) / 2,
94
+ width: nextWidth,
95
+ height: nextHeight,
96
+ };
97
+ }
98
+
99
+ function targetIndexAt(
100
+ targets: readonly ChartInspectorTarget[],
101
+ x: number,
102
+ y: number,
103
+ ): number | null {
104
+ const hit = targets.findIndex(
105
+ (target) =>
106
+ x >= target.left &&
107
+ x <= target.left + target.width &&
108
+ y >= target.top &&
109
+ y <= target.top + target.height,
110
+ );
111
+ return hit >= 0 ? hit : null;
112
+ }
113
+
114
+ export function chartValueText(item: ChartTooltipItem): string {
115
+ return item.label.length > 0 ? `${item.label} ${item.formattedValue}` : item.formattedValue;
116
+ }
117
+
118
+ interface ChartTooltipSize {
119
+ readonly width: number;
120
+ readonly height: number;
121
+ }
122
+
123
+ function clampInto(value: number, min: number, max: number): number {
124
+ // Wider than the frame: center it so it overflows both edges equally.
125
+ if (max < min) {
126
+ return (min + max) / 2;
127
+ }
128
+ return Math.min(Math.max(value, min), max);
129
+ }
130
+
131
+ /**
132
+ * Top-left of a tooltip in frame coordinates. Beside the anchor on the roomier side when it
133
+ * fits, otherwise above or below it; always clamped inside the frame so a clipping ancestor
134
+ * (a `Card`, a `ScrollView`) cannot cut it off.
135
+ */
136
+ export function placeChartTooltip(
137
+ anchor: { readonly x: number; readonly y: number; readonly radius?: number },
138
+ tip: ChartTooltipSize,
139
+ frame: ChartTooltipSize,
140
+ ): { left: number; top: number } {
141
+ const gap = TOOLTIP_GAP + (anchor.radius ?? 0);
142
+ const rightOf = anchor.x + gap;
143
+ const leftOf = anchor.x - gap - tip.width;
144
+ const fitsRight = rightOf + tip.width <= frame.width;
145
+ const fitsLeft = leftOf >= 0;
146
+ const maxLeft = frame.width - tip.width;
147
+ const maxTop = frame.height - tip.height;
148
+ if (fitsLeft || fitsRight) {
149
+ const useLeft = anchor.x > frame.width / 2 ? fitsLeft : !fitsRight;
150
+ const top = anchor.y > frame.height / 2 ? anchor.y - tip.height : anchor.y;
151
+ return { left: useLeft ? leftOf : rightOf, top: clampInto(top, 0, maxTop) };
152
+ }
153
+ const above = anchor.y - TOOLTIP_GAP - tip.height;
154
+ const top = above >= 0 ? above : anchor.y + TOOLTIP_GAP;
155
+ return {
156
+ left: clampInto(anchor.x - tip.width / 2, 0, maxLeft),
157
+ top: clampInto(top, 0, maxTop),
158
+ };
159
+ }
160
+
161
+ /** Renders only from `content`, so a consumer tooltip slot can replace it later. */
162
+ function ChartTooltip({
163
+ content,
164
+ noValueText,
165
+ slot,
166
+ onSize,
167
+ }: {
168
+ content: ChartTooltipContent;
169
+ noValueText: string;
170
+ slot: string;
171
+ onSize: (size: ChartTooltipSize) => void;
172
+ }) {
173
+ return (
174
+ <View
175
+ className="gap-1 rounded-md bg-surface-neutral-strong px-3 py-2 shadow-lg"
176
+ onLayout={(event) => {
177
+ const { width, height } = event.nativeEvent.layout;
178
+ onSize({ width, height });
179
+ }}
180
+ aria-hidden
181
+ accessibilityElementsHidden
182
+ importantForAccessibility="no-hide-descendants"
183
+ {...dataAttributes({ slot: `${slot}-tooltip` })}
184
+ >
185
+ <Text className="body-sm font-medium text-content-neutral-on-strong">{content.title}</Text>
186
+ {content.items.length === 0 ? (
187
+ <Text className="body-sm text-content-neutral-on-strong">{noValueText}</Text>
188
+ ) : (
189
+ content.items.map((item) => (
190
+ <View key={item.seriesId} className="flex-row items-center gap-1.5">
191
+ <View className="size-2 rounded-full" style={{ backgroundColor: item.color }} />
192
+ <Text className="body-sm text-content-neutral-on-strong">{chartValueText(item)}</Text>
193
+ </View>
194
+ ))
195
+ )}
196
+ </View>
197
+ );
198
+ }
199
+
200
+ /**
201
+ * Chart tooltip, measured and clamped inside `width` × `height`. Mount it in the chart's
202
+ * outer plot frame, never inside the plot `ScrollView`, which clips its content.
203
+ */
204
+ export function ChartTooltipLayer({
205
+ content,
206
+ noValueText,
207
+ slot,
208
+ anchorX,
209
+ anchorY,
210
+ anchorRadius,
211
+ width,
212
+ height,
213
+ }: {
214
+ content: ChartTooltipContent;
215
+ noValueText: string;
216
+ slot: string;
217
+ /** Anchor in frame coordinates. */
218
+ anchorX: number;
219
+ anchorY: number;
220
+ anchorRadius?: number;
221
+ width: number;
222
+ height: number;
223
+ }) {
224
+ const [size, setSize] = useState<ChartTooltipSize | null>(null);
225
+ const position =
226
+ size === null
227
+ ? { left: 0, top: 0 }
228
+ : placeChartTooltip({ x: anchorX, y: anchorY, radius: anchorRadius }, size, {
229
+ width,
230
+ height,
231
+ });
232
+ // A fixed-width box at the frame origin lets the tooltip take its natural width wherever it
233
+ // lands; `left`/`right` offsets shrink it to the space left before the frame edge.
234
+ return (
235
+ <View
236
+ pointerEvents="none"
237
+ className="absolute left-0 top-0 z-20 items-start"
238
+ style={{
239
+ width: TOOLTIP_MAX_WIDTH,
240
+ opacity: size === null ? 0 : 1,
241
+ transform: [{ translateX: position.left }, { translateY: position.top }],
242
+ }}
243
+ >
244
+ <ChartTooltip
245
+ content={content}
246
+ noValueText={noValueText}
247
+ slot={slot}
248
+ onSize={(next) => {
249
+ setSize((prev) =>
250
+ prev?.width === next.width && prev.height === next.height ? prev : next,
251
+ );
252
+ }}
253
+ />
254
+ </View>
255
+ );
256
+ }
257
+
258
+ /**
259
+ * Host-element hit targets over an SVG plot. SVG nodes are poor focus and hover targets under
260
+ * React Native Web, so each target is a `Pressable` with roving focus: one tab stop per chart.
261
+ * Web: native `mousemove` (RN Web `Pressable` has no hover). Native: press-and-drag on the
262
+ * overlay; the tooltip tracks the finger and clears on lift. When the plot itself scrolls,
263
+ * scrubbing waits for a short hold so a flick can still pan. Keyboard and VoiceOver still use
264
+ * the buttons (Escape / two-finger Z).
265
+ */
266
+ export function ChartInspector({
267
+ targets,
268
+ summary,
269
+ selectable,
270
+ activeIndex,
271
+ selectedIndex,
272
+ slot,
273
+ holdToScrub = false,
274
+ onActivate,
275
+ onDeactivate,
276
+ onDismiss,
277
+ onSelectIndex,
278
+ hitTest,
279
+ neighborIndex,
280
+ onScrubLockChange,
281
+ }: {
282
+ targets: readonly ChartInspectorTarget[];
283
+ summary: string;
284
+ selectable: boolean;
285
+ activeIndex: number | null;
286
+ selectedIndex: number | null;
287
+ slot: string;
288
+ /** Wait for a hold before capturing the pan, so a scrollable plot can still flick-scroll. */
289
+ holdToScrub?: boolean;
290
+ onActivate: (index: number) => void;
291
+ /** Called with the target that lost hover or focus. Ignore it if another target is already active. */
292
+ onDeactivate: (index: number) => void;
293
+ onDismiss: () => void;
294
+ onSelectIndex: (index: number) => void;
295
+ /** When set, pointer hits use this instead of the rectangular target boxes (line charts). */
296
+ hitTest?: (x: number, y: number) => number | null;
297
+ /** Override linear arrow-key movement (grouped / stacked bar charts). */
298
+ neighborIndex?: (index: number, key: string) => number | null;
299
+ /**
300
+ * Fires when a hold locks the finger to scrubbing (`true`) and when it lifts (`false`).
301
+ * The parent must disable its `ScrollView` while locked, or the native pan steals the drag.
302
+ */
303
+ onScrubLockChange?: (locked: boolean) => void;
304
+ }) {
305
+ const refs = useRef<(View | null)[]>([]);
306
+ const overlayRef = useRef<View>(null);
307
+ const [keyboardFocus, setKeyboardFocus] = useState(false);
308
+ const pointerRef = useRef({
309
+ targets,
310
+ hitTest,
311
+ selectable,
312
+ holdToScrub,
313
+ onActivate,
314
+ onDeactivate,
315
+ onDismiss,
316
+ onSelectIndex,
317
+ activeIndex,
318
+ neighborIndex,
319
+ onScrubLockChange,
320
+ });
321
+ pointerRef.current = {
322
+ targets,
323
+ hitTest,
324
+ selectable,
325
+ holdToScrub,
326
+ onActivate,
327
+ onDeactivate,
328
+ onDismiss,
329
+ onSelectIndex,
330
+ activeIndex,
331
+ neighborIndex,
332
+ onScrubLockChange,
333
+ };
334
+ const scrubRef = useRef({
335
+ locked: false,
336
+ holdTimer: null as ReturnType<typeof setTimeout> | null,
337
+ startPageX: 0,
338
+ startPageY: 0,
339
+ lastX: 0,
340
+ lastY: 0,
341
+ });
342
+
343
+ const hitAtPoint = (x: number, y: number) => {
344
+ const { targets: nextTargets, hitTest: test } = pointerRef.current;
345
+ return test ? test(x, y) : targetIndexAt(nextTargets, x, y);
346
+ };
347
+ const activateAtPoint = (x: number, y: number) => {
348
+ const next = hitAtPoint(x, y);
349
+ const {
350
+ onActivate: activate,
351
+ activeIndex: index,
352
+ onDeactivate: deactivate,
353
+ } = pointerRef.current;
354
+ if (next === null) {
355
+ if (index !== null) {
356
+ deactivate(index);
357
+ }
358
+ return;
359
+ }
360
+ activate(next);
361
+ };
362
+ const clearHoldTimer = () => {
363
+ if (scrubRef.current.holdTimer != null) {
364
+ clearTimeout(scrubRef.current.holdTimer);
365
+ scrubRef.current.holdTimer = null;
366
+ }
367
+ };
368
+ const clearPointer = () => {
369
+ const { activeIndex: index, onDeactivate: deactivate, onDismiss: dismiss } = pointerRef.current;
370
+ dismiss();
371
+ if (index !== null) {
372
+ deactivate(index);
373
+ }
374
+ };
375
+ const finishScrub = () => {
376
+ const wasLocked = scrubRef.current.locked;
377
+ // A tap that never passed the scroll slop still selects. A move that cancelled the
378
+ // hold is a scroll and leaves the chart alone.
379
+ const wasTap =
380
+ pointerRef.current.holdToScrub && !wasLocked && scrubRef.current.holdTimer != null;
381
+ clearHoldTimer();
382
+ const shouldFinish = wasLocked || wasTap || !pointerRef.current.holdToScrub;
383
+ scrubRef.current.locked = false;
384
+ if (wasLocked) {
385
+ pointerRef.current.onScrubLockChange?.(false);
386
+ }
387
+ if (!shouldFinish) {
388
+ return;
389
+ }
390
+ const {
391
+ activeIndex: index,
392
+ selectable: canSelect,
393
+ onSelectIndex: select,
394
+ onActivate: activate,
395
+ } = pointerRef.current;
396
+ const hit = wasTap ? hitAtPoint(scrubRef.current.lastX, scrubRef.current.lastY) : index;
397
+ if (wasTap && hit !== null) {
398
+ activate(hit);
399
+ }
400
+ if (canSelect && hit !== null) {
401
+ select(hit);
402
+ }
403
+ clearPointer();
404
+ };
405
+ const beginHold = (event: InspectorTouchEvent) => {
406
+ if (Platform.OS === 'web') {
407
+ return;
408
+ }
409
+ const point = touchPoint(event);
410
+ clearHoldTimer();
411
+ scrubRef.current.locked = false;
412
+ scrubRef.current.startPageX = point.pageX;
413
+ scrubRef.current.startPageY = point.pageY;
414
+ scrubRef.current.lastX = point.locationX;
415
+ scrubRef.current.lastY = point.locationY;
416
+ if (!pointerRef.current.holdToScrub) {
417
+ activateAtPoint(point.locationX, point.locationY);
418
+ return;
419
+ }
420
+ scrubRef.current.holdTimer = setTimeout(() => {
421
+ scrubRef.current.holdTimer = null;
422
+ scrubRef.current.locked = true;
423
+ pointerRef.current.onScrubLockChange?.(true);
424
+ activateAtPoint(scrubRef.current.lastX, scrubRef.current.lastY);
425
+ }, CHART_SCRUB_HOLD_MS);
426
+ };
427
+ const noteHoldMove = (event: InspectorTouchEvent) => {
428
+ const point = touchPoint(event);
429
+ scrubRef.current.lastX = point.locationX;
430
+ scrubRef.current.lastY = point.locationY;
431
+ if (!pointerRef.current.holdToScrub) {
432
+ activateAtPoint(point.locationX, point.locationY);
433
+ return;
434
+ }
435
+ if (scrubRef.current.locked || scrubRef.current.holdTimer == null) {
436
+ return;
437
+ }
438
+ const dist = Math.hypot(
439
+ point.pageX - scrubRef.current.startPageX,
440
+ point.pageY - scrubRef.current.startPageY,
441
+ );
442
+ if (dist > SCRUB_MOVE_SLOP_PX) {
443
+ clearHoldTimer();
444
+ }
445
+ };
446
+
447
+ const panResponder = useRef(
448
+ PanResponder.create({
449
+ onStartShouldSetPanResponder: () => false,
450
+ onMoveShouldSetPanResponder: () => {
451
+ if (Platform.OS === 'web') {
452
+ return false;
453
+ }
454
+ return pointerRef.current.holdToScrub && scrubRef.current.locked;
455
+ },
456
+ onPanResponderTerminationRequest: () => !scrubRef.current.locked,
457
+ onPanResponderGrant: (event) => {
458
+ activateAtPoint(event.nativeEvent.locationX, event.nativeEvent.locationY);
459
+ },
460
+ onPanResponderMove: (event) => {
461
+ activateAtPoint(event.nativeEvent.locationX, event.nativeEvent.locationY);
462
+ },
463
+ onPanResponderRelease: finishScrub,
464
+ onPanResponderTerminate: finishScrub,
465
+ }),
466
+ ).current;
467
+
468
+ const count = targets.length;
469
+ const rovingIndex = Math.min(activeIndex ?? selectedIndex ?? 0, Math.max(count - 1, 0));
470
+
471
+ // RN Web Pressable does not emit hover. Bind the DOM node directly so mousemove
472
+ // along the line (and across category bands) opens the tooltip.
473
+ useEffect(() => {
474
+ const node = overlayRef.current as unknown as HTMLElement | null;
475
+ if (node == null || typeof node.addEventListener !== 'function') {
476
+ return;
477
+ }
478
+ const hitAt = (event: MouseEvent) => {
479
+ const { targets: nextTargets, hitTest: test } = pointerRef.current;
480
+ const rect = node.getBoundingClientRect();
481
+ const x = event.clientX - rect.left;
482
+ const y = event.clientY - rect.top;
483
+ return test ? test(x, y) : targetIndexAt(nextTargets, x, y);
484
+ };
485
+ const clearActive = () => {
486
+ const {
487
+ activeIndex: index,
488
+ onDeactivate: deactivate,
489
+ onDismiss: dismiss,
490
+ } = pointerRef.current;
491
+ dismiss();
492
+ if (index !== null) {
493
+ deactivate(index);
494
+ }
495
+ };
496
+ const move = (event: MouseEvent) => {
497
+ setKeyboardFocus(false);
498
+ const next = hitAt(event);
499
+ const {
500
+ onActivate: activate,
501
+ activeIndex: index,
502
+ onDeactivate: deactivate,
503
+ } = pointerRef.current;
504
+ if (next === null) {
505
+ if (index !== null) {
506
+ deactivate(index);
507
+ }
508
+ return;
509
+ }
510
+ activate(next);
511
+ };
512
+ const down = (event: MouseEvent) => {
513
+ if (event.button !== 0) {
514
+ return;
515
+ }
516
+ setKeyboardFocus(false);
517
+ const next = hitAt(event);
518
+ const {
519
+ onActivate: activate,
520
+ selectable: canSelect,
521
+ onSelectIndex: select,
522
+ } = pointerRef.current;
523
+ if (next === null) {
524
+ clearActive();
525
+ return;
526
+ }
527
+ activate(next);
528
+ if (canSelect) {
529
+ select(next);
530
+ }
531
+ };
532
+ const leave = (event: MouseEvent) => {
533
+ if (node.contains(event.relatedTarget as Node | null)) {
534
+ return;
535
+ }
536
+ const { activeIndex: index, onDeactivate: deactivate } = pointerRef.current;
537
+ if (index !== null) {
538
+ deactivate(index);
539
+ }
540
+ };
541
+ const docDown = (event: MouseEvent) => {
542
+ if (node.contains(event.target as Node | null)) {
543
+ return;
544
+ }
545
+ clearActive();
546
+ };
547
+ node.addEventListener('mousemove', move);
548
+ node.addEventListener('mousedown', down);
549
+ node.addEventListener('mouseleave', leave);
550
+ document.addEventListener('mousedown', docDown);
551
+ return () => {
552
+ node.removeEventListener('mousemove', move);
553
+ node.removeEventListener('mousedown', down);
554
+ node.removeEventListener('mouseleave', leave);
555
+ document.removeEventListener('mousedown', docDown);
556
+ if (scrubRef.current.holdTimer != null) {
557
+ clearTimeout(scrubRef.current.holdTimer);
558
+ scrubRef.current.holdTimer = null;
559
+ }
560
+ };
561
+ }, []);
562
+
563
+ const handleKeyDown = (index: number, event: InspectorKeyEvent) => {
564
+ const key = event.nativeEvent.key ?? event.key ?? '';
565
+ const shiftKey = event.shiftKey === true || event.nativeEvent.shiftKey === true;
566
+ if (
567
+ key === 'Escape' ||
568
+ key === 'Enter' ||
569
+ key === ' ' ||
570
+ key === 'Tab' ||
571
+ key === 'ArrowRight' ||
572
+ key === 'ArrowLeft' ||
573
+ key === 'ArrowUp' ||
574
+ key === 'ArrowDown' ||
575
+ key === 'Home' ||
576
+ key === 'End'
577
+ ) {
578
+ setKeyboardFocus(true);
579
+ }
580
+ if (key === 'Escape') {
581
+ event.preventDefault?.();
582
+ onDismiss();
583
+ return;
584
+ }
585
+ if (key === 'Enter' || key === ' ') {
586
+ event.preventDefault?.();
587
+ onActivate(index);
588
+ if (selectable) {
589
+ onSelectIndex(index);
590
+ }
591
+ return;
592
+ }
593
+ if (key === 'Tab') {
594
+ const next = neighborIndex?.(index, shiftKey ? 'Shift+Tab' : 'Tab');
595
+ if (next === undefined || next === null || next === index) {
596
+ return;
597
+ }
598
+ event.preventDefault?.();
599
+ onActivate(next);
600
+ refs.current[next]?.focus();
601
+ return;
602
+ }
603
+ let next: number | null = neighborIndex?.(index, key) ?? null;
604
+ if (next === null) {
605
+ if (key === 'ArrowRight' || key === 'ArrowDown') {
606
+ next = Math.min(index + 1, count - 1);
607
+ } else if (key === 'ArrowLeft' || key === 'ArrowUp') {
608
+ next = Math.max(index - 1, 0);
609
+ } else if (key === 'Home') {
610
+ next = 0;
611
+ } else if (key === 'End') {
612
+ next = count - 1;
613
+ }
614
+ }
615
+ if (next === null) {
616
+ return;
617
+ }
618
+ event.preventDefault?.();
619
+ onActivate(next);
620
+ refs.current[next]?.focus();
621
+ };
622
+
623
+ const blurInspector = (event: InspectorBlurEvent | undefined) => {
624
+ const related = event?.nativeEvent?.relatedTarget ?? event?.relatedTarget;
625
+ const node = overlayRef.current as unknown as {
626
+ contains?: (target: unknown) => boolean;
627
+ } | null;
628
+ if (node?.contains?.(related) === true) {
629
+ return;
630
+ }
631
+ setKeyboardFocus(false);
632
+ if (activeIndex !== null) {
633
+ onDeactivate(activeIndex);
634
+ }
635
+ };
636
+
637
+ const outline =
638
+ keyboardFocus && activeIndex !== null
639
+ ? (targets.at(activeIndex)?.outline ?? targets.at(activeIndex))
640
+ : undefined;
641
+
642
+ return (
643
+ <View
644
+ ref={overlayRef}
645
+ className="absolute inset-0 z-10"
646
+ role="group"
647
+ aria-label={summary}
648
+ accessibilityLabel={summary}
649
+ onAccessibilityEscape={onDismiss}
650
+ {...(Platform.OS === 'web'
651
+ ? {}
652
+ : {
653
+ ...panResponder.panHandlers,
654
+ onTouchStart: beginHold,
655
+ onTouchMove: noteHoldMove,
656
+ onTouchEnd: finishScrub,
657
+ onTouchCancel: finishScrub,
658
+ })}
659
+ {...dataAttributes({ slot: `${slot}-inspector` })}
660
+ >
661
+ {outline !== undefined ? (
662
+ <View
663
+ pointerEvents="none"
664
+ className="absolute rounded-sm border-2 border-[var(--color-stroke-ring)]"
665
+ style={{
666
+ left: outline.left,
667
+ top: outline.top,
668
+ width: outline.width,
669
+ height: outline.height,
670
+ }}
671
+ accessibilityElementsHidden
672
+ importantForAccessibility="no-hide-descendants"
673
+ {...dataAttributes({ slot: `${slot}-focus` })}
674
+ />
675
+ ) : null}
676
+ {targets.map((target, index) => (
677
+ <Pressable
678
+ key={target.key}
679
+ ref={(node) => {
680
+ refs.current[index] = node;
681
+ }}
682
+ className="absolute rounded-sm web:outline-none"
683
+ style={{
684
+ left: target.left,
685
+ top: target.top,
686
+ width: target.width,
687
+ height: target.height,
688
+ }}
689
+ accessibilityRole="button"
690
+ accessibilityLabel={target.accessibilityLabel}
691
+ accessibilityState={
692
+ selectable && Platform.OS !== 'web' ? { selected: target.selected } : undefined
693
+ }
694
+ aria-pressed={selectable && Platform.OS === 'web' ? target.selected : undefined}
695
+ pointerEvents="none"
696
+ focusable={Platform.OS !== 'web' || index === rovingIndex}
697
+ tabIndex={index === rovingIndex ? 0 : -1}
698
+ onHoverIn={() => {
699
+ onActivate(index);
700
+ }}
701
+ onHoverOut={() => {
702
+ onDeactivate(index);
703
+ }}
704
+ onFocus={() => {
705
+ setKeyboardFocus(true);
706
+ onActivate(index);
707
+ }}
708
+ onBlur={(event: unknown) => blurInspector(event as InspectorBlurEvent)}
709
+ onPress={() => {
710
+ onActivate(index);
711
+ if (selectable) {
712
+ onSelectIndex(index);
713
+ }
714
+ }}
715
+ onAccessibilityEscape={onDismiss}
716
+ {...{
717
+ onKeyDown: (event: InspectorKeyEvent) => {
718
+ handleKeyDown(index, event);
719
+ },
720
+ }}
721
+ {...dataAttributes({ slot: `${slot}-point`, selected: target.selected })}
722
+ />
723
+ ))}
724
+ </View>
725
+ );
726
+ }