panelui-native 0.71.0 → 0.75.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 (306) hide show
  1. package/README.md +5 -2
  2. package/lib/module/components/accordion/index.js +10 -4
  3. package/lib/module/components/accordion/index.js.map +1 -1
  4. package/lib/module/components/area-chart/index.js +1 -0
  5. package/lib/module/components/area-chart/index.js.map +1 -1
  6. package/lib/module/components/avatar/avatar-group.js +64 -0
  7. package/lib/module/components/avatar/avatar-group.js.map +1 -0
  8. package/lib/module/components/avatar/index.js +84 -2
  9. package/lib/module/components/avatar/index.js.map +1 -1
  10. package/lib/module/components/breadcrumb/index.js +2 -1
  11. package/lib/module/components/breadcrumb/index.js.map +1 -1
  12. package/lib/module/components/button/index.js +7 -5
  13. package/lib/module/components/button/index.js.map +1 -1
  14. package/lib/module/components/carousel/index.js +1 -1
  15. package/lib/module/components/carousel/index.js.map +1 -1
  16. package/lib/module/components/chip/index.js +2 -2
  17. package/lib/module/components/chip/index.js.map +1 -1
  18. package/lib/module/components/code-block/index.js.map +1 -1
  19. package/lib/module/components/collapsible/index.js +5 -1
  20. package/lib/module/components/collapsible/index.js.map +1 -1
  21. package/lib/module/components/color-picker/color-picker-accessibility.js +12 -0
  22. package/lib/module/components/color-picker/color-picker-accessibility.js.map +1 -0
  23. package/lib/module/components/color-picker/index.js +9 -5
  24. package/lib/module/components/color-picker/index.js.map +1 -1
  25. package/lib/module/components/date-time-picker/index.js +11 -3
  26. package/lib/module/components/date-time-picker/index.js.map +1 -1
  27. package/lib/module/components/drawer/index.js +58 -13
  28. package/lib/module/components/drawer/index.js.map +1 -1
  29. package/lib/module/components/fab/index.js +7 -6
  30. package/lib/module/components/fab/index.js.map +1 -1
  31. package/lib/module/components/flow/index.js +1 -1
  32. package/lib/module/components/flow/index.js.map +1 -1
  33. package/lib/module/components/frame/index.js +2 -2
  34. package/lib/module/components/frame/index.js.map +1 -1
  35. package/lib/module/components/grid-item/index.js +2 -2
  36. package/lib/module/components/grid-item/index.js.map +1 -1
  37. package/lib/module/components/heatmap-chart/index.js +1 -0
  38. package/lib/module/components/heatmap-chart/index.js.map +1 -1
  39. package/lib/module/components/hex-chart/index.js +1 -0
  40. package/lib/module/components/hex-chart/index.js.map +1 -1
  41. package/lib/module/components/input/index.js +7 -4
  42. package/lib/module/components/input/index.js.map +1 -1
  43. package/lib/module/components/input/input-content-padding.js +6 -0
  44. package/lib/module/components/input/input-content-padding.js.map +1 -0
  45. package/lib/module/components/input-group/index.js +21 -11
  46. package/lib/module/components/input-group/index.js.map +1 -1
  47. package/lib/module/components/input-group/input-group-measurements.js +60 -0
  48. package/lib/module/components/input-group/input-group-measurements.js.map +1 -0
  49. package/lib/module/components/item/index.js +2 -2
  50. package/lib/module/components/item/index.js.map +1 -1
  51. package/lib/module/components/line-chart/index.js +1 -0
  52. package/lib/module/components/line-chart/index.js.map +1 -1
  53. package/lib/module/components/marker/index.js +2 -2
  54. package/lib/module/components/marker/index.js.map +1 -1
  55. package/lib/module/components/marquee/index.js +9 -12
  56. package/lib/module/components/marquee/index.js.map +1 -1
  57. package/lib/module/components/marquee/marquee-math.js +39 -0
  58. package/lib/module/components/marquee/marquee-math.js.map +1 -0
  59. package/lib/module/components/menu/index.js +11 -3
  60. package/lib/module/components/menu/index.js.map +1 -1
  61. package/lib/module/components/otp-input/index.js +3 -1
  62. package/lib/module/components/otp-input/index.js.map +1 -1
  63. package/lib/module/components/otp-input/otp-input-state.js +7 -0
  64. package/lib/module/components/otp-input/otp-input-state.js.map +1 -0
  65. package/lib/module/components/pagination/index.js +27 -11
  66. package/lib/module/components/pagination/index.js.map +1 -1
  67. package/lib/module/components/panelside/index.js +7 -2
  68. package/lib/module/components/panelside/index.js.map +1 -1
  69. package/lib/module/components/planner/index.js +46 -11
  70. package/lib/module/components/planner/index.js.map +1 -1
  71. package/lib/module/components/planner/planner-today.js +8 -0
  72. package/lib/module/components/planner/planner-today.js.map +1 -0
  73. package/lib/module/components/plot/index.js +113 -53
  74. package/lib/module/components/plot/index.js.map +1 -1
  75. package/lib/module/components/plot/plot-series-registry.js +32 -0
  76. package/lib/module/components/plot/plot-series-registry.js.map +1 -0
  77. package/lib/module/components/post/index.js +4 -4
  78. package/lib/module/components/post/index.js.map +1 -1
  79. package/lib/module/components/radio-group/index.js +2 -1
  80. package/lib/module/components/radio-group/index.js.map +1 -1
  81. package/lib/module/components/radio-group/radio-group-value.js +8 -0
  82. package/lib/module/components/radio-group/radio-group-value.js.map +1 -0
  83. package/lib/module/components/rating/index.js +7 -4
  84. package/lib/module/components/rating/index.js.map +1 -1
  85. package/lib/module/components/rating/rating-inputs.js +15 -0
  86. package/lib/module/components/rating/rating-inputs.js.map +1 -0
  87. package/lib/module/components/search-bar/index.js +331 -0
  88. package/lib/module/components/search-bar/index.js.map +1 -0
  89. package/lib/module/components/search-bar/search-bar-debounce.js +23 -0
  90. package/lib/module/components/search-bar/search-bar-debounce.js.map +1 -0
  91. package/lib/module/components/select/index.js +9 -1
  92. package/lib/module/components/select/index.js.map +1 -1
  93. package/lib/module/components/select/native-select-contract.js +11 -0
  94. package/lib/module/components/select/native-select-contract.js.map +1 -0
  95. package/lib/module/components/split-view/index.js +439 -0
  96. package/lib/module/components/split-view/index.js.map +1 -0
  97. package/lib/module/components/split-view/split-view-math.js +132 -0
  98. package/lib/module/components/split-view/split-view-math.js.map +1 -0
  99. package/lib/module/components/splitter/index.js +433 -0
  100. package/lib/module/components/splitter/index.js.map +1 -0
  101. package/lib/module/components/splitter/splitter-math.js +206 -0
  102. package/lib/module/components/splitter/splitter-math.js.map +1 -0
  103. package/lib/module/components/steps/index.js +16 -5
  104. package/lib/module/components/steps/index.js.map +1 -1
  105. package/lib/module/components/steps/steps-trigger.js +6 -0
  106. package/lib/module/components/steps/steps-trigger.js.map +1 -0
  107. package/lib/module/components/table/index.js +3 -4
  108. package/lib/module/components/table/index.js.map +1 -1
  109. package/lib/module/components/tag-input/index.js +15 -10
  110. package/lib/module/components/tag-input/index.js.map +1 -1
  111. package/lib/module/components/tag-input/tag-input-state.js +14 -0
  112. package/lib/module/components/tag-input/tag-input-state.js.map +1 -0
  113. package/lib/module/components/textarea/index.js +9 -4
  114. package/lib/module/components/textarea/index.js.map +1 -1
  115. package/lib/module/components/textarea/textarea-disabled.js +6 -0
  116. package/lib/module/components/textarea/textarea-disabled.js.map +1 -0
  117. package/lib/module/components/timeline/index.js +352 -47
  118. package/lib/module/components/timeline/index.js.map +1 -1
  119. package/lib/module/components/timeline/timeline-geometry.js +11 -0
  120. package/lib/module/components/timeline/timeline-geometry.js.map +1 -0
  121. package/lib/module/components/toast/index.js +18 -1
  122. package/lib/module/components/toast/index.js.map +1 -1
  123. package/lib/module/components/toast/toast-store.js +36 -4
  124. package/lib/module/components/toast/toast-store.js.map +1 -1
  125. package/lib/module/components/toggle-button/index.js +1 -1
  126. package/lib/module/components/toggle-button/index.js.map +1 -1
  127. package/lib/module/components/tour/index.js +4 -2
  128. package/lib/module/components/tour/index.js.map +1 -1
  129. package/lib/module/components/tour/tour-card-measurement.js +24 -0
  130. package/lib/module/components/tour/tour-card-measurement.js.map +1 -0
  131. package/lib/module/components/tree/index.js +13 -8
  132. package/lib/module/components/tree/index.js.map +1 -1
  133. package/lib/module/hooks/use-keyboard-avoidance.js.map +1 -1
  134. package/lib/module/index.js +3 -0
  135. package/lib/module/index.js.map +1 -1
  136. package/lib/module/utils/chart.js +50 -7
  137. package/lib/module/utils/chart.js.map +1 -1
  138. package/lib/typescript/src/components/accordion/index.d.ts +2 -2
  139. package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
  140. package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
  141. package/lib/typescript/src/components/avatar/avatar-group.d.ts +46 -0
  142. package/lib/typescript/src/components/avatar/avatar-group.d.ts.map +1 -0
  143. package/lib/typescript/src/components/avatar/index.d.ts +23 -1
  144. package/lib/typescript/src/components/avatar/index.d.ts.map +1 -1
  145. package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -1
  146. package/lib/typescript/src/components/button/index.d.ts.map +1 -1
  147. package/lib/typescript/src/components/code-block/index.d.ts +3 -3
  148. package/lib/typescript/src/components/code-block/index.d.ts.map +1 -1
  149. package/lib/typescript/src/components/collapsible/index.d.ts +2 -2
  150. package/lib/typescript/src/components/collapsible/index.d.ts.map +1 -1
  151. package/lib/typescript/src/components/color-picker/color-picker-accessibility.d.ts +8 -0
  152. package/lib/typescript/src/components/color-picker/color-picker-accessibility.d.ts.map +1 -0
  153. package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -1
  154. package/lib/typescript/src/components/date-time-picker/index.d.ts.map +1 -1
  155. package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
  156. package/lib/typescript/src/components/fab/index.d.ts +4 -2
  157. package/lib/typescript/src/components/fab/index.d.ts.map +1 -1
  158. package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
  159. package/lib/typescript/src/components/hex-chart/index.d.ts.map +1 -1
  160. package/lib/typescript/src/components/input/index.d.ts.map +1 -1
  161. package/lib/typescript/src/components/input/input-content-padding.d.ts +2 -0
  162. package/lib/typescript/src/components/input/input-content-padding.d.ts.map +1 -0
  163. package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
  164. package/lib/typescript/src/components/input-group/input-group-measurements.d.ts +16 -0
  165. package/lib/typescript/src/components/input-group/input-group-measurements.d.ts.map +1 -0
  166. package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
  167. package/lib/typescript/src/components/marquee/index.d.ts +1 -1
  168. package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
  169. package/lib/typescript/src/components/marquee/marquee-math.d.ts +21 -0
  170. package/lib/typescript/src/components/marquee/marquee-math.d.ts.map +1 -0
  171. package/lib/typescript/src/components/menu/index.d.ts +1 -1
  172. package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
  173. package/lib/typescript/src/components/otp-input/index.d.ts.map +1 -1
  174. package/lib/typescript/src/components/otp-input/otp-input-state.d.ts +3 -0
  175. package/lib/typescript/src/components/otp-input/otp-input-state.d.ts.map +1 -0
  176. package/lib/typescript/src/components/pagination/index.d.ts.map +1 -1
  177. package/lib/typescript/src/components/panelside/index.d.ts +1 -1
  178. package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
  179. package/lib/typescript/src/components/plan/index.d.ts +2 -2
  180. package/lib/typescript/src/components/planner/index.d.ts.map +1 -1
  181. package/lib/typescript/src/components/planner/planner-today.d.ts +2 -0
  182. package/lib/typescript/src/components/planner/planner-today.d.ts.map +1 -0
  183. package/lib/typescript/src/components/plot/index.d.ts +60 -6
  184. package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
  185. package/lib/typescript/src/components/plot/plot-series-registry.d.ts +8 -0
  186. package/lib/typescript/src/components/plot/plot-series-registry.d.ts.map +1 -0
  187. package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
  188. package/lib/typescript/src/components/radio-group/radio-group-value.d.ts +2 -0
  189. package/lib/typescript/src/components/radio-group/radio-group-value.d.ts.map +1 -0
  190. package/lib/typescript/src/components/rating/index.d.ts.map +1 -1
  191. package/lib/typescript/src/components/rating/rating-inputs.d.ts +4 -0
  192. package/lib/typescript/src/components/rating/rating-inputs.d.ts.map +1 -0
  193. package/lib/typescript/src/components/search-bar/index.d.ts +216 -0
  194. package/lib/typescript/src/components/search-bar/index.d.ts.map +1 -0
  195. package/lib/typescript/src/components/search-bar/search-bar-debounce.d.ts +7 -0
  196. package/lib/typescript/src/components/search-bar/search-bar-debounce.d.ts.map +1 -0
  197. package/lib/typescript/src/components/select/index.d.ts.map +1 -1
  198. package/lib/typescript/src/components/select/native-select-contract.d.ts +11 -0
  199. package/lib/typescript/src/components/select/native-select-contract.d.ts.map +1 -0
  200. package/lib/typescript/src/components/split-view/index.d.ts +159 -0
  201. package/lib/typescript/src/components/split-view/index.d.ts.map +1 -0
  202. package/lib/typescript/src/components/split-view/split-view-math.d.ts +54 -0
  203. package/lib/typescript/src/components/split-view/split-view-math.d.ts.map +1 -0
  204. package/lib/typescript/src/components/splitter/index.d.ts +78 -0
  205. package/lib/typescript/src/components/splitter/index.d.ts.map +1 -0
  206. package/lib/typescript/src/components/splitter/splitter-math.d.ts +67 -0
  207. package/lib/typescript/src/components/splitter/splitter-math.d.ts.map +1 -0
  208. package/lib/typescript/src/components/steps/index.d.ts +2 -2
  209. package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
  210. package/lib/typescript/src/components/steps/steps-trigger.d.ts +2 -0
  211. package/lib/typescript/src/components/steps/steps-trigger.d.ts.map +1 -0
  212. package/lib/typescript/src/components/tag-input/index.d.ts.map +1 -1
  213. package/lib/typescript/src/components/tag-input/tag-input-state.d.ts +12 -0
  214. package/lib/typescript/src/components/tag-input/tag-input-state.d.ts.map +1 -0
  215. package/lib/typescript/src/components/textarea/index.d.ts.map +1 -1
  216. package/lib/typescript/src/components/textarea/textarea-disabled.d.ts +2 -0
  217. package/lib/typescript/src/components/textarea/textarea-disabled.d.ts.map +1 -0
  218. package/lib/typescript/src/components/timeline/index.d.ts +36 -2
  219. package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
  220. package/lib/typescript/src/components/timeline/timeline-geometry.d.ts +4 -0
  221. package/lib/typescript/src/components/timeline/timeline-geometry.d.ts.map +1 -0
  222. package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
  223. package/lib/typescript/src/components/toast/toast-store.d.ts +7 -2
  224. package/lib/typescript/src/components/toast/toast-store.d.ts.map +1 -1
  225. package/lib/typescript/src/components/tour/index.d.ts.map +1 -1
  226. package/lib/typescript/src/components/tour/tour-card-measurement.d.ts +14 -0
  227. package/lib/typescript/src/components/tour/tour-card-measurement.d.ts.map +1 -0
  228. package/lib/typescript/src/components/tree/index.d.ts +3 -4
  229. package/lib/typescript/src/components/tree/index.d.ts.map +1 -1
  230. package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts +2 -2
  231. package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts.map +1 -1
  232. package/lib/typescript/src/index.d.ts +5 -2
  233. package/lib/typescript/src/index.d.ts.map +1 -1
  234. package/lib/typescript/src/utils/chart.d.ts +28 -3
  235. package/lib/typescript/src/utils/chart.d.ts.map +1 -1
  236. package/package.json +1 -1
  237. package/src/components/accordion/index.tsx +17 -7
  238. package/src/components/area-chart/index.tsx +8 -0
  239. package/src/components/avatar/avatar-group.ts +78 -0
  240. package/src/components/avatar/index.tsx +121 -1
  241. package/src/components/breadcrumb/index.tsx +2 -1
  242. package/src/components/button/index.tsx +3 -2
  243. package/src/components/carousel/index.tsx +1 -1
  244. package/src/components/chip/index.tsx +2 -2
  245. package/src/components/code-block/index.tsx +3 -3
  246. package/src/components/collapsible/index.tsx +13 -4
  247. package/src/components/color-picker/color-picker-accessibility.ts +12 -0
  248. package/src/components/color-picker/index.tsx +30 -10
  249. package/src/components/date-time-picker/index.tsx +11 -3
  250. package/src/components/drawer/index.tsx +42 -14
  251. package/src/components/fab/index.tsx +21 -11
  252. package/src/components/flow/index.tsx +1 -1
  253. package/src/components/frame/index.tsx +2 -2
  254. package/src/components/grid-item/index.tsx +2 -2
  255. package/src/components/heatmap-chart/index.tsx +7 -1
  256. package/src/components/hex-chart/index.tsx +7 -1
  257. package/src/components/input/index.tsx +6 -2
  258. package/src/components/input/input-content-padding.ts +3 -0
  259. package/src/components/input-group/index.tsx +45 -12
  260. package/src/components/input-group/input-group-measurements.ts +98 -0
  261. package/src/components/item/index.tsx +2 -2
  262. package/src/components/line-chart/index.tsx +9 -0
  263. package/src/components/marker/index.tsx +2 -2
  264. package/src/components/marquee/index.tsx +14 -12
  265. package/src/components/marquee/marquee-math.ts +37 -0
  266. package/src/components/menu/index.tsx +11 -3
  267. package/src/components/otp-input/index.tsx +3 -1
  268. package/src/components/otp-input/otp-input-state.ts +7 -0
  269. package/src/components/pagination/index.tsx +26 -13
  270. package/src/components/panelside/index.tsx +7 -2
  271. package/src/components/plan/index.tsx +2 -2
  272. package/src/components/planner/index.tsx +44 -11
  273. package/src/components/planner/planner-today.ts +5 -0
  274. package/src/components/plot/index.tsx +199 -51
  275. package/src/components/plot/plot-series-registry.ts +41 -0
  276. package/src/components/post/index.tsx +4 -4
  277. package/src/components/radio-group/index.tsx +4 -1
  278. package/src/components/radio-group/radio-group-value.ts +9 -0
  279. package/src/components/rating/index.tsx +11 -4
  280. package/src/components/rating/rating-inputs.ts +19 -0
  281. package/src/components/search-bar/index.tsx +441 -0
  282. package/src/components/search-bar/search-bar-debounce.ts +35 -0
  283. package/src/components/select/index.tsx +10 -1
  284. package/src/components/select/native-select-contract.ts +12 -0
  285. package/src/components/split-view/index.tsx +565 -0
  286. package/src/components/split-view/split-view-math.ts +146 -0
  287. package/src/components/splitter/index.tsx +589 -0
  288. package/src/components/splitter/splitter-math.ts +244 -0
  289. package/src/components/steps/index.tsx +35 -9
  290. package/src/components/steps/steps-trigger.ts +6 -0
  291. package/src/components/table/index.tsx +2 -2
  292. package/src/components/tag-input/index.tsx +13 -11
  293. package/src/components/tag-input/tag-input-state.ts +18 -0
  294. package/src/components/textarea/index.tsx +8 -4
  295. package/src/components/textarea/textarea-disabled.ts +6 -0
  296. package/src/components/timeline/index.tsx +405 -46
  297. package/src/components/timeline/timeline-geometry.ts +9 -0
  298. package/src/components/toast/index.tsx +26 -1
  299. package/src/components/toast/toast-store.ts +43 -8
  300. package/src/components/toggle-button/index.tsx +1 -1
  301. package/src/components/tour/index.tsx +11 -3
  302. package/src/components/tour/tour-card-measurement.ts +29 -0
  303. package/src/components/tree/index.tsx +17 -12
  304. package/src/hooks/use-keyboard-avoidance.ts +7 -2
  305. package/src/index.ts +19 -0
  306. package/src/utils/chart.ts +66 -7
@@ -105,12 +105,19 @@ import {
105
105
  columnValues,
106
106
  compactNumber,
107
107
  linePath,
108
+ niceDomain,
108
109
  useSeriesColor,
109
110
  xOf,
110
111
  yOf,
111
112
  type Plot as PlotBox,
112
113
  } from '../../utils/chart';
113
114
  import { cn } from '../../utils/cn';
115
+ import {
116
+ registerPlotSeries,
117
+ unregisterPlotSeries,
118
+ visiblePlotSeries,
119
+ type PlotSeriesRegistration,
120
+ } from './plot-series-registry';
114
121
 
115
122
  /*
116
123
  * The scale functions, re-exported so a mark written outside this file can sit
@@ -213,7 +220,7 @@ export interface PlotGeometry {
213
220
  /** Every mark that registered itself, as `[dataKey, colour]`. */
214
221
  series: [string, string][];
215
222
  registerSeries: (key: string, color: string) => void;
216
- unregisterSeries: (key: string) => void;
223
+ unregisterSeries: (key: string, color?: string) => void;
217
224
  /** Row under the cursor, or `-1`. On the UI thread. */
218
225
  activeIndex: SharedValue<number>;
219
226
  /** The same index on the JS thread, for anything that has to re-render. */
@@ -281,6 +288,17 @@ export interface PlotProps extends ViewProps {
281
288
  * arrives.
282
289
  */
283
290
  yDomain?: [PlotBound, PlotBound];
291
+ /**
292
+ * Round the derived ends of the y-domain out to whole numbers.
293
+ *
294
+ * Left off, an axis ends a tenth of the span past the largest value, so it
295
+ * gets labelled 34,650 — true, and not a number anybody was looking for. On,
296
+ * the ends move out to a step of 1, 2 or 5 times a power of ten, and the
297
+ * labels become values a reader can measure against.
298
+ *
299
+ * It only ever widens the axis, and a pinned end is left alone.
300
+ */
301
+ nice?: boolean;
284
302
  /**
285
303
  * How an index becomes an x. Derived from the marks when left out: a plot
286
304
  * with bars in it is banded, and anything else is on points.
@@ -317,6 +335,7 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
317
335
  animationDuration = 700,
318
336
  domainDuration = 500,
319
337
  yDomain,
338
+ nice = false,
320
339
  xScale: xScaleProp,
321
340
  curve = 'monotone',
322
341
  onActiveIndexChange,
@@ -327,7 +346,7 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
327
346
  ref
328
347
  ) {
329
348
  const [size, setSize] = useState({ width: 0, height: 0 });
330
- const [series, setSeries] = useState<[string, string][]>([]);
349
+ const [registrations, setRegistrations] = useState<PlotSeriesRegistration[]>([]);
331
350
  const [activeIndexJS, setActiveIndexJS] = useState(-1);
332
351
  const clipId = `panelui-plot-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
333
352
 
@@ -339,19 +358,18 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
339
358
 
340
359
  const registerSeries = useMemo(
341
360
  () => (key: string, color: string) =>
342
- setSeries((current) => {
343
- const existing = current.find(([k]) => k === key);
344
- if (existing?.[1] === color) return current;
345
- return [...current.filter(([k]) => k !== key), [key, color]];
346
- }),
361
+ setRegistrations((current) => registerPlotSeries(current, key, color)),
347
362
  []
348
363
  );
349
364
 
350
365
  const unregisterSeries = useMemo(
351
- () => (key: string) => setSeries((current) => current.filter(([k]) => k !== key)),
366
+ () => (key: string, color?: string) =>
367
+ setRegistrations((current) => unregisterPlotSeries(current, key, color)),
352
368
  []
353
369
  );
354
370
 
371
+ const series = useMemo(() => visiblePlotSeries(registrations), [registrations]);
372
+
355
373
  /*
356
374
  * What the children need decided before anything is laid out.
357
375
  *
@@ -391,7 +409,7 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
391
409
  * magnitude apart look alike, which is the chart lying rather than the chart
392
410
  * being convenient.
393
411
  */
394
- const seriesKeys = series.map(([key]) => key).join('|');
412
+ const seriesKeys = useMemo(() => series.map(([key]) => key), [series]);
395
413
  const extent = useMemo<[number, number]>(() => {
396
414
  const lowPin = yDomain?.[0];
397
415
  const highPin = yDomain?.[1];
@@ -399,11 +417,10 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
399
417
  return [lowPin, highPin];
400
418
  }
401
419
 
402
- const keys = seriesKeys ? seriesKeys.split('|') : [];
403
420
  let min = Infinity;
404
421
  let max = -Infinity;
405
422
  for (const row of data) {
406
- for (const key of keys) {
423
+ for (const key of seriesKeys) {
407
424
  const value = row[key];
408
425
  if (typeof value !== 'number' || Number.isNaN(value)) continue;
409
426
  if (value < min) min = value;
@@ -450,11 +467,20 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
450
467
  * drifting off the axis is exactly what pinning it was for.
451
468
  */
452
469
  const headroom = (max - min) * 0.1;
470
+ /*
471
+ * `nice` replaces the headroom rather than adding to it: rounding out to a
472
+ * whole step is already room, and doing both would leave a tenth of the
473
+ * span of empty axis above a top the reader was told is round.
474
+ */
475
+ const [niceLow, niceHigh] = nice ? niceDomain(min, max) : [min, max];
476
+ const derivedLow = nice ? niceLow : min - headroom * lowRoom;
477
+ const derivedHigh = nice ? niceHigh : max + headroom * highRoom;
478
+
453
479
  return [
454
- typeof lowPin === 'number' ? lowPin : min - headroom * lowRoom,
455
- typeof highPin === 'number' ? highPin : max + headroom * highRoom,
480
+ typeof lowPin === 'number' ? lowPin : derivedLow,
481
+ typeof highPin === 'number' ? highPin : derivedHigh,
456
482
  ];
457
- }, [data, yDomain, seriesKeys, hasBars]);
483
+ }, [data, yDomain, seriesKeys, hasBars, nice]);
458
484
 
459
485
  const loading = status === 'loading';
460
486
  // Nothing has said what the axis is yet — no mark has registered and no
@@ -603,11 +629,19 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
603
629
  * read as the chart arriving, rather than as four separate
604
630
  * things animating in at once — which is the thing a composed
605
631
  * chart is most at risk of looking like.
632
+ *
633
+ * `width` is set statically as well as animated, and it has
634
+ * to be. Animated props on an element inside `Defs` do not
635
+ * reach the native clip on every platform, and with the width
636
+ * coming only from the animation there is no width at all
637
+ * when they do not — an empty clip, and marks that never
638
+ * appear while the axes draw normally.
606
639
  */}
607
640
  <ClipPath id={clipId}>
608
641
  <AnimatedRect
609
642
  x={plot.left}
610
643
  y={0}
644
+ width={plot.width}
611
645
  height={size.height}
612
646
  animatedProps={clipProps}
613
647
  />
@@ -662,7 +696,7 @@ function useMark(dataKey: string, color: string | undefined, colorIndex: number)
662
696
 
663
697
  useEffect(() => {
664
698
  registerSeries(dataKey, resolved);
665
- return () => unregisterSeries(dataKey);
699
+ return () => unregisterSeries(dataKey, resolved);
666
700
  }, [dataKey, resolved, registerSeries, unregisterSeries]);
667
701
 
668
702
  return resolved;
@@ -759,28 +793,12 @@ function PlotLine({
759
793
  const max = domainMax.value;
760
794
  if (max === min || plot.width <= 0) return { d: '' };
761
795
 
762
- if (!banded) return { d: linePath(values, plot, min, max, curve, false) };
763
-
764
796
  /*
765
- * A banded line is built here rather than through `linePath`, which spreads
766
- * its points across the full width. On a band scale the points belong in
767
- * the middle of their own slice — a line that ignored that would start half
768
- * a slice left of the bar it is describing.
797
+ * `banded` moves the points to the middle of their own slice, which is
798
+ * where a line drawn over columns has to sit — spread edge to edge it
799
+ * starts half a slice left of the bar it is describing.
769
800
  */
770
- let d = '';
771
- let drawn = 0;
772
- for (let index = 0; index < values.length; index += 1) {
773
- const value = values[index];
774
- if (value === null || value === undefined) {
775
- drawn = 0;
776
- continue;
777
- }
778
- const x = bandOf(index, values.length, plot);
779
- const y = yOf(value, plot, min, max);
780
- d += `${drawn === 0 ? 'M' : 'L'}${x},${y}`;
781
- drawn += 1;
782
- }
783
- return { d };
801
+ return { d: linePath(values, plot, min, max, curve, false, undefined, banded) };
784
802
  });
785
803
 
786
804
  if (status === 'loading') return null;
@@ -820,15 +838,19 @@ function PlotArea({
820
838
  opacity = 0.18,
821
839
  curve = 'monotone',
822
840
  }: PlotAreaProps) {
823
- const { data, plot, domainMin, domainMax, status, clipId } = useChart('Plot.Area');
841
+ const { data, plot, xScale, domainMin, domainMax, status, clipId } =
842
+ useChart('Plot.Area');
824
843
  const fill = useMark(dataKey, color, colorIndex);
825
844
  const values = useMemo(() => columnValues(data, dataKey), [data, dataKey]);
845
+ const banded = xScale === 'band';
826
846
 
827
847
  const animatedProps = useAnimatedProps(() => {
828
848
  const min = domainMin.value;
829
849
  const max = domainMax.value;
830
850
  if (max === min || plot.width <= 0) return { d: '' };
831
- return { d: areaPath(values, plot, min, max, curve, false) };
851
+ // Band-aware for the same reason the line is: an area on a plot that also
852
+ // carries columns has to close over the same slices they occupy.
853
+ return { d: areaPath(values, plot, min, max, curve, false, undefined, banded) };
832
854
  });
833
855
 
834
856
  if (status === 'loading') return null;
@@ -848,6 +870,18 @@ export interface PlotBarsProps extends PlotSeriesProps {
848
870
  /** Rounds the end the bar grows towards, in points. */
849
871
  radius?: number;
850
872
  opacity?: number;
873
+ /**
874
+ * The value the columns grow from. Zero by default, and zero is nearly always
875
+ * right — a bar is a length, and a length has to start where the quantity
876
+ * does.
877
+ *
878
+ * Set it for the case where the reader is being shown movement rather than
879
+ * size: temperatures against a seasonal average, a score against a pass mark.
880
+ * Columns then run up and down from that line instead of all standing on the
881
+ * floor. It is clamped into the axis, so a baseline the domain does not cover
882
+ * falls back to the nearer edge.
883
+ */
884
+ baseline?: number;
851
885
  }
852
886
 
853
887
  /**
@@ -866,6 +900,7 @@ function PlotBars({
866
900
  gap = 0.35,
867
901
  radius = 4,
868
902
  opacity = 1,
903
+ baseline = 0,
869
904
  }: PlotBarsProps) {
870
905
  const { data, plot, domainMin, domainMax, status, clipId } = useChart('Plot.Bars');
871
906
  const fill = useMark(dataKey, color, colorIndex);
@@ -881,12 +916,12 @@ function PlotBars({
881
916
  if (max === min || plot.width <= 0 || total === 0) return { d: '' };
882
917
 
883
918
  /*
884
- * Bars grow from the baseline, which is the domain's zero where the domain
885
- * covers it and the nearer edge where it does not. A bar drawn from the
919
+ * Bars grow from the baseline — zero unless the caller moved it — clamped
920
+ * into the domain where the axis does not reach it. A bar drawn from the
886
921
  * bottom of a plot whose axis starts at 40 is a length that is not in the
887
922
  * data.
888
923
  */
889
- const base = yOf(Math.min(Math.max(0, min), max), plot, min, max);
924
+ const base = yOf(Math.min(Math.max(baseline, min), max), plot, min, max);
890
925
  let path = '';
891
926
 
892
927
  for (let index = 0; index < total; index += 1) {
@@ -1059,11 +1094,36 @@ PlotOverlay.displayName = 'Plot.Overlay';
1059
1094
  PlotOverlay.layer = 'overlay' as Layer;
1060
1095
 
1061
1096
  export interface PlotRuleProps {
1062
- /** Where to draw it, in the data's own units. */
1063
- y: number;
1097
+ /**
1098
+ * Where to draw it, in the data's own units. Omit it and pass `x` instead for
1099
+ * a rule down the plot rather than across it.
1100
+ */
1101
+ y?: number;
1102
+ /**
1103
+ * A row to draw a vertical rule at, by index — the release the numbers are
1104
+ * read against, the day a change landed.
1105
+ *
1106
+ * The x axis here carries positions rather than quantities, so this is which
1107
+ * row rather than what value. Exactly one of `y` and `x` is drawn; `y` wins
1108
+ * if both are given.
1109
+ */
1110
+ x?: number;
1064
1111
  /** A name for what the line means. Nothing is drawn without one. */
1065
1112
  label?: string;
1113
+ /** Overrides the line *and* its caption, so the two cannot drift apart. */
1066
1114
  color?: string;
1115
+ /** Thickness in points. */
1116
+ strokeWidth?: number;
1117
+ /**
1118
+ * Break the line into dashes, for a rule that should read as an annotation
1119
+ * rather than as a series the chart is drawing.
1120
+ */
1121
+ dashed?: boolean;
1122
+ /** Fades the line and its caption together. */
1123
+ opacity?: number;
1124
+ /** Which end of the rule the caption sits at. */
1125
+ labelPlacement?: 'start' | 'end';
1126
+ labelClassName?: string;
1067
1127
  className?: string;
1068
1128
  }
1069
1129
 
@@ -1071,15 +1131,34 @@ export interface PlotRuleProps {
1071
1131
  * A reference line across the plot — a target, a limit, an average.
1072
1132
  *
1073
1133
  * A view rather than an SVG line, so its label is real text and follows the
1074
- * theme. Its height is one point and its position is a transform, so it costs
1134
+ * theme. Its thickness is a border and its position is a transform, so it costs
1075
1135
  * no more than the SVG line would while gaining a legible caption.
1136
+ *
1137
+ * It is drawn at full strength in the foreground colour, and it is meant to be.
1138
+ * A reference line is the number the series is being judged against — a target
1139
+ * nobody can read is a target the chart is not actually stating — so what keeps
1140
+ * it from being mistaken for a series is that it is neutral and optionally
1141
+ * dashed, not that it is faint.
1076
1142
  */
1077
- function PlotRule({ y, label, color, className }: PlotRuleProps) {
1078
- const { plot, domainMin, domainMax, status } = useChart('Plot.Rule');
1079
- const token = useCSSVariable('--color-muted-foreground');
1143
+ function PlotRule({
1144
+ y,
1145
+ x,
1146
+ label,
1147
+ color,
1148
+ strokeWidth = 1,
1149
+ dashed = false,
1150
+ opacity = 1,
1151
+ labelPlacement = 'end',
1152
+ labelClassName,
1153
+ className,
1154
+ }: PlotRuleProps) {
1155
+ const { plot, domainMin, domainMax, status, data } = useChart('Plot.Rule');
1156
+ const token = useCSSVariable('--color-foreground');
1080
1157
  const stroke = color ?? (typeof token === 'string' ? token : '#888888');
1158
+ const vertical = y === undefined && x !== undefined;
1081
1159
 
1082
- const style = useAnimatedStyle(() => {
1160
+ const horizontalStyle = useAnimatedStyle(() => {
1161
+ if (vertical || y === undefined) return { opacity: 0 };
1083
1162
  const min = domainMin.value;
1084
1163
  const max = domainMax.value;
1085
1164
  if (max === min) return { opacity: 0 };
@@ -1087,23 +1166,92 @@ function PlotRule({ y, label, color, className }: PlotRuleProps) {
1087
1166
  // Off the top or bottom of the plot is out of the chart's range, and a rule
1088
1167
  // pinned to the edge would claim a value the axis does not cover.
1089
1168
  const inside = at >= plot.top - 0.5 && at <= plot.top + plot.height + 0.5;
1090
- return { opacity: inside ? 1 : 0, transform: [{ translateY: at }] };
1169
+ return { opacity: inside ? opacity : 0, transform: [{ translateY: at }] };
1091
1170
  });
1092
1171
 
1093
1172
  if (status === 'loading') return null;
1094
1173
 
1174
+ const line = dashed
1175
+ ? {
1176
+ borderColor: stroke,
1177
+ borderStyle: 'dashed' as const,
1178
+ borderTopWidth: strokeWidth,
1179
+ }
1180
+ : { backgroundColor: stroke, height: strokeWidth };
1181
+
1182
+ if (vertical) {
1183
+ /*
1184
+ * The x axis carries positions, not quantities, so a vertical rule is
1185
+ * placed from the row's own geometry rather than from the tweening domain —
1186
+ * there is nothing on this axis for it to tween against.
1187
+ */
1188
+ const total = data.length;
1189
+ const at =
1190
+ total > 0
1191
+ ? xOf(Math.max(0, Math.min(total - 1, x!)), total, plot)
1192
+ : plot.left + plot.width / 2;
1193
+
1194
+ return (
1195
+ <View
1196
+ pointerEvents="none"
1197
+ style={{
1198
+ position: 'absolute',
1199
+ left: at,
1200
+ top: plot.top,
1201
+ height: plot.height,
1202
+ opacity,
1203
+ }}
1204
+ className={className}
1205
+ >
1206
+ <View
1207
+ style={
1208
+ dashed
1209
+ ? {
1210
+ borderColor: stroke,
1211
+ borderStyle: 'dashed',
1212
+ borderLeftWidth: strokeWidth,
1213
+ flex: 1,
1214
+ }
1215
+ : { backgroundColor: stroke, width: strokeWidth, flex: 1 }
1216
+ }
1217
+ />
1218
+ {label ? (
1219
+ <Text
1220
+ size="xs"
1221
+ weight="medium"
1222
+ numberOfLines={1}
1223
+ style={color ? { color } : undefined}
1224
+ className={cn('absolute left-1 top-0', labelClassName)}
1225
+ >
1226
+ {label}
1227
+ </Text>
1228
+ ) : null}
1229
+ </View>
1230
+ );
1231
+ }
1232
+
1095
1233
  return (
1096
1234
  <Animated.View
1097
1235
  pointerEvents="none"
1098
1236
  style={[
1099
1237
  { position: 'absolute', left: plot.left, width: plot.width, top: 0 },
1100
- style,
1238
+ horizontalStyle,
1101
1239
  ]}
1102
1240
  className={className}
1103
1241
  >
1104
- <View style={{ height: 1, backgroundColor: stroke, opacity: 0.5 }} />
1242
+ <View style={line} />
1105
1243
  {label ? (
1106
- <Text size="xs" muted numberOfLines={1} className="self-end pt-0.5">
1244
+ <Text
1245
+ size="xs"
1246
+ weight="medium"
1247
+ numberOfLines={1}
1248
+ style={color ? { color } : undefined}
1249
+ className={cn(
1250
+ 'pt-0.5',
1251
+ labelPlacement === 'start' ? 'self-start' : 'self-end',
1252
+ labelClassName
1253
+ )}
1254
+ >
1107
1255
  {label}
1108
1256
  </Text>
1109
1257
  ) : null}
@@ -0,0 +1,41 @@
1
+ export interface PlotSeriesRegistration {
2
+ key: string;
3
+ colors: readonly string[];
4
+ }
5
+
6
+ export function registerPlotSeries(
7
+ current: readonly PlotSeriesRegistration[],
8
+ key: string,
9
+ color: string
10
+ ): PlotSeriesRegistration[] {
11
+ const existing = current.find((entry) => entry.key === key);
12
+ if (!existing) return [...current, { key, colors: [color] }];
13
+
14
+ return current.map((entry) =>
15
+ entry === existing ? { ...entry, colors: [...entry.colors, color] } : entry
16
+ );
17
+ }
18
+
19
+ export function unregisterPlotSeries(
20
+ current: readonly PlotSeriesRegistration[],
21
+ key: string,
22
+ color?: string
23
+ ): PlotSeriesRegistration[] {
24
+ const existing = current.find((entry) => entry.key === key);
25
+ if (!existing) return current as PlotSeriesRegistration[];
26
+
27
+ const index =
28
+ color === undefined ? existing.colors.length - 1 : existing.colors.lastIndexOf(color);
29
+ if (index < 0) return current as PlotSeriesRegistration[];
30
+
31
+ const colors = existing.colors.filter((_, candidate) => candidate !== index);
32
+ if (colors.length === 0) return current.filter((entry) => entry !== existing);
33
+
34
+ return current.map((entry) => (entry === existing ? { ...entry, colors } : entry));
35
+ }
36
+
37
+ export function visiblePlotSeries(
38
+ registrations: readonly PlotSeriesRegistration[]
39
+ ): [string, string][] {
40
+ return registrations.map(({ key, colors }) => [key, colors[colors.length - 1]!]);
41
+ }
@@ -198,7 +198,7 @@ const PostRoot = forwardRef<View, PostProps>(
198
198
 
199
199
  if (!onPress) {
200
200
  return (
201
- <View ref={ref} className={root({ className })} {...props}>
201
+ <View ref={ref} {...props} className={root({ className })}>
202
202
  {body}
203
203
  </View>
204
204
  );
@@ -210,10 +210,10 @@ const PostRoot = forwardRef<View, PostProps>(
210
210
  // as one button buries them.
211
211
  <AnimatedPressable
212
212
  ref={ref}
213
+ {...props}
213
214
  accessibilityRole="link"
214
215
  onPress={onPress}
215
216
  className={root({ className })}
216
- {...props}
217
217
  >
218
218
  {body}
219
219
  </AnimatedPressable>
@@ -681,7 +681,7 @@ const PostMedia = forwardRef<View, PostMediaProps>(
681
681
 
682
682
  if (!onPress) {
683
683
  return (
684
- <View ref={ref} className={media({ className })} style={{ aspectRatio }} {...props}>
684
+ <View ref={ref} {...props} className={media({ className })} style={{ aspectRatio }}>
685
685
  {picture}
686
686
  </View>
687
687
  );
@@ -690,12 +690,12 @@ const PostMedia = forwardRef<View, PostMediaProps>(
690
690
  return (
691
691
  <Pressable
692
692
  ref={ref}
693
+ {...props}
693
694
  accessibilityRole="imagebutton"
694
695
  accessibilityLabel={alt}
695
696
  onPress={onPress}
696
697
  className={media({ className })}
697
698
  style={{ aspectRatio }}
698
- {...props}
699
699
  >
700
700
  {picture}
701
701
  </Pressable>
@@ -36,6 +36,7 @@ import Animated, {
36
36
  import { tv, type VariantProps } from 'tailwind-variants';
37
37
  import { cn } from '../../utils/cn';
38
38
  import { Text, textChildren } from '../../primitives/text';
39
+ import { requestRadioValueChange } from './radio-group-value';
39
40
 
40
41
  type RadioVariant = 'dot' | 'card';
41
42
  type RadioOrientation = 'vertical' | 'horizontal';
@@ -247,7 +248,9 @@ const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
247
248
  accessibilityState={{ selected, disabled: !!disabled }}
248
249
  accessibilityLabel={label}
249
250
  disabled={disabled}
250
- onPress={() => context.onValueChange(value)}
251
+ onPress={() =>
252
+ requestRadioValueChange(context.value, value, context.onValueChange)
253
+ }
251
254
  className={slots.row({ className })}
252
255
  >
253
256
  {body}
@@ -0,0 +1,9 @@
1
+ export function requestRadioValueChange(
2
+ currentValue: string | undefined,
3
+ nextValue: string,
4
+ onValueChange: (value: string) => void
5
+ ) {
6
+ if (currentValue !== nextValue) {
7
+ onValueChange(nextValue);
8
+ }
9
+ }
@@ -43,6 +43,11 @@ import { StarIcon } from '../../icons';
43
43
  import { Text } from '../../primitives/text';
44
44
  import { cn } from '../../utils/cn';
45
45
  import { selectionTick } from '../../utils/haptics';
46
+ import {
47
+ normalizeRatingMax,
48
+ normalizeRatingPrecision,
49
+ normalizeRatingValue,
50
+ } from './rating-inputs';
46
51
 
47
52
  /** Springs the fill onto its resting value after a tap or the end of a drag. */
48
53
  const SPRING = { damping: 18, stiffness: 220, mass: 0.6 } as const;
@@ -197,8 +202,8 @@ export const Rating = forwardRef<View, RatingProps>(
197
202
  rowClassName,
198
203
  value: valueProp,
199
204
  defaultValue = 0,
200
- max = 5,
201
- precision = 1,
205
+ max: maxProp = 5,
206
+ precision: precisionProp = 1,
202
207
  onValueChange,
203
208
  onValueCommit,
204
209
  readOnly = false,
@@ -213,9 +218,11 @@ export const Rating = forwardRef<View, RatingProps>(
213
218
  },
214
219
  ref
215
220
  ) => {
221
+ const max = normalizeRatingMax(maxProp);
222
+ const precision = normalizeRatingPrecision(precisionProp);
216
223
  const isControlled = valueProp !== undefined;
217
- const [internal, setInternal] = useState(defaultValue);
218
- const value = clampJS(isControlled ? valueProp! : internal, 0, max);
224
+ const [internal, setInternal] = useState(() => normalizeRatingValue(defaultValue, max));
225
+ const value = normalizeRatingValue(isControlled ? valueProp : internal, max);
219
226
 
220
227
  const interactive = !readOnly && !disabled;
221
228
  const slots = ratingVariants({ size, disabled });
@@ -0,0 +1,19 @@
1
+ const DEFAULT_MAX = 5;
2
+ const DEFAULT_PRECISION = 1;
3
+
4
+ export function normalizeRatingMax(max: number | undefined): number {
5
+ return typeof max === 'number' && Number.isFinite(max) && max >= 1
6
+ ? Math.floor(max)
7
+ : DEFAULT_MAX;
8
+ }
9
+
10
+ export function normalizeRatingPrecision(precision: number | undefined): number {
11
+ return typeof precision === 'number' && Number.isFinite(precision) && precision > 0
12
+ ? precision
13
+ : DEFAULT_PRECISION;
14
+ }
15
+
16
+ export function normalizeRatingValue(value: number | undefined, max: number): number {
17
+ if (typeof value !== 'number' || !Number.isFinite(value)) return 0;
18
+ return Math.min(Math.max(value, 0), max);
19
+ }