@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,3 @@
1
+ export { SeriesChart } from "./Chart.js";
2
+ export { FrameView } from "./FrameView.js";
3
+ export { columnSeries, frameFromRows, withColumns, } from "./model.js";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * A FRAME is a rectangle of values: columns × rows.
3
+ *
4
+ * It is not a domain record and it is not a set of records. ListView talks
5
+ * about N entities of the same kind (the Facturas list, a queue). Record talks
6
+ * about ONE entity. The frame talks about the PAYLOAD — the cells that a
7
+ * record carries, or that a glance extracted from a file — so they can be
8
+ * shown as a table or as a chart, and so a column can be hidden, without the
9
+ * design system knowing what an invoice is.
10
+ *
11
+ * The value is a function, not a copied matrix: the consumer already has the
12
+ * bytes somewhere and this layer only reads them.
13
+ */
14
+ export type FrameCell = string | number | null;
15
+ export type FrameColumn = {
16
+ id: string;
17
+ header: string;
18
+ type?: string;
19
+ numeric?: boolean;
20
+ width?: number;
21
+ /** Payload columns default to optional. Set `false` to pin a column. */
22
+ optional?: boolean;
23
+ defaultHidden?: boolean;
24
+ };
25
+ export type Frame = {
26
+ columns: readonly FrameColumn[];
27
+ rows: number;
28
+ value: (row: number, column: FrameColumn, columnIndex: number) => FrameCell;
29
+ };
30
+ export type FramePresent = "table" | "chart";
31
+ export type SeriesBar = {
32
+ label: string;
33
+ count: number;
34
+ };
35
+ export declare function frameFromRows(columns: readonly FrameColumn[], rows: readonly (readonly FrameCell[])[]): Frame;
36
+ /** Keep only some columns. The cells still come from the original frame. */
37
+ export declare function withColumns(frame: Frame, ids: readonly string[]): Frame;
38
+ /**
39
+ * Turn one column into bars: a histogram if the values are numbers, the
40
+ * most frequent labels otherwise. Twelve bars is the ceiling — more than
41
+ * that is a table again.
42
+ */
43
+ export declare function columnSeries(frame: Frame, columnId: string): SeriesBar[];
@@ -0,0 +1,101 @@
1
+ import { currentCopy, currentCopyLocale } from "../../foundations/copy.js";
2
+ const BINS = 12;
3
+ const TOP = 12;
4
+ export function frameFromRows(columns, rows) {
5
+ return {
6
+ columns,
7
+ rows: rows.length,
8
+ value: (row, _column, columnIndex) => rows[row]?.[columnIndex] ?? null,
9
+ };
10
+ }
11
+ /** Keep only some columns. The cells still come from the original frame. */
12
+ export function withColumns(frame, ids) {
13
+ const wanted = new Set(ids);
14
+ const columns = frame.columns.filter((column) => wanted.has(column.id));
15
+ const indexOf = new Map(frame.columns.map((column, index) => [column.id, index]));
16
+ return {
17
+ columns,
18
+ rows: frame.rows,
19
+ value: (row, column) => {
20
+ const index = indexOf.get(column.id) ?? 0;
21
+ return frame.value(row, column, index);
22
+ },
23
+ };
24
+ }
25
+ function asNumber(cell) {
26
+ if (typeof cell === "number" && Number.isFinite(cell))
27
+ return cell;
28
+ if (typeof cell === "string" && cell.trim() !== "") {
29
+ const n = Number(cell.replace(",", "."));
30
+ return Number.isFinite(n) ? n : null;
31
+ }
32
+ return null;
33
+ }
34
+ function present(cell) {
35
+ return cell !== null && cell !== "";
36
+ }
37
+ /**
38
+ * Turn one column into bars: a histogram if the values are numbers, the
39
+ * most frequent labels otherwise. Twelve bars is the ceiling — more than
40
+ * that is a table again.
41
+ */
42
+ export function columnSeries(frame, columnId) {
43
+ const columnIndex = frame.columns.findIndex((column) => column.id === columnId);
44
+ const column = frame.columns[columnIndex];
45
+ if (column === undefined || columnIndex < 0)
46
+ return [];
47
+ const cells = [];
48
+ for (let row = 0; row < frame.rows; row += 1) {
49
+ cells.push(frame.value(row, column, columnIndex));
50
+ }
51
+ const numbers = cells.map(asNumber);
52
+ const numeric = numbers.filter((n) => n !== null);
53
+ const filled = cells.filter(present).length;
54
+ if (filled === 0)
55
+ return [];
56
+ if (column.numeric === true || numeric.length / filled >= 0.8) {
57
+ return histogram(numeric);
58
+ }
59
+ return categories(cells);
60
+ }
61
+ function histogram(values) {
62
+ if (values.length === 0)
63
+ return [];
64
+ const min = Math.min(...values);
65
+ const max = Math.max(...values);
66
+ if (min === max)
67
+ return [{ label: String(min), count: values.length }];
68
+ const width = (max - min) / BINS;
69
+ const counts = Array.from({ length: BINS }, () => 0);
70
+ for (const value of values) {
71
+ const slot = Math.min(BINS - 1, Math.floor((value - min) / width));
72
+ counts[slot] = (counts[slot] ?? 0) + 1;
73
+ }
74
+ return counts.map((count, index) => {
75
+ const from = min + index * width;
76
+ const to = index === BINS - 1 ? max : from + width;
77
+ return { label: `${formatTick(from)}–${formatTick(to)}`, count };
78
+ });
79
+ }
80
+ function categories(cells) {
81
+ const tally = new Map();
82
+ for (const cell of cells) {
83
+ if (!present(cell))
84
+ continue;
85
+ const label = String(cell);
86
+ tally.set(label, (tally.get(label) ?? 0) + 1);
87
+ }
88
+ return [...tally.entries()]
89
+ .sort((a, b) => b[1] - a[1])
90
+ .slice(0, TOP)
91
+ .map(([label, count]) => ({
92
+ label: label === "" ? currentCopy().cell.emptyBin : label,
93
+ count,
94
+ }));
95
+ }
96
+ function formatTick(n) {
97
+ const abs = Math.abs(n);
98
+ if (abs >= 100 || Number.isInteger(n))
99
+ return String(Math.round(n));
100
+ return n.toLocaleString(currentCopyLocale(), { maximumFractionDigits: 1 });
101
+ }
@@ -0,0 +1,10 @@
1
+ export * from "./activity/index.js";
2
+ export * from "./assist/index.js";
3
+ export * from "./form/index.js";
4
+ export * from "./frame/index.js";
5
+ export * from "./lines/index.js";
6
+ export * from "./listing/index.js";
7
+ export * from "./navigation/index.js";
8
+ export * from "./record/index.js";
9
+ export * from "./search/index.js";
10
+ export * from "./shell/index.js";
@@ -0,0 +1,11 @@
1
+ /* COMPONENTS — primitives composed to answer ONE question of the product, by family. */
2
+ export * from "./activity/index.js";
3
+ export * from "./assist/index.js";
4
+ export * from "./form/index.js";
5
+ export * from "./frame/index.js";
6
+ export * from "./lines/index.js";
7
+ export * from "./listing/index.js";
8
+ export * from "./navigation/index.js";
9
+ export * from "./record/index.js";
10
+ export * from "./search/index.js";
11
+ export * from "./shell/index.js";
@@ -0,0 +1,111 @@
1
+ import { type DialogContext } from "../../primitives/dialog/Dialog.js";
2
+ import { type FormMode } from "../../primitives/field/mode.js";
3
+ import type { FlagTone } from "../../primitives/notice/Flag.js";
4
+ import type { DataColumn } from "../../primitives/table/DataTable.js";
5
+ import { type EditColumn, type EditLayout } from "../../primitives/table/EditableTable.js";
6
+ import { type TotalsProps, type TotalsRow } from "../../primitives/value/Totals.js";
7
+ import { type ReactNode } from "react";
8
+ /**
9
+ * A column of the lines. The same contract as `DataTable`'s columns —`value` is
10
+ * the raw value (what the search reads), `cell` how it is drawn, `text` how it
11
+ * reads as plain text— plus `edit`, the control that replaces the cell when the
12
+ * row is being edited in place.
13
+ */
14
+ export type LineItemColumn<T> = Pick<DataColumn<T>, "id" | "header" | "value" | "cell" | "text" | "numeric" | "width" | "optional" | "defaultHidden"> & Pick<EditColumn<T>, "stack" | "brief"> & {
15
+ /** The control while the row is edited in place. Without it the cell stays read
16
+ * only — a computed amount, a code. Only simple values belong here; the whole
17
+ * line is edited in the `editor`. */
18
+ edit?: (line: T, set: (next: T) => void) => ReactNode;
19
+ /** What the lines of a section add up to in this column, already formatted. Only
20
+ * with `structure`; without it the section says nothing in this column. */
21
+ subtotal?: (lines: readonly T[]) => ReactNode;
22
+ };
23
+ /** Which lines are sections or notes, and how their text is read and written. See
24
+ * «Sections and notes» in the file's header. */
25
+ export type LineStructure<T> = {
26
+ kind: (line: T) => "line" | "section" | "note";
27
+ text: (line: T) => string;
28
+ withText: (line: T, text: string) => T;
29
+ /** A blank section; enables «Agregar una sección». */
30
+ newSection?: () => T;
31
+ /** A blank note; enables «Agregar una nota». */
32
+ newNote?: () => T;
33
+ };
34
+ /** What the popup editor receives besides the draft. */
35
+ export type LineEditorState = {
36
+ /** «Listo» was pressed and the draft was not valid: show the errors now, not before. */
37
+ tried: boolean;
38
+ /** A line that does not exist yet. */
39
+ isNew: boolean;
40
+ };
41
+ export type LineItemsProps<T> = {
42
+ lines: readonly T[];
43
+ columns: readonly LineItemColumn<T>[];
44
+ getLineId: (line: T) => string;
45
+ /** How a line is named — in the removal notice, the editor's header and for the
46
+ * screen reader next to each row's buttons. Defaults to the first column's text. */
47
+ lineLabel?: (line: T) => string;
48
+ /** `read` for a document that can no longer change (a posted invoice): no add, no
49
+ * edit, no remove — just the lines. By default it inherits the form's mode. */
50
+ mode?: FormMode;
51
+ /** A line changed, in place or in the editor. Without it nothing is editable. */
52
+ onChange?: (id: string, next: T) => void;
53
+ /** A new line, already filled in the editor (or blank, without an editor). */
54
+ onAdd?: (line: T) => void;
55
+ /** The blank line «Agregar una línea» starts from. Required for adding. */
56
+ newLine?: () => T;
57
+ /** A line was removed. It is gone at once; the notice offers to undo it. */
58
+ onRemove?: (id: string) => void;
59
+ /** Put a removed line back where it was. Without it the notice has no «Deshacer». */
60
+ onRestore?: (line: T, index: number) => void;
61
+ /** The whole line as a form: the product, the quantity, the price, the discount.
62
+ * Written with the system's `Field` controls; `set` replaces the draft. */
63
+ editor?: (draft: T, set: (next: T) => void, state: LineEditorState) => ReactNode;
64
+ /** Whether the draft can be saved. When it cannot, «Listo» keeps the editor open
65
+ * and `state.tried` turns on so the editor can say what is missing. */
66
+ isValid?: (draft: T) => boolean;
67
+ /** What the editor is about, in its header: «factura · FAC/2026/0118». */
68
+ context?: DialogContext;
69
+ /** What the search reads. Defaults to every column's `text` or `value`. */
70
+ searchText?: (line: T) => string;
71
+ /** The search shows up only with MORE lines than this. Four lines are read, not
72
+ * searched; a search box over them is a control that answers nothing. */
73
+ searchFrom?: number;
74
+ /** The money summary under the lines, on the right: the system's `Totals`. Its rows,
75
+ * or all of it — payments deducted, the balance due, the credits that can be applied. */
76
+ totals?: readonly TotalsRow[] | TotalsProps;
77
+ /** `auto` (the default) draws the lines as stacked cards below
78
+ * `--container-handheld`; `table` and `stacked` force one. */
79
+ layout?: EditLayout;
80
+ /** How «Agregar una línea» adds: `editor` opens the blank line in the dialog;
81
+ * `inline` puts it in the table, open, under the others (see the file's header).
82
+ * By default the dialog when there is an `editor`, the table when there is not. */
83
+ adding?: "inline" | "editor";
84
+ /** Open the whole line somewhere else than this block's dialog — the app's `Drill`, so
85
+ * the line opens as «S00142 › Línea 3» and can go deeper (its product, its lot) without
86
+ * a modal over a modal. When given, it replaces `editor` for existing lines. */
87
+ onOpenLine?: (id: string) => void;
88
+ /** A warning about one line, on the line: «Sin existencias», «Precio bajo el costo».
89
+ * `EditableTable`'s `flag`, passed through. */
90
+ flag?: (line: T) => {
91
+ tone: FlagTone;
92
+ label: string;
93
+ detail?: ReactNode;
94
+ owner?: string;
95
+ } | null;
96
+ /** Sections and notes among the lines. See the file's header. */
97
+ structure?: LineStructure<T>;
98
+ /** Which columns are on screen, when the owner remembers it (a preference). */
99
+ visibleColumnIds?: readonly string[];
100
+ onVisibleColumnIdsChange?: (ids: readonly string[]) => void;
101
+ };
102
+ /**
103
+ * The lines of a document inside its record: a count and a search above, the lines
104
+ * (edited in place or in a popup editor), and the totals under them on the right.
105
+ *
106
+ * Context: the record used to compose this by hand — a `Table`, its rows, a
107
+ * `Totals` — and every module did it a little differently: where «add» went, whether
108
+ * removing asked first, what an empty document said. One component, built on
109
+ * `EditableTable` and `Dialog`, makes the one2many the same block everywhere.
110
+ */
111
+ export declare function LineItems<T>({ lines, columns, getLineId, lineLabel, mode, onChange, onAdd, newLine, onRemove, onRestore, editor, isValid, context, searchText, searchFrom, totals, layout, adding, onOpenLine, flag, structure, visibleColumnIds, onVisibleColumnIdsChange, }: LineItemsProps<T>): import("react").JSX.Element;
@@ -0,0 +1,317 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { commitDraft, filterLines, planRemoval, } from "./lines.js";
4
+ import { useColumnControl, } from "../listing/columns.js";
5
+ import { ColumnsMenu } from "../listing/ColumnsMenu.js";
6
+ import { fillCopy, useUiCopy } from "../../foundations/copy.js";
7
+ import { Button, IconButton } from "../../primitives/button/Button.js";
8
+ import { SearchField } from "../../primitives/choice/SearchField.js";
9
+ import { Dialog } from "../../primitives/dialog/Dialog.js";
10
+ import { FieldIdsContext } from "../../primitives/field/context.js";
11
+ import { TextInput } from "../../primitives/field/Field.js";
12
+ import { useFormMode } from "../../primitives/field/mode.js";
13
+ import { Empty } from "../../primitives/notice/Notice.js";
14
+ import { useToast } from "../../primitives/notice/Toast.js";
15
+ import { EditableTable, } from "../../primitives/table/EditableTable.js";
16
+ import { Totals, } from "../../primitives/value/Totals.js";
17
+ import { Pencil, Plus, Trash2 } from "lucide-react";
18
+ import { useEffect, useId, useMemo, useRef, useState } from "react";
19
+ /** The notice is optional: without a `ToastProvider` the line is still removed, just
20
+ * without the way back — a missing provider must not break a form. */
21
+ function useOptionalToast() {
22
+ try {
23
+ return useToast();
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ function plain(v) {
30
+ return v === null ? "" : String(v);
31
+ }
32
+ /**
33
+ * The lines of a document inside its record: a count and a search above, the lines
34
+ * (edited in place or in a popup editor), and the totals under them on the right.
35
+ *
36
+ * Context: the record used to compose this by hand — a `Table`, its rows, a
37
+ * `Totals` — and every module did it a little differently: where «add» went, whether
38
+ * removing asked first, what an empty document said. One component, built on
39
+ * `EditableTable` and `Dialog`, makes the one2many the same block everywhere.
40
+ */
41
+ export function LineItems({ lines, columns, getLineId, lineLabel, mode, onChange, onAdd, newLine, onRemove, onRestore, editor, isValid, context, searchText, searchFrom = 8, totals, layout = "auto", adding, onOpenLine, flag, structure, visibleColumnIds, onVisibleColumnIdsChange, }) {
42
+ const copy = useUiCopy();
43
+ const toast = useOptionalToast();
44
+ const read = useFormMode(mode) === "read";
45
+ const root = useRef(null);
46
+ const searchLabelId = useId();
47
+ const formId = useId();
48
+ const [query, setQuery] = useState("");
49
+ const [session, setSession] = useState(null);
50
+ const [open, setOpen] = useState(false);
51
+ /* The draft the form submits, read from a ref: Enter in a number field commits
52
+ the typed text and submits in the same keystroke, before a re-render. */
53
+ const draftRef = useRef(null);
54
+ /* Where focus goes after a removal: the button that was pressed is gone with its
55
+ row, and focus falling to the page is losing the place. */
56
+ const focusNext = useRef(null);
57
+ const canChange = !read && onChange !== undefined;
58
+ const canAdd = !read && onAdd !== undefined && newLine !== undefined;
59
+ const canRemove = !read && onRemove !== undefined;
60
+ /* The whole line opens either in this block's own dialog (`editor`) or wherever the app
61
+ opens records (`onOpenLine`, a `Drill` context) — never both, so a line's form never
62
+ sits on top of another modal. */
63
+ const canEdit = (canChange && editor !== undefined) || onOpenLine !== undefined;
64
+ const openLine = (id, line) => {
65
+ if (onOpenLine !== undefined)
66
+ onOpenLine(id);
67
+ else
68
+ openEditor(id, line);
69
+ };
70
+ const inline = canAdd && (adding ?? (editor === undefined ? "inline" : "editor")) === "inline";
71
+ const labelOf = (line) => {
72
+ if (lineLabel !== undefined)
73
+ return lineLabel(line);
74
+ const first = columns[0];
75
+ if (first === undefined)
76
+ return getLineId(line);
77
+ return first.text?.(line) ?? plain(first.value(line));
78
+ };
79
+ const kindOf = (line) => structure?.kind(line) ?? "line";
80
+ /* Sections and notes are not lines: they are not counted, and a search leaves only lines. */
81
+ const lineCount = structure === undefined ? lines.length : lines.filter((l) => kindOf(l) === "line").length;
82
+ const searchable = lineCount > searchFrom;
83
+ const q = searchable ? query.trim() : "";
84
+ const shown = useMemo(() => {
85
+ const found = filterLines(lines, q, (line) => searchText !== undefined
86
+ ? searchText(line)
87
+ : columns.map((c) => c.text?.(line) ?? plain(c.value(line))).join(" "), copy.locale);
88
+ return q === "" || structure === undefined
89
+ ? found
90
+ : found.filter((l) => structure.kind(l) === "line");
91
+ }, [lines, columns, searchText, q, copy.locale, structure]);
92
+ /* ── which columns are on screen ── */
93
+ const specs = useMemo(() => columns.map((c) => ({
94
+ id: c.id,
95
+ label: c.header,
96
+ optional: c.optional === true,
97
+ ...(c.defaultHidden === true ? { defaultHidden: true } : {}),
98
+ })), [columns]);
99
+ /* `visibleColumnIds` alone is a fixed choice, the way a controlled input without
100
+ `onChange` is: the owner said which columns, and the menu cannot change them. */
101
+ const columnControl = useColumnControl(specs, visibleColumnIds === undefined && onVisibleColumnIdsChange === undefined
102
+ ? {}
103
+ : {
104
+ ...(visibleColumnIds === undefined ? {} : { visibleIds: visibleColumnIds }),
105
+ onVisibleIdsChange: onVisibleColumnIdsChange ?? (() => { }),
106
+ });
107
+ const onScreen = columns.filter((c) => columnControl.isVisible(c.id));
108
+ useEffect(() => {
109
+ const target = focusNext.current;
110
+ if (target === null)
111
+ return;
112
+ focusNext.current = null;
113
+ const scope = root.current;
114
+ if (scope === null)
115
+ return;
116
+ /* The next line's first button on a desk; on a phone its card, which has none. */
117
+ const next = target.id === null
118
+ ? null
119
+ : (scope.querySelector(`[data-line="${CSS.escape(target.id)}"] button`) ?? scope.querySelector(`[data-row-id="${CSS.escape(target.id)}"]`));
120
+ (next ?? scope.querySelector("[data-line-add]"))?.focus();
121
+ }, [lines]);
122
+ /* ── the editor ─────────────────────────────────────────────────── */
123
+ const openEditor = (id, draft) => {
124
+ draftRef.current = draft;
125
+ setSession({ id, draft, tried: false });
126
+ setOpen(true);
127
+ };
128
+ const setDraft = (next) => {
129
+ draftRef.current = next;
130
+ setSession((s) => (s === null ? s : { ...s, draft: next }));
131
+ };
132
+ const save = () => {
133
+ const draft = draftRef.current;
134
+ if (session === null || draft === null)
135
+ return;
136
+ const commit = commitDraft(session.id, draft, isValid);
137
+ if (commit.kind === "invalid") {
138
+ setSession({ ...session, draft, tried: true });
139
+ return;
140
+ }
141
+ if (commit.kind === "add") {
142
+ /* A new line is shown even if it does not match the search: adding one and
143
+ not seeing it is the worst answer. */
144
+ setQuery("");
145
+ onAdd?.(commit.line);
146
+ }
147
+ else
148
+ onChange?.(commit.id, commit.line);
149
+ setOpen(false);
150
+ };
151
+ const add = () => {
152
+ if (newLine === undefined)
153
+ return;
154
+ const blank = newLine();
155
+ if (editor === undefined) {
156
+ setQuery("");
157
+ onAdd?.(blank);
158
+ }
159
+ else
160
+ openEditor(null, blank);
161
+ };
162
+ /* In place: the table opens the row whose id this returns. */
163
+ const addInline = (make = newLine) => {
164
+ if (make === undefined)
165
+ return undefined;
166
+ const blank = make();
167
+ setQuery("");
168
+ onAdd?.(blank);
169
+ return getLineId(blank);
170
+ };
171
+ /* A section and a note are always written in place, editor or not. */
172
+ const bandAdds = read || onAdd === undefined || structure === undefined
173
+ ? []
174
+ : [
175
+ ...(structure.newSection === undefined
176
+ ? []
177
+ : [
178
+ {
179
+ label: copy.lines.addSection,
180
+ onAdd: () => addInline(structure.newSection),
181
+ },
182
+ ]),
183
+ ...(structure.newNote === undefined
184
+ ? []
185
+ : [{ label: copy.lines.addNote, onAdd: () => addInline(structure.newNote) }]),
186
+ ];
187
+ const inPlace = inline || bandAdds.length > 0;
188
+ const bands = structure === undefined
189
+ ? undefined
190
+ : {
191
+ kind: (line) => {
192
+ const k = structure.kind(line);
193
+ return k === "line" ? null : k;
194
+ },
195
+ render: (line) => structure.text(line),
196
+ ...(canChange
197
+ ? {
198
+ edit: (line, set) => (_jsx(TextInput, { "aria-label": structure.kind(line) === "section"
199
+ ? copy.lines.section
200
+ : copy.lines.note, value: structure.text(line), onChange: (text) => set(structure.withText(line, text)) })),
201
+ }
202
+ : {}),
203
+ total: (column, under) => columns.find((c) => c.id === column.id)?.subtotal?.(under) ?? null,
204
+ };
205
+ const remove = (line) => {
206
+ const id = getLineId(line);
207
+ const { index, focus } = planRemoval(lines, shown, id, getLineId);
208
+ focusNext.current = { id: focus };
209
+ onRemove?.(id);
210
+ toast?.show({
211
+ message: fillCopy(copy.lineItems.removed, { name: labelOf(line) }),
212
+ ...(onRestore === undefined
213
+ ? {}
214
+ : {
215
+ action: {
216
+ label: copy.lineItems.undo,
217
+ onSelect: () => onRestore(line, index),
218
+ },
219
+ }),
220
+ });
221
+ };
222
+ /* ── the table ──────────────────────────────────────────────────── */
223
+ const tableColumns = onScreen.map((c) => ({
224
+ id: c.id,
225
+ header: c.header,
226
+ ...(c.width === undefined ? {} : { width: c.width }),
227
+ ...(c.numeric === undefined ? {} : { numeric: c.numeric }),
228
+ ...(c.stack === undefined ? {} : { stack: c.stack }),
229
+ ...(c.brief === undefined ? {} : { brief: c.brief }),
230
+ render: (line) => {
231
+ const drawn = c.cell === undefined ? plain(c.value(line)) : c.cell(line);
232
+ /* An amount never breaks in two: «Bs.» over «12.500,00» reads as two values. */
233
+ return c.numeric === true ? _jsx("span", { className: "whitespace-nowrap", children: drawn }) : drawn;
234
+ },
235
+ ...(c.edit === undefined || !canChange
236
+ ? {}
237
+ : {
238
+ /* The control takes the column's width and does not impose its own: an input's
239
+ natural width is twenty characters, and in an auto-sized table that pushes
240
+ the amounts off the card the moment a row goes into editing. */
241
+ edit: (line, set) => (_jsx("div", { className: "contain-inline-size", children: c.edit?.(line, set) })),
242
+ }),
243
+ }));
244
+ if (canEdit || canRemove) {
245
+ tableColumns.push({
246
+ id: "\u0000actions",
247
+ header: "",
248
+ width: 1,
249
+ /* On a card the press opens the editor, and the editor removes: the buttons
250
+ would be two more targets on a small screen. Without an editor the trash
251
+ is the only way to remove, so it stays — at the card's top right, lit. */
252
+ stack: canEdit ? "hidden" : "trail",
253
+ onBands: true,
254
+ render: (line) => {
255
+ const id = getLineId(line);
256
+ const nameId = `${formId}-line-${id}`;
257
+ return (
258
+ /* Hidden until the row is hovered or holds focus, like `RowActions`: a pencil
259
+ and a trash can lit on each of twenty lines are forty invitations. They
260
+ fade instead of unmounting, so Tab still reaches them. Pressing them does
261
+ not put the row into in-place editing. */
262
+ _jsxs("span", { "data-line": id, className: "flex items-center justify-end gap-0.5 whitespace-nowrap opacity-0 transition-opacity duration-(--duration-instant) group-hover/row:opacity-100 focus-within:opacity-100 in-data-stacked:opacity-100", onClick: (e) => e.stopPropagation(), children: [_jsx("span", { id: nameId, className: "sr-only", children: labelOf(line) }), canEdit && kindOf(line) === "line" ? (_jsx(IconButton, { icon: Pencil, label: copy.lines.edit, "aria-describedby": nameId, haspopup: "dialog", onClick: () => openLine(id, line) })) : null, canRemove ? (_jsx(IconButton, { icon: Trash2, label: copy.lines.remove, tone: "danger", "aria-describedby": nameId, onClick: () => remove(line) })) : null] }));
263
+ },
264
+ });
265
+ }
266
+ /* In place, «add» is the table's own row under the grid: where the line will appear. */
267
+ const addButton = canAdd && !inline ? (_jsx(Button, { role: "secondary", size: "sm", icon: Plus, onClick: add, "data-line-add": "", children: copy.lines.add })) : null;
268
+ const count = q === ""
269
+ ? fillCopy(lineCount === 1 ? copy.lineItems.one : copy.lineItems.many, {
270
+ count: lineCount.toLocaleString(copy.locale),
271
+ })
272
+ : fillCopy(copy.lineItems.shown, {
273
+ shown: shown.length.toLocaleString(copy.locale),
274
+ count: lineCount.toLocaleString(copy.locale),
275
+ });
276
+ return (_jsxs("section", { ref: root, "aria-label": copy.lineItems.table, className: "flex min-w-0 flex-col gap-4", children: [lines.length === 0 && !canAdd ? (
277
+ /* Said, with its way out: an empty document is a question («what is sold
278
+ here?»), not a blank box. In read mode there is no way out to offer. */
279
+ _jsx(Empty, { align: "start", sentence: copy.lines.empty, action: addButton ?? undefined })) : (_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 px-1", children: [searchable ? (_jsxs("div", { className: "w-full max-w-(--container-search) min-w-0 flex-1", children: [_jsx("span", { id: searchLabelId, className: "sr-only", children: copy.lines.search }), _jsx(FieldIdsContext.Provider, { value: {
280
+ id: `${searchLabelId}-input`,
281
+ labelId: searchLabelId,
282
+ describedBy: undefined,
283
+ invalid: false,
284
+ }, children: _jsx(SearchField, { value: query, onChange: setQuery, placeholder: copy.lines.search }) })] })) : null, _jsx("span", { className: "text-meta text-muted tabular-nums", "aria-live": "polite", children: count }), columnControl.hasOptional || addButton !== null ? (_jsxs("span", { className: "ml-auto flex items-center gap-2", children: [_jsx(ColumnsMenu, { control: columnControl }), addButton] })) : null] }), _jsx(EditableTable, { rows: shown, columns: tableColumns, getRowId: getLineId, empty: lines.length === 0
285
+ ? copy.lines.empty
286
+ : fillCopy(copy.lineItems.noMatch, { query: query.trim() }), layout: layout, ...(inline ? { onAdd: () => addInline(), addLabel: copy.lines.add } : {}), ...(bandAdds.length === 0 ? {} : { adds: bandAdds }), ...(bands === undefined ? {} : { bands }), ...(flag === undefined ? {} : { flag }), ...(inPlace && onRemove !== undefined
287
+ ? /* A blank row nobody wrote in is taken back without a notice: nobody
288
+ removed anything, so there is nothing to undo. */
289
+ { onDiscard: onRemove }
290
+ : {}), ...(canEdit
291
+ ? {
292
+ onOpen: (id) => {
293
+ const line = lines.find((l) => getLineId(l) === id);
294
+ if (line !== undefined)
295
+ openLine(id, line);
296
+ },
297
+ }
298
+ : {}), ...(canChange && columns.some((c) => c.edit !== undefined)
299
+ ? { onChange: (id, next) => onChange?.(id, next) }
300
+ : {}) })] })), totals === undefined || ("length" in totals && totals.length === 0) ? null : (
301
+ /* Inset like the table's cells, so the last figure lines up with the amounts. */
302
+ _jsx("div", { className: "px-4", children: "length" in totals ? _jsx(Totals, { rows: totals }) : _jsx(Totals, { ...totals }) })), editor === undefined || session === null ? null : (_jsx(Dialog, { open: open, width: "sm", title: session.id === null ? copy.lineItems.newLine : copy.lines.edit, ...(context === undefined ? {} : { context }), ...(session.id === null ? {} : { subtitle: labelOf(session.draft) }), onClose: () => setOpen(false), footer: _jsxs(_Fragment, { children: [session.id === null || !canRemove ? null : (
303
+ /* Apart, at the other end from «Listo»: removing is not the way the
304
+ form is meant to close. */
305
+ _jsx("span", { className: "mr-auto", children: _jsx(IconButton, { icon: Trash2, label: copy.lines.remove, tone: "danger", onClick: () => {
306
+ const line = lines.find((l) => getLineId(l) === session.id);
307
+ setOpen(false);
308
+ if (line !== undefined)
309
+ remove(line);
310
+ } }) })), _jsx(Button, { role: "ghost", size: "sm", onClick: () => setOpen(false), children: copy.dialog.cancel }), _jsx(Button, { role: "primary", size: "sm", type: "submit", form: formId, children: copy.lines.done })] }), children: _jsx("form", { id: formId, noValidate: true, onSubmit: (e) => {
311
+ e.preventDefault();
312
+ save();
313
+ }, className: "flex flex-col gap-3", children: editor(session.draft, setDraft, {
314
+ tried: session.tried,
315
+ isNew: session.id === null,
316
+ }) }) }))] }));
317
+ }
@@ -0,0 +1 @@
1
+ export { type LineEditorState, type LineItemColumn, LineItems, type LineItemsProps, } from "./LineItems.js";
@@ -0,0 +1,2 @@
1
+ /* LINES — the sub-list of lines inside a record (Odoo's one2many): an invoice's, an order's. */
2
+ export { LineItems, } from "./LineItems.js";
@@ -0,0 +1,23 @@
1
+ /** The lines whose text contains the query, case and locale aware. An empty query is all. */
2
+ export declare function filterLines<T>(lines: readonly T[], query: string, textOf: (line: T) => string, locale: string): readonly T[];
3
+ /**
4
+ * What removing a line needs to know BEFORE it is gone: its position among all the lines (so
5
+ * «Deshacer» puts it back exactly there) and the visible line that inherits the focus — the
6
+ * next one, or the previous one when it was the last. `null` when none is left.
7
+ */
8
+ export declare function planRemoval<T>(lines: readonly T[], shown: readonly T[], id: string, getId: (line: T) => string): {
9
+ index: number;
10
+ focus: string | null;
11
+ };
12
+ /** What «Listo» does with the draft: nothing yet (it is not valid), add it, or replace a line. */
13
+ export type DraftCommit<T> = {
14
+ kind: "invalid";
15
+ } | {
16
+ kind: "add";
17
+ line: T;
18
+ } | {
19
+ kind: "change";
20
+ id: string;
21
+ line: T;
22
+ };
23
+ export declare function commitDraft<T>(id: string | null, draft: T, isValid?: (draft: T) => boolean): DraftCommit<T>;
@@ -0,0 +1,26 @@
1
+ /* The decisions of `LineItems` that do not need a screen: which lines a search leaves, where a
2
+ removed line was and who takes the focus after it, and what «Listo» hands to the owner of
3
+ the lines. Pure, so a test reads them without a browser. */
4
+ /** The lines whose text contains the query, case and locale aware. An empty query is all. */
5
+ export function filterLines(lines, query, textOf, locale) {
6
+ const q = query.trim().toLocaleLowerCase(locale);
7
+ if (q === "")
8
+ return lines;
9
+ return lines.filter((line) => textOf(line).toLocaleLowerCase(locale).includes(q));
10
+ }
11
+ /**
12
+ * What removing a line needs to know BEFORE it is gone: its position among all the lines (so
13
+ * «Deshacer» puts it back exactly there) and the visible line that inherits the focus — the
14
+ * next one, or the previous one when it was the last. `null` when none is left.
15
+ */
16
+ export function planRemoval(lines, shown, id, getId) {
17
+ const index = lines.findIndex((l) => getId(l) === id);
18
+ const at = shown.findIndex((l) => getId(l) === id);
19
+ const neighbour = shown[at + 1] ?? (at > 0 ? shown[at - 1] : undefined);
20
+ return { index, focus: neighbour === undefined ? null : getId(neighbour) };
21
+ }
22
+ export function commitDraft(id, draft, isValid) {
23
+ if (isValid !== undefined && !isValid(draft))
24
+ return { kind: "invalid" };
25
+ return id === null ? { kind: "add", line: draft } : { kind: "change", id, line: draft };
26
+ }