@sincpro/web-ui 0.0.0-stage → 0.1.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 (354) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -2
  3. package/dist/components/assist/AssistMark.d.ts +64 -0
  4. package/dist/components/assist/AssistMark.js +108 -0
  5. package/dist/components/assist/AssistPending.d.ts +24 -0
  6. package/dist/components/assist/AssistPending.js +26 -0
  7. package/dist/components/assist/AssistSuggestion.d.ts +42 -0
  8. package/dist/components/assist/AssistSuggestion.js +55 -0
  9. package/dist/components/assist/ChangeReview.d.ts +56 -0
  10. package/dist/components/assist/ChangeReview.js +121 -0
  11. package/dist/components/assist/assist.d.ts +77 -0
  12. package/dist/components/assist/assist.js +87 -0
  13. package/dist/components/assist/index.d.ts +5 -0
  14. package/dist/components/assist/index.js +7 -0
  15. package/dist/components/chatter/Chatter.d.ts +161 -0
  16. package/dist/components/chatter/Chatter.js +232 -0
  17. package/dist/components/chatter/Messages.d.ts +66 -0
  18. package/dist/components/chatter/Messages.js +43 -0
  19. package/dist/components/chatter/Timeline.d.ts +21 -0
  20. package/dist/components/chatter/Timeline.js +30 -0
  21. package/dist/components/chatter/index.d.ts +3 -0
  22. package/dist/components/chatter/index.js +4 -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 +43 -0
  26. package/dist/components/form/Sheet.js +83 -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 +74 -0
  40. package/dist/components/lines/LineItems.js +229 -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 +73 -0
  52. package/dist/components/listing/ListingBar.js +105 -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 +106 -0
  60. package/dist/components/listing/Row.js +122 -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 +71 -0
  96. package/dist/components/record/RecordHeader.js +72 -0
  97. package/dist/components/record/RecordSkeleton.d.ts +3 -0
  98. package/dist/components/record/RecordSkeleton.js +21 -0
  99. package/dist/components/record/SmartButtons.d.ts +35 -0
  100. package/dist/components/record/SmartButtons.js +38 -0
  101. package/dist/components/record/StatusBar.d.ts +51 -0
  102. package/dist/components/record/StatusBar.js +168 -0
  103. package/dist/components/record/ThumbBar.d.ts +36 -0
  104. package/dist/components/record/ThumbBar.js +86 -0
  105. package/dist/components/record/index.d.ts +6 -0
  106. package/dist/components/record/index.js +8 -0
  107. package/dist/components/search/CriteriaBuilder.d.ts +58 -0
  108. package/dist/components/search/CriteriaBuilder.js +278 -0
  109. package/dist/components/search/CriteriaChips.d.ts +22 -0
  110. package/dist/components/search/CriteriaChips.js +64 -0
  111. package/dist/components/search/FilterBuilder.d.ts +142 -0
  112. package/dist/components/search/FilterBuilder.js +421 -0
  113. package/dist/components/search/JoinWord.d.ts +8 -0
  114. package/dist/components/search/JoinWord.js +47 -0
  115. package/dist/components/search/JsonEditor.d.ts +19 -0
  116. package/dist/components/search/JsonEditor.js +78 -0
  117. package/dist/components/search/SearchBar.d.ts +78 -0
  118. package/dist/components/search/SearchBar.js +387 -0
  119. package/dist/components/search/criteria.d.ts +116 -0
  120. package/dist/components/search/criteria.js +159 -0
  121. package/dist/components/search/datePeriods.d.ts +122 -0
  122. package/dist/components/search/datePeriods.js +134 -0
  123. package/dist/components/search/dateRanges.d.ts +54 -0
  124. package/dist/components/search/dateRanges.js +89 -0
  125. package/dist/components/search/index.d.ts +9 -0
  126. package/dist/components/search/index.js +20 -0
  127. package/dist/components/search/model.d.ts +54 -0
  128. package/dist/components/search/model.js +37 -0
  129. package/dist/components/search/query.d.ts +166 -0
  130. package/dist/components/search/query.js +436 -0
  131. package/dist/components/shell/AppFrame.d.ts +14 -0
  132. package/dist/components/shell/AppFrame.js +80 -0
  133. package/dist/components/shell/TopBar.d.ts +73 -0
  134. package/dist/components/shell/TopBar.js +54 -0
  135. package/dist/components/shell/headerSlot.d.ts +17 -0
  136. package/dist/components/shell/headerSlot.js +21 -0
  137. package/dist/components/shell/index.d.ts +2 -0
  138. package/dist/components/shell/index.js +3 -0
  139. package/dist/foundations/aria.d.ts +14 -0
  140. package/dist/foundations/aria.js +18 -0
  141. package/dist/foundations/calendar.d.ts +28 -0
  142. package/dist/foundations/calendar.js +183 -0
  143. package/dist/foundations/copy.d.ts +619 -0
  144. package/dist/foundations/copy.es.d.ts +2 -0
  145. package/dist/foundations/copy.es.js +377 -0
  146. package/dist/foundations/copy.js +472 -0
  147. package/dist/foundations/density.d.ts +16 -0
  148. package/dist/foundations/density.js +21 -0
  149. package/dist/foundations/grouping.d.ts +281 -0
  150. package/dist/foundations/grouping.js +0 -0
  151. package/dist/foundations/index.d.ts +14 -0
  152. package/dist/foundations/index.js +30 -0
  153. package/dist/foundations/layout.d.ts +9 -0
  154. package/dist/foundations/layout.js +9 -0
  155. package/dist/foundations/pivot.d.ts +222 -0
  156. package/dist/foundations/pivot.js +198 -0
  157. package/dist/foundations/pivotSheet.d.ts +61 -0
  158. package/dist/foundations/pivotSheet.js +0 -0
  159. package/dist/foundations/slots.d.ts +67 -0
  160. package/dist/foundations/slots.js +63 -0
  161. package/dist/foundations/sticky.d.ts +53 -0
  162. package/dist/foundations/sticky.js +150 -0
  163. package/dist/foundations/surface.d.ts +9 -0
  164. package/dist/foundations/surface.js +26 -0
  165. package/dist/foundations/theme.d.ts +42 -0
  166. package/dist/foundations/theme.js +88 -0
  167. package/dist/foundations/types.d.ts +23 -0
  168. package/dist/foundations/types.js +50 -0
  169. package/dist/foundations/variants.d.ts +19 -0
  170. package/dist/foundations/variants.js +48 -0
  171. package/dist/foundations/xlsx.d.ts +86 -0
  172. package/dist/foundations/xlsx.js +0 -0
  173. package/dist/index.d.ts +19 -0
  174. package/dist/index.js +19 -0
  175. package/dist/primitives/block/Block.d.ts +66 -0
  176. package/dist/primitives/block/Block.js +34 -0
  177. package/dist/primitives/block/Line.d.ts +18 -0
  178. package/dist/primitives/block/Line.js +45 -0
  179. package/dist/primitives/block/SectionGrid.d.ts +30 -0
  180. package/dist/primitives/block/SectionGrid.js +29 -0
  181. package/dist/primitives/block/Steps.d.ts +76 -0
  182. package/dist/primitives/block/Steps.js +43 -0
  183. package/dist/primitives/block/index.d.ts +4 -0
  184. package/dist/primitives/block/index.js +7 -0
  185. package/dist/primitives/button/Button.d.ts +137 -0
  186. package/dist/primitives/button/Button.js +234 -0
  187. package/dist/primitives/button/index.d.ts +1 -0
  188. package/dist/primitives/button/index.js +2 -0
  189. package/dist/primitives/cell/CellStack.d.ts +47 -0
  190. package/dist/primitives/cell/CellStack.js +33 -0
  191. package/dist/primitives/cell/index.d.ts +1 -0
  192. package/dist/primitives/cell/index.js +3 -0
  193. package/dist/primitives/choice/ComboBox.d.ts +16 -0
  194. package/dist/primitives/choice/ComboBox.js +52 -0
  195. package/dist/primitives/choice/RadioGroup.d.ts +18 -0
  196. package/dist/primitives/choice/RadioGroup.js +50 -0
  197. package/dist/primitives/choice/SearchField.d.ts +24 -0
  198. package/dist/primitives/choice/SearchField.js +27 -0
  199. package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
  200. package/dist/primitives/choice/SegmentedControl.js +69 -0
  201. package/dist/primitives/choice/Slider.d.ts +16 -0
  202. package/dist/primitives/choice/Slider.js +46 -0
  203. package/dist/primitives/choice/Switch.d.ts +14 -0
  204. package/dist/primitives/choice/Switch.js +65 -0
  205. package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
  206. package/dist/primitives/choice/ThemeSwitcher.js +25 -0
  207. package/dist/primitives/choice/index.d.ts +8 -0
  208. package/dist/primitives/choice/index.js +11 -0
  209. package/dist/primitives/choice/options.d.ts +28 -0
  210. package/dist/primitives/choice/options.js +6 -0
  211. package/dist/primitives/date/Calendar.d.ts +31 -0
  212. package/dist/primitives/date/Calendar.js +110 -0
  213. package/dist/primitives/date/DatePicker.d.ts +34 -0
  214. package/dist/primitives/date/DatePicker.js +99 -0
  215. package/dist/primitives/date/index.d.ts +2 -0
  216. package/dist/primitives/date/index.js +4 -0
  217. package/dist/primitives/dialog/Dialog.d.ts +118 -0
  218. package/dist/primitives/dialog/Dialog.js +121 -0
  219. package/dist/primitives/dialog/index.d.ts +1 -0
  220. package/dist/primitives/dialog/index.js +2 -0
  221. package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
  222. package/dist/primitives/disclosure/Disclosure.js +52 -0
  223. package/dist/primitives/disclosure/index.d.ts +1 -0
  224. package/dist/primitives/disclosure/index.js +2 -0
  225. package/dist/primitives/field/Checks.d.ts +57 -0
  226. package/dist/primitives/field/Checks.js +72 -0
  227. package/dist/primitives/field/DropZone.d.ts +42 -0
  228. package/dist/primitives/field/DropZone.js +52 -0
  229. package/dist/primitives/field/Field.d.ts +166 -0
  230. package/dist/primitives/field/Field.js +240 -0
  231. package/dist/primitives/field/Suggest.d.ts +48 -0
  232. package/dist/primitives/field/Suggest.js +43 -0
  233. package/dist/primitives/field/aria.d.ts +61 -0
  234. package/dist/primitives/field/aria.js +70 -0
  235. package/dist/primitives/field/context.d.ts +30 -0
  236. package/dist/primitives/field/context.js +13 -0
  237. package/dist/primitives/field/index.d.ts +7 -0
  238. package/dist/primitives/field/index.js +11 -0
  239. package/dist/primitives/field/layout.d.ts +24 -0
  240. package/dist/primitives/field/layout.js +10 -0
  241. package/dist/primitives/field/mode.d.ts +9 -0
  242. package/dist/primitives/field/mode.js +13 -0
  243. package/dist/primitives/hint/Hint.d.ts +31 -0
  244. package/dist/primitives/hint/Hint.js +42 -0
  245. package/dist/primitives/hint/index.d.ts +1 -0
  246. package/dist/primitives/hint/index.js +1 -0
  247. package/dist/primitives/index.d.ts +19 -0
  248. package/dist/primitives/index.js +20 -0
  249. package/dist/primitives/menu/Menu.d.ts +100 -0
  250. package/dist/primitives/menu/Menu.js +59 -0
  251. package/dist/primitives/menu/index.d.ts +1 -0
  252. package/dist/primitives/menu/index.js +2 -0
  253. package/dist/primitives/notice/Callout.d.ts +49 -0
  254. package/dist/primitives/notice/Callout.js +28 -0
  255. package/dist/primitives/notice/Notice.d.ts +45 -0
  256. package/dist/primitives/notice/Notice.js +61 -0
  257. package/dist/primitives/notice/Toast.d.ts +55 -0
  258. package/dist/primitives/notice/Toast.js +142 -0
  259. package/dist/primitives/notice/index.d.ts +3 -0
  260. package/dist/primitives/notice/index.js +5 -0
  261. package/dist/primitives/overlay/Popover.d.ts +6 -0
  262. package/dist/primitives/overlay/Popover.js +27 -0
  263. package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
  264. package/dist/primitives/overlay/PopoverTrigger.js +38 -0
  265. package/dist/primitives/overlay/Tooltip.d.ts +38 -0
  266. package/dist/primitives/overlay/Tooltip.js +74 -0
  267. package/dist/primitives/overlay/index.d.ts +3 -0
  268. package/dist/primitives/overlay/index.js +5 -0
  269. package/dist/primitives/picker/Picker.d.ts +48 -0
  270. package/dist/primitives/picker/Picker.js +150 -0
  271. package/dist/primitives/picker/index.d.ts +1 -0
  272. package/dist/primitives/picker/index.js +2 -0
  273. package/dist/primitives/surface/Peek.d.ts +60 -0
  274. package/dist/primitives/surface/Peek.js +71 -0
  275. package/dist/primitives/surface/index.d.ts +1 -0
  276. package/dist/primitives/surface/index.js +2 -0
  277. package/dist/primitives/table/CellEditor.d.ts +14 -0
  278. package/dist/primitives/table/CellEditor.js +83 -0
  279. package/dist/primitives/table/DataTable.d.ts +135 -0
  280. package/dist/primitives/table/DataTable.js +244 -0
  281. package/dist/primitives/table/EditableTable.d.ts +96 -0
  282. package/dist/primitives/table/EditableTable.js +423 -0
  283. package/dist/primitives/table/PivotTable.d.ts +14 -0
  284. package/dist/primitives/table/PivotTable.js +0 -0
  285. package/dist/primitives/table/ReportTable.d.ts +38 -0
  286. package/dist/primitives/table/ReportTable.js +106 -0
  287. package/dist/primitives/table/RowActions.d.ts +16 -0
  288. package/dist/primitives/table/RowActions.js +28 -0
  289. package/dist/primitives/table/SheetTable.d.ts +70 -0
  290. package/dist/primitives/table/SheetTable.js +197 -0
  291. package/dist/primitives/table/Table.d.ts +106 -0
  292. package/dist/primitives/table/Table.js +116 -0
  293. package/dist/primitives/table/index.d.ts +7 -0
  294. package/dist/primitives/table/index.js +9 -0
  295. package/dist/primitives/tabs/Tabs.d.ts +91 -0
  296. package/dist/primitives/tabs/Tabs.js +56 -0
  297. package/dist/primitives/tabs/index.d.ts +1 -0
  298. package/dist/primitives/tabs/index.js +1 -0
  299. package/dist/primitives/tag/Tag.d.ts +36 -0
  300. package/dist/primitives/tag/Tag.js +68 -0
  301. package/dist/primitives/tag/Tags.d.ts +57 -0
  302. package/dist/primitives/tag/Tags.js +104 -0
  303. package/dist/primitives/tag/TagsField.d.ts +32 -0
  304. package/dist/primitives/tag/TagsField.js +182 -0
  305. package/dist/primitives/tag/choices.d.ts +38 -0
  306. package/dist/primitives/tag/choices.js +26 -0
  307. package/dist/primitives/tag/index.d.ts +4 -0
  308. package/dist/primitives/tag/index.js +8 -0
  309. package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
  310. package/dist/primitives/toolbar/Toolbar.js +36 -0
  311. package/dist/primitives/toolbar/index.d.ts +1 -0
  312. package/dist/primitives/toolbar/index.js +2 -0
  313. package/dist/primitives/value/Copyable.d.ts +37 -0
  314. package/dist/primitives/value/Copyable.js +50 -0
  315. package/dist/primitives/value/Fecha.d.ts +12 -0
  316. package/dist/primitives/value/Fecha.js +80 -0
  317. package/dist/primitives/value/Meter.d.ts +29 -0
  318. package/dist/primitives/value/Meter.js +46 -0
  319. package/dist/primitives/value/Metric.d.ts +42 -0
  320. package/dist/primitives/value/Metric.js +38 -0
  321. package/dist/primitives/value/Person.d.ts +90 -0
  322. package/dist/primitives/value/Person.js +208 -0
  323. package/dist/primitives/value/Relation.d.ts +69 -0
  324. package/dist/primitives/value/Relation.js +57 -0
  325. package/dist/primitives/value/Totals.d.ts +26 -0
  326. package/dist/primitives/value/Totals.js +4 -0
  327. package/dist/primitives/value/Value.d.ts +77 -0
  328. package/dist/primitives/value/Value.js +119 -0
  329. package/dist/primitives/value/index.d.ts +8 -0
  330. package/dist/primitives/value/index.js +9 -0
  331. package/dist/tokens.css +1025 -0
  332. package/dist/views/index.d.ts +2 -0
  333. package/dist/views/index.js +3 -0
  334. package/dist/views/list/List.d.ts +113 -0
  335. package/dist/views/list/List.js +97 -0
  336. package/dist/views/list/ListView.d.ts +6 -0
  337. package/dist/views/list/ListView.js +269 -0
  338. package/dist/views/list/columns.d.ts +18 -0
  339. package/dist/views/list/columns.js +27 -0
  340. package/dist/views/list/derive.d.ts +76 -0
  341. package/dist/views/list/derive.js +181 -0
  342. package/dist/views/list/index.d.ts +2 -0
  343. package/dist/views/list/index.js +4 -0
  344. package/dist/views/list/presentations.d.ts +39 -0
  345. package/dist/views/list/presentations.js +103 -0
  346. package/dist/views/list/types.d.ts +272 -0
  347. package/dist/views/list/types.js +1 -0
  348. package/dist/views/list/useNarrowList.d.ts +17 -0
  349. package/dist/views/list/useNarrowList.js +32 -0
  350. package/dist/views/record/Record.d.ts +126 -0
  351. package/dist/views/record/Record.js +141 -0
  352. package/dist/views/record/index.d.ts +1 -0
  353. package/dist/views/record/index.js +2 -0
  354. package/package.json +109 -4
@@ -0,0 +1,105 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { Menu } from "../../primitives/menu/Menu.js";
5
+ import { ArrowDownUp, ArrowDownWideNarrow, ArrowUpNarrowWide } from "lucide-react";
6
+ /**
7
+ * Las filas, ordenadas acá.
8
+ *
9
+ * Sólo cuando el campo activo declara `value`; si no, se devuelven tal cual —
10
+ * ordenar una página de un conjunto que se pagina afuera produce un orden que es
11
+ * mentira, porque ordena las doce que llegaron y no las ciento ochenta y siete.
12
+ *
13
+ * Los faltantes van SIEMPRE al final, suba o baje el orden. Es lo que hace
14
+ * cualquier planilla, y la razón es que un vacío no es «el más chico»: es la
15
+ * ausencia de respuesta, y ponerlo primero al invertir el orden llena la primera
16
+ * pantalla con las filas que menos dicen.
17
+ */
18
+ export function sortItems(items, fields, sort) {
19
+ if (sort === null || sort === undefined)
20
+ return items;
21
+ const field = fields.find((one) => one.field === sort.key);
22
+ if (field?.value === undefined)
23
+ return items;
24
+ const read = field.value;
25
+ const sign = sort.direction === "asc" ? 1 : -1;
26
+ return [...items].sort((a, b) => {
27
+ const left = read(a);
28
+ const right = read(b);
29
+ const leftMissing = left === null || left === undefined || left === "";
30
+ const rightMissing = right === null || right === undefined || right === "";
31
+ if (leftMissing && rightMissing)
32
+ return 0;
33
+ if (leftMissing)
34
+ return 1;
35
+ if (rightMissing)
36
+ return -1;
37
+ if (typeof left === "number" && typeof right === "number")
38
+ return (left - right) * sign;
39
+ return String(left).localeCompare(String(right)) * sign;
40
+ });
41
+ }
42
+ export function SortMenu({ fields, sort, onSortChange, surface }) {
43
+ const copy = useUiCopy();
44
+ if (fields.length === 0)
45
+ return null;
46
+ const current = fields.find((one) => one.field === sort?.key);
47
+ const groups = [
48
+ {
49
+ label: copy.sorting.by,
50
+ options: fields.map((one) => ({
51
+ id: one.field,
52
+ label: one.label,
53
+ checked: one.field === sort?.key,
54
+ })),
55
+ },
56
+ /* La dirección sólo cuando hay algo ordenado: «descendente» sin campo no
57
+ ordena nada, y una opción que no hace nada es una opción que se prueba. */
58
+ ...(current === undefined
59
+ ? []
60
+ : [
61
+ {
62
+ options: [
63
+ {
64
+ id: "__asc__",
65
+ label: current.ascLabel ?? copy.sorting.asc,
66
+ icon: ArrowUpNarrowWide,
67
+ checked: sort?.direction === "asc",
68
+ },
69
+ {
70
+ id: "__desc__",
71
+ label: current.descLabel ?? copy.sorting.desc,
72
+ icon: ArrowDownWideNarrow,
73
+ checked: sort?.direction === "desc",
74
+ },
75
+ ],
76
+ },
77
+ { options: [{ id: "__none__", label: copy.sorting.clear }] },
78
+ ]),
79
+ ];
80
+ return (_jsx(Menu, { label: current === undefined ? copy.sorting.label : current.label, icon: sort === null || sort === undefined
81
+ ? ArrowDownUp
82
+ : sort.direction === "asc"
83
+ ? ArrowUpNarrowWide
84
+ : ArrowDownWideNarrow, groups: groups, active: sort !== null && sort !== undefined, size: "sm", width: "sm", closeOnSelect: false, ...(surface === undefined ? {} : { surface }), onSelect: (id) => {
85
+ if (id === "__none__")
86
+ return onSortChange(null);
87
+ if (id === "__asc__") {
88
+ return onSortChange(sort === null ? null : { ...sort, direction: "asc" });
89
+ }
90
+ if (id === "__desc__") {
91
+ return onSortChange(sort === null ? null : { ...sort, direction: "desc" });
92
+ }
93
+ /* Volver a elegir el campo que ya está puesto DA VUELTA la dirección, que
94
+ es lo que hace el clic en un encabezado. Sin eso, invertir el orden son
95
+ dos gestos en un menú y uno en la tabla — la misma capacidad costando
96
+ distinto según la presentación. */
97
+ if (sort?.key === id) {
98
+ return onSortChange({
99
+ key: id,
100
+ direction: sort.direction === "asc" ? "desc" : "asc",
101
+ });
102
+ }
103
+ onSortChange({ key: id, direction: "asc" });
104
+ } }));
105
+ }
@@ -0,0 +1,24 @@
1
+ import type { Surface } from "../../foundations/types.js";
2
+ import type { LucideIcon } from "lucide-react";
3
+ /** How the same list is shown: table, board, timeline.
4
+ *
5
+ * The icons go together and touching —a segmented group and not three loose
6
+ * buttons— because they are MUTUALLY EXCLUSIVE options of a single question.
7
+ * Three separate buttons read as three different actions, and then it is not
8
+ * understood that choosing one turns the other off.
9
+ *
10
+ * The label goes in the `title` and in the `aria-label`, never drawn: they are
11
+ * three silhouettes that are learned once and recognized afterwards without
12
+ * reading, and writing them out would burn the width the search box needs. */
13
+ export interface ViewOption {
14
+ id: string;
15
+ label: string;
16
+ icon: LucideIcon;
17
+ }
18
+ export declare function ViewSwitcher({ options, current, onSelect, surface, }: {
19
+ options: readonly ViewOption[];
20
+ current: string;
21
+ onSelect?: (id: string) => void;
22
+ /** By default it inherits it from the surface that wraps it. */
23
+ surface?: Surface;
24
+ }): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { bySurface, useSurface } from "../../foundations/surface.js";
5
+ export function ViewSwitcher({ options, current, onSelect, surface, }) {
6
+ const activeSurface = useSurface(surface);
7
+ const copy = useUiCopy();
8
+ const rule = bySurface(activeSurface, "border-line-strong", "border-chrome-ink/45");
9
+ return (_jsx("div", { role: "group", "aria-label": copy.present.group, className: `flex shrink-0 items-center overflow-hidden rounded-control border ${rule}`, children: options.map((o, i) => {
10
+ const here = o.id === current;
11
+ const Icon = o.icon;
12
+ return (_jsx("button", { type: "button", onClick: () => onSelect?.(o.id), "aria-pressed": here, title: o.label, "aria-label": o.label, className: `inline-flex size-(--size-control-sm) items-center justify-center transition-colors duration-(--duration-instant) focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-primary ${i === 0 ? "" : `border-l ${rule}`} ${here
13
+ ? bySurface(activeSurface, "bg-ink/8 text-ink", "bg-chrome-link/20 text-chrome-link")
14
+ : bySurface(activeSurface, "bg-surface text-muted hover:bg-ink/5 hover:text-ink", "bg-chrome-ink/6 text-chrome-muted hover:bg-chrome-ink/14 hover:text-chrome-ink")}`, children: _jsx(Icon, { className: "size-(--size-icon-md)", strokeWidth: 1.75 }) }, o.id));
15
+ }) }));
16
+ }
@@ -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
+ };