@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,198 @@
1
+ import { pathKey, pathLabels, pileLabel, } from "./grouping.js";
2
+ /**
3
+ * CÓMO SE LEE UNA CLAVE del eje, tramo por tramo.
4
+ *
5
+ * La clave identifica —«2026-09» es lo que el motor escribe y lo que se compara
6
+ * con lo que contó el servidor— y el rótulo se lee. Son dos textos, igual que en
7
+ * un montón, y por la misma razón: con el rótulo por identidad, dos septiembres
8
+ * de años distintos serían el mismo.
9
+ *
10
+ * `within` sale de los niveles de ARRIBA del mismo campo: con el año afuera, el
11
+ * mes se dice «Septiembre» y no «Septiembre de 2026», porque eso ya está escrito
12
+ * dos columnas antes.
13
+ */
14
+ export function axisLabels(axis, key, label = pileLabel) {
15
+ return pathLabels(axis.levels, key, label);
16
+ }
17
+ /**
18
+ * El separador entre las dos rutas de un cruce.
19
+ *
20
+ * Es un carácter distinto del que separa los tramos de una ruta —ése es el NUL
21
+ * de `pathKey`— porque si fuera el mismo, una fila `["a"]` cruzada con una
22
+ * columna `["b","c"]` y una fila `["a","b"]` cruzada con `["c"]` producirían la
23
+ * misma clave, y serían dos celdas distintas de la grilla apuntando al mismo
24
+ * número.
25
+ */
26
+ const CROSS = "";
27
+ function crossKey(row, column) {
28
+ return `${pathKey(row)}${CROSS}${pathKey(column)}`;
29
+ }
30
+ /** Sólo lo que es una celda, sin las claves con las que viajó. */
31
+ function cellOf(one) {
32
+ return { count: one.count, values: one.values };
33
+ }
34
+ /**
35
+ * Indexa un pivote que ya viene plegado.
36
+ *
37
+ * Los cruces viajan como lista —es lo que sabe serializar cualquier transporte—
38
+ * y se dibujan preguntando de a uno, así que en el medio hace falta un índice.
39
+ * Se arma una vez acá y no en cada celda de cada render: una grilla de 288
40
+ * celdas buscando en una lista de 40 son 11.520 comparaciones por pintada.
41
+ */
42
+ export function pivotOf(source) {
43
+ const crossings = new Map();
44
+ for (const one of source.crossings)
45
+ crossings.set(crossKey(one.row, one.column), cellOf(one));
46
+ const rowMargins = new Map();
47
+ for (const one of source.rowMargins ?? [])
48
+ rowMargins.set(pathKey(one.key), cellOf(one));
49
+ const columnMargins = new Map();
50
+ for (const one of source.columnMargins ?? [])
51
+ columnMargins.set(pathKey(one.key), cellOf(one));
52
+ return {
53
+ rows: source.rows,
54
+ columns: source.columns,
55
+ measures: source.measures,
56
+ cell: (row, column) => crossings.get(crossKey(row, column)),
57
+ /* Los márgenes se ofrecen sólo si vinieron. La alternativa —contestar
58
+ siempre y devolver `undefined` adentro— haría que la grilla dibujara una
59
+ banda de totales entera llena de agujeros. */
60
+ ...(source.rowMargins === undefined
61
+ ? {}
62
+ : { rowTotal: (row) => rowMargins.get(pathKey(row)) }),
63
+ ...(source.columnMargins === undefined
64
+ ? {}
65
+ : { columnTotal: (column) => columnMargins.get(pathKey(column)) }),
66
+ ...(source.total === undefined ? {} : { total: source.total }),
67
+ };
68
+ }
69
+ /** Las filas de un eje, agrupadas por su clave, en orden de primera aparición.
70
+ * El orden de las filas ya lo decidió alguien —una columna, un criterio, el
71
+ * motor— y reordenar los ejes acá lo contradiría. */
72
+ function axisBuckets(items, levels, of) {
73
+ const out = new Map();
74
+ for (const item of items) {
75
+ const key = levels.map((level) => of(item, level));
76
+ const id = pathKey(key);
77
+ const bucket = out.get(id);
78
+ if (bucket === undefined)
79
+ out.set(id, { key, items: [item] });
80
+ else
81
+ bucket.items.push(item);
82
+ }
83
+ return out;
84
+ }
85
+ function fold(measures, items) {
86
+ const values = {};
87
+ for (const measure of measures)
88
+ values[measure.id] = measure.fold(items);
89
+ return { count: items.length, values };
90
+ }
91
+ /**
92
+ * Arma el pivote con las filas que hay EN LA MANO.
93
+ *
94
+ * Sirve cuando el conjunto entero está en el navegador. Cuando no lo está, el
95
+ * pivote lo pliega quien guarda los datos y esto no se usa: la grilla recibe un
96
+ * `Pivot` de `pivotOf` y dibuja igual, porque dibuja el mismo tipo.
97
+ *
98
+ * **Los márgenes se pliegan sobre las filas de la base y no sobre los cruces**,
99
+ * que es la regla 2 escrita en código: el margen de «Ferretería Sur» pliega sus 187
100
+ * facturas de una, no los doce números mensuales que ya se dibujaron. Es
101
+ * lo único que hace que una medida que no sea una suma —un promedio, una
102
+ * mediana, un distintos— diga la verdad en el borde.
103
+ */
104
+ export function pivotFrom(items, spec) {
105
+ const label = (level) => spec.labelOf?.(level) ?? level.field;
106
+ const rowBuckets = axisBuckets(items, spec.rows, spec.of);
107
+ const columnBuckets = axisBuckets(items, spec.columns, spec.of);
108
+ const crossings = new Map();
109
+ for (const row of rowBuckets.values()) {
110
+ const inside = axisBuckets(row.items, spec.columns, spec.of);
111
+ for (const [columnId, column] of inside) {
112
+ crossings.set(`${pathKey(row.key)}${CROSS}${columnId}`, fold(spec.measures, column.items));
113
+ }
114
+ }
115
+ const rowMargins = new Map();
116
+ for (const [id, row] of rowBuckets)
117
+ rowMargins.set(id, fold(spec.measures, row.items));
118
+ const columnMargins = new Map();
119
+ for (const [id, column] of columnBuckets) {
120
+ columnMargins.set(id, fold(spec.measures, column.items));
121
+ }
122
+ return {
123
+ rows: {
124
+ levels: spec.rows.map((one) => ({ ...one, label: label(one) })),
125
+ keys: [...rowBuckets.values()].map((one) => one.key),
126
+ },
127
+ columns: {
128
+ levels: spec.columns.map((one) => ({ ...one, label: label(one) })),
129
+ keys: [...columnBuckets.values()].map((one) => one.key),
130
+ },
131
+ measures: spec.measures.map((one) => ({
132
+ id: one.id,
133
+ header: one.header,
134
+ ...(one.format === undefined ? {} : { format: one.format }),
135
+ ...(one.numberFormat === undefined ? {} : { numberFormat: one.numberFormat }),
136
+ })),
137
+ cell: (row, column) => crossings.get(crossKey(row, column)),
138
+ rowTotal: (row) => rowMargins.get(pathKey(row)),
139
+ columnTotal: (column) => columnMargins.get(pathKey(column)),
140
+ total: fold(spec.measures, items),
141
+ };
142
+ }
143
+ /** Si el pivote no tiene ni una fila que cruzar. Un eje vacío no es un pivote
144
+ * chico: es la vista vacía, y se dice con la frase del conjunto y no con una
145
+ * grilla de un renglón. */
146
+ export function pivotIsEmpty(pivot) {
147
+ return pivot.rows.keys.length === 0 || pivot.columns.keys.length === 0;
148
+ }
149
+ /**
150
+ * Los tramos de una clave que hay que ESCRIBIR, comparada con la anterior.
151
+ *
152
+ * En el área de filas de un pivote, el valor de un nivel se escribe sólo cuando
153
+ * cambia: repetir «Ferretería Sur» en las doce filas de sus doce meses convierte la
154
+ * jerarquía en una columna de ruido y hace desaparecer justamente el corte que
155
+ * se pidió. Devuelve, por nivel, si ese tramo arranca acá.
156
+ */
157
+ export function keyStarts(key, previous) {
158
+ let broken = previous === undefined;
159
+ return key.map((part, level) => {
160
+ if (broken)
161
+ return true;
162
+ /* Una vez que un nivel de arriba cambió, todos los de abajo arrancan de
163
+ nuevo aunque digan lo mismo: el «2026-09» de «Distribuidora Andina» no es la
164
+ continuación del «2026-09» de «Ferretería Sur». */
165
+ if (part !== previous?.[level]) {
166
+ broken = true;
167
+ return true;
168
+ }
169
+ return false;
170
+ });
171
+ }
172
+ /**
173
+ * Cuánto abarca cada tramo del eje horizontal, nivel por nivel.
174
+ *
175
+ * El encabezado de un pivote es una jerarquía acostada: un renglón por nivel, y
176
+ * cada rótulo cubriendo las columnas que cuelgan de él. Esto lo resuelve una vez
177
+ * —de a tramos consecutivos iguales, que es lo que hace que «2026» cubra sus
178
+ * cuatro trimestres— y la grilla sólo lo dibuja.
179
+ */
180
+ export function axisSpans(axis, level) {
181
+ const keys = axis.keys;
182
+ const out = [];
183
+ keys.forEach((key, at) => {
184
+ const label = axisLabels(axis, key)[level] ?? "";
185
+ const last = out[out.length - 1];
186
+ /* Se compara el PREFIJO entero y no el rótulo: dos «2026-09» debajo de dos
187
+ clientes distintos son dos tramos, y fundirlos en uno dibujaría una
188
+ celda que abarca columnas que no son hermanas. */
189
+ const samePrefix = last !== undefined &&
190
+ last.label === label &&
191
+ key.slice(0, level).join(CROSS) === (keys[at - 1]?.slice(0, level).join(CROSS) ?? null);
192
+ if (samePrefix)
193
+ last.span += 1;
194
+ else
195
+ out.push({ label, span: 1, at });
196
+ });
197
+ return out;
198
+ }
@@ -0,0 +1,61 @@
1
+ import { type Pivot } from "./pivot.js";
2
+ import { type XlsxSheet } from "./xlsx.js";
3
+ /**
4
+ * EL PIVOTE COMO PLANILLA: la misma grilla, en un archivo.
5
+ *
6
+ * ── Por qué esto existe y no alcanza con «exportar la tabla» ────────
7
+ *
8
+ * Una grilla que sale a Excel como texto plano pierde las tres cosas que la
9
+ * hacían una grilla: el encabezado deja de ser una jerarquía, los números dejan
10
+ * de sumarse y los agujeros se vuelven ceros. Las tres se arreglan acá:
11
+ *
12
+ * el encabezado va con celdas COMBINADAS, así «2026» sigue cubriendo sus
13
+ * cuatro trimestres
14
+ * los números entran como números, con su formato — no como texto que
15
+ * parece un número y no se puede sumar
16
+ * los agujeros no se escriben. Una celda ausente en el archivo es lo
17
+ * mismo que un cruce sin filas en la pantalla
18
+ *
19
+ * Y los rótulos quedan congelados, por la misma razón que en la pantalla se
20
+ * quedan pegados.
21
+ *
22
+ * ── Lo que el archivo NO puede decir ────────────────────────────────
23
+ *
24
+ * En la grilla, un cruce vacío y una medida que no aplica se dibujan distinto:
25
+ * blanco el primero, un punto el segundo. En la planilla los dos son una celda
26
+ * vacía, y es deliberado — un punto adentro de una columna de números la
27
+ * convierte en una columna de texto, y ahí se pierde poder sumarla, que es para
28
+ * lo que alguien baja una planilla.
29
+ *
30
+ * ── Lo que esto exporta es lo que está en la mano ───────────────────
31
+ *
32
+ * Que es exactamente todo, y por eso el pivote es la presentación donde exportar
33
+ * desde el navegador no miente: la grilla entera ya bajó —son rótulos y números
34
+ * plegados, no los registros— así que el archivo dice lo mismo que la pantalla.
35
+ * Una lista paginada no tiene esa suerte y su exportación es otra conversación.
36
+ */
37
+ export type PivotSheetOptions = {
38
+ /** El nombre de la pestaña, y del archivo cuando se baja. */
39
+ name?: string;
40
+ /** Un renglón arriba de todo que dice qué es esta grilla. */
41
+ caption?: string;
42
+ };
43
+ /**
44
+ * La grilla, como hoja de cálculo.
45
+ *
46
+ * Devuelve la FORMA y no el archivo: los mismos datos sirven para bajarlos acá,
47
+ * para mandarlos a un servidor que escriba el libro con más hojas, o para
48
+ * mirarlos en una prueba sin descomprimir nada.
49
+ */
50
+ export declare function pivotSheet(pivot: Pivot, options?: PivotSheetOptions): XlsxSheet;
51
+ /** Los bytes del `.xlsx` de una grilla. */
52
+ export declare function pivotXlsx(pivot: Pivot, options?: PivotSheetOptions): Uint8Array;
53
+ /**
54
+ * Bajar la grilla como planilla, en una línea.
55
+ *
56
+ * Existe para que el que la usa no vuelva a escribir el armado: tres consumidores
57
+ * encadenando `pivotSheet → writeXlsx → downloadXlsx` a mano son tres lugares
58
+ * donde alguien se olvida del título o del nombre del archivo, y entonces la
59
+ * misma grilla baja distinta según de qué pantalla salió.
60
+ */
61
+ export declare function downloadPivot(pivot: Pivot, options?: PivotSheetOptions): void;
Binary file
@@ -0,0 +1,67 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * LAS RANURAS — cómo un arquetipo se escribe como ÁRBOL.
4
+ *
5
+ * Context: la forma de una pantalla es opcional de una manera que una lista de
6
+ * props no sabe decir. ¿Hay riel? ¿Hay pestañas? ¿Hay paginador? ¿Esta lista se
7
+ * puede ver como tabla? Una lista de props contesta eso con `pager={undefined}`
8
+ * y con una segunda prop que declara qué caras se ofrecen — dos lugares que se
9
+ * pueden contradecir. Un árbol lo dice en voz alta: **si no está escrito, no
10
+ * está.**
11
+ *
12
+ * <Record>
13
+ * <Record.Rail>
14
+ * <Record.Pager index={12} total={80} /> ← hay paginador
15
+ * </Record.Rail>
16
+ * </Record>
17
+ *
18
+ * ── Por qué una MARCA y no la identidad del componente ──────────────
19
+ *
20
+ * Reconocer una ranura comparando `node.type === Record.Pager` falla en
21
+ * silencio el día que hay dos copias del módulo en memoria —recarga en caliente,
22
+ * un bundle mal deduplicado— y entonces el árbol se dibuja vacío sin un solo
23
+ * error. La marca viaja con el componente y sobrevive a eso.
24
+ *
25
+ * ── Esto vive en `system/` y no adentro de un arquetipo ─────────────
26
+ *
27
+ * Porque lo hablan todos los que se escriben como árbol. Mientras estuvo
28
+ * escondido adentro de `Record.tsx`, ser un árbol era un privilegio de los
29
+ * registros: cualquier otro arquetipo tenía que copiar el mecanismo o quedarse
30
+ * con treinta y seis props.
31
+ */
32
+ /** Un componente marcado como ranura. La marca es el nombre, así que un árbol la
33
+ * lee sin saber de dónde salió el componente. */
34
+ export type Slotted<Name extends string> = {
35
+ __slot?: Name;
36
+ };
37
+ /**
38
+ * Marca un componente como ranura y lo devuelve.
39
+ *
40
+ * El componente NO dibuja nada: existe para ser leído por el arquetipo, que es
41
+ * quien decide dónde va lo que trae adentro. Devolver `null` es lo correcto —
42
+ * una ranura que se dibujara sola aparecería dos veces, una donde la escribieron
43
+ * y otra donde el arquetipo la puso.
44
+ */
45
+ export declare function slot<Name extends string, P>(name: Name, component: (props: P) => ReactNode): ((props: P) => ReactNode) & Slotted<Name>;
46
+ /** Qué ranura es un nodo, si es alguna. */
47
+ export declare function slotOf<Name extends string>(node: ReactNode): Name | undefined;
48
+ /**
49
+ * Los hijos, con los fragmentos abiertos.
50
+ *
51
+ * `Children.toArray` aplana arreglos pero NO fragmentos, y un `<>…</>` en el
52
+ * medio del árbol —lo que escribe cualquiera que ponga dos ranuras bajo un
53
+ * condicional— escondería las ranuras que tiene adentro.
54
+ */
55
+ export declare function flatten(children: ReactNode): ReactNode[];
56
+ /** La primera ranura de ese nombre. */
57
+ export declare function pick<Name extends string>(nodes: readonly ReactNode[], name: Name): ReactNode | undefined;
58
+ /** Todas las ranuras de ese nombre, en el orden en que se escribieron. Lo usa el
59
+ * que acepta varias hermanas —las páginas de un cuaderno. */
60
+ export declare function pickAll<Name extends string>(nodes: readonly ReactNode[], name: Name): ReactNode[];
61
+ /** Las props con las que se escribió una ranura. Es lo único que el arquetipo
62
+ * necesita de ella: la ranura no dibuja, declara. */
63
+ export declare function propsOf<P>(node: ReactNode | undefined): P | undefined;
64
+ /** Lo que NO es ninguna de esas ranuras. Es la escotilla: lo que el arquetipo no
65
+ * previó se dibuja igual, y que aparezca algo acá es la señal de que al
66
+ * arquetipo le falta una pieza. */
67
+ export declare function loose<Name extends string>(nodes: readonly ReactNode[], names: readonly Name[]): ReactNode[];
@@ -0,0 +1,63 @@
1
+ import { Children, Fragment, isValidElement } from "react";
2
+ /**
3
+ * Marca un componente como ranura y lo devuelve.
4
+ *
5
+ * El componente NO dibuja nada: existe para ser leído por el arquetipo, que es
6
+ * quien decide dónde va lo que trae adentro. Devolver `null` es lo correcto —
7
+ * una ranura que se dibujara sola aparecería dos veces, una donde la escribieron
8
+ * y otra donde el arquetipo la puso.
9
+ */
10
+ export function slot(name, component) {
11
+ const marked = component;
12
+ marked.__slot = name;
13
+ return marked;
14
+ }
15
+ /** Qué ranura es un nodo, si es alguna. */
16
+ export function slotOf(node) {
17
+ if (!isValidElement(node))
18
+ return undefined;
19
+ const type = node.type;
20
+ return typeof type === "string" ? undefined : type.__slot;
21
+ }
22
+ /**
23
+ * Los hijos, con los fragmentos abiertos.
24
+ *
25
+ * `Children.toArray` aplana arreglos pero NO fragmentos, y un `<>…</>` en el
26
+ * medio del árbol —lo que escribe cualquiera que ponga dos ranuras bajo un
27
+ * condicional— escondería las ranuras que tiene adentro.
28
+ */
29
+ export function flatten(children) {
30
+ const out = [];
31
+ for (const child of Children.toArray(children)) {
32
+ if (isValidElement(child) && child.type === Fragment) {
33
+ out.push(...flatten(child.props.children));
34
+ }
35
+ else {
36
+ out.push(child);
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+ /** La primera ranura de ese nombre. */
42
+ export function pick(nodes, name) {
43
+ return nodes.find((node) => slotOf(node) === name);
44
+ }
45
+ /** Todas las ranuras de ese nombre, en el orden en que se escribieron. Lo usa el
46
+ * que acepta varias hermanas —las páginas de un cuaderno. */
47
+ export function pickAll(nodes, name) {
48
+ return nodes.filter((node) => slotOf(node) === name);
49
+ }
50
+ /** Las props con las que se escribió una ranura. Es lo único que el arquetipo
51
+ * necesita de ella: la ranura no dibuja, declara. */
52
+ export function propsOf(node) {
53
+ return isValidElement(node) ? node.props : undefined;
54
+ }
55
+ /** Lo que NO es ninguna de esas ranuras. Es la escotilla: lo que el arquetipo no
56
+ * previó se dibuja igual, y que aparezca algo acá es la señal de que al
57
+ * arquetipo le falta una pieza. */
58
+ export function loose(nodes, names) {
59
+ return nodes.filter((node) => {
60
+ const name = slotOf(node);
61
+ return name === undefined || !names.includes(name);
62
+ });
63
+ }
@@ -0,0 +1,53 @@
1
+ import { type CSSProperties, type RefObject } from "react";
2
+ /** La variable que dice a qué altura arranca lo próximo que se pegue. */
3
+ export declare const STICKY_TOP = "--sticky-top";
4
+ /** Lo que va en `style` de algo pegajoso: se pega debajo de lo que ya hay. */
5
+ export declare const stickyTop: CSSProperties;
6
+ /** Para una caja con scroll propio: adentro, la pila arranca de cero. */
7
+ export declare function stickyReset(): CSSProperties;
8
+ /**
9
+ * LA CAJA DE UNA TABLA: cuándo tiene que ser un contenedor de scroll y cuándo no.
10
+ *
11
+ * Las tres tablas del sistema tienen el mismo dilema y lo resolvían con tres
12
+ * copias de la misma medición. El dilema es de CSS y no se puede esquivar:
13
+ * `overflow-x: auto` **obliga** a `overflow-y: auto` —la especificación dice que
14
+ * `visible` junto a un valor que no sea `visible` ni `clip` computa a `auto`—,
15
+ * así que en cuanto la caja permite scroll horizontal pasa a ser el scroller, y
16
+ * el encabezado pegajoso se pega a SU borde en vez de al de la página.
17
+ *
18
+ * De ahí salen los tres casos, y son los únicos:
19
+ *
20
+ * alto propio la caja scrollea de verdad. `auto`, y la pila de cosas
21
+ * pegadas arranca de cero adentro.
22
+ * sin alto, entra no hace falta ninguna caja. `clip` —recorta las esquinas
23
+ * redondeadas sin ser un scroller— y el encabezado se pega
24
+ * a la página, debajo de la barra de la lista.
25
+ * sin alto, no entra vuelve el scroll horizontal, y el encabezado se va
26
+ * con las filas. Es el precio, y es el que hay.
27
+ *
28
+ * Devuelve la clase y el estilo ya resueltos para que ninguna tabla vuelva a
29
+ * decidirlo por su cuenta.
30
+ */
31
+ export declare function useScrollBox<T extends HTMLElement>(options: {
32
+ /** El alto declarado. `undefined` o `"auto"` = crece con el contenido. */
33
+ height?: number | string;
34
+ }): {
35
+ ref: RefObject<T | null>;
36
+ className: string;
37
+ style: CSSProperties;
38
+ };
39
+ /**
40
+ * Mide el alto de un elemento y devuelve el estilo que empuja la pila para sus
41
+ * hermanos de abajo.
42
+ *
43
+ * `useLayoutEffect` y `ResizeObserver` y no un alto declarado: el alto depende
44
+ * de si las fichas del buscador envolvieron, y eso recién se sabe después de
45
+ * pintar.
46
+ */
47
+ export declare function useStickyLayer<T extends HTMLElement>(): {
48
+ ref: RefObject<T | null>;
49
+ /** Va en el elemento que se pega. */
50
+ self: CSSProperties;
51
+ /** Va en lo que viene DEBAJO, para que se pegue más abajo todavía. */
52
+ below: CSSProperties;
53
+ };
@@ -0,0 +1,150 @@
1
+ "use client";
2
+ import { useLayoutEffect, useRef, useState } from "react";
3
+ /* ==================================================================== *
4
+ * LA PILA DE COSAS PEGADAS
5
+ *
6
+ * Tres bandas se quedan arriba mientras se scrollea una vista larga, y cada
7
+ * una tiene que quedar DEBAJO de la anterior:
8
+ *
9
+ * ┌─ TopNav ─────────────────────────────────┐ top: 0
10
+ * ├─ barra de la lista ──────────────────────┤ top: alto del TopNav
11
+ * │ 🔍 buscar… ⚙3 1–80 / 187 ‹ › │
12
+ * ├─ encabezado de la tabla ─────────────────┤ top: los dos de arriba
13
+ * │ número │ cliente │ fecha │ total │
14
+ * ├──────────────────────────────────────────┤
15
+ * │ …las filas, que sí se van… │
16
+ *
17
+ * **Ninguna de las tres tiene alto fijo.** El `TopNav` crece cuando envuelve;
18
+ * la barra de la lista tiene dos renglones, o uno en compacto, o tres cuando
19
+ * las fichas del buscador envuelven. Un `top-[52px]` escrito a mano acierta en
20
+ * una ventana y deja un hueco o un solapamiento en las otras — y es la clase
21
+ * de número que nadie vuelve a tocar cuando la barra cambia.
22
+ *
23
+ * Por eso cada banda MIDE lo que ocupa y lo suma a `--sticky-top` para lo que
24
+ * venga abajo. La variable cascadea, así que cada capa lee la de arriba sin
25
+ * que ninguna sepa cuántas capas hay.
26
+ *
27
+ * ── La regla del que abre una caja con scroll propio ────────────────
28
+ *
29
+ * Adentro de una caja con alto y `overflow`, el que scrollea es la caja y no
30
+ * la página, así que lo pegado se pega al borde de la caja: ahí la pila
31
+ * arranca de cero otra vez. Eso es `stickyReset()`.
32
+ * ==================================================================== */
33
+ /** La variable que dice a qué altura arranca lo próximo que se pegue. */
34
+ export const STICKY_TOP = "--sticky-top";
35
+ /** Lo que va en `style` de algo pegajoso: se pega debajo de lo que ya hay. */
36
+ export const stickyTop = { top: `var(${STICKY_TOP}, 0px)` };
37
+ /** Para una caja con scroll propio: adentro, la pila arranca de cero. */
38
+ export function stickyReset() {
39
+ return { [STICKY_TOP]: "0px" };
40
+ }
41
+ /**
42
+ * LA CAJA DE UNA TABLA: cuándo tiene que ser un contenedor de scroll y cuándo no.
43
+ *
44
+ * Las tres tablas del sistema tienen el mismo dilema y lo resolvían con tres
45
+ * copias de la misma medición. El dilema es de CSS y no se puede esquivar:
46
+ * `overflow-x: auto` **obliga** a `overflow-y: auto` —la especificación dice que
47
+ * `visible` junto a un valor que no sea `visible` ni `clip` computa a `auto`—,
48
+ * así que en cuanto la caja permite scroll horizontal pasa a ser el scroller, y
49
+ * el encabezado pegajoso se pega a SU borde en vez de al de la página.
50
+ *
51
+ * De ahí salen los tres casos, y son los únicos:
52
+ *
53
+ * alto propio la caja scrollea de verdad. `auto`, y la pila de cosas
54
+ * pegadas arranca de cero adentro.
55
+ * sin alto, entra no hace falta ninguna caja. `clip` —recorta las esquinas
56
+ * redondeadas sin ser un scroller— y el encabezado se pega
57
+ * a la página, debajo de la barra de la lista.
58
+ * sin alto, no entra vuelve el scroll horizontal, y el encabezado se va
59
+ * con las filas. Es el precio, y es el que hay.
60
+ *
61
+ * Devuelve la clase y el estilo ya resueltos para que ninguna tabla vuelva a
62
+ * decidirlo por su cuenta.
63
+ */
64
+ export function useScrollBox(options) {
65
+ const ref = useRef(null);
66
+ const crece = options.height === undefined || options.height === "auto";
67
+ const [desborda, setDesborda] = useState(false);
68
+ useLayoutEffect(() => {
69
+ const node = ref.current;
70
+ if (node === null || !crece)
71
+ return;
72
+ /* Se mide el CONTENIDO y no la caja, y es la diferencia entre que esto
73
+ funcione y que se trabe: con `overflow: clip` la caja deja de ser un
74
+ contenedor de scroll, así que su `scrollWidth` pasa a ser su propio ancho
75
+ y nunca volvería a reportar desborde. Una vez recortada, se quedaría
76
+ recortada para siempre. El hijo sí sabe cuánto mide de verdad. */
77
+ const dentro = node.firstElementChild;
78
+ const medir = () => {
79
+ const ancho = dentro === null ? node.scrollWidth : dentro.getBoundingClientRect().width;
80
+ setDesborda(ancho > node.clientWidth + 1);
81
+ };
82
+ medir();
83
+ if (typeof ResizeObserver === "undefined")
84
+ return;
85
+ const observer = new ResizeObserver(medir);
86
+ observer.observe(node);
87
+ /* También el contenido: las columnas cambian de ancho sin que la caja
88
+ cambie de tamaño. */
89
+ if (dentro !== null)
90
+ observer.observe(dentro);
91
+ return () => observer.disconnect();
92
+ }, [crece]);
93
+ const suelta = crece && !desborda;
94
+ return {
95
+ ref,
96
+ className: suelta ? "overflow-clip" : "overflow-auto",
97
+ style: suelta
98
+ ? {}
99
+ : {
100
+ ...(crece || options.height === undefined ? {} : { height: options.height }),
101
+ ...stickyReset(),
102
+ },
103
+ };
104
+ }
105
+ /**
106
+ * Mide el alto de un elemento y devuelve el estilo que empuja la pila para sus
107
+ * hermanos de abajo.
108
+ *
109
+ * `useLayoutEffect` y `ResizeObserver` y no un alto declarado: el alto depende
110
+ * de si las fichas del buscador envolvieron, y eso recién se sabe después de
111
+ * pintar.
112
+ */
113
+ export function useStickyLayer() {
114
+ const ref = useRef(null);
115
+ const [top, setTop] = useState(0);
116
+ useLayoutEffect(() => {
117
+ const node = ref.current;
118
+ if (node === null)
119
+ return;
120
+ /* El valor de abajo se calcula en JS y se escribe en PÍXELES, no como
121
+ `calc(var(--sticky-top) + 44px)`. Context: esa forma parece la natural y
122
+ no funciona — poner `--sticky-top` en un elemento leyendo `var(--sticky-top)`
123
+ del MISMO elemento es un ciclo, CSS lo declara inválido, y los
124
+ descendientes heredan «nada»: todo se pegaba en 0 y las bandas se
125
+ superponían. Se midió en el navegador, no se dedujo. */
126
+ const measure = () => {
127
+ const inherited = Number.parseFloat(getComputedStyle(node).getPropertyValue(STICKY_TOP));
128
+ const above = Number.isFinite(inherited) ? inherited : 0;
129
+ setTop(above + node.getBoundingClientRect().height);
130
+ };
131
+ measure();
132
+ if (typeof ResizeObserver === "undefined")
133
+ return;
134
+ const observer = new ResizeObserver(measure);
135
+ observer.observe(node);
136
+ /* La altura de una capa de ARRIBA cambia sin que este observador se entere
137
+ —el `TopNav` envuelve al angostar—, así que también se remide al cambiar
138
+ el tamaño de la ventana, que es cuando eso pasa. */
139
+ window.addEventListener("resize", measure);
140
+ return () => {
141
+ observer.disconnect();
142
+ window.removeEventListener("resize", measure);
143
+ };
144
+ }, []);
145
+ return {
146
+ ref,
147
+ self: stickyTop,
148
+ below: { [STICKY_TOP]: `${top}px` },
149
+ };
150
+ }
@@ -0,0 +1,9 @@
1
+ import type { Surface } from "./types.js";
2
+ import { type ReactNode } from "react";
3
+ export declare function SurfaceProvider({ value, children, }: {
4
+ value: Surface;
5
+ children: ReactNode;
6
+ }): import("react").JSX.Element;
7
+ export declare function useSurface(explicit?: Surface): Surface;
8
+ /** Picks between two values according to the surface. The twin of `byDensity`. */
9
+ export declare const bySurface: <T>(surface: Surface, canvas: T, chrome: T) => T;
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from "react";
4
+ /**
5
+ * Context: which surface whatever is inside is drawing on.
6
+ *
7
+ * It is a context and not a prop —the opposite of `Button`— for the same reason
8
+ * as density: a surface has to be UNIFORM inside. A chrome bar with a canvas
9
+ * search box inside is not a mix, it is a bug, and that happened: the status bar
10
+ * was drawn with the canvas palette on top of the navy because whoever put it
11
+ * there forgot to pass `surface="chrome"`.
12
+ *
13
+ * The pieces that live on both still accept it as a prop: the prop wins, and the
14
+ * context is only the default value. That way a one-off decision can be audited
15
+ * by reading the JSX, which is the system's rule for color.
16
+ */
17
+ const Ctx = createContext("canvas");
18
+ export function SurfaceProvider({ value, children, }) {
19
+ return _jsx(Ctx.Provider, { value: value, children: children });
20
+ }
21
+ export function useSurface(explicit) {
22
+ const inherited = useContext(Ctx);
23
+ return explicit ?? inherited;
24
+ }
25
+ /** Picks between two values according to the surface. The twin of `byDensity`. */
26
+ export const bySurface = (surface, canvas, chrome) => surface === "chrome" ? chrome : canvas;