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
@@ -0,0 +1,589 @@
1
+ /**
2
+ * Splitter — panes that share a container, with a seam between them the reader
3
+ * can drag.
4
+ *
5
+ * ```tsx
6
+ * <Splitter className="h-64" defaultLayout={[65, 35]}>
7
+ * <Splitter.Panel minSize={30}>{list}</Splitter.Panel>
8
+ * <Splitter.Handle />
9
+ * <Splitter.Panel minSize={20} collapsible>{detail}</Splitter.Panel>
10
+ * </Splitter>
11
+ * ```
12
+ *
13
+ * Sizes are percentages of the splitter, so a layout dragged in portrait is
14
+ * still the same layout in landscape. The splitter itself has no height of its
15
+ * own — give it one, or put it in something that does, or it collapses to
16
+ * nothing and takes its panes with it.
17
+ *
18
+ * The seams are drawn over the panes rather than between them. A handle that
19
+ * took layout space would have to be measured before the panes could be sized,
20
+ * which puts the whole layout downstream of a number nobody controls; floating
21
+ * it means the panes add up to exactly the container and the handle can be as
22
+ * wide a target as a finger needs without changing anything.
23
+ *
24
+ * Dragging runs on the UI thread. `onLayoutChange` fires when the seam is let
25
+ * go rather than on every frame, because a layout that round-trips through
26
+ * React sixty times a second is the one thing that makes this feel slow.
27
+ */
28
+ import {
29
+ Children,
30
+ createContext,
31
+ isValidElement,
32
+ useCallback,
33
+ useContext,
34
+ useEffect,
35
+ useMemo,
36
+ useRef,
37
+ useState,
38
+ type ReactElement,
39
+ type ReactNode,
40
+ } from 'react';
41
+ import {
42
+ View,
43
+ type AccessibilityActionEvent,
44
+ type LayoutChangeEvent,
45
+ type ViewProps,
46
+ } from 'react-native';
47
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
48
+ import Animated, {
49
+ runOnJS,
50
+ useAnimatedStyle,
51
+ useSharedValue,
52
+ type SharedValue,
53
+ } from 'react-native-reanimated';
54
+ import { tv } from 'tailwind-variants';
55
+ import { useDirectionSign } from '../../hooks/use-direction';
56
+ import { selectionTick } from '../../utils/haptics';
57
+ import {
58
+ isCollapsed,
59
+ layoutOffset,
60
+ normalizeConstraint,
61
+ resizeLayout,
62
+ resolveLayout,
63
+ type SplitterConstraint,
64
+ } from './splitter-math';
65
+
66
+ export type SplitterOrientation = 'horizontal' | 'vertical';
67
+
68
+ /**
69
+ * How thick the seam's touch target is, in points. Wider than the line it
70
+ * draws, because the line is a hairline and a hairline is not a target.
71
+ */
72
+ const HANDLE_THICKNESS = 24;
73
+
74
+ const splitterVariants = tv({
75
+ slots: {
76
+ root: 'relative overflow-hidden',
77
+ panel: 'overflow-hidden',
78
+ handle: 'absolute items-center justify-center',
79
+ line: 'bg-border',
80
+ grip: 'absolute rounded-full bg-muted-foreground/30',
81
+ },
82
+ variants: {
83
+ orientation: {
84
+ horizontal: {
85
+ // `w-full`, because the panes are shares of a width the splitter has to
86
+ // already know. Left to size itself, a row asks its children how wide
87
+ // they are and the children answer with a share of that answer — a
88
+ // circle that resolves to zero and takes the whole splitter with it.
89
+ // Inside anything that centres its children, that is what happens.
90
+ root: 'w-full flex-row',
91
+ // Over the pane that follows it, not under. A seam is drawn between two
92
+ // panes but written between them too, and a later sibling paints on top
93
+ // — which left the outer half of every touch target dead.
94
+ handle: 'bottom-0 start-0 top-0 z-10 w-6',
95
+ line: 'h-full w-px',
96
+ grip: 'h-8 w-1',
97
+ },
98
+ vertical: {
99
+ root: 'flex-col',
100
+ handle: 'end-0 start-0 top-0 z-10 h-6',
101
+ line: 'h-px w-full',
102
+ grip: 'h-1 w-8',
103
+ },
104
+ },
105
+ },
106
+ defaultVariants: {
107
+ orientation: 'horizontal',
108
+ },
109
+ });
110
+
111
+ interface SplitterContextValue {
112
+ orientation: SplitterOrientation;
113
+ /** Live percentages, one per panel. Written by drags, read by every pane. */
114
+ layout: SharedValue<number[]>;
115
+ /** The splitter's own size along its axis, in points. `0` until measured. */
116
+ available: SharedValue<number>;
117
+ /** Whether that measurement has landed. Panes size themselves by flex until it has. */
118
+ measured: boolean;
119
+ /** The last settled layout, for the props that have to be React state. */
120
+ settled: number[];
121
+ /** The layout the splitter resolved to before anybody dragged it. */
122
+ initial: number[];
123
+ constraints: SplitterConstraint[];
124
+ disabled: boolean;
125
+ step: number;
126
+ sign: 1 | -1;
127
+ commit: (next: number[]) => void;
128
+ }
129
+
130
+ const SplitterContext = createContext<SplitterContextValue | null>(null);
131
+ const PanelIndexContext = createContext(0);
132
+ const BoundaryIndexContext = createContext(-1);
133
+
134
+ function useSplitter(component: string): SplitterContextValue {
135
+ const context = useContext(SplitterContext);
136
+ if (!context) throw new Error(`${component} must be used within a <Splitter>`);
137
+ return context;
138
+ }
139
+
140
+ export interface SplitterProps extends ViewProps {
141
+ className?: string;
142
+ /** Which way the panes are laid out. Defaults to `horizontal`. */
143
+ orientation?: SplitterOrientation;
144
+ /**
145
+ * Controlled layout, as one percentage per panel. Pair it with
146
+ * `onLayoutChange`: a seam that is let go snaps back to this unless the value
147
+ * moves with it.
148
+ */
149
+ layout?: number[];
150
+ /**
151
+ * Starting layout when uncontrolled, as one percentage per panel. Panels left
152
+ * out of it fall back to their own `defaultSize`, and then to an even share.
153
+ */
154
+ defaultLayout?: number[];
155
+ /** Called with the new layout once a seam is let go, or stepped. */
156
+ onLayoutChange?: (layout: number[]) => void;
157
+ /** Freezes every seam. */
158
+ disabled?: boolean;
159
+ /** How far one accessibility step moves a seam, in percent. Defaults to `5`. */
160
+ step?: number;
161
+ }
162
+
163
+ function isElementOfType(child: ReactNode, type: unknown): child is ReactElement<Record<string, unknown>> {
164
+ return isValidElement(child) && child.type === type;
165
+ }
166
+
167
+ function SplitterRoot({
168
+ className,
169
+ orientation = 'horizontal',
170
+ layout: layoutProp,
171
+ defaultLayout,
172
+ onLayoutChange,
173
+ disabled = false,
174
+ step = 5,
175
+ children,
176
+ onLayout,
177
+ ...props
178
+ }: SplitterProps) {
179
+ const horizontal = orientation === 'horizontal';
180
+ const sign = useDirectionSign();
181
+ const { root } = splitterVariants({ orientation });
182
+
183
+ const items = Children.toArray(children);
184
+ const panels = items.filter((child) => isElementOfType(child, SplitterPanel)) as ReactElement<SplitterPanelProps>[];
185
+
186
+ // The panes' limits are read straight off their elements rather than
187
+ // registered from an effect. A layout that is only correct after the children
188
+ // have mounted and reported in is a layout that is wrong on the first frame,
189
+ // and this one is knowable before any of them render.
190
+ const constraintsKey = JSON.stringify(
191
+ panels.map((panel) => [
192
+ panel.props.minSize,
193
+ panel.props.maxSize,
194
+ panel.props.collapsible === true,
195
+ panel.props.collapsedSize,
196
+ panel.props.defaultSize,
197
+ ])
198
+ );
199
+ const constraints = useMemo(
200
+ () => panels.map((panel) => normalizeConstraint(panel.props)),
201
+ // eslint-disable-next-line react-hooks/exhaustive-deps
202
+ [constraintsKey]
203
+ );
204
+
205
+ const sizesKey = JSON.stringify([
206
+ layoutProp ?? null,
207
+ defaultLayout ?? null,
208
+ panels.map((panel) => panel.props.defaultSize ?? null),
209
+ ]);
210
+ const resolved = useMemo(() => {
211
+ const declared = layoutProp ?? defaultLayout;
212
+ return resolveLayout(
213
+ panels.map((panel, index) => declared?.[index] ?? panel.props.defaultSize),
214
+ constraints
215
+ );
216
+ // eslint-disable-next-line react-hooks/exhaustive-deps
217
+ }, [sizesKey, constraints]);
218
+
219
+ const layout = useSharedValue<number[]>(resolved);
220
+ const available = useSharedValue(0);
221
+ const [measured, setMeasured] = useState(false);
222
+ const [settled, setSettled] = useState(resolved);
223
+ // Bumped when a controlled splitter is let go, so a caller that ignored the
224
+ // change gets the seam put back where its props still say it is.
225
+ const [syncToken, setSyncToken] = useState(0);
226
+
227
+ const controlled = layoutProp !== undefined;
228
+ const controlledRef = useRef(controlled);
229
+ controlledRef.current = controlled;
230
+
231
+ useEffect(() => {
232
+ layout.value = resolved;
233
+ setSettled(resolved);
234
+ }, [resolved, syncToken, layout]);
235
+
236
+ const commit = useCallback(
237
+ (next: number[]) => {
238
+ if (controlledRef.current) setSyncToken((token) => token + 1);
239
+ else setSettled(next);
240
+ onLayoutChange?.(next);
241
+ },
242
+ [onLayoutChange]
243
+ );
244
+
245
+ const handleLayout = useCallback(
246
+ (event: LayoutChangeEvent) => {
247
+ const box = event.nativeEvent.layout;
248
+ const size = horizontal ? box.width : box.height;
249
+ available.value = size;
250
+ if (size > 0) setMeasured(true);
251
+ onLayout?.(event);
252
+ },
253
+ [available, horizontal, onLayout]
254
+ );
255
+
256
+ const context = useMemo<SplitterContextValue>(
257
+ () => ({
258
+ orientation,
259
+ layout,
260
+ available,
261
+ measured,
262
+ settled,
263
+ initial: resolved,
264
+ constraints,
265
+ disabled,
266
+ // A step of zero or less is a seam that cannot be moved without a drag,
267
+ // which is the one thing the accessibility actions exist to avoid.
268
+ step: Number.isFinite(step) && step > 0 ? step : 5,
269
+ sign,
270
+ commit,
271
+ }),
272
+ [
273
+ orientation,
274
+ layout,
275
+ available,
276
+ measured,
277
+ settled,
278
+ resolved,
279
+ constraints,
280
+ disabled,
281
+ step,
282
+ sign,
283
+ commit,
284
+ ]
285
+ );
286
+
287
+ // Panes learn their index and seams learn which pair they sit between from
288
+ // where they appear, so nothing has to be numbered by hand and a pane cannot
289
+ // be given the wrong one.
290
+ let panelIndex = -1;
291
+ const laidOut = items.map((child, index) => {
292
+ if (isElementOfType(child, SplitterPanel)) {
293
+ panelIndex += 1;
294
+ return (
295
+ <PanelIndexContext.Provider key={`panel-${panelIndex}`} value={panelIndex}>
296
+ {child}
297
+ </PanelIndexContext.Provider>
298
+ );
299
+ }
300
+ if (isElementOfType(child, SplitterHandle)) {
301
+ return (
302
+ <BoundaryIndexContext.Provider key={`handle-${index}`} value={panelIndex}>
303
+ {child}
304
+ </BoundaryIndexContext.Provider>
305
+ );
306
+ }
307
+ return child;
308
+ });
309
+
310
+ return (
311
+ <SplitterContext.Provider value={context}>
312
+ <View className={root({ className })} onLayout={handleLayout} {...props}>
313
+ {laidOut}
314
+ </View>
315
+ </SplitterContext.Provider>
316
+ );
317
+ }
318
+
319
+ export interface SplitterPanelProps extends ViewProps {
320
+ className?: string;
321
+ /** Starting share of the splitter, in percent. Unsized panes split the rest. */
322
+ defaultSize?: number;
323
+ /** Smallest share this pane may hold while open, in percent. Defaults to `10`. */
324
+ minSize?: number;
325
+ /** Largest share this pane may hold, in percent. Defaults to `100`. */
326
+ maxSize?: number;
327
+ /** Lets a drag past `minSize` shut the pane rather than stopping at it. */
328
+ collapsible?: boolean;
329
+ /** Share this pane holds while shut, in percent. Defaults to `0`. */
330
+ collapsedSize?: number;
331
+ }
332
+
333
+ /**
334
+ * One pane.
335
+ *
336
+ * It clips what is inside it, so a pane dragged narrower hides its content
337
+ * rather than pushing it into the pane beside it.
338
+ */
339
+ function SplitterPanel({ className, children, style, ...props }: SplitterPanelProps) {
340
+ const context = useSplitter('Splitter.Panel');
341
+ const index = useContext(PanelIndexContext);
342
+ const { panel } = splitterVariants({ orientation: context.orientation });
343
+ const horizontal = context.orientation === 'horizontal';
344
+
345
+ const animatedStyle = useAnimatedStyle(() => {
346
+ const size = (context.available.value * (context.layout.value[index] ?? 0)) / 100;
347
+ return horizontal ? { width: size } : { height: size };
348
+ }, [index, horizontal]);
349
+
350
+ // Nothing is measured on the first frame, and a pane sized from a container of
351
+ // zero is a pane nobody can see. Until the measurement lands, a definite flex
352
+ // basis of zero takes precedence over that width and the shares below carry
353
+ // the layout — so the first frame is already in proportion, and the switch to
354
+ // measured points changes nothing anybody can see.
355
+ const unmeasured = { flexGrow: context.settled[index] ?? 0, flexBasis: 0, flexShrink: 1 };
356
+
357
+ return (
358
+ <Animated.View
359
+ className={panel({ className })}
360
+ style={[context.measured ? null : unmeasured, style, animatedStyle]}
361
+ {...props}
362
+ >
363
+ {children}
364
+ </Animated.View>
365
+ );
366
+ }
367
+
368
+ export interface SplitterHandleProps extends ViewProps {
369
+ className?: string;
370
+ /** Freezes this seam on its own, leaving the others draggable. */
371
+ disabled?: boolean;
372
+ /** Draws the grip in the middle of the seam. Defaults to `true`. */
373
+ withGrip?: boolean;
374
+ /** What a screen reader calls the seam. Defaults to "Resize panels". */
375
+ accessibilityLabel?: string;
376
+ }
377
+
378
+ /**
379
+ * The seam between two panes.
380
+ *
381
+ * Put one between each pair. It is `adjustable` to a screen reader, so the
382
+ * layout can be moved a step at a time without a drag, and a double tap puts
383
+ * the pair back where it started — the fastest way out of a pane dragged shut
384
+ * by accident.
385
+ */
386
+ function SplitterHandle({
387
+ className,
388
+ disabled,
389
+ withGrip = true,
390
+ accessibilityLabel = 'Resize panels',
391
+ ...props
392
+ }: SplitterHandleProps) {
393
+ const context = useSplitter('Splitter.Handle');
394
+ const boundary = useContext(BoundaryIndexContext);
395
+ const { handle, line, grip } = splitterVariants({ orientation: context.orientation });
396
+ const horizontal = context.orientation === 'horizontal';
397
+ const frozen = context.disabled || disabled === true;
398
+
399
+ const { layout, available, constraints, sign, commit } = context;
400
+ const start = useSharedValue<number[]>([]);
401
+ const collapsed = useSharedValue(false);
402
+ const moved = useSharedValue(false);
403
+
404
+ const pan = useMemo(() => {
405
+ const gesture = Gesture.Pan()
406
+ .enabled(!frozen && boundary >= 0)
407
+ .onBegin(() => {
408
+ start.value = layout.value.slice();
409
+ moved.value = false;
410
+ const constraint = constraints[boundary];
411
+ collapsed.value = constraint
412
+ ? isCollapsed(layout.value[boundary] ?? 0, constraint)
413
+ : false;
414
+ })
415
+ .onUpdate((event) => {
416
+ if (available.value <= 0) return;
417
+ const travelled = horizontal ? event.translationX * sign : event.translationY;
418
+ const delta = (travelled / available.value) * 100;
419
+ const next = resizeLayout(start.value, boundary, delta, constraints);
420
+ moved.value = true;
421
+ layout.value = next;
422
+
423
+ // The one moment in a drag worth feeling: a pane arriving at shut, or
424
+ // leaving it. Both are a jump the finger did not make, so the tick is
425
+ // what says the jump was the control and not a stutter.
426
+ const constraint = constraints[boundary];
427
+ if (constraint) {
428
+ const shut = isCollapsed(next[boundary] ?? 0, constraint);
429
+ if (shut !== collapsed.value) {
430
+ collapsed.value = shut;
431
+ runOnJS(selectionTick)();
432
+ }
433
+ }
434
+ })
435
+ // A seam that was pressed and not moved has nothing to report, and a
436
+ // layout change nobody made is a re-render nobody asked for.
437
+ .onFinalize(() => {
438
+ if (moved.value) runOnJS(commit)(layout.value.slice());
439
+ });
440
+
441
+ /*
442
+ * A seam only answers to the axis it moves on. Without that it claims any
443
+ * movement at all — which is a splitter inside a scroller taking the
444
+ * scroll, and a drag with no way to fail, which is the one thing the double
445
+ * tap behind it is waiting for.
446
+ */
447
+ return horizontal
448
+ ? gesture.activeOffsetX([-8, 8]).failOffsetY([-16, 16])
449
+ : gesture.activeOffsetY([-8, 8]).failOffsetX([-16, 16]);
450
+ }, [
451
+ available,
452
+ boundary,
453
+ collapsed,
454
+ commit,
455
+ constraints,
456
+ frozen,
457
+ horizontal,
458
+ layout,
459
+ moved,
460
+ sign,
461
+ start,
462
+ ]);
463
+
464
+ const { initial } = context;
465
+ /**
466
+ * Puts this pair back to the proportions the splitter started with, without
467
+ * touching the panes further along — the two of them share exactly the room
468
+ * they already have between them.
469
+ */
470
+ const reset = useCallback(() => {
471
+ const pair = boundary >= 0 ? boundary : 0;
472
+ const next = layout.value.slice();
473
+ const room = (next[pair] ?? 0) + (next[pair + 1] ?? 0);
474
+ const first = initial[pair] ?? 0;
475
+ const span = first + (initial[pair + 1] ?? 0);
476
+ if (span <= 0) return;
477
+ next[pair] = (first / span) * room;
478
+ next[pair + 1] = room - next[pair]!;
479
+ layout.value = next;
480
+ commit(next);
481
+ }, [boundary, commit, initial, layout]);
482
+
483
+ const doubleTap = useMemo(
484
+ () =>
485
+ Gesture.Tap()
486
+ .numberOfTaps(2)
487
+ // A tap that has travelled this far is a drag, and saying so is what
488
+ // lets it give up on the movement rather than on a timer.
489
+ .maxDistance(HANDLE_THICKNESS / 2)
490
+ .enabled(!frozen && boundary >= 0)
491
+ .onEnd((_event, success) => {
492
+ if (success) runOnJS(reset)();
493
+ }),
494
+ [boundary, frozen, reset]
495
+ );
496
+
497
+ /*
498
+ * The drag comes first, and the order is the whole of it.
499
+ *
500
+ * A race gives its first gesture the priority and makes the rest wait for it
501
+ * to fail. With the tap in front, every drag was held back until the tap gave
502
+ * up — which a two-tap gesture only does once its window has expired, half a
503
+ * second after the finger lands. A slow first drag outlasted that and worked;
504
+ * the quicker one after it did not, and the seam ignored it.
505
+ *
506
+ * Round this way the pan activates on movement, and a press that never moves
507
+ * fails it and hands the touch on, which is all the tap was waiting for.
508
+ */
509
+ const gesture = useMemo(() => Gesture.Race(pan, doubleTap), [doubleTap, pan]);
510
+
511
+ const animatedStyle = useAnimatedStyle(() => {
512
+ if (available.value <= 0) return { opacity: 0 };
513
+ const seam = (available.value * layoutOffset(layout.value, boundary)) / 100;
514
+ const shift = seam - HANDLE_THICKNESS / 2;
515
+ return {
516
+ opacity: 1,
517
+ transform: horizontal ? [{ translateX: shift * sign }] : [{ translateY: shift }],
518
+ };
519
+ }, [boundary, horizontal, sign]);
520
+
521
+ // A step moves the seam along the layout, not along the screen, so it is the
522
+ // one piece of this that the reading direction does not touch: incrementing
523
+ // grows the pane that comes first either way.
524
+ const step = useCallback(
525
+ (direction: 1 | -1) => {
526
+ const next = resizeLayout(
527
+ layout.value.slice(),
528
+ boundary,
529
+ direction * context.step,
530
+ constraints
531
+ );
532
+ layout.value = next;
533
+ commit(next);
534
+ },
535
+ [boundary, commit, constraints, context.step, layout]
536
+ );
537
+
538
+ const onAccessibilityAction = useCallback(
539
+ (event: AccessibilityActionEvent) => {
540
+ if (event.nativeEvent.actionName === 'increment') step(1);
541
+ else if (event.nativeEvent.actionName === 'decrement') step(-1);
542
+ },
543
+ [step]
544
+ );
545
+
546
+ const now = Math.round(layoutOffset(context.settled, boundary));
547
+
548
+ return (
549
+ <GestureDetector gesture={gesture}>
550
+ <Animated.View
551
+ accessible
552
+ accessibilityRole="adjustable"
553
+ accessibilityLabel={accessibilityLabel}
554
+ accessibilityState={{ disabled: frozen }}
555
+ accessibilityValue={{ min: 0, max: 100, now }}
556
+ accessibilityActions={frozen ? undefined : [{ name: 'increment' }, { name: 'decrement' }]}
557
+ onAccessibilityAction={frozen ? undefined : onAccessibilityAction}
558
+ className={handle({ className })}
559
+ style={animatedStyle}
560
+ {...props}
561
+ >
562
+ <View className={line()} />
563
+ {withGrip ? <View className={grip()} /> : null}
564
+ </Animated.View>
565
+ </GestureDetector>
566
+ );
567
+ }
568
+
569
+ SplitterRoot.displayName = 'Splitter';
570
+ SplitterPanel.displayName = 'Splitter.Panel';
571
+ SplitterHandle.displayName = 'Splitter.Handle';
572
+
573
+ export const Splitter = Object.assign(SplitterRoot, {
574
+ Panel: SplitterPanel,
575
+ Handle: SplitterHandle,
576
+ });
577
+
578
+ export {
579
+ DEFAULT_MAX_SIZE,
580
+ DEFAULT_MIN_SIZE,
581
+ isCollapsed,
582
+ layoutOffset,
583
+ normalizeConstraint,
584
+ panelFloor,
585
+ resizeLayout,
586
+ resolveLayout,
587
+ type SplitterConstraint,
588
+ type SplitterConstraintInput,
589
+ } from './splitter-math';