@vertesia/ui 1.5.0-dev.20260717.131047Z → 1.5.0-dev.20260722.120446Z

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 (329) hide show
  1. package/lib/core/components/SidePanel.js +1 -1
  2. package/lib/core/components/SidePanel.js.map +1 -1
  3. package/lib/core/components/shadcn/button.d.ts +10 -3
  4. package/lib/core/components/shadcn/button.d.ts.map +1 -1
  5. package/lib/core/components/shadcn/button.js +41 -9
  6. package/lib/core/components/shadcn/button.js.map +1 -1
  7. package/lib/core/components/shadcn/modal/dialog.js +1 -1
  8. package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
  9. package/lib/core/components/shadcn/theme/ThemeSwitcher.js +1 -1
  10. package/lib/core/components/shadcn/theme/ThemeSwitcher.js.map +1 -1
  11. package/lib/features/agent/chat/AgentChatPlaybackControls.d.ts.map +1 -1
  12. package/lib/features/agent/chat/AgentChatPlaybackControls.js +2 -2
  13. package/lib/features/agent/chat/AgentChatPlaybackControls.js.map +1 -1
  14. package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts.map +1 -1
  15. package/lib/features/agent/chat/AgentRequestInputOverlay.js +2 -2
  16. package/lib/features/agent/chat/AgentRequestInputOverlay.js.map +1 -1
  17. package/lib/features/agent/chat/ArtifactsTab.d.ts.map +1 -1
  18. package/lib/features/agent/chat/ArtifactsTab.js +5 -2
  19. package/lib/features/agent/chat/ArtifactsTab.js.map +1 -1
  20. package/lib/features/agent/chat/ImageLightbox.d.ts.map +1 -1
  21. package/lib/features/agent/chat/ImageLightbox.js +1 -1
  22. package/lib/features/agent/chat/ImageLightbox.js.map +1 -1
  23. package/lib/features/agent/chat/ModernAgentConversation.js +1 -1
  24. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  25. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  26. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +5 -5
  27. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  28. package/lib/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
  29. package/lib/features/agent/chat/ModernAgentOutput/Header.js +4 -2
  30. package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
  31. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
  32. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +2 -2
  33. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
  34. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  35. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +2 -1
  36. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  37. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts +6 -0
  38. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts.map +1 -1
  39. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js +15 -1
  40. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js.map +1 -1
  41. package/lib/features/environment/SelectEnvironment.d.ts +28 -0
  42. package/lib/features/environment/SelectEnvironment.d.ts.map +1 -0
  43. package/lib/features/environment/SelectEnvironment.js +97 -0
  44. package/lib/features/environment/SelectEnvironment.js.map +1 -0
  45. package/lib/features/environment/index.d.ts +2 -0
  46. package/lib/features/environment/index.d.ts.map +1 -0
  47. package/lib/features/environment/index.js +2 -0
  48. package/lib/features/environment/index.js.map +1 -0
  49. package/lib/features/index.d.ts +2 -0
  50. package/lib/features/index.d.ts.map +1 -1
  51. package/lib/features/index.js +2 -0
  52. package/lib/features/index.js.map +1 -1
  53. package/lib/features/magic-pdf/AnnotatedImageSlider.js +12 -12
  54. package/lib/features/magic-pdf/AnnotatedImageSlider.js.map +1 -1
  55. package/lib/features/magic-pdf/DownloadPopover.d.ts.map +1 -1
  56. package/lib/features/magic-pdf/DownloadPopover.js +3 -17
  57. package/lib/features/magic-pdf/DownloadPopover.js.map +1 -1
  58. package/lib/features/magic-pdf/ExtractedContentView.js +2 -6
  59. package/lib/features/magic-pdf/ExtractedContentView.js.map +1 -1
  60. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.d.ts +25 -0
  61. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.d.ts.map +1 -0
  62. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.js +42 -0
  63. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.js.map +1 -0
  64. package/lib/features/magic-pdf/GroundedExtractionView.d.ts +27 -0
  65. package/lib/features/magic-pdf/GroundedExtractionView.d.ts.map +1 -0
  66. package/lib/features/magic-pdf/GroundedExtractionView.js +377 -0
  67. package/lib/features/magic-pdf/GroundedExtractionView.js.map +1 -0
  68. package/lib/features/magic-pdf/MagicPdfProvider.d.ts +0 -2
  69. package/lib/features/magic-pdf/MagicPdfProvider.d.ts.map +1 -1
  70. package/lib/features/magic-pdf/MagicPdfProvider.js +15 -42
  71. package/lib/features/magic-pdf/MagicPdfProvider.js.map +1 -1
  72. package/lib/features/magic-pdf/MagicPdfView.d.ts.map +1 -1
  73. package/lib/features/magic-pdf/MagicPdfView.js +2 -19
  74. package/lib/features/magic-pdf/MagicPdfView.js.map +1 -1
  75. package/lib/features/magic-pdf/index.d.ts +1 -0
  76. package/lib/features/magic-pdf/index.d.ts.map +1 -1
  77. package/lib/features/magic-pdf/index.js +1 -0
  78. package/lib/features/magic-pdf/index.js.map +1 -1
  79. package/lib/features/magic-pdf/types.d.ts +1 -1
  80. package/lib/features/magic-pdf/types.d.ts.map +1 -1
  81. package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -1
  82. package/lib/features/oauth/McpConnectionsButton.js +2 -2
  83. package/lib/features/oauth/McpConnectionsButton.js.map +1 -1
  84. package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -1
  85. package/lib/features/oauth/McpConnectionsDialog.js +1 -1
  86. package/lib/features/oauth/McpConnectionsDialog.js.map +1 -1
  87. package/lib/features/oauth/RemoteMcpConnectionButton.d.ts.map +1 -1
  88. package/lib/features/oauth/RemoteMcpConnectionButton.js +1 -1
  89. package/lib/features/oauth/RemoteMcpConnectionButton.js.map +1 -1
  90. package/lib/features/pdf-viewer/PdfPageSlider.js +2 -2
  91. package/lib/features/pdf-viewer/PdfPageSlider.js.map +1 -1
  92. package/lib/features/pdf-viewer/SimplePdfViewer.d.ts.map +1 -1
  93. package/lib/features/pdf-viewer/SimplePdfViewer.js +3 -3
  94. package/lib/features/pdf-viewer/SimplePdfViewer.js.map +1 -1
  95. package/lib/features/permissions/UserPermissionsProvider.js +1 -1
  96. package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
  97. package/lib/features/store/objects/DocumentQuickFilter.d.ts.map +1 -1
  98. package/lib/features/store/objects/DocumentQuickFilter.js +5 -5
  99. package/lib/features/store/objects/DocumentQuickFilter.js.map +1 -1
  100. package/lib/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
  101. package/lib/features/store/objects/DocumentSearchResults.js +2 -2
  102. package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
  103. package/lib/features/store/objects/components/ContentDispositionButton.d.ts.map +1 -1
  104. package/lib/features/store/objects/components/ContentDispositionButton.js +1 -1
  105. package/lib/features/store/objects/components/ContentDispositionButton.js.map +1 -1
  106. package/lib/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  107. package/lib/features/store/objects/components/ContentOverview.js +78 -21
  108. package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
  109. package/lib/features/store/objects/components/DocumentIcon.js +1 -1
  110. package/lib/features/store/objects/components/VectorSearchWidget.js +1 -1
  111. package/lib/features/store/objects/components/VectorSearchWidget.js.map +1 -1
  112. package/lib/features/store/objects/components/useContentPanelHooks.d.ts +1 -2
  113. package/lib/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -1
  114. package/lib/features/store/objects/components/useContentPanelHooks.js +4 -8
  115. package/lib/features/store/objects/components/useContentPanelHooks.js.map +1 -1
  116. package/lib/features/store/objects/layout/renderers.d.ts.map +1 -1
  117. package/lib/features/store/objects/layout/renderers.js +2 -2
  118. package/lib/features/store/objects/layout/renderers.js.map +1 -1
  119. package/lib/features/store/objects/selection/SelectionActions.js +1 -1
  120. package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
  121. package/lib/features/store/types/ContentObjectTypesTable.d.ts.map +1 -1
  122. package/lib/features/store/types/ContentObjectTypesTable.js +4 -4
  123. package/lib/features/store/types/ContentObjectTypesTable.js.map +1 -1
  124. package/lib/features/store/types/IntakePolicyEditor.d.ts +15 -0
  125. package/lib/features/store/types/IntakePolicyEditor.d.ts.map +1 -0
  126. package/lib/features/store/types/IntakePolicyEditor.js +455 -0
  127. package/lib/features/store/types/IntakePolicyEditor.js.map +1 -0
  128. package/lib/features/store/types/IntakePolicyForm.d.ts +11 -0
  129. package/lib/features/store/types/IntakePolicyForm.d.ts.map +1 -0
  130. package/lib/features/store/types/IntakePolicyForm.js +91 -0
  131. package/lib/features/store/types/IntakePolicyForm.js.map +1 -0
  132. package/lib/features/store/types/SelectContentTypeModal.js +1 -1
  133. package/lib/features/store/types/SelectContentTypeModal.js.map +1 -1
  134. package/lib/features/store/types/index.d.ts +2 -0
  135. package/lib/features/store/types/index.d.ts.map +1 -1
  136. package/lib/features/store/types/index.js +2 -0
  137. package/lib/features/store/types/index.js.map +1 -1
  138. package/lib/features/store/types/intake-policy-editor.logic.d.ts +7 -0
  139. package/lib/features/store/types/intake-policy-editor.logic.d.ts.map +1 -0
  140. package/lib/features/store/types/intake-policy-editor.logic.js +60 -0
  141. package/lib/features/store/types/intake-policy-editor.logic.js.map +1 -0
  142. package/lib/features/store/types/typeCatalogCache.d.ts +5 -0
  143. package/lib/features/store/types/typeCatalogCache.d.ts.map +1 -0
  144. package/lib/features/store/types/typeCatalogCache.js +40 -0
  145. package/lib/features/store/types/typeCatalogCache.js.map +1 -0
  146. package/lib/features/user/UserInfo.js +7 -3
  147. package/lib/features/user/UserInfo.js.map +1 -1
  148. package/lib/features/views/ViewExperience.d.ts +20 -0
  149. package/lib/features/views/ViewExperience.d.ts.map +1 -0
  150. package/lib/features/views/ViewExperience.js +188 -0
  151. package/lib/features/views/ViewExperience.js.map +1 -0
  152. package/lib/features/views/ViewNavigation.d.ts +3 -0
  153. package/lib/features/views/ViewNavigation.d.ts.map +1 -0
  154. package/lib/features/views/ViewNavigation.js +36 -0
  155. package/lib/features/views/ViewNavigation.js.map +1 -0
  156. package/lib/features/views/ViewResults.d.ts +9 -0
  157. package/lib/features/views/ViewResults.d.ts.map +1 -0
  158. package/lib/features/views/ViewResults.js +229 -0
  159. package/lib/features/views/ViewResults.js.map +1 -0
  160. package/lib/features/views/ViewSearch.d.ts +3 -0
  161. package/lib/features/views/ViewSearch.d.ts.map +1 -0
  162. package/lib/features/views/ViewSearch.js +74 -0
  163. package/lib/features/views/ViewSearch.js.map +1 -0
  164. package/lib/features/views/index.d.ts +7 -0
  165. package/lib/features/views/index.d.ts.map +1 -0
  166. package/lib/features/views/index.js +7 -0
  167. package/lib/features/views/index.js.map +1 -0
  168. package/lib/features/views/types.d.ts +39 -0
  169. package/lib/features/views/types.d.ts.map +1 -0
  170. package/lib/features/views/types.js +2 -0
  171. package/lib/features/views/types.js.map +1 -0
  172. package/lib/features/views/viewState.d.ts +14 -0
  173. package/lib/features/views/viewState.d.ts.map +1 -0
  174. package/lib/features/views/viewState.js +117 -0
  175. package/lib/features/views/viewState.js.map +1 -0
  176. package/lib/i18n/locales/ar.json +16 -0
  177. package/lib/i18n/locales/de.json +12 -0
  178. package/lib/i18n/locales/en.json +245 -33
  179. package/lib/i18n/locales/es.json +13 -0
  180. package/lib/i18n/locales/fr.json +13 -0
  181. package/lib/i18n/locales/it.json +13 -0
  182. package/lib/i18n/locales/ja.json +11 -0
  183. package/lib/i18n/locales/ko.json +11 -0
  184. package/lib/i18n/locales/pt.json +13 -0
  185. package/lib/i18n/locales/ru.json +14 -0
  186. package/lib/i18n/locales/tr.json +12 -0
  187. package/lib/i18n/locales/zh-TW.json +11 -0
  188. package/lib/i18n/locales/zh.json +11 -0
  189. package/lib/router/HistoryNavigator.d.ts.map +1 -1
  190. package/lib/router/HistoryNavigator.js +23 -13
  191. package/lib/router/HistoryNavigator.js.map +1 -1
  192. package/lib/router/Nav.d.ts.map +1 -1
  193. package/lib/router/Nav.js +15 -6
  194. package/lib/router/Nav.js.map +1 -1
  195. package/lib/session/UserSession.d.ts.map +1 -1
  196. package/lib/session/UserSession.js +5 -3
  197. package/lib/session/UserSession.js.map +1 -1
  198. package/lib/vertesia-ui-core.js +4 -4
  199. package/lib/vertesia-ui-core.js.map +1 -1
  200. package/lib/vertesia-ui-features.js +34 -27
  201. package/lib/vertesia-ui-features.js.map +1 -1
  202. package/lib/vertesia-ui-i18n.js +15 -15
  203. package/lib/vertesia-ui-i18n.js.map +1 -1
  204. package/lib/vertesia-ui-router.js +1 -1
  205. package/lib/vertesia-ui-router.js.map +1 -1
  206. package/lib/vertesia-ui-session.js +1 -1
  207. package/lib/vertesia-ui-session.js.map +1 -1
  208. package/lib/vertesia-ui-shell.js +1 -1
  209. package/lib/vertesia-ui-shell.js.map +1 -1
  210. package/lib/vertesia-ui-widgets.js +7 -7
  211. package/lib/vertesia-ui-widgets.js.map +1 -1
  212. package/lib/widgets/form/Form.js +1 -1
  213. package/lib/widgets/form/Form.js.map +1 -1
  214. package/lib/widgets/json-view/JSONSwitcher.js +1 -1
  215. package/lib/widgets/json-view/JSONSwitcher.js.map +1 -1
  216. package/lib/widgets/json-view/JSONView.js +6 -6
  217. package/lib/widgets/json-view/JSONView.js.map +1 -1
  218. package/lib/widgets/monacoEditor/MonacoEditor.d.ts.map +1 -1
  219. package/lib/widgets/monacoEditor/MonacoEditor.js +29 -0
  220. package/lib/widgets/monacoEditor/MonacoEditor.js.map +1 -1
  221. package/lib/widgets/schema-editor/ManagedSchema.d.ts +8 -0
  222. package/lib/widgets/schema-editor/ManagedSchema.d.ts.map +1 -1
  223. package/lib/widgets/schema-editor/ManagedSchema.js +16 -0
  224. package/lib/widgets/schema-editor/ManagedSchema.js.map +1 -1
  225. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.d.ts +2 -0
  226. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.d.ts.map +1 -1
  227. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.js +1 -0
  228. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.js.map +1 -1
  229. package/lib/widgets/schema-editor/editor/PropertyViewer.d.ts.map +1 -1
  230. package/lib/widgets/schema-editor/editor/PropertyViewer.js +4 -1
  231. package/lib/widgets/schema-editor/editor/PropertyViewer.js.map +1 -1
  232. package/lib/widgets/schema-editor/editor/SchemaEditor.d.ts.map +1 -1
  233. package/lib/widgets/schema-editor/editor/SchemaEditor.js +22 -11
  234. package/lib/widgets/schema-editor/editor/SchemaEditor.js.map +1 -1
  235. package/package.json +32 -32
  236. package/src/__tests__/a11y.test.tsx +73 -0
  237. package/src/core/components/SidePanel.tsx +1 -1
  238. package/src/core/components/shadcn/button.tsx +67 -12
  239. package/src/core/components/shadcn/modal/dialog.tsx +1 -1
  240. package/src/core/components/shadcn/theme/ThemeSwitcher.tsx +1 -1
  241. package/src/features/agent/chat/AgentChatPlaybackControls.tsx +0 -5
  242. package/src/features/agent/chat/AgentRequestInputOverlay.test.tsx +31 -0
  243. package/src/features/agent/chat/AgentRequestInputOverlay.tsx +6 -2
  244. package/src/features/agent/chat/ArtifactsTab.tsx +19 -9
  245. package/src/features/agent/chat/ImageLightbox.tsx +0 -1
  246. package/src/features/agent/chat/ModernAgentConversation.test.tsx +48 -0
  247. package/src/features/agent/chat/ModernAgentConversation.tsx +0 -2
  248. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +6 -1
  249. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +21 -15
  250. package/src/features/agent/chat/ModernAgentOutput/Header.tsx +3 -3
  251. package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +0 -2
  252. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +4 -1
  253. package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.test.ts +80 -0
  254. package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.ts +21 -1
  255. package/src/features/environment/SelectEnvironment.tsx +186 -0
  256. package/src/features/environment/index.ts +1 -0
  257. package/src/features/index.ts +2 -0
  258. package/src/features/magic-pdf/AnnotatedImageSlider.tsx +62 -66
  259. package/src/features/magic-pdf/DownloadPopover.tsx +5 -48
  260. package/src/features/magic-pdf/ExtractedContentView.tsx +2 -14
  261. package/src/features/magic-pdf/GroundedExtractionAssistantPanel.tsx +86 -0
  262. package/src/features/magic-pdf/GroundedExtractionView.test.tsx +65 -0
  263. package/src/features/magic-pdf/GroundedExtractionView.tsx +1055 -0
  264. package/src/features/magic-pdf/MagicPdfProvider.tsx +17 -47
  265. package/src/features/magic-pdf/MagicPdfView.tsx +3 -51
  266. package/src/features/magic-pdf/index.ts +1 -0
  267. package/src/features/magic-pdf/types.ts +1 -1
  268. package/src/features/oauth/McpConnectionsButton.tsx +0 -2
  269. package/src/features/oauth/McpConnectionsDialog.tsx +0 -1
  270. package/src/features/oauth/RemoteMcpConnectionButton.tsx +0 -1
  271. package/src/features/pdf-viewer/PdfPageSlider.tsx +5 -5
  272. package/src/features/pdf-viewer/SimplePdfViewer.tsx +11 -12
  273. package/src/features/permissions/UserPermissionsProvider.tsx +1 -1
  274. package/src/features/store/objects/DocumentQuickFilter.tsx +13 -15
  275. package/src/features/store/objects/DocumentSearchResults.tsx +3 -6
  276. package/src/features/store/objects/components/ContentDispositionButton.tsx +5 -1
  277. package/src/features/store/objects/components/ContentOverview.tsx +144 -44
  278. package/src/features/store/objects/components/DocumentIcon.tsx +1 -1
  279. package/src/features/store/objects/components/VectorSearchWidget.tsx +2 -2
  280. package/src/features/store/objects/components/useContentPanelHooks.ts +3 -15
  281. package/src/features/store/objects/layout/renderers.tsx +1 -2
  282. package/src/features/store/objects/selection/SelectionActions.tsx +1 -1
  283. package/src/features/store/types/ContentObjectTypesTable.tsx +12 -14
  284. package/src/features/store/types/IntakePolicyEditor.tsx +679 -0
  285. package/src/features/store/types/IntakePolicyForm.tsx +838 -0
  286. package/src/features/store/types/SelectContentTypeModal.tsx +2 -2
  287. package/src/features/store/types/index.ts +2 -0
  288. package/src/features/store/types/intake-policy-editor.logic.test.ts +109 -0
  289. package/src/features/store/types/intake-policy-editor.logic.ts +87 -0
  290. package/src/features/store/types/typeCatalogCache.ts +45 -0
  291. package/src/features/user/UserInfo.tsx +7 -7
  292. package/src/features/views/ViewExperience.test.tsx +273 -0
  293. package/src/features/views/ViewExperience.tsx +408 -0
  294. package/src/features/views/ViewNavigation.test.tsx +86 -0
  295. package/src/features/views/ViewNavigation.tsx +187 -0
  296. package/src/features/views/ViewResults.test.ts +82 -0
  297. package/src/features/views/ViewResults.tsx +609 -0
  298. package/src/features/views/ViewSearch.test.tsx +147 -0
  299. package/src/features/views/ViewSearch.tsx +205 -0
  300. package/src/features/views/index.ts +6 -0
  301. package/src/features/views/types.ts +57 -0
  302. package/src/features/views/viewState.test.ts +111 -0
  303. package/src/features/views/viewState.ts +132 -0
  304. package/src/i18n/locales/ar.json +16 -0
  305. package/src/i18n/locales/de.json +12 -0
  306. package/src/i18n/locales/en.json +965 -753
  307. package/src/i18n/locales/es.json +13 -0
  308. package/src/i18n/locales/fr.json +13 -0
  309. package/src/i18n/locales/it.json +13 -0
  310. package/src/i18n/locales/ja.json +11 -0
  311. package/src/i18n/locales/ko.json +11 -0
  312. package/src/i18n/locales/pt.json +13 -0
  313. package/src/i18n/locales/ru.json +14 -0
  314. package/src/i18n/locales/tr.json +12 -0
  315. package/src/i18n/locales/zh-TW.json +11 -0
  316. package/src/i18n/locales/zh.json +11 -0
  317. package/src/router/HistoryNavigator.test.ts +48 -0
  318. package/src/router/HistoryNavigator.ts +24 -13
  319. package/src/router/Nav.tsx +18 -8
  320. package/src/session/UserSession.test.ts +39 -0
  321. package/src/session/UserSession.ts +5 -3
  322. package/src/widgets/form/Form.tsx +1 -1
  323. package/src/widgets/json-view/JSONSwitcher.tsx +2 -2
  324. package/src/widgets/json-view/JSONView.tsx +7 -7
  325. package/src/widgets/monacoEditor/MonacoEditor.tsx +29 -0
  326. package/src/widgets/schema-editor/ManagedSchema.ts +22 -0
  327. package/src/widgets/schema-editor/editor/EditableSchemaProperty.tsx +3 -0
  328. package/src/widgets/schema-editor/editor/PropertyViewer.tsx +14 -3
  329. package/src/widgets/schema-editor/editor/SchemaEditor.tsx +80 -29
@@ -0,0 +1,82 @@
1
+ import { ContentObjectStatus, type ViewHit } from '@vertesia/common';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { formatViewFieldValue, getViewFieldValue } from './ViewResults.js';
4
+
5
+ const hit: ViewHit = {
6
+ id: 'doc-1',
7
+ score: 1.25,
8
+ annotation: { why_match: 'Matches the renewal clause.' },
9
+ document: {
10
+ id: 'doc-1',
11
+ name: 'Contract',
12
+ description: 'Example',
13
+ created_at: '2026-01-01T00:00:00.000Z',
14
+ created_by: 'user-1',
15
+ updated_at: '2026-02-01T00:00:00.000Z',
16
+ updated_by: 'user-1',
17
+ location: '/Customers/Acme',
18
+ status: ContentObjectStatus.completed,
19
+ properties: { brand: 'Acme', size: 2500, parties: ['Acme', 'Vertesia'] },
20
+ revision: { root: 'doc-1', head: true },
21
+ },
22
+ };
23
+
24
+ describe('View result fields', () => {
25
+ it('reads document, annotation, and score fields', () => {
26
+ expect(getViewFieldValue(hit, 'properties.brand')).toBe('Acme');
27
+ expect(getViewFieldValue(hit, 'annotation.why_match')).toBe('Matches the renewal clause.');
28
+ expect(getViewFieldValue(hit, 'score')).toBe(1.25);
29
+ });
30
+
31
+ it('formats arrays, numbers, and fallbacks', () => {
32
+ expect(formatViewFieldValue(hit, { field: 'properties.parties' })).toBe('Acme, Vertesia');
33
+ expect(formatViewFieldValue(hit, { field: 'properties.size', format: 'number' })).toBe('2,500');
34
+ expect(formatViewFieldValue(hit, { field: 'properties.missing', fallback: 'Unknown' })).toBe('Unknown');
35
+ });
36
+
37
+ it('renders date-only values as calendar dates independent of the client time zone', () => {
38
+ const dateOnlyHit = {
39
+ ...hit,
40
+ document: {
41
+ ...hit.document,
42
+ properties: { ...hit.document.properties, release_date: '2026-01-01' },
43
+ },
44
+ };
45
+ const expected = new Intl.DateTimeFormat(undefined, { timeZone: 'UTC' }).format(
46
+ new Date('2026-01-01T00:00:00.000Z'),
47
+ );
48
+
49
+ expect(formatViewFieldValue(dateOnlyHit, { field: 'properties.release_date', format: 'date' })).toBe(expected);
50
+ });
51
+
52
+ it('uses the active locale formatters supplied by the View renderer', () => {
53
+ const formatDate = (date: Date | string | number | null | undefined, options?: Intl.DateTimeFormatOptions) =>
54
+ new Intl.DateTimeFormat('fr', options).format(new Date(date ?? 0));
55
+ const formatNumber = (value: number | null | undefined) =>
56
+ value === null || value === undefined ? '—' : new Intl.NumberFormat('fr').format(value);
57
+
58
+ expect(
59
+ formatViewFieldValue(hit, { field: 'properties.size', format: 'number' }, { formatDate, formatNumber }),
60
+ ).toBe(new Intl.NumberFormat('fr').format(2500));
61
+ expect(
62
+ formatViewFieldValue(
63
+ {
64
+ ...hit,
65
+ document: {
66
+ ...hit.document,
67
+ properties: { ...hit.document.properties, release_date: '2026-01-01' },
68
+ },
69
+ },
70
+ { field: 'properties.release_date', format: 'date' },
71
+ { formatDate, formatNumber },
72
+ ),
73
+ ).toBe(
74
+ new Intl.DateTimeFormat('fr', {
75
+ year: 'numeric',
76
+ month: 'numeric',
77
+ day: 'numeric',
78
+ timeZone: 'UTC',
79
+ }).format(new Date('2026-01-01T00:00:00.000Z')),
80
+ );
81
+ });
82
+ });
@@ -0,0 +1,609 @@
1
+ import type {
2
+ ViewBoardCardConfiguration,
3
+ ViewCardsDisplay,
4
+ ViewDisplayConfiguration,
5
+ ViewGalleryDisplay,
6
+ ViewHit,
7
+ ViewListDisplay,
8
+ ViewResultField,
9
+ ViewResultMedia,
10
+ ViewSortOption,
11
+ ViewTableDisplay,
12
+ } from '@vertesia/common';
13
+ import {
14
+ Badge,
15
+ Button,
16
+ Card,
17
+ CardContent,
18
+ CardDescription,
19
+ CardFooter,
20
+ CardHeader,
21
+ CardTitle,
22
+ SortableTableHeaderCell,
23
+ Table,
24
+ TableHeaderCell,
25
+ TBody,
26
+ THead,
27
+ VTooltip,
28
+ } from '@vertesia/ui/core';
29
+ import { type LocaleFormat, useLocaleFormat, useUITranslation } from '@vertesia/ui/i18n';
30
+ import { ChevronDown, ChevronsUpDown, ChevronUp, ExternalLink, FileText, ImageIcon } from 'lucide-react';
31
+ import { createContext, type ReactNode, useContext, useEffect, useState } from 'react';
32
+ import type { ViewMediaResolver, ViewResultsRendererProps } from './types.js';
33
+ import { resolveViewSort } from './viewState.js';
34
+
35
+ function nestedValue(root: unknown, path: string): unknown {
36
+ if (!path || path === '.') return root;
37
+ return path.split('.').reduce<unknown>((value, segment) => {
38
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
39
+ return (value as Record<string, unknown>)[segment];
40
+ }, root);
41
+ }
42
+
43
+ export function getViewFieldValue(hit: ViewHit, field: string): unknown {
44
+ if (field === 'score') return hit.score;
45
+ if (field.startsWith('annotation.')) return nestedValue(hit.annotation, field.slice('annotation.'.length));
46
+ if (field.startsWith('document.')) return nestedValue(hit.document, field.slice('document.'.length));
47
+ return nestedValue(hit.document, field);
48
+ }
49
+
50
+ type ViewValueFormatters = Pick<LocaleFormat, 'formatDate' | 'formatNumber'>;
51
+
52
+ const ViewValueFormattersContext = createContext<ViewValueFormatters | undefined>(undefined);
53
+
54
+ function primitiveText(value: unknown): string | undefined {
55
+ if (value === undefined || value === null || value === '') return undefined;
56
+ if (Array.isArray(value)) return value.map(primitiveText).filter(Boolean).join(', ');
57
+ if (typeof value === 'object') return JSON.stringify(value);
58
+ return String(value);
59
+ }
60
+
61
+ export function formatViewFieldValue(hit: ViewHit, field: ViewResultField, formatters?: ViewValueFormatters): string {
62
+ const value = getViewFieldValue(hit, field.field);
63
+ const fallback = field.fallback ?? '';
64
+ if (value === undefined || value === null || value === '') return fallback;
65
+
66
+ if (field.format === 'date') {
67
+ const text = String(value);
68
+ const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(text);
69
+ const date = new Date(text);
70
+ if (Number.isNaN(date.getTime())) return primitiveText(value) ?? fallback;
71
+ const options: Intl.DateTimeFormatOptions = {
72
+ year: 'numeric',
73
+ month: 'numeric',
74
+ day: 'numeric',
75
+ ...(dateOnly ? { timeZone: 'UTC' } : {}),
76
+ };
77
+ return formatters?.formatDate(date, options) ?? new Intl.DateTimeFormat(undefined, options).format(date);
78
+ }
79
+ if (field.format === 'number') {
80
+ const number = typeof value === 'number' ? value : Number(value);
81
+ if (!Number.isFinite(number)) return primitiveText(value) ?? fallback;
82
+ return formatters?.formatNumber(number) ?? new Intl.NumberFormat().format(number);
83
+ }
84
+ return primitiveText(value) ?? fallback;
85
+ }
86
+
87
+ function ViewField({ hit, field, className }: { hit: ViewHit; field: ViewResultField; className?: string }) {
88
+ const formatters = useContext(ViewValueFormattersContext);
89
+ const text = formatViewFieldValue(hit, field, formatters);
90
+ if (!text) return null;
91
+ if (field.format === 'badge' || field.format === 'content_type') {
92
+ return <Badge className={className}>{text}</Badge>;
93
+ }
94
+ return <span className={className}>{text}</span>;
95
+ }
96
+
97
+ function OpenResultButton({
98
+ hit,
99
+ onOpenHit,
100
+ compact = false,
101
+ }: {
102
+ hit: ViewHit;
103
+ onOpenHit?: (hit: ViewHit) => void;
104
+ compact?: boolean;
105
+ }) {
106
+ const { t } = useUITranslation();
107
+ if (!onOpenHit) return null;
108
+ if (compact) {
109
+ return (
110
+ <VTooltip description={t('agent.openDocument')} asChild>
111
+ <Button
112
+ type="button"
113
+ variant="ghost"
114
+ size="icon"
115
+ aria-label={t('agent.openDocument')}
116
+ onClick={() => onOpenHit(hit)}
117
+ >
118
+ <ExternalLink className="size-4" />
119
+ </Button>
120
+ </VTooltip>
121
+ );
122
+ }
123
+ return (
124
+ <Button type="button" variant="outline" size="sm" onClick={() => onOpenHit(hit)}>
125
+ <ExternalLink className="size-4" />
126
+ {t('agent.openDocument')}
127
+ </Button>
128
+ );
129
+ }
130
+
131
+ function safeMediaUrl(hit: ViewHit, media: ViewResultMedia): string | undefined {
132
+ if (media.source !== 'property' || !media.field) return undefined;
133
+ const value = primitiveText(getViewFieldValue(hit, media.field));
134
+ if (!value) return undefined;
135
+ try {
136
+ const url = new URL(value, typeof window === 'undefined' ? 'https://localhost' : window.location.origin);
137
+ return url.protocol === 'http:' || url.protocol === 'https:' ? url.toString() : undefined;
138
+ } catch {
139
+ return undefined;
140
+ }
141
+ }
142
+
143
+ function ViewMedia({
144
+ hit,
145
+ media,
146
+ resolveMedia,
147
+ className,
148
+ }: {
149
+ hit: ViewHit;
150
+ media?: ViewResultMedia;
151
+ resolveMedia?: ViewMediaResolver;
152
+ className?: string;
153
+ }) {
154
+ const directUrl = media ? safeMediaUrl(hit, media) : undefined;
155
+ const [resolvedUrl, setResolvedUrl] = useState<string>();
156
+ const [failed, setFailed] = useState(false);
157
+
158
+ useEffect(() => {
159
+ setResolvedUrl(undefined);
160
+ setFailed(false);
161
+ if (directUrl || !media || !resolveMedia) return;
162
+
163
+ let active = true;
164
+ Promise.resolve(resolveMedia(hit, media))
165
+ .then((url) => {
166
+ if (active) setResolvedUrl(url);
167
+ })
168
+ .catch(() => {
169
+ if (active) setFailed(true);
170
+ });
171
+ return () => {
172
+ active = false;
173
+ };
174
+ }, [directUrl, hit, media, resolveMedia]);
175
+
176
+ const url = failed ? undefined : (directUrl ?? resolvedUrl);
177
+ const fit = media?.fit === 'contain' ? 'object-contain' : 'object-cover';
178
+ if (url) {
179
+ return <img src={url} alt="" className={`${fit} ${className ?? ''}`} onError={() => setFailed(true)} />;
180
+ }
181
+ const fallback = media?.source === 'type_icon' ? 'type_icon' : (media?.fallback ?? 'type_icon');
182
+ if (fallback === 'none') return null;
183
+ return (
184
+ <div className={`flex items-center justify-center bg-muted ${className ?? ''}`} aria-hidden="true">
185
+ {fallback === 'placeholder' ? (
186
+ <ImageIcon className="size-10 text-muted-foreground" />
187
+ ) : (
188
+ <FileText className="size-10 text-muted-foreground" />
189
+ )}
190
+ </div>
191
+ );
192
+ }
193
+
194
+ function Annotation({ hit }: { hit: ViewHit }) {
195
+ const annotation = hit.annotation?.why_match ?? hit.annotation?.answer ?? hit.annotation?.excerpt;
196
+ return annotation ? <p className="text-sm text-info">{annotation}</p> : null;
197
+ }
198
+
199
+ function BadgeFields({ hit, fields }: { hit: ViewHit; fields?: ViewResultField[] }) {
200
+ if (!fields?.length) return null;
201
+ return (
202
+ <div className="flex flex-wrap gap-1">
203
+ {fields.map((field) => (
204
+ <ViewField key={`${field.field}-${field.label ?? ''}`} hit={hit} field={field} />
205
+ ))}
206
+ </div>
207
+ );
208
+ }
209
+
210
+ function LabeledFields({ hit, fields }: { hit: ViewHit; fields?: ViewResultField[] }) {
211
+ const formatters = useContext(ViewValueFormattersContext);
212
+ if (!fields?.length) return null;
213
+ return (
214
+ <dl className="space-y-1 text-sm">
215
+ {fields.map((field) => {
216
+ const value = formatViewFieldValue(hit, field, formatters);
217
+ if (!value) return null;
218
+ return (
219
+ <div key={`${field.field}-${field.label ?? ''}`} className="flex gap-2">
220
+ {field.label && <dt className="shrink-0 text-muted">{field.label}</dt>}
221
+ <dd className="min-w-0 truncate">{value}</dd>
222
+ </div>
223
+ );
224
+ })}
225
+ </dl>
226
+ );
227
+ }
228
+
229
+ function ListResults({
230
+ display,
231
+ hits,
232
+ onOpenHit,
233
+ resolveMedia,
234
+ }: {
235
+ display: ViewListDisplay;
236
+ hits: ViewHit[];
237
+ onOpenHit?: (hit: ViewHit) => void;
238
+ resolveMedia?: ViewMediaResolver;
239
+ }) {
240
+ return (
241
+ <div className="divide-y divide-border rounded-md border">
242
+ {hits.map((hit) => (
243
+ <article key={hit.id} className="flex gap-4 p-4">
244
+ {display.media && (
245
+ <ViewMedia
246
+ hit={hit}
247
+ media={display.media}
248
+ resolveMedia={resolveMedia}
249
+ className="size-20 shrink-0 rounded-md"
250
+ />
251
+ )}
252
+ <div className="min-w-0 flex-1 space-y-2">
253
+ <div>
254
+ <h3 className="font-semibold">
255
+ <ViewField hit={hit} field={display.title} />
256
+ </h3>
257
+ {display.subtitle && (
258
+ <div className="flex flex-wrap gap-x-3 gap-y-1 text-sm text-muted">
259
+ {display.subtitle.map((field) => (
260
+ <ViewField key={field.field} hit={hit} field={field} />
261
+ ))}
262
+ </div>
263
+ )}
264
+ </div>
265
+ {display.description && (
266
+ <p className="line-clamp-3 text-sm text-muted-foreground">
267
+ <ViewField hit={hit} field={display.description} />
268
+ </p>
269
+ )}
270
+ <BadgeFields hit={hit} fields={display.badges} />
271
+ <Annotation hit={hit} />
272
+ </div>
273
+ <div className="shrink-0 self-center">
274
+ <OpenResultButton hit={hit} onOpenHit={onOpenHit} compact />
275
+ </div>
276
+ </article>
277
+ ))}
278
+ </div>
279
+ );
280
+ }
281
+
282
+ function TableResults({
283
+ display,
284
+ hits,
285
+ isLoading,
286
+ onOpenHit,
287
+ sortOptions,
288
+ currentSort,
289
+ onSortChange,
290
+ }: {
291
+ display: ViewTableDisplay;
292
+ hits: ViewHit[];
293
+ isLoading: boolean;
294
+ onOpenHit?: (hit: ViewHit) => void;
295
+ sortOptions?: ViewSortOption[];
296
+ currentSort?: string;
297
+ onSortChange?: (sort: string) => void;
298
+ }) {
299
+ const { t } = useUITranslation();
300
+ return (
301
+ <div className="overflow-x-auto rounded-md border">
302
+ <Table className="w-full">
303
+ <THead>
304
+ <tr>
305
+ {display.columns.map((column) => {
306
+ const sortOption = column.sort_option
307
+ ? sortOptions?.find((option) => option.id === column.sort_option)
308
+ : undefined;
309
+ if (!column.sortable || !sortOption) {
310
+ return (
311
+ <TableHeaderCell key={`${column.field}-${column.label ?? ''}`}>
312
+ {column.label ?? column.field}
313
+ </TableHeaderCell>
314
+ );
315
+ }
316
+ const direction =
317
+ currentSort === sortOption.id
318
+ ? sortOption.sort[0]?.order === 'desc'
319
+ ? 'descending'
320
+ : 'ascending'
321
+ : 'none';
322
+ return (
323
+ <SortableTableHeaderCell
324
+ key={`${column.field}-${column.label ?? ''}`}
325
+ sortDirection={direction}
326
+ onSort={() => onSortChange?.(sortOption.id)}
327
+ disabled={isLoading || !onSortChange}
328
+ sortIndicator={(sortDirection) => {
329
+ if (sortDirection === 'ascending') {
330
+ return <ChevronUp className="size-3" aria-hidden="true" />;
331
+ }
332
+ if (sortDirection === 'descending') {
333
+ return <ChevronDown className="size-3" aria-hidden="true" />;
334
+ }
335
+ return <ChevronsUpDown className="size-3 text-muted" aria-hidden="true" />;
336
+ }}
337
+ >
338
+ {column.label ?? column.field}
339
+ </SortableTableHeaderCell>
340
+ );
341
+ })}
342
+ {onOpenHit && (
343
+ <TableHeaderCell className="w-0">
344
+ <span className="sr-only">{t('agent.openDocument')}</span>
345
+ </TableHeaderCell>
346
+ )}
347
+ </tr>
348
+ </THead>
349
+ <TBody
350
+ isLoading={isLoading && hits.length === 0}
351
+ columns={display.columns.length + (onOpenHit ? 1 : 0)}
352
+ >
353
+ {hits.map((hit) => (
354
+ <tr key={hit.id}>
355
+ {display.columns.map((column) => (
356
+ <td
357
+ key={`${hit.id}-${column.field}`}
358
+ style={column.width ? { width: column.width } : undefined}
359
+ >
360
+ <ViewField hit={hit} field={column} />
361
+ </td>
362
+ ))}
363
+ {onOpenHit && (
364
+ <td className="w-0 whitespace-nowrap text-end">
365
+ <OpenResultButton hit={hit} onOpenHit={onOpenHit} compact />
366
+ </td>
367
+ )}
368
+ </tr>
369
+ ))}
370
+ </TBody>
371
+ </Table>
372
+ </div>
373
+ );
374
+ }
375
+
376
+ const GRID_COLUMNS: Record<number, string> = {
377
+ 2: 'md:grid-cols-2',
378
+ 3: 'md:grid-cols-2 xl:grid-cols-3',
379
+ 4: 'md:grid-cols-2 xl:grid-cols-4',
380
+ 5: 'md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-5',
381
+ 6: 'md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-6',
382
+ };
383
+
384
+ function ResultCard({
385
+ hit,
386
+ configuration,
387
+ onOpenHit,
388
+ resolveMedia,
389
+ }: {
390
+ hit: ViewHit;
391
+ configuration: ViewCardsDisplay | ViewBoardCardConfiguration;
392
+ onOpenHit?: (hit: ViewHit) => void;
393
+ resolveMedia?: ViewMediaResolver;
394
+ }) {
395
+ return (
396
+ <Card className="flex h-full min-w-0 flex-col overflow-hidden">
397
+ {configuration.media && (
398
+ <ViewMedia
399
+ hit={hit}
400
+ media={configuration.media}
401
+ resolveMedia={resolveMedia}
402
+ className="h-40 w-full border-b"
403
+ />
404
+ )}
405
+ <CardHeader className="gap-2 p-4">
406
+ <BadgeFields hit={hit} fields={configuration.badges} />
407
+ <CardTitle className="truncate text-base">
408
+ <ViewField hit={hit} field={configuration.title} />
409
+ </CardTitle>
410
+ {configuration.description && (
411
+ <CardDescription className="line-clamp-3">
412
+ <ViewField hit={hit} field={configuration.description} />
413
+ </CardDescription>
414
+ )}
415
+ </CardHeader>
416
+ <CardContent className="flex-1 p-4 pt-0">
417
+ <LabeledFields hit={hit} fields={configuration.fields} />
418
+ <Annotation hit={hit} />
419
+ </CardContent>
420
+ {onOpenHit && (
421
+ <CardFooter className="justify-end p-4 pt-0">
422
+ <OpenResultButton hit={hit} onOpenHit={onOpenHit} />
423
+ </CardFooter>
424
+ )}
425
+ </Card>
426
+ );
427
+ }
428
+
429
+ function CardsResults({
430
+ display,
431
+ hits,
432
+ onOpenHit,
433
+ resolveMedia,
434
+ }: {
435
+ display: ViewCardsDisplay;
436
+ hits: ViewHit[];
437
+ onOpenHit?: (hit: ViewHit) => void;
438
+ resolveMedia?: ViewMediaResolver;
439
+ }) {
440
+ return (
441
+ <div className={`grid grid-cols-1 gap-4 ${GRID_COLUMNS[display.columns ?? 3]}`}>
442
+ {hits.map((hit) => (
443
+ <ResultCard
444
+ key={hit.id}
445
+ hit={hit}
446
+ configuration={display}
447
+ onOpenHit={onOpenHit}
448
+ resolveMedia={resolveMedia}
449
+ />
450
+ ))}
451
+ </div>
452
+ );
453
+ }
454
+
455
+ function GalleryResults({
456
+ display,
457
+ hits,
458
+ onOpenHit,
459
+ resolveMedia,
460
+ }: {
461
+ display: ViewGalleryDisplay;
462
+ hits: ViewHit[];
463
+ onOpenHit?: (hit: ViewHit) => void;
464
+ resolveMedia?: ViewMediaResolver;
465
+ }) {
466
+ return (
467
+ <div className={`grid grid-cols-1 gap-4 ${GRID_COLUMNS[display.columns ?? 4]}`}>
468
+ {hits.map((hit) => (
469
+ <Card key={hit.id} className="overflow-hidden">
470
+ <ViewMedia
471
+ hit={hit}
472
+ media={display.media}
473
+ resolveMedia={resolveMedia}
474
+ className="aspect-square w-full border-b"
475
+ />
476
+ <CardContent className="space-y-2 p-3">
477
+ <h3 className="truncate font-semibold">
478
+ <ViewField hit={hit} field={display.title} />
479
+ </h3>
480
+ {display.caption && (
481
+ <div className="space-y-1 text-sm text-muted">
482
+ {display.caption.map((field) => (
483
+ <div key={field.field} className="truncate">
484
+ <ViewField hit={hit} field={field} />
485
+ </div>
486
+ ))}
487
+ </div>
488
+ )}
489
+ <OpenResultButton hit={hit} onOpenHit={onOpenHit} />
490
+ </CardContent>
491
+ </Card>
492
+ ))}
493
+ </div>
494
+ );
495
+ }
496
+
497
+ function BoardResults({
498
+ display,
499
+ hits,
500
+ onOpenHit,
501
+ resolveMedia,
502
+ }: {
503
+ display: Extract<ViewDisplayConfiguration, { type: 'board' }>;
504
+ hits: ViewHit[];
505
+ onOpenHit?: (hit: ViewHit) => void;
506
+ resolveMedia?: ViewMediaResolver;
507
+ }) {
508
+ const grouped = new Map<string, ViewHit[]>();
509
+ for (const hit of hits) {
510
+ const key = primitiveText(getViewFieldValue(hit, display.group_by)) ?? '';
511
+ grouped.set(key, [...(grouped.get(key) ?? []), hit]);
512
+ }
513
+ const columns = display.columns?.length
514
+ ? [...display.columns].sort((left, right) => (left.order ?? 0) - (right.order ?? 0))
515
+ : [...grouped.keys()].sort().map((value) => ({ value, label: value || '—' }));
516
+
517
+ return (
518
+ <div className="flex min-w-max gap-4 overflow-x-auto pb-2">
519
+ {columns.map((column) => {
520
+ const columnHits = grouped.get(column.value) ?? [];
521
+ return (
522
+ <section key={column.value} className="w-80 shrink-0 space-y-3 rounded-lg bg-muted/40 p-3">
523
+ <div className="flex items-center justify-between gap-2">
524
+ <h3 className="font-semibold">{column.label}</h3>
525
+ <Badge>{columnHits.length}</Badge>
526
+ </div>
527
+ <div className="space-y-3">
528
+ {columnHits.map((hit) => (
529
+ <ResultCard
530
+ key={hit.id}
531
+ hit={hit}
532
+ configuration={display.card}
533
+ onOpenHit={onOpenHit}
534
+ resolveMedia={resolveMedia}
535
+ />
536
+ ))}
537
+ </div>
538
+ </section>
539
+ );
540
+ })}
541
+ </div>
542
+ );
543
+ }
544
+
545
+ function renderResults(
546
+ configuration: ViewDisplayConfiguration,
547
+ hits: ViewHit[],
548
+ isLoading: boolean,
549
+ onOpenHit?: (hit: ViewHit) => void,
550
+ resolveMedia?: ViewMediaResolver,
551
+ sortOptions?: ViewSortOption[],
552
+ currentSort?: string,
553
+ onSortChange?: (sort: string) => void,
554
+ ): ReactNode {
555
+ if (configuration.type === 'table') {
556
+ return (
557
+ <TableResults
558
+ display={configuration}
559
+ hits={hits}
560
+ isLoading={isLoading}
561
+ onOpenHit={onOpenHit}
562
+ sortOptions={sortOptions}
563
+ currentSort={currentSort}
564
+ onSortChange={onSortChange}
565
+ />
566
+ );
567
+ }
568
+ if (configuration.type === 'cards') {
569
+ return <CardsResults display={configuration} hits={hits} onOpenHit={onOpenHit} resolveMedia={resolveMedia} />;
570
+ }
571
+ if (configuration.type === 'gallery') {
572
+ return <GalleryResults display={configuration} hits={hits} onOpenHit={onOpenHit} resolveMedia={resolveMedia} />;
573
+ }
574
+ if (configuration.type === 'board') {
575
+ return <BoardResults display={configuration} hits={hits} onOpenHit={onOpenHit} resolveMedia={resolveMedia} />;
576
+ }
577
+ return <ListResults display={configuration} hits={hits} onOpenHit={onOpenHit} resolveMedia={resolveMedia} />;
578
+ }
579
+
580
+ export function DefaultViewResults({
581
+ configuration,
582
+ definition,
583
+ request,
584
+ result,
585
+ isLoading,
586
+ onSortChange,
587
+ onOpenHit,
588
+ resolveMedia,
589
+ }: ViewResultsRendererProps) {
590
+ const { t } = useUITranslation();
591
+ const formatters = useLocaleFormat();
592
+ if (!isLoading && result.hits.length === 0) {
593
+ return <p className="py-12 text-center text-sm text-muted">{t('filter.noResultsFound')}</p>;
594
+ }
595
+ return (
596
+ <ViewValueFormattersContext.Provider value={formatters}>
597
+ {renderResults(
598
+ configuration,
599
+ result.hits,
600
+ isLoading,
601
+ onOpenHit,
602
+ resolveMedia,
603
+ definition.results?.sort_options,
604
+ resolveViewSort(request, result, definition.results?.default_sort),
605
+ onSortChange,
606
+ )}
607
+ </ViewValueFormattersContext.Provider>
608
+ );
609
+ }