@sincpro/web-ui 0.0.0-stage → 0.2.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 (376) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +184 -2
  3. package/dist/components/activity/Activity.d.ts +69 -0
  4. package/dist/components/activity/Activity.js +279 -0
  5. package/dist/components/activity/Messages.d.ts +66 -0
  6. package/dist/components/activity/Messages.js +46 -0
  7. package/dist/components/activity/Timeline.d.ts +21 -0
  8. package/dist/components/activity/Timeline.js +30 -0
  9. package/dist/components/activity/index.d.ts +3 -0
  10. package/dist/components/activity/index.js +4 -0
  11. package/dist/components/assist/AssistMark.d.ts +64 -0
  12. package/dist/components/assist/AssistMark.js +108 -0
  13. package/dist/components/assist/AssistPending.d.ts +24 -0
  14. package/dist/components/assist/AssistPending.js +26 -0
  15. package/dist/components/assist/AssistSuggestion.d.ts +42 -0
  16. package/dist/components/assist/AssistSuggestion.js +55 -0
  17. package/dist/components/assist/ChangeReview.d.ts +56 -0
  18. package/dist/components/assist/ChangeReview.js +121 -0
  19. package/dist/components/assist/assist.d.ts +77 -0
  20. package/dist/components/assist/assist.js +87 -0
  21. package/dist/components/assist/index.d.ts +5 -0
  22. package/dist/components/assist/index.js +7 -0
  23. package/dist/components/form/Form.d.ts +52 -0
  24. package/dist/components/form/Form.js +49 -0
  25. package/dist/components/form/Sheet.d.ts +47 -0
  26. package/dist/components/form/Sheet.js +90 -0
  27. package/dist/components/form/index.d.ts +2 -0
  28. package/dist/components/form/index.js +4 -0
  29. package/dist/components/frame/Chart.d.ts +13 -0
  30. package/dist/components/frame/Chart.js +24 -0
  31. package/dist/components/frame/FrameView.d.ts +33 -0
  32. package/dist/components/frame/FrameView.js +125 -0
  33. package/dist/components/frame/index.d.ts +3 -0
  34. package/dist/components/frame/index.js +3 -0
  35. package/dist/components/frame/model.d.ts +43 -0
  36. package/dist/components/frame/model.js +101 -0
  37. package/dist/components/index.d.ts +10 -0
  38. package/dist/components/index.js +11 -0
  39. package/dist/components/lines/LineItems.d.ts +111 -0
  40. package/dist/components/lines/LineItems.js +317 -0
  41. package/dist/components/lines/index.d.ts +1 -0
  42. package/dist/components/lines/index.js +2 -0
  43. package/dist/components/lines/lines.d.ts +23 -0
  44. package/dist/components/lines/lines.js +26 -0
  45. package/dist/components/listing/ColumnsMenu.d.ts +20 -0
  46. package/dist/components/listing/ColumnsMenu.js +46 -0
  47. package/dist/components/listing/GroupingLevels.d.ts +35 -0
  48. package/dist/components/listing/GroupingLevels.js +45 -0
  49. package/dist/components/listing/Listing.d.ts +27 -0
  50. package/dist/components/listing/Listing.js +86 -0
  51. package/dist/components/listing/ListingBar.d.ts +112 -0
  52. package/dist/components/listing/ListingBar.js +144 -0
  53. package/dist/components/listing/Pagination.d.ts +36 -0
  54. package/dist/components/listing/Pagination.js +69 -0
  55. package/dist/components/listing/RecordCard.d.ts +33 -0
  56. package/dist/components/listing/RecordCard.js +22 -0
  57. package/dist/components/listing/RecordLink.d.ts +7 -0
  58. package/dist/components/listing/RecordLink.js +22 -0
  59. package/dist/components/listing/Row.d.ts +110 -0
  60. package/dist/components/listing/Row.js +123 -0
  61. package/dist/components/listing/SortMenu.d.ts +62 -0
  62. package/dist/components/listing/SortMenu.js +105 -0
  63. package/dist/components/listing/ViewSwitcher.d.ts +24 -0
  64. package/dist/components/listing/ViewSwitcher.js +16 -0
  65. package/dist/components/listing/columns.d.ts +47 -0
  66. package/dist/components/listing/columns.js +72 -0
  67. package/dist/components/listing/grouping.d.ts +35 -0
  68. package/dist/components/listing/grouping.js +47 -0
  69. package/dist/components/listing/index.d.ts +17 -0
  70. package/dist/components/listing/index.js +20 -0
  71. package/dist/components/listing/modelColumns.d.ts +50 -0
  72. package/dist/components/listing/modelColumns.js +103 -0
  73. package/dist/components/listing/pages.d.ts +78 -0
  74. package/dist/components/listing/pages.js +135 -0
  75. package/dist/components/listing/selection.d.ts +21 -0
  76. package/dist/components/listing/selection.js +43 -0
  77. package/dist/components/listing/set.d.ts +48 -0
  78. package/dist/components/listing/set.js +24 -0
  79. package/dist/components/listing/tree.d.ts +28 -0
  80. package/dist/components/listing/tree.js +23 -0
  81. package/dist/components/navigation/CommandPalette.d.ts +40 -0
  82. package/dist/components/navigation/CommandPalette.js +114 -0
  83. package/dist/components/navigation/NavigationTree.d.ts +31 -0
  84. package/dist/components/navigation/NavigationTree.js +79 -0
  85. package/dist/components/navigation/commands.d.ts +30 -0
  86. package/dist/components/navigation/commands.js +30 -0
  87. package/dist/components/navigation/index.d.ts +3 -0
  88. package/dist/components/navigation/index.js +4 -0
  89. package/dist/components/record/Breadcrumb.d.ts +42 -0
  90. package/dist/components/record/Breadcrumb.js +98 -0
  91. package/dist/components/record/DirtyActions.d.ts +26 -0
  92. package/dist/components/record/DirtyActions.js +22 -0
  93. package/dist/components/record/Pager.d.ts +18 -0
  94. package/dist/components/record/Pager.js +33 -0
  95. package/dist/components/record/RecordHeader.d.ts +75 -0
  96. package/dist/components/record/RecordHeader.js +104 -0
  97. package/dist/components/record/RecordNotices.d.ts +37 -0
  98. package/dist/components/record/RecordNotices.js +75 -0
  99. package/dist/components/record/RecordSkeleton.d.ts +3 -0
  100. package/dist/components/record/RecordSkeleton.js +21 -0
  101. package/dist/components/record/SmartButtons.d.ts +35 -0
  102. package/dist/components/record/SmartButtons.js +38 -0
  103. package/dist/components/record/StatusBar.d.ts +51 -0
  104. package/dist/components/record/StatusBar.js +168 -0
  105. package/dist/components/record/ThumbBar.d.ts +36 -0
  106. package/dist/components/record/ThumbBar.js +86 -0
  107. package/dist/components/record/index.d.ts +6 -0
  108. package/dist/components/record/index.js +8 -0
  109. package/dist/components/search/CriteriaBuilder.d.ts +58 -0
  110. package/dist/components/search/CriteriaBuilder.js +278 -0
  111. package/dist/components/search/CriteriaChips.d.ts +22 -0
  112. package/dist/components/search/CriteriaChips.js +64 -0
  113. package/dist/components/search/FilterBuilder.d.ts +142 -0
  114. package/dist/components/search/FilterBuilder.js +421 -0
  115. package/dist/components/search/JoinWord.d.ts +8 -0
  116. package/dist/components/search/JoinWord.js +47 -0
  117. package/dist/components/search/JsonEditor.d.ts +19 -0
  118. package/dist/components/search/JsonEditor.js +78 -0
  119. package/dist/components/search/SearchBar.d.ts +78 -0
  120. package/dist/components/search/SearchBar.js +387 -0
  121. package/dist/components/search/criteria.d.ts +116 -0
  122. package/dist/components/search/criteria.js +159 -0
  123. package/dist/components/search/datePeriods.d.ts +122 -0
  124. package/dist/components/search/datePeriods.js +134 -0
  125. package/dist/components/search/dateRanges.d.ts +54 -0
  126. package/dist/components/search/dateRanges.js +89 -0
  127. package/dist/components/search/index.d.ts +9 -0
  128. package/dist/components/search/index.js +20 -0
  129. package/dist/components/search/model.d.ts +54 -0
  130. package/dist/components/search/model.js +37 -0
  131. package/dist/components/search/query.d.ts +166 -0
  132. package/dist/components/search/query.js +436 -0
  133. package/dist/components/shell/AppFrame.d.ts +14 -0
  134. package/dist/components/shell/AppFrame.js +80 -0
  135. package/dist/components/shell/TopBar.d.ts +73 -0
  136. package/dist/components/shell/TopBar.js +54 -0
  137. package/dist/components/shell/headerSlot.d.ts +17 -0
  138. package/dist/components/shell/headerSlot.js +21 -0
  139. package/dist/components/shell/index.d.ts +2 -0
  140. package/dist/components/shell/index.js +3 -0
  141. package/dist/foundations/aria.d.ts +14 -0
  142. package/dist/foundations/aria.js +18 -0
  143. package/dist/foundations/calendar.d.ts +28 -0
  144. package/dist/foundations/calendar.js +183 -0
  145. package/dist/foundations/copy.d.ts +703 -0
  146. package/dist/foundations/copy.es.d.ts +2 -0
  147. package/dist/foundations/copy.es.js +430 -0
  148. package/dist/foundations/copy.js +520 -0
  149. package/dist/foundations/density.d.ts +16 -0
  150. package/dist/foundations/density.js +21 -0
  151. package/dist/foundations/grouping.d.ts +281 -0
  152. package/dist/foundations/grouping.js +0 -0
  153. package/dist/foundations/index.d.ts +14 -0
  154. package/dist/foundations/index.js +30 -0
  155. package/dist/foundations/layout.d.ts +9 -0
  156. package/dist/foundations/layout.js +9 -0
  157. package/dist/foundations/pivot.d.ts +222 -0
  158. package/dist/foundations/pivot.js +198 -0
  159. package/dist/foundations/pivotSheet.d.ts +61 -0
  160. package/dist/foundations/pivotSheet.js +0 -0
  161. package/dist/foundations/slots.d.ts +67 -0
  162. package/dist/foundations/slots.js +63 -0
  163. package/dist/foundations/sticky.d.ts +53 -0
  164. package/dist/foundations/sticky.js +150 -0
  165. package/dist/foundations/surface.d.ts +9 -0
  166. package/dist/foundations/surface.js +26 -0
  167. package/dist/foundations/theme.d.ts +42 -0
  168. package/dist/foundations/theme.js +88 -0
  169. package/dist/foundations/types.d.ts +23 -0
  170. package/dist/foundations/types.js +50 -0
  171. package/dist/foundations/variants.d.ts +19 -0
  172. package/dist/foundations/variants.js +48 -0
  173. package/dist/foundations/xlsx.d.ts +86 -0
  174. package/dist/foundations/xlsx.js +0 -0
  175. package/dist/index.d.ts +19 -0
  176. package/dist/index.js +19 -0
  177. package/dist/primitives/block/Block.d.ts +66 -0
  178. package/dist/primitives/block/Block.js +34 -0
  179. package/dist/primitives/block/Line.d.ts +18 -0
  180. package/dist/primitives/block/Line.js +45 -0
  181. package/dist/primitives/block/SectionGrid.d.ts +30 -0
  182. package/dist/primitives/block/SectionGrid.js +29 -0
  183. package/dist/primitives/block/Steps.d.ts +76 -0
  184. package/dist/primitives/block/Steps.js +43 -0
  185. package/dist/primitives/block/index.d.ts +4 -0
  186. package/dist/primitives/block/index.js +7 -0
  187. package/dist/primitives/button/Button.d.ts +137 -0
  188. package/dist/primitives/button/Button.js +234 -0
  189. package/dist/primitives/button/index.d.ts +1 -0
  190. package/dist/primitives/button/index.js +2 -0
  191. package/dist/primitives/cell/CellStack.d.ts +47 -0
  192. package/dist/primitives/cell/CellStack.js +33 -0
  193. package/dist/primitives/cell/index.d.ts +1 -0
  194. package/dist/primitives/cell/index.js +3 -0
  195. package/dist/primitives/choice/ComboBox.d.ts +19 -0
  196. package/dist/primitives/choice/ComboBox.js +52 -0
  197. package/dist/primitives/choice/RadioGroup.d.ts +18 -0
  198. package/dist/primitives/choice/RadioGroup.js +50 -0
  199. package/dist/primitives/choice/SearchField.d.ts +33 -0
  200. package/dist/primitives/choice/SearchField.js +46 -0
  201. package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
  202. package/dist/primitives/choice/SegmentedControl.js +69 -0
  203. package/dist/primitives/choice/Slider.d.ts +16 -0
  204. package/dist/primitives/choice/Slider.js +46 -0
  205. package/dist/primitives/choice/Switch.d.ts +14 -0
  206. package/dist/primitives/choice/Switch.js +65 -0
  207. package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
  208. package/dist/primitives/choice/ThemeSwitcher.js +25 -0
  209. package/dist/primitives/choice/index.d.ts +8 -0
  210. package/dist/primitives/choice/index.js +11 -0
  211. package/dist/primitives/choice/options.d.ts +28 -0
  212. package/dist/primitives/choice/options.js +6 -0
  213. package/dist/primitives/date/Calendar.d.ts +31 -0
  214. package/dist/primitives/date/Calendar.js +134 -0
  215. package/dist/primitives/date/DatePicker.d.ts +42 -0
  216. package/dist/primitives/date/DatePicker.js +103 -0
  217. package/dist/primitives/date/DateTime.d.ts +35 -0
  218. package/dist/primitives/date/DateTime.js +100 -0
  219. package/dist/primitives/date/index.d.ts +3 -0
  220. package/dist/primitives/date/index.js +6 -0
  221. package/dist/primitives/dialog/Dialog.d.ts +118 -0
  222. package/dist/primitives/dialog/Dialog.js +121 -0
  223. package/dist/primitives/dialog/Drill.d.ts +90 -0
  224. package/dist/primitives/dialog/Drill.js +172 -0
  225. package/dist/primitives/dialog/index.d.ts +2 -0
  226. package/dist/primitives/dialog/index.js +4 -0
  227. package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
  228. package/dist/primitives/disclosure/Disclosure.js +52 -0
  229. package/dist/primitives/disclosure/index.d.ts +1 -0
  230. package/dist/primitives/disclosure/index.js +2 -0
  231. package/dist/primitives/field/Checks.d.ts +57 -0
  232. package/dist/primitives/field/Checks.js +72 -0
  233. package/dist/primitives/field/DropZone.d.ts +42 -0
  234. package/dist/primitives/field/DropZone.js +52 -0
  235. package/dist/primitives/field/Field.d.ts +178 -0
  236. package/dist/primitives/field/Field.js +251 -0
  237. package/dist/primitives/field/Suggest.d.ts +48 -0
  238. package/dist/primitives/field/Suggest.js +43 -0
  239. package/dist/primitives/field/aria.d.ts +61 -0
  240. package/dist/primitives/field/aria.js +70 -0
  241. package/dist/primitives/field/context.d.ts +30 -0
  242. package/dist/primitives/field/context.js +13 -0
  243. package/dist/primitives/field/copy.d.ts +16 -0
  244. package/dist/primitives/field/copy.js +69 -0
  245. package/dist/primitives/field/index.d.ts +9 -0
  246. package/dist/primitives/field/index.js +15 -0
  247. package/dist/primitives/field/layout.d.ts +24 -0
  248. package/dist/primitives/field/layout.js +10 -0
  249. package/dist/primitives/field/mode.d.ts +9 -0
  250. package/dist/primitives/field/mode.js +13 -0
  251. package/dist/primitives/field/note.d.ts +55 -0
  252. package/dist/primitives/field/note.js +117 -0
  253. package/dist/primitives/hint/Hint.d.ts +31 -0
  254. package/dist/primitives/hint/Hint.js +42 -0
  255. package/dist/primitives/hint/index.d.ts +1 -0
  256. package/dist/primitives/hint/index.js +1 -0
  257. package/dist/primitives/index.d.ts +20 -0
  258. package/dist/primitives/index.js +21 -0
  259. package/dist/primitives/menu/Menu.d.ts +128 -0
  260. package/dist/primitives/menu/Menu.js +89 -0
  261. package/dist/primitives/menu/index.d.ts +1 -0
  262. package/dist/primitives/menu/index.js +2 -0
  263. package/dist/primitives/notice/Callout.d.ts +49 -0
  264. package/dist/primitives/notice/Callout.js +28 -0
  265. package/dist/primitives/notice/Flag.d.ts +13 -0
  266. package/dist/primitives/notice/Flag.js +31 -0
  267. package/dist/primitives/notice/Notice.d.ts +45 -0
  268. package/dist/primitives/notice/Notice.js +61 -0
  269. package/dist/primitives/notice/Toast.d.ts +55 -0
  270. package/dist/primitives/notice/Toast.js +142 -0
  271. package/dist/primitives/notice/index.d.ts +4 -0
  272. package/dist/primitives/notice/index.js +6 -0
  273. package/dist/primitives/overlay/Popover.d.ts +6 -0
  274. package/dist/primitives/overlay/Popover.js +27 -0
  275. package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
  276. package/dist/primitives/overlay/PopoverTrigger.js +38 -0
  277. package/dist/primitives/overlay/Preview.d.ts +19 -0
  278. package/dist/primitives/overlay/Preview.js +60 -0
  279. package/dist/primitives/overlay/Tooltip.d.ts +38 -0
  280. package/dist/primitives/overlay/Tooltip.js +74 -0
  281. package/dist/primitives/overlay/index.d.ts +4 -0
  282. package/dist/primitives/overlay/index.js +7 -0
  283. package/dist/primitives/picker/Picker.d.ts +89 -0
  284. package/dist/primitives/picker/Picker.js +204 -0
  285. package/dist/primitives/picker/index.d.ts +1 -0
  286. package/dist/primitives/picker/index.js +2 -0
  287. package/dist/primitives/surface/Peek.d.ts +60 -0
  288. package/dist/primitives/surface/Peek.js +71 -0
  289. package/dist/primitives/surface/index.d.ts +1 -0
  290. package/dist/primitives/surface/index.js +2 -0
  291. package/dist/primitives/table/CellCopy.d.ts +20 -0
  292. package/dist/primitives/table/CellCopy.js +23 -0
  293. package/dist/primitives/table/CellEditor.d.ts +14 -0
  294. package/dist/primitives/table/CellEditor.js +83 -0
  295. package/dist/primitives/table/DataTable.d.ts +189 -0
  296. package/dist/primitives/table/DataTable.js +330 -0
  297. package/dist/primitives/table/EditableTable.d.ts +191 -0
  298. package/dist/primitives/table/EditableTable.js +617 -0
  299. package/dist/primitives/table/PivotTable.d.ts +14 -0
  300. package/dist/primitives/table/PivotTable.js +0 -0
  301. package/dist/primitives/table/ReportTable.d.ts +38 -0
  302. package/dist/primitives/table/ReportTable.js +106 -0
  303. package/dist/primitives/table/RowActions.d.ts +16 -0
  304. package/dist/primitives/table/RowActions.js +28 -0
  305. package/dist/primitives/table/SheetTable.d.ts +73 -0
  306. package/dist/primitives/table/SheetTable.js +230 -0
  307. package/dist/primitives/table/Table.d.ts +106 -0
  308. package/dist/primitives/table/Table.js +116 -0
  309. package/dist/primitives/table/index.d.ts +7 -0
  310. package/dist/primitives/table/index.js +9 -0
  311. package/dist/primitives/tabs/Tabs.d.ts +91 -0
  312. package/dist/primitives/tabs/Tabs.js +56 -0
  313. package/dist/primitives/tabs/index.d.ts +1 -0
  314. package/dist/primitives/tabs/index.js +1 -0
  315. package/dist/primitives/tag/Tag.d.ts +36 -0
  316. package/dist/primitives/tag/Tag.js +68 -0
  317. package/dist/primitives/tag/Tags.d.ts +57 -0
  318. package/dist/primitives/tag/Tags.js +104 -0
  319. package/dist/primitives/tag/TagsField.d.ts +35 -0
  320. package/dist/primitives/tag/TagsField.js +186 -0
  321. package/dist/primitives/tag/choices.d.ts +38 -0
  322. package/dist/primitives/tag/choices.js +26 -0
  323. package/dist/primitives/tag/index.d.ts +4 -0
  324. package/dist/primitives/tag/index.js +8 -0
  325. package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
  326. package/dist/primitives/toolbar/Toolbar.js +36 -0
  327. package/dist/primitives/toolbar/index.d.ts +1 -0
  328. package/dist/primitives/toolbar/index.js +2 -0
  329. package/dist/primitives/tree/Tree.d.ts +56 -0
  330. package/dist/primitives/tree/Tree.js +74 -0
  331. package/dist/primitives/tree/index.d.ts +1 -0
  332. package/dist/primitives/tree/index.js +3 -0
  333. package/dist/primitives/value/Copyable.d.ts +48 -0
  334. package/dist/primitives/value/Copyable.js +71 -0
  335. package/dist/primitives/value/Fecha.d.ts +12 -0
  336. package/dist/primitives/value/Fecha.js +89 -0
  337. package/dist/primitives/value/Meter.d.ts +29 -0
  338. package/dist/primitives/value/Meter.js +46 -0
  339. package/dist/primitives/value/Metric.d.ts +42 -0
  340. package/dist/primitives/value/Metric.js +38 -0
  341. package/dist/primitives/value/Person.d.ts +90 -0
  342. package/dist/primitives/value/Person.js +208 -0
  343. package/dist/primitives/value/ProgressBar.d.ts +31 -0
  344. package/dist/primitives/value/ProgressBar.js +53 -0
  345. package/dist/primitives/value/Relation.d.ts +74 -0
  346. package/dist/primitives/value/Relation.js +74 -0
  347. package/dist/primitives/value/Totals.d.ts +121 -0
  348. package/dist/primitives/value/Totals.js +54 -0
  349. package/dist/primitives/value/Value.d.ts +40 -0
  350. package/dist/primitives/value/Value.js +80 -0
  351. package/dist/primitives/value/index.d.ts +9 -0
  352. package/dist/primitives/value/index.js +10 -0
  353. package/dist/tokens.css +1062 -0
  354. package/dist/views/index.d.ts +2 -0
  355. package/dist/views/index.js +3 -0
  356. package/dist/views/list/List.d.ts +119 -0
  357. package/dist/views/list/List.js +101 -0
  358. package/dist/views/list/ListView.d.ts +6 -0
  359. package/dist/views/list/ListView.js +295 -0
  360. package/dist/views/list/columns.d.ts +18 -0
  361. package/dist/views/list/columns.js +27 -0
  362. package/dist/views/list/derive.d.ts +87 -0
  363. package/dist/views/list/derive.js +207 -0
  364. package/dist/views/list/index.d.ts +2 -0
  365. package/dist/views/list/index.js +4 -0
  366. package/dist/views/list/presentations.d.ts +39 -0
  367. package/dist/views/list/presentations.js +105 -0
  368. package/dist/views/list/types.d.ts +284 -0
  369. package/dist/views/list/types.js +1 -0
  370. package/dist/views/list/useNarrowList.d.ts +17 -0
  371. package/dist/views/list/useNarrowList.js +32 -0
  372. package/dist/views/record/Record.d.ts +145 -0
  373. package/dist/views/record/Record.js +211 -0
  374. package/dist/views/record/index.d.ts +1 -0
  375. package/dist/views/record/index.js +2 -0
  376. package/package.json +113 -4
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Which FIELDS of a set are on screen.
3
+ *
4
+ * Odoo lists split columns in two: fields without `optional` cannot leave the
5
+ * view; fields with `optional="show"|"hide"` appear in the columns submenu
6
+ * and can be added or removed. ListView and FrameView were inventing that
7
+ * menu each on their own — FrameView let every column hide, ListView had no
8
+ * menu at all — so the same question («may I hide this?») had two answers.
9
+ *
10
+ * This hook is the third knob of a SET, next to `useSetControl` (how I look)
11
+ * and `useSelection` (which rows). Presentation can change; the visible
12
+ * field set stays.
13
+ */
14
+ export type ColumnSpec = {
15
+ id: string;
16
+ label: string;
17
+ /** When true, the column may leave the view. Required columns stay, and
18
+ * the submenu shows them checked and disabled so the split is visible. */
19
+ optional?: boolean;
20
+ /** Odoo `optional="hide"`: start off. Only for optional columns. */
21
+ defaultHidden?: boolean;
22
+ };
23
+ export type ColumnControl = {
24
+ visibleIds: readonly string[];
25
+ isVisible: (id: string) => boolean;
26
+ isRequired: (id: string) => boolean;
27
+ toggle: (id: string) => void;
28
+ optional: readonly ColumnSpec[];
29
+ required: readonly ColumnSpec[];
30
+ hasOptional: boolean;
31
+ };
32
+ export declare function isRequiredColumn(column: ColumnSpec): boolean;
33
+ export declare function defaultVisibleIds(columns: readonly ColumnSpec[]): string[];
34
+ /**
35
+ * Required columns always stay. Unknown ids drop out. If everything optional
36
+ * was unchecked, the first column remains — a table with zero columns is not
37
+ * a table.
38
+ */
39
+ export declare function reconcileVisible(columns: readonly ColumnSpec[], visibleIds: readonly string[]): string[];
40
+ export declare function toggleColumn(columns: readonly ColumnSpec[], visibleIds: readonly string[], id: string): string[];
41
+ export declare function visibleColumns<T extends {
42
+ id: string;
43
+ }>(columns: readonly T[], visibleIds: readonly string[]): T[];
44
+ export declare function useColumnControl(columns: readonly ColumnSpec[], options?: {
45
+ visibleIds?: readonly string[];
46
+ onVisibleIdsChange?: (ids: readonly string[]) => void;
47
+ }): ColumnControl;
@@ -0,0 +1,72 @@
1
+ "use client";
2
+ import { useCallback, useState } from "react";
3
+ export function isRequiredColumn(column) {
4
+ return column.optional !== true;
5
+ }
6
+ export function defaultVisibleIds(columns) {
7
+ return columns
8
+ .filter((column) => isRequiredColumn(column) || column.defaultHidden !== true)
9
+ .map((column) => column.id);
10
+ }
11
+ /**
12
+ * Required columns always stay. Unknown ids drop out. If everything optional
13
+ * was unchecked, the first column remains — a table with zero columns is not
14
+ * a table.
15
+ */
16
+ export function reconcileVisible(columns, visibleIds) {
17
+ if (columns.length === 0)
18
+ return [];
19
+ const known = new Set(columns.map((column) => column.id));
20
+ const visible = new Set(visibleIds.filter((id) => known.has(id)));
21
+ for (const column of columns) {
22
+ if (isRequiredColumn(column))
23
+ visible.add(column.id);
24
+ }
25
+ if (visible.size === 0)
26
+ visible.add(columns[0].id);
27
+ return columns.filter((column) => visible.has(column.id)).map((column) => column.id);
28
+ }
29
+ export function toggleColumn(columns, visibleIds, id) {
30
+ const column = columns.find((one) => one.id === id);
31
+ if (column === undefined || isRequiredColumn(column)) {
32
+ return reconcileVisible(columns, visibleIds);
33
+ }
34
+ const current = reconcileVisible(columns, visibleIds);
35
+ const next = current.includes(id) ? current.filter((one) => one !== id) : [...current, id];
36
+ return reconcileVisible(columns, next);
37
+ }
38
+ export function visibleColumns(columns, visibleIds) {
39
+ const wanted = new Set(visibleIds);
40
+ return columns.filter((column) => wanted.has(column.id));
41
+ }
42
+ export function useColumnControl(columns, options = {}) {
43
+ const signature = columns.map((column) => column.id).join("\0");
44
+ const [owned, setOwned] = useState(() => defaultVisibleIds(columns));
45
+ const [seen, setSeen] = useState(signature);
46
+ const controlled = options.onVisibleIdsChange !== undefined;
47
+ if (signature !== seen) {
48
+ setSeen(signature);
49
+ if (!controlled)
50
+ setOwned(defaultVisibleIds(columns));
51
+ }
52
+ const raw = controlled ? (options.visibleIds ?? defaultVisibleIds(columns)) : owned;
53
+ const visibleIds = reconcileVisible(columns, raw);
54
+ const setVisible = controlled ? options.onVisibleIdsChange : setOwned;
55
+ const toggle = useCallback((id) => {
56
+ setVisible(toggleColumn(columns, visibleIds, id));
57
+ }, [columns, visibleIds, setVisible]);
58
+ const optional = columns.filter((column) => column.optional === true);
59
+ const required = columns.filter((column) => isRequiredColumn(column));
60
+ return {
61
+ visibleIds,
62
+ isVisible: (id) => visibleIds.includes(id),
63
+ isRequired: (id) => {
64
+ const column = columns.find((one) => one.id === id);
65
+ return column === undefined ? true : isRequiredColumn(column);
66
+ },
67
+ toggle,
68
+ optional,
69
+ required,
70
+ hasOptional: optional.length > 0,
71
+ };
72
+ }
@@ -0,0 +1,35 @@
1
+ import { type GroupGrain, type GroupLevel } from "../../foundations/grouping.js";
2
+ /**
3
+ * LOS NIVELES, como estado que alguien edita.
4
+ *
5
+ * El vocabulario —qué es un nivel, qué es un montón, cómo se identifica— vive en
6
+ * `system/grouping.ts`, porque lo hablan las DOS capas: la lista arma el árbol y
7
+ * la tabla lo dibuja, y una primitiva no puede importar de un contexto. Acá
8
+ * queda sólo el CONTROL, que es de la lista como lo son el de columnas y el de
9
+ * plegado.
10
+ */
11
+ /** Los niveles puestos, y las cuatro cosas que se les hacen. El orden ES la
12
+ * jerarquía: mover un nivel de lugar cambia por qué se parte primero. */
13
+ export type GroupingControl = {
14
+ levels: readonly GroupLevel[];
15
+ /**
16
+ * Pone un nivel. Un campo de fecha puede estar puesto con VARIOS granos —«por
17
+ * mes y adentro por día»— así que lo que no se duplica es el par campo+grano,
18
+ * no el campo. Agrupar dos veces por exactamente lo mismo parte en montones de
19
+ * uno, y eso siempre es un error de tipeo.
20
+ */
21
+ add: (field: string, grain?: GroupGrain) => void;
22
+ remove: (field: string, grain?: GroupGrain) => void;
23
+ /** Tildar y destildar en un gesto, que es lo que hace una casilla. */
24
+ toggle: (field: string, grain?: GroupGrain) => void;
25
+ has: (field: string, grain?: GroupGrain) => boolean;
26
+ /** Mueve un nivel `delta` lugares. Se queda en los bordes en vez de dar la
27
+ * vuelta: llegar al primero y volver al último no es lo que nadie espera de
28
+ * una flecha «subir». */
29
+ move: (field: string, delta: number) => void;
30
+ clear: () => void;
31
+ };
32
+ export declare function useGroupingControl(options?: {
33
+ levels?: readonly GroupLevel[];
34
+ onLevelsChange?: (levels: readonly GroupLevel[]) => void;
35
+ }): GroupingControl;
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import { levelKey, moveLevel, normalizeLevels, } from "../../foundations/grouping.js";
3
+ import { useCallback, useState } from "react";
4
+ // One empty array for every render: a fresh `[]` would change the callbacks below each time.
5
+ const NO_LEVELS = [];
6
+ export function useGroupingControl(options = {}) {
7
+ const [owned, setOwned] = useState(options.levels ?? NO_LEVELS);
8
+ const controlled = options.onLevelsChange !== undefined;
9
+ const levels = controlled ? (options.levels ?? NO_LEVELS) : owned;
10
+ const setLevels = controlled ? options.onLevelsChange : setOwned;
11
+ const keyOf = (field, grain) => levelKey(grain === undefined ? { field } : { field, grain });
12
+ const has = useCallback((field, grain) => levels.some((one) => levelKey(one) === keyOf(field, grain)), [levels]);
13
+ const add = useCallback((field, grain) => {
14
+ if (levels.some((one) => levelKey(one) === keyOf(field, grain)))
15
+ return;
16
+ /* Se normaliza SIEMPRE y no sólo al agregar fechas: es la única forma de
17
+ que tildar «día» y después «mes» dé «mes › día» sin que quien tilda
18
+ tenga que saber en qué orden hacerlo. */
19
+ setLevels(normalizeLevels([...levels, grain === undefined ? { field } : { field, grain }]));
20
+ }, [levels, setLevels]);
21
+ const remove = useCallback((field, grain) => {
22
+ setLevels(grain === undefined && !levels.some((one) => levelKey(one) === field)
23
+ ? /* Sin grano y sin coincidencia exacta: se saca el campo ENTERO, con
24
+ todos sus granos. Es lo que quiere decir la × de un campo de
25
+ fecha que está puesto por mes y por día. */
26
+ levels.filter((one) => one.field !== field)
27
+ : levels.filter((one) => levelKey(one) !== keyOf(field, grain)));
28
+ }, [levels, setLevels]);
29
+ return {
30
+ levels,
31
+ has,
32
+ add,
33
+ remove,
34
+ toggle: useCallback((field, grain) => {
35
+ if (levels.some((one) => levelKey(one) === keyOf(field, grain)))
36
+ remove(field, grain);
37
+ else
38
+ add(field, grain);
39
+ }, [levels, add, remove]),
40
+ move: useCallback((field, delta) => {
41
+ setLevels(moveLevel(levels, field, delta));
42
+ }, [levels, setLevels]),
43
+ clear: useCallback(() => {
44
+ setLevels([]);
45
+ }, [setLevels]),
46
+ };
47
+ }
@@ -0,0 +1,17 @@
1
+ export { type ColumnControl, type ColumnSpec, defaultVisibleIds, reconcileVisible, toggleColumn, useColumnControl, visibleColumns, } from "./columns.js";
2
+ export { ColumnsMenu, specsFromColumns, } from "./ColumnsMenu.js";
3
+ export { Listing } from "./Listing.js";
4
+ export { type ListIdentity, ListingBar } from "./ListingBar.js";
5
+ export { type ClaimedField, claimedFields, columnsFromModel, readModelValue, } from "./modelColumns.js";
6
+ export { type CursorSource, DEFAULT_PAGE_SIZE, PAGE_SIZES, type Pages, type PaginationBridge, type ServerPage, useCursorPagination, usePagination, } from "./pages.js";
7
+ export { Pagination } from "./Pagination.js";
8
+ export { RecordLink } from "./RecordLink.js";
9
+ export { GroupHeader, Row } from "./Row.js";
10
+ export { type Selection, useSelection } from "./selection.js";
11
+ export { type SetControl, useSetControl } from "./set.js";
12
+ export { toggleCollapsed, type TreeControl, useTreeControl, } from "./tree.js";
13
+ export { type ViewOption, ViewSwitcher, } from "./ViewSwitcher.js";
14
+ export { type GroupingControl, useGroupingControl, } from "./grouping.js";
15
+ export { GroupingLevels, type GroupingLevelsProps, } from "./GroupingLevels.js";
16
+ export { RecordCard } from "./RecordCard.js";
17
+ export { type SortField, SortMenu, type SortMenuProps, } from "./SortMenu.js";
@@ -0,0 +1,20 @@
1
+ /* LISTING — the pieces a collection of N records is drawn with: rows, columns, paging,
2
+ sorting, grouping. The `List` view composes them; any other component may reuse them. */
3
+ export { defaultVisibleIds, reconcileVisible, toggleColumn, useColumnControl, visibleColumns, } from "./columns.js";
4
+ export { ColumnsMenu, specsFromColumns, } from "./ColumnsMenu.js";
5
+ export { Listing } from "./Listing.js";
6
+ export { ListingBar } from "./ListingBar.js";
7
+ export { claimedFields, columnsFromModel, readModelValue, } from "./modelColumns.js";
8
+ export { DEFAULT_PAGE_SIZE, PAGE_SIZES, useCursorPagination, usePagination, } from "./pages.js";
9
+ export { Pagination } from "./Pagination.js";
10
+ export { RecordLink } from "./RecordLink.js";
11
+ export { GroupHeader, Row } from "./Row.js";
12
+ export { useSelection } from "./selection.js";
13
+ export { useSetControl } from "./set.js";
14
+ export { toggleCollapsed, useTreeControl, } from "./tree.js";
15
+ export { ViewSwitcher, } from "./ViewSwitcher.js";
16
+ /* El control de los niveles. El vocabulario está en `system/grouping.ts`. */
17
+ export { useGroupingControl, } from "./grouping.js";
18
+ export { GroupingLevels, } from "./GroupingLevels.js";
19
+ export { RecordCard } from "./RecordCard.js";
20
+ export { SortMenu, } from "./SortMenu.js";
@@ -0,0 +1,50 @@
1
+ import { type ModelDefinition } from "../search/model.js";
2
+ import type { DataColumn } from "../../primitives/table/DataTable.js";
3
+ /** Qué campo del modelo muestra una columna declarada a mano.
4
+ *
5
+ * Sin esto, «filas» y `row_count` son dos cosas distintas y el campo
6
+ * aparecería dos veces: una con el rótulo bueno y otra cruda. */
7
+ export type ClaimedField = {
8
+ id: string;
9
+ field?: string;
10
+ };
11
+ /** Los campos que las columnas declaradas ya contestan.
12
+ *
13
+ * in [{id:"rows", field:"row_count"}, {id:"name"}]
14
+ * out Set {"row_count", "name"}
15
+ *
16
+ * El `id` cuenta como reclamo cuando coincide con el nombre del campo: la
17
+ * mayoría de las columnas se llaman como lo que muestran. */
18
+ export declare function claimedFields(columns: readonly ClaimedField[]): Set<string>;
19
+ /** Un valor del registro, en algo que una celda pueda ordenar y dibujar.
20
+ *
21
+ * in ["edad","sexo"] un `text[]`
22
+ * out "edad, sexo"
23
+ * in [] una lista vacía no es un texto vacío
24
+ * out null la tabla dibuja «—»
25
+ * in "2026-09-13T04:17:00"
26
+ * out "2026-09-13T04:17:00"
27
+ *
28
+ * Las fechas se dejan como vienen —ISO— porque en ISO el orden alfabético ES
29
+ * el cronológico, y así la columna ordena bien sin parsear nada. */
30
+ export declare function readModelValue(raw: unknown): string | number | boolean | null;
31
+ /**
32
+ * Las columnas declaradas, más una por cada campo del modelo que ninguna
33
+ * contestaba.
34
+ *
35
+ * in model el `model_meta_data` de `account.move` — 12 campos
36
+ * in declared 6 columnas escritas a mano en la vista
37
+ * out las 6, y detrás `journal_id`, `currency_id`, `invoice_origin`,
38
+ * `payment_reference`, `fiscal_position_id`, `company_id`,
39
+ * `move_type`, todas apagadas
40
+ *
41
+ * El rótulo de las agregadas sale de `model.translations` en el idioma pedido —el mismo
42
+ * `translations()` que el modelo publica para todo lo demás—, con el `default` que ese mismo
43
+ * modelo declaró si el idioma no está, y el nombre del campo tal cual sólo si el modelo no
44
+ * dijo ninguna palabra sobre él. Nunca puede desincronizarse con el motor porque sale de la
45
+ * MISMA respuesta que nombra el campo — a diferencia de un diccionario mantenido acá.
46
+ *
47
+ * `model === null` —todavía no llegó la definición— devuelve las declaradas sin
48
+ * tocar, que es exactamente lo que había antes.
49
+ */
50
+ export declare function columnsFromModel<T extends object>(declared: readonly DataColumn<T>[], model: ModelDefinition | null | undefined, locale?: string): DataColumn<T>[];
@@ -0,0 +1,103 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { fieldLabel } from "../search/model.js";
3
+ /** Los campos que las columnas declaradas ya contestan.
4
+ *
5
+ * in [{id:"rows", field:"row_count"}, {id:"name"}]
6
+ * out Set {"row_count", "name"}
7
+ *
8
+ * El `id` cuenta como reclamo cuando coincide con el nombre del campo: la
9
+ * mayoría de las columnas se llaman como lo que muestran. */
10
+ export function claimedFields(columns) {
11
+ const claimed = new Set();
12
+ for (const column of columns) {
13
+ claimed.add(column.field ?? column.id);
14
+ if (column.field !== undefined)
15
+ claimed.add(column.id);
16
+ }
17
+ return claimed;
18
+ }
19
+ /** Un valor del registro, en algo que una celda pueda ordenar y dibujar.
20
+ *
21
+ * in ["edad","sexo"] un `text[]`
22
+ * out "edad, sexo"
23
+ * in [] una lista vacía no es un texto vacío
24
+ * out null la tabla dibuja «—»
25
+ * in "2026-09-13T04:17:00"
26
+ * out "2026-09-13T04:17:00"
27
+ *
28
+ * Las fechas se dejan como vienen —ISO— porque en ISO el orden alfabético ES
29
+ * el cronológico, y así la columna ordena bien sin parsear nada. */
30
+ export function readModelValue(raw) {
31
+ if (raw === null || raw === undefined)
32
+ return null;
33
+ if (Array.isArray(raw))
34
+ return raw.length === 0 ? null : raw.join(", ");
35
+ if (typeof raw === "number" || typeof raw === "boolean")
36
+ return raw;
37
+ if (typeof raw === "string")
38
+ return raw === "" ? null : raw;
39
+ return JSON.stringify(raw);
40
+ }
41
+ /** Cuánto ancho merece un campo, por su tipo. Una huella son 64 caracteres y
42
+ * una cuenta son seis: darles el mismo ancho arruina las dos. */
43
+ function widthFor(type) {
44
+ if (type === "integer" || type === "number")
45
+ return 120;
46
+ if (type === "datetime" || type === "date")
47
+ return 190;
48
+ if (type === "boolean")
49
+ return 90;
50
+ return 260;
51
+ }
52
+ /**
53
+ * Las columnas declaradas, más una por cada campo del modelo que ninguna
54
+ * contestaba.
55
+ *
56
+ * in model el `model_meta_data` de `account.move` — 12 campos
57
+ * in declared 6 columnas escritas a mano en la vista
58
+ * out las 6, y detrás `journal_id`, `currency_id`, `invoice_origin`,
59
+ * `payment_reference`, `fiscal_position_id`, `company_id`,
60
+ * `move_type`, todas apagadas
61
+ *
62
+ * El rótulo de las agregadas sale de `model.translations` en el idioma pedido —el mismo
63
+ * `translations()` que el modelo publica para todo lo demás—, con el `default` que ese mismo
64
+ * modelo declaró si el idioma no está, y el nombre del campo tal cual sólo si el modelo no
65
+ * dijo ninguna palabra sobre él. Nunca puede desincronizarse con el motor porque sale de la
66
+ * MISMA respuesta que nombra el campo — a diferencia de un diccionario mantenido acá.
67
+ *
68
+ * `model === null` —todavía no llegó la definición— devuelve las declaradas sin
69
+ * tocar, que es exactamente lo que había antes.
70
+ */
71
+ export function columnsFromModel(declared, model, locale = "default") {
72
+ if (model === null || model === undefined)
73
+ return [...declared];
74
+ const claimed = claimedFields(declared);
75
+ const extra = [];
76
+ for (const [field, definition] of Object.entries(model.fields)) {
77
+ if (claimed.has(field))
78
+ continue;
79
+ const numeric = definition.type === "integer" || definition.type === "number";
80
+ extra.push({
81
+ id: field,
82
+ field,
83
+ header: fieldLabel(model, field, locale),
84
+ value: (row) => readModelValue(row[field]),
85
+ /* El valor entero en el `title`: una huella de 64 caracteres no entra en
86
+ ninguna celda, y cortada no sirve para nada — salvo para pasar el mouse
87
+ por encima y leerla. */
88
+ cell: (row) => {
89
+ const value = readModelValue(row[field]);
90
+ if (value === null)
91
+ return _jsx("span", { className: "text-subtle", children: "\u2014" });
92
+ const text = String(value);
93
+ return (_jsx("span", { className: "block truncate text-muted", title: text, children: text }));
94
+ },
95
+ ...(numeric ? { numeric: true } : {}),
96
+ sortable: definition.sortable,
97
+ optional: true,
98
+ defaultHidden: true,
99
+ width: widthFor(definition.type),
100
+ });
101
+ }
102
+ return [...declared, ...extra];
103
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * How many records a list shows until someone asks for another size.
3
+ *
4
+ * 80 is the product default: enough to scan a catalog without paging every
5
+ * twenty-five rows, still small enough that a table stays a table. 25 / 80 / 100
6
+ * are the presets; any other positive integer is valid and is kept.
7
+ */
8
+ export declare const DEFAULT_PAGE_SIZE = 80;
9
+ export declare const PAGE_SIZES: readonly [25, 80, 100];
10
+ /** What `Pagination` needs, already computed from a set in memory. */
11
+ export interface PaginationBridge {
12
+ from: number;
13
+ to: number;
14
+ total: number;
15
+ pageSize: number;
16
+ onPageSizeChange: (next: number) => void;
17
+ onPrev?: () => void;
18
+ onNext?: () => void;
19
+ }
20
+ export interface Pages<Row> {
21
+ pageItems: Row[];
22
+ bridge: PaginationBridge;
23
+ }
24
+ /**
25
+ * Cut an in-memory set into pages.
26
+ *
27
+ * `pageSize` is controlled when `onPageSizeChange` is passed — that is how a
28
+ * view context keeps the size across filters. Without it the hook owns the
29
+ * size, defaulting to 80.
30
+ *
31
+ * The current page is clamped in the calculation, not in an effect: if a
32
+ * filter leaves fewer pages than the current one, the next read already
33
+ * lands on the last page that exists.
34
+ */
35
+ export declare function usePagination<Row>(items: readonly Row[], options?: {
36
+ pageSize?: number;
37
+ onPageSizeChange?: (next: number) => void;
38
+ }): Pages<Row>;
39
+ /** What a server answered about a page: the records, where the next one starts, how many
40
+ * match in total. `exact: false` on the count means *at least* that many — a server that
41
+ * stops counting at a ceiling so its cost does not grow with the table. */
42
+ export interface ServerPage<Row> {
43
+ items: readonly Row[];
44
+ cursor: string | null;
45
+ count: {
46
+ value: number;
47
+ exact: boolean;
48
+ } | null;
49
+ }
50
+ /** What a cursor-paged list needs from whoever fetches it. */
51
+ export interface CursorSource<Row> {
52
+ /** The page at this cursor. `undefined` asks for the first one. */
53
+ fetch: (cursor: string | undefined, pageSize: number) => Promise<ServerPage<Row>>;
54
+ /** Anything that changes WHICH records are being paged — a filter, an ordering. Pagination
55
+ * starts over when this changes, because page 3 of another reading is not page 3. */
56
+ resetKey?: unknown;
57
+ }
58
+ /**
59
+ * Cut a *server-side* set into pages, by cursor.
60
+ *
61
+ * `usePagination` slices an array that is already in hand; this one never holds the whole set.
62
+ * Same `bridge`, so `Pagination` draws it identically — the difference is invisible to a view.
63
+ *
64
+ * **Going back is a walk, not a jump.** A keyset cursor names the last row seen, so it points
65
+ * forward and nothing points back. Rather than fake it with an offset — which would reintroduce
66
+ * exactly the incoherence the cursor exists to avoid — the cursors already walked are kept, and
67
+ * `onPrev` re-fetches with the one before. That is one request, not a re-walk from the start.
68
+ *
69
+ * **Changing the size starts over**, and that is not a limitation: a cursor belongs to the page
70
+ * size it was minted under, so keeping it would land somewhere nobody asked for.
71
+ */
72
+ export declare function useCursorPagination<Row>(source: CursorSource<Row>, options?: {
73
+ pageSize?: number;
74
+ onPageSizeChange?: (next: number) => void;
75
+ }): Pages<Row> & {
76
+ loading: boolean;
77
+ error: unknown;
78
+ };
@@ -0,0 +1,135 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ /**
4
+ * How many records a list shows until someone asks for another size.
5
+ *
6
+ * 80 is the product default: enough to scan a catalog without paging every
7
+ * twenty-five rows, still small enough that a table stays a table. 25 / 80 / 100
8
+ * are the presets; any other positive integer is valid and is kept.
9
+ */
10
+ export const DEFAULT_PAGE_SIZE = 80;
11
+ export const PAGE_SIZES = [25, 80, 100];
12
+ function clampSize(next) {
13
+ if (!Number.isFinite(next))
14
+ return DEFAULT_PAGE_SIZE;
15
+ return Math.max(1, Math.floor(next));
16
+ }
17
+ /**
18
+ * Cut an in-memory set into pages.
19
+ *
20
+ * `pageSize` is controlled when `onPageSizeChange` is passed — that is how a
21
+ * view context keeps the size across filters. Without it the hook owns the
22
+ * size, defaulting to 80.
23
+ *
24
+ * The current page is clamped in the calculation, not in an effect: if a
25
+ * filter leaves fewer pages than the current one, the next read already
26
+ * lands on the last page that exists.
27
+ */
28
+ export function usePagination(items, options = {}) {
29
+ const controlled = options.pageSize !== undefined && options.onPageSizeChange !== undefined;
30
+ const [ownedSize, setOwnedSize] = useState(options.pageSize ?? DEFAULT_PAGE_SIZE);
31
+ const [page, setPage] = useState(1);
32
+ const pageSize = clampSize(controlled ? (options.pageSize ?? DEFAULT_PAGE_SIZE) : ownedSize);
33
+ const setPageSize = controlled ? options.onPageSizeChange : setOwnedSize;
34
+ const total = items.length;
35
+ const pages = Math.max(1, Math.ceil(total / pageSize));
36
+ const current = Math.min(page, pages);
37
+ const from = total === 0 ? 0 : (current - 1) * pageSize + 1;
38
+ const to = Math.min(current * pageSize, total);
39
+ return {
40
+ pageItems: items.slice(from > 0 ? from - 1 : 0, to),
41
+ bridge: {
42
+ from,
43
+ to,
44
+ total,
45
+ pageSize,
46
+ onPageSizeChange: (next) => {
47
+ setPageSize(clampSize(next));
48
+ setPage(1);
49
+ },
50
+ /* Se mueve desde `current` y no desde `page`, que es el que puede haber
51
+ quedado alto: si estabas en la 3 y un filtro dejó dos páginas, `page`
52
+ sigue en 3 mientras se muestra la 2, y `p - 1` volvía a 2 — o sea que
53
+ «anterior» no hacía nada hasta el segundo clic. */
54
+ ...(current > 1 ? { onPrev: () => setPage(current - 1) } : {}),
55
+ ...(current < pages ? { onNext: () => setPage(current + 1) } : {}),
56
+ },
57
+ };
58
+ }
59
+ /**
60
+ * Cut a *server-side* set into pages, by cursor.
61
+ *
62
+ * `usePagination` slices an array that is already in hand; this one never holds the whole set.
63
+ * Same `bridge`, so `Pagination` draws it identically — the difference is invisible to a view.
64
+ *
65
+ * **Going back is a walk, not a jump.** A keyset cursor names the last row seen, so it points
66
+ * forward and nothing points back. Rather than fake it with an offset — which would reintroduce
67
+ * exactly the incoherence the cursor exists to avoid — the cursors already walked are kept, and
68
+ * `onPrev` re-fetches with the one before. That is one request, not a re-walk from the start.
69
+ *
70
+ * **Changing the size starts over**, and that is not a limitation: a cursor belongs to the page
71
+ * size it was minted under, so keeping it would land somewhere nobody asked for.
72
+ */
73
+ export function useCursorPagination(source, options = {}) {
74
+ const controlled = options.pageSize !== undefined && options.onPageSizeChange !== undefined;
75
+ const [ownedSize, setOwnedSize] = useState(options.pageSize ?? DEFAULT_PAGE_SIZE);
76
+ const pageSize = clampSize(controlled ? options.pageSize : ownedSize);
77
+ /* The cursor that opened each page walked so far. `[undefined]` is the first page, which
78
+ opens with no cursor — so `walked.length` is the page number. */
79
+ const [walked, setWalked] = useState([undefined]);
80
+ const [page, setPage] = useState(null);
81
+ const [loading, setLoading] = useState(true);
82
+ const [error, setError] = useState(null);
83
+ const at = walked[walked.length - 1];
84
+ /* Starting over is keyed on what changes the reading — the filter, the ordering, the size.
85
+ Without this an `onPrev` after a filter change would re-fetch a cursor minted for the
86
+ previous reading, and the server would refuse it. */
87
+ useEffect(() => {
88
+ setWalked([undefined]);
89
+ }, [source.resetKey, pageSize]);
90
+ useEffect(() => {
91
+ let current = true;
92
+ setLoading(true);
93
+ source
94
+ .fetch(at, pageSize)
95
+ .then((answer) => {
96
+ if (!current)
97
+ return;
98
+ setPage(answer);
99
+ setError(null);
100
+ })
101
+ .catch((failure) => current && setError(failure))
102
+ .finally(() => current && setLoading(false));
103
+ return () => {
104
+ current = false;
105
+ };
106
+ // eslint-disable-next-line react-hooks/exhaustive-deps
107
+ }, [at, pageSize, source.resetKey]);
108
+ const items = page?.items ?? [];
109
+ const total = page?.count?.value ?? items.length;
110
+ const from = items.length === 0 ? 0 : (walked.length - 1) * pageSize + 1;
111
+ const to = from === 0 ? 0 : from + items.length - 1;
112
+ return {
113
+ pageItems: [...items],
114
+ loading,
115
+ error,
116
+ bridge: {
117
+ from,
118
+ to,
119
+ total,
120
+ pageSize,
121
+ onPageSizeChange: (next) => {
122
+ const size = clampSize(next);
123
+ if (controlled)
124
+ options.onPageSizeChange(size);
125
+ else
126
+ setOwnedSize(size);
127
+ setWalked([undefined]);
128
+ },
129
+ ...(walked.length > 1 ? { onPrev: () => setWalked((so) => so.slice(0, -1)) } : {}),
130
+ ...(page?.cursor != null
131
+ ? { onNext: () => setWalked((so) => [...so, page.cursor ?? undefined]) }
132
+ : {}),
133
+ },
134
+ };
135
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Choosing rows of a SET. Lives next to pagination because both are how N
3
+ * records are controlled, not how they are drawn.
4
+ *
5
+ * «All» means the ones ON SCREEN: with a filter on, select-all cannot take
6
+ * what the filter hid. Chosen ids survive filtering — the Set stores ids,
7
+ * not positions — so you can choose, filter again, choose more, and act on
8
+ * both batches.
9
+ */
10
+ export interface Selection {
11
+ chosen: ReadonlySet<string>;
12
+ count: number;
13
+ has: (id: string) => boolean;
14
+ toggle: (id: string) => void;
15
+ toggleAll: () => void;
16
+ /** `true` all, `false` none, `"some"` part — the third state is the half
17
+ * checkbox, and without it the header lies when three of ten are chosen. */
18
+ allState: boolean | "some";
19
+ clear: () => void;
20
+ }
21
+ export declare function useSelection(visible: readonly string[]): Selection;