@sincpro/web-ui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -2
  3. package/dist/components/assist/AssistMark.d.ts +64 -0
  4. package/dist/components/assist/AssistMark.js +108 -0
  5. package/dist/components/assist/AssistPending.d.ts +24 -0
  6. package/dist/components/assist/AssistPending.js +26 -0
  7. package/dist/components/assist/AssistSuggestion.d.ts +42 -0
  8. package/dist/components/assist/AssistSuggestion.js +55 -0
  9. package/dist/components/assist/ChangeReview.d.ts +56 -0
  10. package/dist/components/assist/ChangeReview.js +121 -0
  11. package/dist/components/assist/assist.d.ts +77 -0
  12. package/dist/components/assist/assist.js +87 -0
  13. package/dist/components/assist/index.d.ts +5 -0
  14. package/dist/components/assist/index.js +7 -0
  15. package/dist/components/chatter/Chatter.d.ts +161 -0
  16. package/dist/components/chatter/Chatter.js +232 -0
  17. package/dist/components/chatter/Messages.d.ts +66 -0
  18. package/dist/components/chatter/Messages.js +43 -0
  19. package/dist/components/chatter/Timeline.d.ts +21 -0
  20. package/dist/components/chatter/Timeline.js +30 -0
  21. package/dist/components/chatter/index.d.ts +3 -0
  22. package/dist/components/chatter/index.js +4 -0
  23. package/dist/components/form/Form.d.ts +52 -0
  24. package/dist/components/form/Form.js +49 -0
  25. package/dist/components/form/Sheet.d.ts +43 -0
  26. package/dist/components/form/Sheet.js +83 -0
  27. package/dist/components/form/index.d.ts +2 -0
  28. package/dist/components/form/index.js +4 -0
  29. package/dist/components/frame/Chart.d.ts +13 -0
  30. package/dist/components/frame/Chart.js +24 -0
  31. package/dist/components/frame/FrameView.d.ts +33 -0
  32. package/dist/components/frame/FrameView.js +125 -0
  33. package/dist/components/frame/index.d.ts +3 -0
  34. package/dist/components/frame/index.js +3 -0
  35. package/dist/components/frame/model.d.ts +43 -0
  36. package/dist/components/frame/model.js +101 -0
  37. package/dist/components/index.d.ts +10 -0
  38. package/dist/components/index.js +11 -0
  39. package/dist/components/lines/LineItems.d.ts +74 -0
  40. package/dist/components/lines/LineItems.js +229 -0
  41. package/dist/components/lines/index.d.ts +1 -0
  42. package/dist/components/lines/index.js +2 -0
  43. package/dist/components/lines/lines.d.ts +23 -0
  44. package/dist/components/lines/lines.js +26 -0
  45. package/dist/components/listing/ColumnsMenu.d.ts +20 -0
  46. package/dist/components/listing/ColumnsMenu.js +46 -0
  47. package/dist/components/listing/GroupingLevels.d.ts +35 -0
  48. package/dist/components/listing/GroupingLevels.js +45 -0
  49. package/dist/components/listing/Listing.d.ts +27 -0
  50. package/dist/components/listing/Listing.js +86 -0
  51. package/dist/components/listing/ListingBar.d.ts +73 -0
  52. package/dist/components/listing/ListingBar.js +105 -0
  53. package/dist/components/listing/Pagination.d.ts +36 -0
  54. package/dist/components/listing/Pagination.js +69 -0
  55. package/dist/components/listing/RecordCard.d.ts +33 -0
  56. package/dist/components/listing/RecordCard.js +22 -0
  57. package/dist/components/listing/RecordLink.d.ts +7 -0
  58. package/dist/components/listing/RecordLink.js +22 -0
  59. package/dist/components/listing/Row.d.ts +106 -0
  60. package/dist/components/listing/Row.js +122 -0
  61. package/dist/components/listing/SortMenu.d.ts +62 -0
  62. package/dist/components/listing/SortMenu.js +105 -0
  63. package/dist/components/listing/ViewSwitcher.d.ts +24 -0
  64. package/dist/components/listing/ViewSwitcher.js +16 -0
  65. package/dist/components/listing/columns.d.ts +47 -0
  66. package/dist/components/listing/columns.js +72 -0
  67. package/dist/components/listing/grouping.d.ts +35 -0
  68. package/dist/components/listing/grouping.js +47 -0
  69. package/dist/components/listing/index.d.ts +17 -0
  70. package/dist/components/listing/index.js +20 -0
  71. package/dist/components/listing/modelColumns.d.ts +50 -0
  72. package/dist/components/listing/modelColumns.js +103 -0
  73. package/dist/components/listing/pages.d.ts +78 -0
  74. package/dist/components/listing/pages.js +135 -0
  75. package/dist/components/listing/selection.d.ts +21 -0
  76. package/dist/components/listing/selection.js +43 -0
  77. package/dist/components/listing/set.d.ts +48 -0
  78. package/dist/components/listing/set.js +24 -0
  79. package/dist/components/listing/tree.d.ts +28 -0
  80. package/dist/components/listing/tree.js +23 -0
  81. package/dist/components/navigation/CommandPalette.d.ts +40 -0
  82. package/dist/components/navigation/CommandPalette.js +114 -0
  83. package/dist/components/navigation/NavigationTree.d.ts +31 -0
  84. package/dist/components/navigation/NavigationTree.js +79 -0
  85. package/dist/components/navigation/commands.d.ts +30 -0
  86. package/dist/components/navigation/commands.js +30 -0
  87. package/dist/components/navigation/index.d.ts +3 -0
  88. package/dist/components/navigation/index.js +4 -0
  89. package/dist/components/record/Breadcrumb.d.ts +42 -0
  90. package/dist/components/record/Breadcrumb.js +98 -0
  91. package/dist/components/record/DirtyActions.d.ts +26 -0
  92. package/dist/components/record/DirtyActions.js +22 -0
  93. package/dist/components/record/Pager.d.ts +18 -0
  94. package/dist/components/record/Pager.js +33 -0
  95. package/dist/components/record/RecordHeader.d.ts +71 -0
  96. package/dist/components/record/RecordHeader.js +72 -0
  97. package/dist/components/record/RecordSkeleton.d.ts +3 -0
  98. package/dist/components/record/RecordSkeleton.js +21 -0
  99. package/dist/components/record/SmartButtons.d.ts +35 -0
  100. package/dist/components/record/SmartButtons.js +38 -0
  101. package/dist/components/record/StatusBar.d.ts +51 -0
  102. package/dist/components/record/StatusBar.js +168 -0
  103. package/dist/components/record/ThumbBar.d.ts +36 -0
  104. package/dist/components/record/ThumbBar.js +86 -0
  105. package/dist/components/record/index.d.ts +6 -0
  106. package/dist/components/record/index.js +8 -0
  107. package/dist/components/search/CriteriaBuilder.d.ts +58 -0
  108. package/dist/components/search/CriteriaBuilder.js +278 -0
  109. package/dist/components/search/CriteriaChips.d.ts +22 -0
  110. package/dist/components/search/CriteriaChips.js +64 -0
  111. package/dist/components/search/FilterBuilder.d.ts +142 -0
  112. package/dist/components/search/FilterBuilder.js +421 -0
  113. package/dist/components/search/JoinWord.d.ts +8 -0
  114. package/dist/components/search/JoinWord.js +47 -0
  115. package/dist/components/search/JsonEditor.d.ts +19 -0
  116. package/dist/components/search/JsonEditor.js +78 -0
  117. package/dist/components/search/SearchBar.d.ts +78 -0
  118. package/dist/components/search/SearchBar.js +387 -0
  119. package/dist/components/search/criteria.d.ts +116 -0
  120. package/dist/components/search/criteria.js +159 -0
  121. package/dist/components/search/datePeriods.d.ts +122 -0
  122. package/dist/components/search/datePeriods.js +134 -0
  123. package/dist/components/search/dateRanges.d.ts +54 -0
  124. package/dist/components/search/dateRanges.js +89 -0
  125. package/dist/components/search/index.d.ts +9 -0
  126. package/dist/components/search/index.js +20 -0
  127. package/dist/components/search/model.d.ts +54 -0
  128. package/dist/components/search/model.js +37 -0
  129. package/dist/components/search/query.d.ts +166 -0
  130. package/dist/components/search/query.js +436 -0
  131. package/dist/components/shell/AppFrame.d.ts +14 -0
  132. package/dist/components/shell/AppFrame.js +80 -0
  133. package/dist/components/shell/TopBar.d.ts +73 -0
  134. package/dist/components/shell/TopBar.js +54 -0
  135. package/dist/components/shell/headerSlot.d.ts +17 -0
  136. package/dist/components/shell/headerSlot.js +21 -0
  137. package/dist/components/shell/index.d.ts +2 -0
  138. package/dist/components/shell/index.js +3 -0
  139. package/dist/foundations/aria.d.ts +14 -0
  140. package/dist/foundations/aria.js +18 -0
  141. package/dist/foundations/calendar.d.ts +28 -0
  142. package/dist/foundations/calendar.js +183 -0
  143. package/dist/foundations/copy.d.ts +619 -0
  144. package/dist/foundations/copy.es.d.ts +2 -0
  145. package/dist/foundations/copy.es.js +377 -0
  146. package/dist/foundations/copy.js +472 -0
  147. package/dist/foundations/density.d.ts +16 -0
  148. package/dist/foundations/density.js +21 -0
  149. package/dist/foundations/grouping.d.ts +281 -0
  150. package/dist/foundations/grouping.js +0 -0
  151. package/dist/foundations/index.d.ts +14 -0
  152. package/dist/foundations/index.js +30 -0
  153. package/dist/foundations/layout.d.ts +9 -0
  154. package/dist/foundations/layout.js +9 -0
  155. package/dist/foundations/pivot.d.ts +222 -0
  156. package/dist/foundations/pivot.js +198 -0
  157. package/dist/foundations/pivotSheet.d.ts +61 -0
  158. package/dist/foundations/pivotSheet.js +0 -0
  159. package/dist/foundations/slots.d.ts +67 -0
  160. package/dist/foundations/slots.js +63 -0
  161. package/dist/foundations/sticky.d.ts +53 -0
  162. package/dist/foundations/sticky.js +150 -0
  163. package/dist/foundations/surface.d.ts +9 -0
  164. package/dist/foundations/surface.js +26 -0
  165. package/dist/foundations/theme.d.ts +42 -0
  166. package/dist/foundations/theme.js +88 -0
  167. package/dist/foundations/types.d.ts +23 -0
  168. package/dist/foundations/types.js +50 -0
  169. package/dist/foundations/variants.d.ts +19 -0
  170. package/dist/foundations/variants.js +48 -0
  171. package/dist/foundations/xlsx.d.ts +86 -0
  172. package/dist/foundations/xlsx.js +0 -0
  173. package/dist/index.d.ts +19 -0
  174. package/dist/index.js +19 -0
  175. package/dist/primitives/block/Block.d.ts +66 -0
  176. package/dist/primitives/block/Block.js +34 -0
  177. package/dist/primitives/block/Line.d.ts +18 -0
  178. package/dist/primitives/block/Line.js +45 -0
  179. package/dist/primitives/block/SectionGrid.d.ts +30 -0
  180. package/dist/primitives/block/SectionGrid.js +29 -0
  181. package/dist/primitives/block/Steps.d.ts +76 -0
  182. package/dist/primitives/block/Steps.js +43 -0
  183. package/dist/primitives/block/index.d.ts +4 -0
  184. package/dist/primitives/block/index.js +7 -0
  185. package/dist/primitives/button/Button.d.ts +137 -0
  186. package/dist/primitives/button/Button.js +234 -0
  187. package/dist/primitives/button/index.d.ts +1 -0
  188. package/dist/primitives/button/index.js +2 -0
  189. package/dist/primitives/cell/CellStack.d.ts +47 -0
  190. package/dist/primitives/cell/CellStack.js +33 -0
  191. package/dist/primitives/cell/index.d.ts +1 -0
  192. package/dist/primitives/cell/index.js +3 -0
  193. package/dist/primitives/choice/ComboBox.d.ts +16 -0
  194. package/dist/primitives/choice/ComboBox.js +52 -0
  195. package/dist/primitives/choice/RadioGroup.d.ts +18 -0
  196. package/dist/primitives/choice/RadioGroup.js +50 -0
  197. package/dist/primitives/choice/SearchField.d.ts +24 -0
  198. package/dist/primitives/choice/SearchField.js +27 -0
  199. package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
  200. package/dist/primitives/choice/SegmentedControl.js +69 -0
  201. package/dist/primitives/choice/Slider.d.ts +16 -0
  202. package/dist/primitives/choice/Slider.js +46 -0
  203. package/dist/primitives/choice/Switch.d.ts +14 -0
  204. package/dist/primitives/choice/Switch.js +65 -0
  205. package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
  206. package/dist/primitives/choice/ThemeSwitcher.js +25 -0
  207. package/dist/primitives/choice/index.d.ts +8 -0
  208. package/dist/primitives/choice/index.js +11 -0
  209. package/dist/primitives/choice/options.d.ts +28 -0
  210. package/dist/primitives/choice/options.js +6 -0
  211. package/dist/primitives/date/Calendar.d.ts +31 -0
  212. package/dist/primitives/date/Calendar.js +110 -0
  213. package/dist/primitives/date/DatePicker.d.ts +34 -0
  214. package/dist/primitives/date/DatePicker.js +99 -0
  215. package/dist/primitives/date/index.d.ts +2 -0
  216. package/dist/primitives/date/index.js +4 -0
  217. package/dist/primitives/dialog/Dialog.d.ts +118 -0
  218. package/dist/primitives/dialog/Dialog.js +121 -0
  219. package/dist/primitives/dialog/index.d.ts +1 -0
  220. package/dist/primitives/dialog/index.js +2 -0
  221. package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
  222. package/dist/primitives/disclosure/Disclosure.js +52 -0
  223. package/dist/primitives/disclosure/index.d.ts +1 -0
  224. package/dist/primitives/disclosure/index.js +2 -0
  225. package/dist/primitives/field/Checks.d.ts +57 -0
  226. package/dist/primitives/field/Checks.js +72 -0
  227. package/dist/primitives/field/DropZone.d.ts +42 -0
  228. package/dist/primitives/field/DropZone.js +52 -0
  229. package/dist/primitives/field/Field.d.ts +166 -0
  230. package/dist/primitives/field/Field.js +240 -0
  231. package/dist/primitives/field/Suggest.d.ts +48 -0
  232. package/dist/primitives/field/Suggest.js +43 -0
  233. package/dist/primitives/field/aria.d.ts +61 -0
  234. package/dist/primitives/field/aria.js +70 -0
  235. package/dist/primitives/field/context.d.ts +30 -0
  236. package/dist/primitives/field/context.js +13 -0
  237. package/dist/primitives/field/index.d.ts +7 -0
  238. package/dist/primitives/field/index.js +11 -0
  239. package/dist/primitives/field/layout.d.ts +24 -0
  240. package/dist/primitives/field/layout.js +10 -0
  241. package/dist/primitives/field/mode.d.ts +9 -0
  242. package/dist/primitives/field/mode.js +13 -0
  243. package/dist/primitives/hint/Hint.d.ts +31 -0
  244. package/dist/primitives/hint/Hint.js +42 -0
  245. package/dist/primitives/hint/index.d.ts +1 -0
  246. package/dist/primitives/hint/index.js +1 -0
  247. package/dist/primitives/index.d.ts +19 -0
  248. package/dist/primitives/index.js +20 -0
  249. package/dist/primitives/menu/Menu.d.ts +100 -0
  250. package/dist/primitives/menu/Menu.js +59 -0
  251. package/dist/primitives/menu/index.d.ts +1 -0
  252. package/dist/primitives/menu/index.js +2 -0
  253. package/dist/primitives/notice/Callout.d.ts +49 -0
  254. package/dist/primitives/notice/Callout.js +28 -0
  255. package/dist/primitives/notice/Notice.d.ts +45 -0
  256. package/dist/primitives/notice/Notice.js +61 -0
  257. package/dist/primitives/notice/Toast.d.ts +55 -0
  258. package/dist/primitives/notice/Toast.js +142 -0
  259. package/dist/primitives/notice/index.d.ts +3 -0
  260. package/dist/primitives/notice/index.js +5 -0
  261. package/dist/primitives/overlay/Popover.d.ts +6 -0
  262. package/dist/primitives/overlay/Popover.js +27 -0
  263. package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
  264. package/dist/primitives/overlay/PopoverTrigger.js +38 -0
  265. package/dist/primitives/overlay/Tooltip.d.ts +38 -0
  266. package/dist/primitives/overlay/Tooltip.js +74 -0
  267. package/dist/primitives/overlay/index.d.ts +3 -0
  268. package/dist/primitives/overlay/index.js +5 -0
  269. package/dist/primitives/picker/Picker.d.ts +48 -0
  270. package/dist/primitives/picker/Picker.js +150 -0
  271. package/dist/primitives/picker/index.d.ts +1 -0
  272. package/dist/primitives/picker/index.js +2 -0
  273. package/dist/primitives/surface/Peek.d.ts +60 -0
  274. package/dist/primitives/surface/Peek.js +71 -0
  275. package/dist/primitives/surface/index.d.ts +1 -0
  276. package/dist/primitives/surface/index.js +2 -0
  277. package/dist/primitives/table/CellEditor.d.ts +14 -0
  278. package/dist/primitives/table/CellEditor.js +83 -0
  279. package/dist/primitives/table/DataTable.d.ts +135 -0
  280. package/dist/primitives/table/DataTable.js +244 -0
  281. package/dist/primitives/table/EditableTable.d.ts +96 -0
  282. package/dist/primitives/table/EditableTable.js +423 -0
  283. package/dist/primitives/table/PivotTable.d.ts +14 -0
  284. package/dist/primitives/table/PivotTable.js +0 -0
  285. package/dist/primitives/table/ReportTable.d.ts +38 -0
  286. package/dist/primitives/table/ReportTable.js +106 -0
  287. package/dist/primitives/table/RowActions.d.ts +16 -0
  288. package/dist/primitives/table/RowActions.js +28 -0
  289. package/dist/primitives/table/SheetTable.d.ts +70 -0
  290. package/dist/primitives/table/SheetTable.js +197 -0
  291. package/dist/primitives/table/Table.d.ts +106 -0
  292. package/dist/primitives/table/Table.js +116 -0
  293. package/dist/primitives/table/index.d.ts +7 -0
  294. package/dist/primitives/table/index.js +9 -0
  295. package/dist/primitives/tabs/Tabs.d.ts +91 -0
  296. package/dist/primitives/tabs/Tabs.js +56 -0
  297. package/dist/primitives/tabs/index.d.ts +1 -0
  298. package/dist/primitives/tabs/index.js +1 -0
  299. package/dist/primitives/tag/Tag.d.ts +36 -0
  300. package/dist/primitives/tag/Tag.js +68 -0
  301. package/dist/primitives/tag/Tags.d.ts +57 -0
  302. package/dist/primitives/tag/Tags.js +104 -0
  303. package/dist/primitives/tag/TagsField.d.ts +32 -0
  304. package/dist/primitives/tag/TagsField.js +182 -0
  305. package/dist/primitives/tag/choices.d.ts +38 -0
  306. package/dist/primitives/tag/choices.js +26 -0
  307. package/dist/primitives/tag/index.d.ts +4 -0
  308. package/dist/primitives/tag/index.js +8 -0
  309. package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
  310. package/dist/primitives/toolbar/Toolbar.js +36 -0
  311. package/dist/primitives/toolbar/index.d.ts +1 -0
  312. package/dist/primitives/toolbar/index.js +2 -0
  313. package/dist/primitives/value/Copyable.d.ts +37 -0
  314. package/dist/primitives/value/Copyable.js +50 -0
  315. package/dist/primitives/value/Fecha.d.ts +12 -0
  316. package/dist/primitives/value/Fecha.js +80 -0
  317. package/dist/primitives/value/Meter.d.ts +29 -0
  318. package/dist/primitives/value/Meter.js +46 -0
  319. package/dist/primitives/value/Metric.d.ts +42 -0
  320. package/dist/primitives/value/Metric.js +38 -0
  321. package/dist/primitives/value/Person.d.ts +90 -0
  322. package/dist/primitives/value/Person.js +208 -0
  323. package/dist/primitives/value/Relation.d.ts +69 -0
  324. package/dist/primitives/value/Relation.js +57 -0
  325. package/dist/primitives/value/Totals.d.ts +26 -0
  326. package/dist/primitives/value/Totals.js +4 -0
  327. package/dist/primitives/value/Value.d.ts +77 -0
  328. package/dist/primitives/value/Value.js +119 -0
  329. package/dist/primitives/value/index.d.ts +8 -0
  330. package/dist/primitives/value/index.js +9 -0
  331. package/dist/tokens.css +1025 -0
  332. package/dist/views/index.d.ts +2 -0
  333. package/dist/views/index.js +3 -0
  334. package/dist/views/list/List.d.ts +113 -0
  335. package/dist/views/list/List.js +97 -0
  336. package/dist/views/list/ListView.d.ts +6 -0
  337. package/dist/views/list/ListView.js +269 -0
  338. package/dist/views/list/columns.d.ts +18 -0
  339. package/dist/views/list/columns.js +27 -0
  340. package/dist/views/list/derive.d.ts +76 -0
  341. package/dist/views/list/derive.js +181 -0
  342. package/dist/views/list/index.d.ts +2 -0
  343. package/dist/views/list/index.js +4 -0
  344. package/dist/views/list/presentations.d.ts +39 -0
  345. package/dist/views/list/presentations.js +103 -0
  346. package/dist/views/list/types.d.ts +272 -0
  347. package/dist/views/list/types.js +1 -0
  348. package/dist/views/list/useNarrowList.d.ts +17 -0
  349. package/dist/views/list/useNarrowList.js +32 -0
  350. package/dist/views/record/Record.d.ts +126 -0
  351. package/dist/views/record/Record.js +141 -0
  352. package/dist/views/record/index.d.ts +1 -0
  353. package/dist/views/record/index.js +2 -0
  354. package/package.json +109 -4
@@ -0,0 +1,37 @@
1
+ /* ==================================================================== *
2
+ * EL VOCABULARIO DEL MODELO — lo que el servidor publica sobre sus campos.
3
+ *
4
+ * `model_meta_data` dice, por campo, de qué tipo es, qué comparaciones acepta,
5
+ * si es relacional y por qué columna se lo identifica, y qué valores puede
6
+ * tomar si es un enum. Todo lo que una pantalla ofrece sobre un modelo sale de
7
+ * acá: los filtros del constructor, los niveles del agrupado y las preguntas de
8
+ * la barra de búsqueda.
9
+ *
10
+ * Vive aparte de quien lo dibuja porque lo hablan los tres. Mientras estuvo
11
+ * adentro de `FilterBuilder.tsx`, la barra tenía que importar del constructor
12
+ * de filtros para saber cómo se llama un campo — y el constructor, de la barra
13
+ * para saber cómo se llama un operador. Un vocabulario compartido no puede
14
+ * colgar de uno de los que lo hablan.
15
+ * ==================================================================== */
16
+ /** Si este campo se puede usar en un `where`, un `order` o un `grouping.by` directamente.
17
+ * `ops.length === 0` alcanza como señal: la publican tanto `embedded` como los tres
18
+ * relacionales, así que no hace falta mirar `kind` aparte. */
19
+ export function isFilterable(field) {
20
+ return field.ops.length > 0;
21
+ }
22
+ /** El rótulo de un campo en el idioma pedido: el que el modelo publicó para ese idioma, o el
23
+ * que publicó como `default`, o el nombre del campo tal cual si el modelo no dijo nada —un
24
+ * modelo sintético, o uno que todavía no llegó. Nunca inventa una palabra: lo que no está en
25
+ * `translations` se ve tal cual sale de la tabla, y eso ya avisa que falta declararlo. */
26
+ export function fieldLabel(model, field, locale) {
27
+ const words = model.translations?.labels[field];
28
+ return words?.[locale] ?? words?.default ?? field;
29
+ }
30
+ /** Los nombres del modelo que se pueden usar como blanco de un filtro, un orden o un nivel de
31
+ * agrupado — nunca un relacional ni un valor-objeto, filtrados por `isFilterable`. Ordenados,
32
+ * porque es la lista que arma cada `<select>` de este archivo. */
33
+ export function filterableNames(model) {
34
+ return Object.keys(model.fields)
35
+ .filter((name) => isFilterable(model.fields[name]))
36
+ .sort();
37
+ }
@@ -0,0 +1,166 @@
1
+ import { type ModelDefinition } from "./model.js";
2
+ /** Qué clase de valor tiene un campo. Sale del `type` que publica el modelo y es
3
+ * lo que decide qué se le puede preguntar. */
4
+ export type QueryKind = "text" | "number" | "date" | "boolean" | "choice" | "relation";
5
+ /** Un campo por el que se puede preguntar desde la barra. */
6
+ export type QueryField = {
7
+ /** Contra qué campo va la condición. Para un relacional es la columna que lo
8
+ * identifica y no el relacional en sí: el motor no compara un agregado. */
9
+ field: string;
10
+ label: string;
11
+ kind: QueryKind;
12
+ /** Los operadores que el modelo acepta para él, en el orden que los publicó. */
13
+ ops: readonly string[];
14
+ /** Los valores posibles, cuando es un enum: de ahí sale la tercera lista. */
15
+ choices?: readonly string[];
16
+ /** Cuando se llega por una relación: contra qué agregado se está preguntando
17
+ * de verdad. Es lo que deja escribir «el proyecto se llama X» sin que nadie
18
+ * sepa que por dentro se compara otra columna. */
19
+ through?: string;
20
+ };
21
+ /** Qué forma tiene lo que alguien escribió. Es lo que decide qué campos pueden
22
+ * contestarlo. */
23
+ export type ValueShape = "empty" | "text" | "glob" | "number" | "date";
24
+ /** Lo tipeado, ya leído: el operador que trae puesto —si lo trae— y su forma. */
25
+ export type TypedValue = {
26
+ op?: string;
27
+ value: string;
28
+ shape: ValueShape;
29
+ /** Se escribió `#`: se está eligiendo el ATRIBUTO y no un valor. */
30
+ attribute?: true;
31
+ /** Se escribió `#and`, `#or` o `#not`: no se está eligiendo un atributo, se
32
+ * está ABRIENDO UN GRUPO para meter adentro lo que venga. */
33
+ opens?: "all" | "any" | "not";
34
+ };
35
+ /**
36
+ * Lee lo que alguien escribió: el operador que trae adelante, el valor, y de qué
37
+ * forma es.
38
+ *
39
+ * Un molde —`*.csv`, `ventas_*`— es distinto de un texto: se busca con `like` y
40
+ * el `*` se traduce al comodín del motor. Un texto suelto NO es un molde
41
+ * implícito: quien escribe `test` está pidiendo lo que contenga «test», y quien
42
+ * escribe `*.csv` está pidiendo lo que TERMINE en «.csv», que es otra pregunta.
43
+ */
44
+ export declare function readTyped(text: string): TypedValue;
45
+ /** El molde, con el comodín que entiende el motor. `*` es lo que la gente
46
+ * escribe —y lo que escribe una terminal— y `%` es lo que entiende un `like`. */
47
+ export declare function globToLike(value: string): string;
48
+ /**
49
+ * Los campos por los que la barra puede preguntar, sacados del modelo.
50
+ *
51
+ * Un relacional entra por la columna que lo IDENTIFICA y con el rótulo de la
52
+ * relación: preguntar «el cliente se llama Ferretería» es una pregunta sobre el
53
+ * cliente aunque por dentro compare su nombre. Un relacional sin `identifiedBy`
54
+ * no entra: no hay nada contra qué comparar, y ofrecerlo sería ofrecer una
55
+ * pregunta que no se puede contestar.
56
+ */
57
+ export declare function queryFields(model: ModelDefinition, locale: string): QueryField[];
58
+ /**
59
+ * Si un campo puede contestar lo que se escribió.
60
+ *
61
+ * Manda el OPERADOR cuando se escribió uno, porque es lo que dice qué clase de
62
+ * pregunta es:
63
+ *
64
+ * = / != todo. Cualquier cosa se compara contra sí misma, así que
65
+ * escribir `=` abre la lista entera en vez de adivinar de qué
66
+ * clase es un valor que todavía no se escribió.
67
+ * > >= < <= sólo lo ORDENADO —un número, una fecha—. «Mayor que» sobre
68
+ * un texto es una comparación alfabética que nadie pidió.
69
+ *
70
+ * Sin operador escrito manda la FORMA del valor: `*.csv` es un molde y sólo lo
71
+ * contesta algo con letras; `100` sólo algo con números. Ofrecer «filas» para
72
+ * `*.csv` es ofrecer una pregunta que da cero.
73
+ */
74
+ export declare function answers(field: QueryField, typed: TypedValue): boolean;
75
+ /**
76
+ * Con qué operador se pregunta, cuando nadie lo eligió.
77
+ *
78
+ * El que se escribió gana. Si no, el que la forma pide: un molde se pregunta con
79
+ * `like`, un texto con `like` si el campo lo acepta —«contiene» es lo que quiere
80
+ * quien escribe media palabra— y todo lo demás con `=`.
81
+ */
82
+ export declare function defaultOp(field: QueryField, typed: TypedValue): string;
83
+ /** El valor tal como viaja: un molde se traduce al comodín del motor, y lo demás
84
+ * va como se escribió. */
85
+ export declare function valueFor(op: string, typed: TypedValue): string;
86
+ /** Una pregunta a medio escribir: lo que ya se eligió y lo que se está
87
+ * tipeando. */
88
+ export type QueryDraft = {
89
+ field?: QueryField;
90
+ op?: string;
91
+ text: string;
92
+ };
93
+ export type QuerySuggestion =
94
+ /** Seguir por este campo. Todavía falta el operador y el valor. */
95
+ {
96
+ kind: "field";
97
+ id: string;
98
+ label: string;
99
+ hint?: string;
100
+ field: QueryField;
101
+ }
102
+ /** Preguntar con este operador. */
103
+ | {
104
+ kind: "op";
105
+ id: string;
106
+ label: string;
107
+ op: string;
108
+ }
109
+ /** Abrir un GRUPO: lo que se escriba después entra adentro. */
110
+ | {
111
+ kind: "group";
112
+ id: string;
113
+ label: string;
114
+ join: "all" | "any" | "not";
115
+ }
116
+ /** La pregunta entera, lista para entrar como ficha. */
117
+ | {
118
+ kind: "condition";
119
+ id: string;
120
+ label: string;
121
+ hint: string;
122
+ field: QueryField;
123
+ op: string;
124
+ value: string;
125
+ };
126
+ /** Cómo se llama un operador para quien lo lee. Sale de la copia, así que un
127
+ * operador que el servidor agregue aparece con su símbolo hasta que alguien le
128
+ * ponga palabra — y no desaparece. */
129
+ export declare function opLabel(op: string): string;
130
+ /** Si el operador no lleva valor: preguntar si algo está vacío no necesita que
131
+ * se escriba nada. */
132
+ export declare function withoutValue(op: string): boolean;
133
+ /**
134
+ * Si el operador compara contra DOS valores.
135
+ *
136
+ * La barra escribe UNO —es una línea, no un formulario— así que éstos no se
137
+ * ofrecen acá: elegir «está entre» y escribir un número arma medio rango, que
138
+ * no es una pregunta más laxa sino una que el motor no puede contestar. Se
139
+ * preguntan en el constructor, que tiene los dos campos.
140
+ */
141
+ export declare function withTwoValues(op: string): boolean;
142
+ /**
143
+ * Qué se ofrece ahora mismo.
144
+ *
145
+ * Es una sola lista y no tres pantallas: en el primer paso conviven los campos
146
+ * que se llaman como lo tipeado —«nombr» → «nombre»— con las preguntas enteras
147
+ * que lo tipeado ya permite armar —«test» en el nombre—. Quien escribe el nombre
148
+ * de un campo quiere seguir por ahí; quien escribe un valor quiere preguntarlo
149
+ * ya. Las dos intenciones se escriben igual, así que se ofrecen las dos, con los
150
+ * campos primero: es el paso que todavía no decidió nada.
151
+ */
152
+ export declare function suggest(draft: QueryDraft, fields: readonly QueryField[]): QuerySuggestion[];
153
+ /**
154
+ * CÓMO SE LEE UNA CONDICIÓN, en un solo lugar.
155
+ *
156
+ * La usan la barra —para la ficha de lo que alguien acaba de preguntar— y el
157
+ * constructor —para la ficha y para el árbol de lo que ya está armado—. Una
158
+ * sola, porque la misma condición escrita de dos maneras se lee distinta en dos
159
+ * pantallas y nadie sabe cuál de las dos es la que filtra.
160
+ *
161
+ * Toma el RÓTULO ya resuelto y no el campo: quién sabe cómo se llama
162
+ * `row_count` es el modelo, y esto sólo sabe armar la frase.
163
+ */
164
+ export declare function sayCondition(label: string, op: string, value: string): string;
165
+ /** Cómo se lee una pregunta ya hecha, para la ficha: «el nombre contiene test». */
166
+ export declare function describeCondition(field: QueryField, op: string, value: string): string;
@@ -0,0 +1,436 @@
1
+ import { fieldLabel, isFilterable, } from "./model.js";
2
+ import { currentCopy, fillCopy } from "../../foundations/copy.js";
3
+ /**
4
+ * Las palabras que, después del numeral, abren un GRUPO en vez de nombrar un
5
+ * atributo.
6
+ *
7
+ * Se escriben como se piensan —`#and`, `#or`, `#not`— y también como se leen en
8
+ * la barra —`#todas`, `#cualquiera`, `#ninguna`—, porque son la misma cosa dicha
9
+ * de dos maneras y obligar a una sola es obligar a aprenderla.
10
+ */
11
+ const OPENS = {
12
+ and: "all",
13
+ all: "all",
14
+ todas: "all",
15
+ or: "any",
16
+ any: "any",
17
+ cualquiera: "any",
18
+ not: "not",
19
+ none: "not",
20
+ ninguna: "not",
21
+ };
22
+ /* Los operadores que se pueden escribir adelante del valor, de más largo a más
23
+ corto: si `<` se probara antes que `<=`, «<=100» se leería como «< =100». */
24
+ const WRITTEN_OPS = ["<=", ">=", "!=", "<", ">", "="];
25
+ /**
26
+ * Los que ORDENAN. Sólo los puede contestar algo que tenga orden —un número, una
27
+ * fecha—, y no porque el campo no publique el operador: «mayor que» sobre un
28
+ * texto es una comparación alfabética que nadie pidió nunca y que da resultados
29
+ * que no se pueden explicar.
30
+ */
31
+ const ORDERING = new Set([">", ">=", "<", "<="]);
32
+ /**
33
+ * Los que sólo comparan IGUALDAD. Estos no discriminan por tipo: todo se puede
34
+ * comparar contra sí mismo, así que escribir `=` abre la lista entera en vez de
35
+ * adivinar de qué clase es lo que todavía no se escribió.
36
+ */
37
+ const EQUALITY = new Set(["=", "!="]);
38
+ /** Qué clases tienen orden. Un enum no: sus valores son palabras aunque se
39
+ * escriban con números. */
40
+ const ORDERED = new Set(["number", "date"]);
41
+ const NUMBER = /^-?\d+([.,]\d+)?$/;
42
+ const DATE = /^\d{4}-\d{2}(-\d{2})?$/;
43
+ /**
44
+ * Lee lo que alguien escribió: el operador que trae adelante, el valor, y de qué
45
+ * forma es.
46
+ *
47
+ * Un molde —`*.csv`, `ventas_*`— es distinto de un texto: se busca con `like` y
48
+ * el `*` se traduce al comodín del motor. Un texto suelto NO es un molde
49
+ * implícito: quien escribe `test` está pidiendo lo que contenga «test», y quien
50
+ * escribe `*.csv` está pidiendo lo que TERMINE en «.csv», que es otra pregunta.
51
+ */
52
+ export function readTyped(text) {
53
+ const clean = text.trim();
54
+ if (clean === "")
55
+ return { value: "", shape: "empty" };
56
+ /* `#` es «elegí el atributo»: lo que sigue no es un valor, es el nombre del
57
+ campo por el que se va a preguntar. Existe porque las dos intenciones se
58
+ escriben con las mismas letras —«nombre» puede ser el campo o el texto que
59
+ se busca— y hasta acá había que confiar en que la barra adivinara cuál. Con
60
+ el numeral no hay nada que adivinar. */
61
+ if (clean.startsWith("#")) {
62
+ const rest = clean.slice(1).trim();
63
+ const opens = OPENS[rest.toLowerCase()];
64
+ /* `#and` no es un atributo que se llame «and»: es la orden de abrir un
65
+ grupo. Se mira primero porque un modelo podría tener un campo llamado
66
+ `and`, y entre las dos lecturas gana la que alguien escribió a propósito
67
+ —nadie tipea el numeral para buscar la palabra «or». */
68
+ /* `#` a secas es «elegí el atributo» sin haber empezado a nombrarlo: la forma
69
+ es «vacío», como en cualquier otro lado. Decir «texto» de una cadena vacía
70
+ es mentir sobre lo que hay escrito. */
71
+ const shape = rest === "" ? "empty" : "text";
72
+ if (opens !== undefined)
73
+ return { value: rest, shape, attribute: true, opens };
74
+ return { value: rest, shape, attribute: true };
75
+ }
76
+ const written = WRITTEN_OPS.find((op) => clean.startsWith(op));
77
+ const rest = written === undefined ? clean : clean.slice(written.length).trim();
78
+ const quoted = rest.length > 1 && rest.startsWith('"') && rest.endsWith('"')
79
+ ? rest.slice(1, -1)
80
+ : undefined;
81
+ const value = quoted ?? rest;
82
+ const shape = value === ""
83
+ ? "empty"
84
+ : quoted !== undefined
85
+ ? "text"
86
+ : NUMBER.test(value)
87
+ ? "number"
88
+ : DATE.test(value)
89
+ ? "date"
90
+ : value.includes("*")
91
+ ? "glob"
92
+ : "text";
93
+ return { ...(written === undefined ? {} : { op: written }), value, shape };
94
+ }
95
+ /** El molde, con el comodín que entiende el motor. `*` es lo que la gente
96
+ * escribe —y lo que escribe una terminal— y `%` es lo que entiende un `like`. */
97
+ export function globToLike(value) {
98
+ return value.replace(/\*/g, "%");
99
+ }
100
+ /** Qué clase de valor es un campo, leída del tipo que publica el modelo. */
101
+ function kindOf(definition) {
102
+ if (definition.kind === "relation")
103
+ return "relation";
104
+ if (definition.choices !== undefined && definition.choices.length > 0)
105
+ return "choice";
106
+ const type = definition.type.toLowerCase();
107
+ if (type === "integer" || type === "number" || type === "float")
108
+ return "number";
109
+ if (type === "date" || type === "datetime")
110
+ return "date";
111
+ if (type === "boolean" || type === "bool")
112
+ return "boolean";
113
+ return "text";
114
+ }
115
+ /**
116
+ * Los campos por los que la barra puede preguntar, sacados del modelo.
117
+ *
118
+ * Un relacional entra por la columna que lo IDENTIFICA y con el rótulo de la
119
+ * relación: preguntar «el cliente se llama Ferretería» es una pregunta sobre el
120
+ * cliente aunque por dentro compare su nombre. Un relacional sin `identifiedBy`
121
+ * no entra: no hay nada contra qué comparar, y ofrecerlo sería ofrecer una
122
+ * pregunta que no se puede contestar.
123
+ */
124
+ export function queryFields(model, locale) {
125
+ /* Las columnas que ya nombran a una relación no se ofrecen por su cuenta: son
126
+ la misma pregunta con dos rótulos —«proyecto» y «nombre del proyecto»— y la
127
+ que se entiende es la de la relación. */
128
+ const naming = new Set(Object.values(model.fields)
129
+ .filter((one) => one.kind === "relation")
130
+ .map((one) => one.identifiedBy)
131
+ .filter((one) => one !== null && one !== undefined));
132
+ const out = [];
133
+ for (const [name, definition] of Object.entries(model.fields)) {
134
+ const kind = kindOf(definition);
135
+ if (kind === "relation") {
136
+ const through = definition.identifiedBy;
137
+ if (through === null || through === undefined)
138
+ continue;
139
+ const identifying = model.fields[through];
140
+ if (identifying === undefined || !isFilterable(identifying))
141
+ continue;
142
+ out.push({
143
+ field: through,
144
+ label: fieldLabel(model, name, locale),
145
+ kind: "relation",
146
+ ops: identifying.ops,
147
+ through: definition.relation ?? name,
148
+ });
149
+ continue;
150
+ }
151
+ if (!isFilterable(definition) || naming.has(name))
152
+ continue;
153
+ out.push({
154
+ field: name,
155
+ label: fieldLabel(model, name, locale),
156
+ kind,
157
+ ops: definition.ops,
158
+ ...(definition.choices === undefined
159
+ ? {}
160
+ : { choices: definition.choices.map((one) => String(one)) }),
161
+ });
162
+ }
163
+ return out;
164
+ }
165
+ /**
166
+ * Si un campo puede contestar lo que se escribió.
167
+ *
168
+ * Manda el OPERADOR cuando se escribió uno, porque es lo que dice qué clase de
169
+ * pregunta es:
170
+ *
171
+ * = / != todo. Cualquier cosa se compara contra sí misma, así que
172
+ * escribir `=` abre la lista entera en vez de adivinar de qué
173
+ * clase es un valor que todavía no se escribió.
174
+ * > >= < <= sólo lo ORDENADO —un número, una fecha—. «Mayor que» sobre
175
+ * un texto es una comparación alfabética que nadie pidió.
176
+ *
177
+ * Sin operador escrito manda la FORMA del valor: `*.csv` es un molde y sólo lo
178
+ * contesta algo con letras; `100` sólo algo con números. Ofrecer «filas» para
179
+ * `*.csv` es ofrecer una pregunta que da cero.
180
+ */
181
+ export function answers(field, typed) {
182
+ /* El operador se mira ANTES que el valor, y no después: escribir `>` ya
183
+ descarta los textos aunque todavía no se haya escrito contra qué. Al revés,
184
+ la barra ofrecía las cinco columnas hasta que se tipeaba el primer dígito. */
185
+ if (typed.op !== undefined) {
186
+ if (!field.ops.includes(typed.op))
187
+ return false;
188
+ if (EQUALITY.has(typed.op))
189
+ return true;
190
+ if (ORDERING.has(typed.op) && !ORDERED.has(field.kind))
191
+ return false;
192
+ }
193
+ if (typed.shape === "empty")
194
+ return true;
195
+ switch (typed.shape) {
196
+ case "number":
197
+ return field.kind === "number" || field.kind === "choice";
198
+ case "date":
199
+ return field.kind === "date";
200
+ case "glob":
201
+ return field.kind === "text" || field.kind === "relation";
202
+ default:
203
+ /* Un texto lo contesta lo que tiene letras adentro, y también un enum: sus
204
+ valores son palabras. Un número no —«test» no es un número— y una fecha
205
+ tampoco. */
206
+ return field.kind === "text" || field.kind === "relation" || field.kind === "choice";
207
+ }
208
+ }
209
+ /**
210
+ * Con qué operador se pregunta, cuando nadie lo eligió.
211
+ *
212
+ * El que se escribió gana. Si no, el que la forma pide: un molde se pregunta con
213
+ * `like`, un texto con `like` si el campo lo acepta —«contiene» es lo que quiere
214
+ * quien escribe media palabra— y todo lo demás con `=`.
215
+ */
216
+ export function defaultOp(field, typed) {
217
+ if (typed.op !== undefined && field.ops.includes(typed.op))
218
+ return typed.op;
219
+ if (typed.shape === "glob" && field.ops.includes("like"))
220
+ return "like";
221
+ if (typed.shape === "text" && field.ops.includes("like"))
222
+ return "like";
223
+ return field.ops.includes("=") ? "=" : (field.ops[0] ?? "=");
224
+ }
225
+ /** El valor tal como viaja: un molde se traduce al comodín del motor, y lo demás
226
+ * va como se escribió. */
227
+ export function valueFor(op, typed) {
228
+ return op === "like" && typed.shape === "glob" ? globToLike(typed.value) : typed.value;
229
+ }
230
+ /** Cómo se llama un operador para quien lo lee. Sale de la copia, así que un
231
+ * operador que el servidor agregue aparece con su símbolo hasta que alguien le
232
+ * ponga palabra — y no desaparece. */
233
+ export function opLabel(op) {
234
+ return currentCopy().ops[op] ?? op;
235
+ }
236
+ /** Si el operador no lleva valor: preguntar si algo está vacío no necesita que
237
+ * se escriba nada. */
238
+ export function withoutValue(op) {
239
+ return op === "is null" || op === "is not null";
240
+ }
241
+ /**
242
+ * Si el operador compara contra DOS valores.
243
+ *
244
+ * La barra escribe UNO —es una línea, no un formulario— así que éstos no se
245
+ * ofrecen acá: elegir «está entre» y escribir un número arma medio rango, que
246
+ * no es una pregunta más laxa sino una que el motor no puede contestar. Se
247
+ * preguntan en el constructor, que tiene los dos campos.
248
+ */
249
+ export function withTwoValues(op) {
250
+ return op === "between";
251
+ }
252
+ function matches(field, text) {
253
+ const needle = text.trim().toLowerCase();
254
+ if (needle === "")
255
+ return true;
256
+ return (field.label.toLowerCase().includes(needle) || field.field.toLowerCase().includes(needle));
257
+ }
258
+ /**
259
+ * Qué se ofrece ahora mismo.
260
+ *
261
+ * Es una sola lista y no tres pantallas: en el primer paso conviven los campos
262
+ * que se llaman como lo tipeado —«nombr» → «nombre»— con las preguntas enteras
263
+ * que lo tipeado ya permite armar —«test» en el nombre—. Quien escribe el nombre
264
+ * de un campo quiere seguir por ahí; quien escribe un valor quiere preguntarlo
265
+ * ya. Las dos intenciones se escriben igual, así que se ofrecen las dos, con los
266
+ * campos primero: es el paso que todavía no decidió nada.
267
+ */
268
+ export function suggest(draft, fields) {
269
+ const typed = readTyped(draft.text);
270
+ /* Paso 3 — el campo y el operador ya están: falta el valor. */
271
+ if (draft.field !== undefined && draft.op !== undefined) {
272
+ const field = draft.field;
273
+ /* Escribir un molde CAMBIA la comparación: «igual a ventas_*» busca un
274
+ nombre que termine en asterisco, que no existe y que nadie quiso pedir.
275
+ Sólo pisa la igualdad —quien eligió «mayor que» y escribió un `*` está
276
+ escribiendo cualquier otra cosa— y sólo si el campo publica `like`. */
277
+ const op = typed.shape === "glob" && EQUALITY.has(draft.op) && field.ops.includes("like")
278
+ ? "like"
279
+ : draft.op;
280
+ const values = field.choices === undefined
281
+ ? []
282
+ : field.choices.filter((one) => one.toLowerCase().includes(typed.value.toLowerCase()));
283
+ const ready = values.map((one) => ({
284
+ kind: "condition",
285
+ id: `v:${one}`,
286
+ label: one,
287
+ hint: `${field.label} ${opLabel(op)}`,
288
+ field,
289
+ op,
290
+ value: one,
291
+ }));
292
+ if (typed.shape !== "empty" && !values.includes(typed.value)) {
293
+ ready.unshift({
294
+ kind: "condition",
295
+ id: "v:typed",
296
+ label: valueFor(op, typed),
297
+ hint: `${field.label} ${opLabel(op)}`,
298
+ field,
299
+ op,
300
+ value: valueFor(op, typed),
301
+ });
302
+ }
303
+ return ready;
304
+ }
305
+ /* Paso 2 — el campo ya está: se ofrecen SUS operadores. El que la forma de lo
306
+ tipeado pide va primero, porque escribir «100» después de elegir «filas» es
307
+ casi siempre «igual a 100». */
308
+ if (draft.field !== undefined) {
309
+ const field = draft.field;
310
+ const first = defaultOp(field, typed);
311
+ const ops = [first, ...field.ops.filter((one) => one !== first)].filter((op) => !withTwoValues(op));
312
+ return ops.map((op) =>
313
+ /* El que NO lleva valor es una pregunta entera apenas se elige: mandarlo
314
+ al paso del valor dejaba al que eligió «está vacío» tipeando algo que no
315
+ significa nada, con una lista vacía abajo y Enter sin hacer nada. */
316
+ withoutValue(op)
317
+ ? {
318
+ kind: "condition",
319
+ id: `op:${op}`,
320
+ label: opLabel(op),
321
+ hint: field.label,
322
+ field,
323
+ op,
324
+ value: "",
325
+ }
326
+ : { kind: "op", id: `op:${op}`, label: opLabel(op), op });
327
+ }
328
+ /* `#and`, `#or`, `#not`: lo que se pidió es abrir un grupo, no elegir un
329
+ campo. Se ofrece ESO solo — quien lo escribió ya sabe lo que quiere. */
330
+ if (typed.opens !== undefined) {
331
+ const join = typed.opens;
332
+ return [
333
+ {
334
+ kind: "group",
335
+ id: `g:${join}`,
336
+ label: currentCopy().search.joins[join === "not" ? "none" : join],
337
+ join,
338
+ },
339
+ ];
340
+ }
341
+ /* Con `#` a secas se está eligiendo el ATRIBUTO, así que se ofrecen los campos
342
+ —y los tres abridores de grupo, porque también se piden con el numeral y
343
+ esconderlos sería tener una notación que nadie descubre. */
344
+ if (typed.attribute === true) {
345
+ const opening = typed.value === ""
346
+ ? ["all", "any", "not"].map((join) => ({
347
+ kind: "group",
348
+ id: `g:${join}`,
349
+ label: currentCopy().search.joins[join === "not" ? "none" : join],
350
+ join,
351
+ }))
352
+ : [];
353
+ return [
354
+ ...opening,
355
+ ...fields
356
+ .filter((field) => matches(field, typed.value))
357
+ .map((field) => ({
358
+ kind: "field",
359
+ id: `f:${field.field}`,
360
+ label: field.label,
361
+ ...(field.through === undefined ? {} : { hint: field.through }),
362
+ field,
363
+ })),
364
+ ];
365
+ }
366
+ /* Paso 1 — todavía no se eligió nada. */
367
+ const byName = typed.shape === "empty"
368
+ ? []
369
+ : fields
370
+ .filter((field) => matches(field, draft.text))
371
+ .map((field) => ({
372
+ kind: "field",
373
+ id: `f:${field.field}`,
374
+ label: field.label,
375
+ ...(field.through === undefined ? {} : { hint: field.through }),
376
+ field,
377
+ }));
378
+ const ready = fields
379
+ .filter((field) => answers(field, typed))
380
+ .map((field) => {
381
+ const op = defaultOp(field, typed);
382
+ return {
383
+ kind: "condition",
384
+ id: `c:${field.field}`,
385
+ label: field.label,
386
+ hint: `${opLabel(op)} ${valueFor(op, typed)}`,
387
+ field,
388
+ op,
389
+ value: valueFor(op, typed),
390
+ };
391
+ });
392
+ /* Sin VALOR todavía no hay ninguna pregunta que armar —una pregunta sin valor
393
+ no es una pregunta— así que se ofrecen campos. Pero si ya se escribió el
394
+ operador, eso solo recorta: `>` deja los que tienen orden y saca los textos
395
+ antes de que se escriba el primer dígito. */
396
+ if (typed.shape === "empty") {
397
+ return fields
398
+ .filter((field) => answers(field, typed))
399
+ .map((field) => ({
400
+ kind: "field",
401
+ id: `f:${field.field}`,
402
+ label: field.label,
403
+ ...(field.through === undefined ? {} : { hint: field.through }),
404
+ field,
405
+ }));
406
+ }
407
+ /* Un campo que se llama como lo tipeado no se ofrece dos veces: si «nombre»
408
+ aparece arriba para seguir por él, abajo ya no hace falta como pregunta
409
+ hecha — salvo que lo tipeado sea también un valor razonable, que es
410
+ justamente el caso de «test». */
411
+ const named = new Set(byName.map((one) => (one.kind === "field" ? one.field.field : "")));
412
+ return [
413
+ ...byName,
414
+ ...ready.filter((one) => one.kind !== "condition" || !named.has(one.field.field)),
415
+ ];
416
+ }
417
+ /**
418
+ * CÓMO SE LEE UNA CONDICIÓN, en un solo lugar.
419
+ *
420
+ * La usan la barra —para la ficha de lo que alguien acaba de preguntar— y el
421
+ * constructor —para la ficha y para el árbol de lo que ya está armado—. Una
422
+ * sola, porque la misma condición escrita de dos maneras se lee distinta en dos
423
+ * pantallas y nadie sabe cuál de las dos es la que filtra.
424
+ *
425
+ * Toma el RÓTULO ya resuelto y no el campo: quién sabe cómo se llama
426
+ * `row_count` es el modelo, y esto sólo sabe armar la frase.
427
+ */
428
+ export function sayCondition(label, op, value) {
429
+ return withoutValue(op)
430
+ ? `${label} ${opLabel(op)}`
431
+ : fillCopy(currentCopy().search.condition, { field: label, op: opLabel(op), value });
432
+ }
433
+ /** Cómo se lee una pregunta ya hecha, para la ficha: «el nombre contiene test». */
434
+ export function describeCondition(field, op, value) {
435
+ return sayCondition(field.label, op, value);
436
+ }
@@ -0,0 +1,14 @@
1
+ import { type ReactNode } from "react";
2
+ export declare function AppFrame({
3
+ /** The bar at the top. It stays in view while scrolling. */
4
+ menu,
5
+ /** The band at the bottom. It goes AFTER the content, not pinned to the
6
+ * floor of the window — pinning left a hole between it and a short view. */
7
+ status,
8
+ /** The module's navigation, on the left of the view: a `NavigationTree`. */
9
+ sidebar, children, }: {
10
+ menu?: ReactNode;
11
+ status?: ReactNode;
12
+ sidebar?: ReactNode;
13
+ children: ReactNode;
14
+ }): import("react").JSX.Element;