@vertesia/ui 0.80.0-dev.20251121 → 0.80.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 (277) hide show
  1. package/README.md +105 -0
  2. package/lib/esm/core/components/MenuList.js +2 -5
  3. package/lib/esm/core/components/MenuList.js.map +1 -1
  4. package/lib/esm/core/components/MessageBox.js +1 -1
  5. package/lib/esm/core/components/MessageBox.js.map +1 -1
  6. package/lib/esm/core/components/shadcn/dialog.js +16 -2
  7. package/lib/esm/core/components/shadcn/dialog.js.map +1 -1
  8. package/lib/esm/core/components/shadcn/filters/filter/SelectFilter.js +6 -9
  9. package/lib/esm/core/components/shadcn/filters/filter/SelectFilter.js.map +1 -1
  10. package/lib/esm/core/components/shadcn/filters/filterBar.js +1 -1
  11. package/lib/esm/core/components/shadcn/filters/filterBar.js.map +1 -1
  12. package/lib/esm/core/components/shadcn/selectBox.js +23 -5
  13. package/lib/esm/core/components/shadcn/selectBox.js.map +1 -1
  14. package/lib/esm/core/components/shadcn/tabs.js +3 -3
  15. package/lib/esm/core/components/shadcn/tabs.js.map +1 -1
  16. package/lib/esm/env/index.js +3 -0
  17. package/lib/esm/env/index.js.map +1 -1
  18. package/lib/esm/features/agent/chat/AgentChart.js +184 -0
  19. package/lib/esm/features/agent/chat/AgentChart.js.map +1 -0
  20. package/lib/esm/features/agent/chat/ModernAgentConversation.js +87 -10
  21. package/lib/esm/features/agent/chat/ModernAgentConversation.js.map +1 -1
  22. package/lib/esm/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +6 -2
  23. package/lib/esm/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  24. package/lib/esm/features/agent/chat/ModernAgentOutput/Header.js +4 -4
  25. package/lib/esm/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
  26. package/lib/esm/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.js +4 -1
  27. package/lib/esm/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.js.map +1 -1
  28. package/lib/esm/features/agent/chat/ModernAgentOutput/MessageInput.js +12 -4
  29. package/lib/esm/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
  30. package/lib/esm/features/agent/chat/ModernAgentOutput/MessageItem.js +60 -56
  31. package/lib/esm/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  32. package/lib/esm/features/agent/chat/index.js +1 -0
  33. package/lib/esm/features/agent/chat/index.js.map +1 -1
  34. package/lib/esm/features/agent/createChartTool.js +354 -0
  35. package/lib/esm/features/agent/createChartTool.js.map +1 -0
  36. package/lib/esm/features/agent/examples.js +295 -0
  37. package/lib/esm/features/agent/examples.js.map +1 -0
  38. package/lib/esm/features/agent/index.js +2 -0
  39. package/lib/esm/features/agent/index.js.map +1 -1
  40. package/lib/esm/features/agent/visualization.js +165 -0
  41. package/lib/esm/features/agent/visualization.js.map +1 -0
  42. package/lib/esm/features/facets/CollectionsFacetsNav.js +5 -1
  43. package/lib/esm/features/facets/CollectionsFacetsNav.js.map +1 -1
  44. package/lib/esm/features/index.js +1 -0
  45. package/lib/esm/features/index.js.map +1 -1
  46. package/lib/esm/features/layout/GenericPageNavHeader.js +14 -4
  47. package/lib/esm/features/layout/GenericPageNavHeader.js.map +1 -1
  48. package/lib/esm/features/magic-pdf/AnnotatedImageSlider.js +268 -0
  49. package/lib/esm/features/magic-pdf/AnnotatedImageSlider.js.map +1 -0
  50. package/lib/esm/features/magic-pdf/DownloadPopover.js +11 -11
  51. package/lib/esm/features/magic-pdf/DownloadPopover.js.map +1 -1
  52. package/lib/esm/features/magic-pdf/ExtractedContentView.js +77 -0
  53. package/lib/esm/features/magic-pdf/ExtractedContentView.js.map +1 -0
  54. package/lib/esm/features/magic-pdf/MagicPdfProvider.js +242 -0
  55. package/lib/esm/features/magic-pdf/MagicPdfProvider.js.map +1 -0
  56. package/lib/esm/features/magic-pdf/MagicPdfView.js +41 -47
  57. package/lib/esm/features/magic-pdf/MagicPdfView.js.map +1 -1
  58. package/lib/esm/features/pdf-viewer/PdfPageRenderer.js +261 -0
  59. package/lib/esm/features/pdf-viewer/PdfPageRenderer.js.map +1 -0
  60. package/lib/esm/features/pdf-viewer/PdfPageSlider.js +276 -0
  61. package/lib/esm/features/pdf-viewer/PdfPageSlider.js.map +1 -0
  62. package/lib/esm/features/pdf-viewer/SimplePdfViewer.js +71 -0
  63. package/lib/esm/features/pdf-viewer/SimplePdfViewer.js.map +1 -0
  64. package/lib/esm/features/pdf-viewer/index.js +4 -0
  65. package/lib/esm/features/pdf-viewer/index.js.map +1 -0
  66. package/lib/esm/features/store/collections/EditCollectionView.js +3 -5
  67. package/lib/esm/features/store/collections/EditCollectionView.js.map +1 -1
  68. package/lib/esm/features/store/collections/SharedPropsEditor.js +1 -2
  69. package/lib/esm/features/store/collections/SharedPropsEditor.js.map +1 -1
  70. package/lib/esm/features/store/objects/DocumentSearchResults.js +0 -7
  71. package/lib/esm/features/store/objects/DocumentSearchResults.js.map +1 -1
  72. package/lib/esm/features/store/objects/components/ContentOverview.js +273 -83
  73. package/lib/esm/features/store/objects/components/ContentOverview.js.map +1 -1
  74. package/lib/esm/features/store/objects/components/useContentPanelHooks.js +153 -0
  75. package/lib/esm/features/store/objects/components/useContentPanelHooks.js.map +1 -0
  76. package/lib/esm/features/store/objects/layout/DocumentTableColumn.js +3 -3
  77. package/lib/esm/features/store/objects/layout/DocumentTableColumn.js.map +1 -1
  78. package/lib/esm/features/store/objects/layout/renderers.js +13 -0
  79. package/lib/esm/features/store/objects/layout/renderers.js.map +1 -1
  80. package/lib/esm/features/utils/index.js +2 -0
  81. package/lib/esm/features/utils/index.js.map +1 -1
  82. package/lib/esm/features/utils/mimeType.js +8 -0
  83. package/lib/esm/features/utils/mimeType.js.map +1 -1
  84. package/lib/esm/features/utils/print.js +181 -0
  85. package/lib/esm/features/utils/print.js.map +1 -0
  86. package/lib/esm/features/utils/workflowStatus.js +43 -0
  87. package/lib/esm/features/utils/workflowStatus.js.map +1 -0
  88. package/lib/esm/router/HistoryNavigator.js +22 -2
  89. package/lib/esm/router/HistoryNavigator.js.map +1 -1
  90. package/lib/esm/shell/login/UserInfo.js +2 -1
  91. package/lib/esm/shell/login/UserInfo.js.map +1 -1
  92. package/lib/esm/shell/login/UserSessionMenu.js +7 -1
  93. package/lib/esm/shell/login/UserSessionMenu.js.map +1 -1
  94. package/lib/esm/widgets/form/Form.js +6 -2
  95. package/lib/esm/widgets/form/Form.js.map +1 -1
  96. package/lib/esm/widgets/markdown/MarkdownRenderer.js +226 -4
  97. package/lib/esm/widgets/markdown/MarkdownRenderer.js.map +1 -1
  98. package/lib/esm/widgets/schema-editor/ManagedSchema.js +0 -3
  99. package/lib/esm/widgets/schema-editor/ManagedSchema.js.map +1 -1
  100. package/lib/esm/widgets/schema-editor/json-schema4-utils.js +1 -1
  101. package/lib/esm/widgets/schema-editor/json-schema4-utils.js.map +1 -1
  102. package/lib/esm/widgets/xml-viewer/components/XMLViewer.js +18 -9
  103. package/lib/esm/widgets/xml-viewer/components/XMLViewer.js.map +1 -1
  104. package/lib/esm/widgets/xml-viewer/constants/index.js +10 -0
  105. package/lib/esm/widgets/xml-viewer/constants/index.js.map +1 -1
  106. package/lib/tsconfig.tsbuildinfo +1 -1
  107. package/lib/types/core/components/MessageBox.d.ts.map +1 -1
  108. package/lib/types/core/components/shadcn/dialog.d.ts +2 -1
  109. package/lib/types/core/components/shadcn/dialog.d.ts.map +1 -1
  110. package/lib/types/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
  111. package/lib/types/core/components/shadcn/selectBox.d.ts +5 -1
  112. package/lib/types/core/components/shadcn/selectBox.d.ts.map +1 -1
  113. package/lib/types/core/components/shadcn/tabs.d.ts +3 -1
  114. package/lib/types/core/components/shadcn/tabs.d.ts.map +1 -1
  115. package/lib/types/env/index.d.ts +2 -0
  116. package/lib/types/env/index.d.ts.map +1 -1
  117. package/lib/types/features/agent/chat/AgentChart.d.ts +48 -0
  118. package/lib/types/features/agent/chat/AgentChart.d.ts.map +1 -0
  119. package/lib/types/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  120. package/lib/types/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +3 -2
  121. package/lib/types/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  122. package/lib/types/features/agent/chat/ModernAgentOutput/Header.d.ts +2 -1
  123. package/lib/types/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
  124. package/lib/types/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.d.ts +4 -2
  125. package/lib/types/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.d.ts.map +1 -1
  126. package/lib/types/features/agent/chat/ModernAgentOutput/MessageInput.d.ts +2 -4
  127. package/lib/types/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
  128. package/lib/types/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  129. package/lib/types/features/agent/chat/index.d.ts +1 -0
  130. package/lib/types/features/agent/chat/index.d.ts.map +1 -1
  131. package/lib/types/features/agent/createChartTool.d.ts +178 -0
  132. package/lib/types/features/agent/createChartTool.d.ts.map +1 -0
  133. package/lib/types/features/agent/examples.d.ts +59 -0
  134. package/lib/types/features/agent/examples.d.ts.map +1 -0
  135. package/lib/types/features/agent/index.d.ts +2 -0
  136. package/lib/types/features/agent/index.d.ts.map +1 -1
  137. package/lib/types/features/agent/visualization.d.ts +95 -0
  138. package/lib/types/features/agent/visualization.d.ts.map +1 -0
  139. package/lib/types/features/facets/CollectionsFacetsNav.d.ts.map +1 -1
  140. package/lib/types/features/index.d.ts +1 -0
  141. package/lib/types/features/index.d.ts.map +1 -1
  142. package/lib/types/features/layout/GenericPageNavHeader.d.ts.map +1 -1
  143. package/lib/types/features/magic-pdf/AnnotatedImageSlider.d.ts +13 -0
  144. package/lib/types/features/magic-pdf/AnnotatedImageSlider.d.ts.map +1 -0
  145. package/lib/types/features/magic-pdf/DownloadPopover.d.ts.map +1 -1
  146. package/lib/types/features/magic-pdf/ExtractedContentView.d.ts +8 -0
  147. package/lib/types/features/magic-pdf/ExtractedContentView.d.ts.map +1 -0
  148. package/lib/types/features/magic-pdf/MagicPdfProvider.d.ts +58 -0
  149. package/lib/types/features/magic-pdf/MagicPdfProvider.d.ts.map +1 -0
  150. package/lib/types/features/magic-pdf/MagicPdfView.d.ts +1 -1
  151. package/lib/types/features/magic-pdf/MagicPdfView.d.ts.map +1 -1
  152. package/lib/types/features/pdf-viewer/PdfPageRenderer.d.ts +83 -0
  153. package/lib/types/features/pdf-viewer/PdfPageRenderer.d.ts.map +1 -0
  154. package/lib/types/features/pdf-viewer/PdfPageSlider.d.ts +29 -0
  155. package/lib/types/features/pdf-viewer/PdfPageSlider.d.ts.map +1 -0
  156. package/lib/types/features/pdf-viewer/SimplePdfViewer.d.ts +19 -0
  157. package/lib/types/features/pdf-viewer/SimplePdfViewer.d.ts.map +1 -0
  158. package/lib/types/features/pdf-viewer/index.d.ts +4 -0
  159. package/lib/types/features/pdf-viewer/index.d.ts.map +1 -0
  160. package/lib/types/features/store/collections/EditCollectionView.d.ts.map +1 -1
  161. package/lib/types/features/store/collections/SharedPropsEditor.d.ts.map +1 -1
  162. package/lib/types/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
  163. package/lib/types/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  164. package/lib/types/features/store/objects/components/useContentPanelHooks.d.ts +30 -0
  165. package/lib/types/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -0
  166. package/lib/types/features/store/objects/layout/renderers.d.ts.map +1 -1
  167. package/lib/types/features/utils/index.d.ts +2 -0
  168. package/lib/types/features/utils/index.d.ts.map +1 -1
  169. package/lib/types/features/utils/mimeType.d.ts +1 -0
  170. package/lib/types/features/utils/mimeType.d.ts.map +1 -1
  171. package/lib/types/features/utils/print.d.ts +10 -0
  172. package/lib/types/features/utils/print.d.ts.map +1 -0
  173. package/lib/types/features/utils/workflowStatus.d.ts +10 -0
  174. package/lib/types/features/utils/workflowStatus.d.ts.map +1 -0
  175. package/lib/types/router/HistoryNavigator.d.ts +3 -0
  176. package/lib/types/router/HistoryNavigator.d.ts.map +1 -1
  177. package/lib/types/shell/login/UserInfo.d.ts.map +1 -1
  178. package/lib/types/shell/login/UserSessionMenu.d.ts.map +1 -1
  179. package/lib/types/widgets/form/Form.d.ts.map +1 -1
  180. package/lib/types/widgets/markdown/MarkdownRenderer.d.ts +5 -1
  181. package/lib/types/widgets/markdown/MarkdownRenderer.d.ts.map +1 -1
  182. package/lib/types/widgets/schema-editor/ManagedSchema.d.ts.map +1 -1
  183. package/lib/types/widgets/xml-viewer/components/XMLViewer.d.ts.map +1 -1
  184. package/lib/types/widgets/xml-viewer/constants/index.d.ts +10 -0
  185. package/lib/types/widgets/xml-viewer/constants/index.d.ts.map +1 -1
  186. package/lib/vertesia-ui-core.js +1 -1
  187. package/lib/vertesia-ui-core.js.map +1 -1
  188. package/lib/vertesia-ui-env.js +1 -1
  189. package/lib/vertesia-ui-env.js.map +1 -1
  190. package/lib/vertesia-ui-features.js +1 -1
  191. package/lib/vertesia-ui-features.js.map +1 -1
  192. package/lib/vertesia-ui-layout.js +1 -1
  193. package/lib/vertesia-ui-layout.js.map +1 -1
  194. package/lib/vertesia-ui-router.js +1 -1
  195. package/lib/vertesia-ui-router.js.map +1 -1
  196. package/lib/vertesia-ui-session.js +1 -1
  197. package/lib/vertesia-ui-session.js.map +1 -1
  198. package/lib/vertesia-ui-shell.js +1 -1
  199. package/lib/vertesia-ui-shell.js.map +1 -1
  200. package/lib/vertesia-ui-widgets.js +1 -1
  201. package/lib/vertesia-ui-widgets.js.map +1 -1
  202. package/package.json +11 -8
  203. package/src/core/components/MenuList.tsx +3 -6
  204. package/src/core/components/MessageBox.tsx +7 -2
  205. package/src/core/components/SelectBox.tsx +1 -1
  206. package/src/core/components/shadcn/dialog.tsx +19 -1
  207. package/src/core/components/shadcn/filters/filter/SelectFilter.tsx +31 -31
  208. package/src/core/components/shadcn/filters/filterBar.tsx +1 -0
  209. package/src/core/components/shadcn/selectBox.tsx +32 -6
  210. package/src/core/components/shadcn/tabs.tsx +3 -2
  211. package/src/env/index.ts +5 -0
  212. package/src/features/agent/CHART_INSTRUCTIONS.md +228 -0
  213. package/src/features/agent/chat/AgentChart.tsx +601 -0
  214. package/src/features/agent/chat/ModernAgentConversation.tsx +123 -11
  215. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +8 -2
  216. package/src/features/agent/chat/ModernAgentOutput/Header.tsx +12 -2
  217. package/src/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.tsx +6 -1
  218. package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +15 -10
  219. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +122 -87
  220. package/src/features/agent/chat/index.ts +1 -0
  221. package/src/features/agent/createChartTool.ts +364 -0
  222. package/src/features/agent/examples.ts +321 -0
  223. package/src/features/agent/index.ts +2 -0
  224. package/src/features/agent/visualization.ts +227 -0
  225. package/src/features/facets/CollectionsFacetsNav.tsx +5 -1
  226. package/src/features/index.ts +1 -0
  227. package/src/features/layout/GenericPageNavHeader.tsx +15 -4
  228. package/src/features/magic-pdf/AnnotatedImageSlider.tsx +482 -0
  229. package/src/features/magic-pdf/DownloadPopover.tsx +45 -40
  230. package/src/features/magic-pdf/ExtractedContentView.tsx +132 -0
  231. package/src/features/magic-pdf/MagicPdfProvider.tsx +297 -0
  232. package/src/features/magic-pdf/MagicPdfView.tsx +184 -91
  233. package/src/features/pdf-viewer/PdfPageRenderer.tsx +612 -0
  234. package/src/features/pdf-viewer/PdfPageSlider.tsx +473 -0
  235. package/src/features/pdf-viewer/SimplePdfViewer.tsx +142 -0
  236. package/src/features/pdf-viewer/index.ts +3 -0
  237. package/src/features/store/collections/EditCollectionView.tsx +3 -5
  238. package/src/features/store/collections/SharedPropsEditor.tsx +1 -2
  239. package/src/features/store/objects/DocumentSearchResults.tsx +0 -7
  240. package/src/features/store/objects/components/ContentOverview.tsx +677 -210
  241. package/src/features/store/objects/components/useContentPanelHooks.ts +169 -0
  242. package/src/features/store/objects/layout/DocumentTableColumn.tsx +3 -3
  243. package/src/features/store/objects/layout/knowledge.md +1 -0
  244. package/src/features/store/objects/layout/renderers.tsx +25 -0
  245. package/src/features/utils/index.ts +3 -1
  246. package/src/features/utils/mimeType.ts +10 -1
  247. package/src/features/utils/print.ts +189 -0
  248. package/src/features/utils/workflowStatus.ts +44 -0
  249. package/src/router/HistoryNavigator.ts +30 -2
  250. package/src/shell/login/UserInfo.tsx +2 -0
  251. package/src/shell/login/UserSessionMenu.tsx +12 -1
  252. package/src/widgets/form/Form.tsx +8 -3
  253. package/src/widgets/markdown/MarkdownRenderer.tsx +350 -6
  254. package/src/widgets/schema-editor/ManagedSchema.ts +0 -3
  255. package/src/widgets/schema-editor/json-schema4-utils.ts +1 -1
  256. package/src/widgets/xml-viewer/components/XMLViewer.tsx +22 -10
  257. package/src/widgets/xml-viewer/constants/index.ts +11 -0
  258. package/lib/esm/features/magic-pdf/PageSlider.js +0 -70
  259. package/lib/esm/features/magic-pdf/PageSlider.js.map +0 -1
  260. package/lib/esm/features/magic-pdf/PdfPageProvider.js +0 -188
  261. package/lib/esm/features/magic-pdf/PdfPageProvider.js.map +0 -1
  262. package/lib/esm/features/magic-pdf/TextPageView.js +0 -62
  263. package/lib/esm/features/magic-pdf/TextPageView.js.map +0 -1
  264. package/lib/esm/features/magic-pdf/useResizeOnDrag.js +0 -34
  265. package/lib/esm/features/magic-pdf/useResizeOnDrag.js.map +0 -1
  266. package/lib/types/features/magic-pdf/PageSlider.d.ts +0 -9
  267. package/lib/types/features/magic-pdf/PageSlider.d.ts.map +0 -1
  268. package/lib/types/features/magic-pdf/PdfPageProvider.d.ts +0 -39
  269. package/lib/types/features/magic-pdf/PdfPageProvider.d.ts.map +0 -1
  270. package/lib/types/features/magic-pdf/TextPageView.d.ts +0 -8
  271. package/lib/types/features/magic-pdf/TextPageView.d.ts.map +0 -1
  272. package/lib/types/features/magic-pdf/useResizeOnDrag.d.ts +0 -9
  273. package/lib/types/features/magic-pdf/useResizeOnDrag.d.ts.map +0 -1
  274. package/src/features/magic-pdf/PageSlider.tsx +0 -142
  275. package/src/features/magic-pdf/PdfPageProvider.tsx +0 -310
  276. package/src/features/magic-pdf/TextPageView.tsx +0 -91
  277. package/src/features/magic-pdf/useResizeOnDrag.ts +0 -42
@@ -0,0 +1,612 @@
1
+ import { useState, useEffect, useRef, createContext, useContext, useCallback } from 'react';
2
+ import { Document, Page, pdfjs } from 'react-pdf';
3
+ import { Loader2 } from 'lucide-react';
4
+
5
+ // Configure PDF.js worker - use CDN for the worker
6
+ pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
7
+
8
+ // Loading spinner component
9
+ function LoadingSpinner({ className, size = 'md' }: { className?: string; size?: 'sm' | 'md' | 'lg' }) {
10
+ const sizeClasses = {
11
+ sm: 'w-4 h-4',
12
+ md: 'w-6 h-6',
13
+ lg: 'w-8 h-8'
14
+ };
15
+ return (
16
+ <div className={`flex items-center justify-center ${className || ''}`}>
17
+ <Loader2 className={`${sizeClasses[size]} animate-spin text-muted-foreground`} />
18
+ </div>
19
+ );
20
+ }
21
+
22
+ interface PdfPageRendererProps {
23
+ pdfUrl: string;
24
+ pageNumber: number;
25
+ width?: number;
26
+ className?: string;
27
+ renderTextLayer?: boolean;
28
+ renderAnnotationLayer?: boolean;
29
+ onLoadSuccess?: (numPages: number) => void;
30
+ onError?: (error: Error) => void;
31
+ }
32
+
33
+ export function PdfPageRenderer({
34
+ pdfUrl,
35
+ pageNumber,
36
+ width,
37
+ className,
38
+ renderTextLayer = false,
39
+ renderAnnotationLayer = false,
40
+ onLoadSuccess,
41
+ onError
42
+ }: PdfPageRendererProps) {
43
+ const [loading, setLoading] = useState(true);
44
+ const [error, setError] = useState<Error | null>(null);
45
+
46
+ const handleLoadSuccess = ({ numPages }: { numPages: number }) => {
47
+ setLoading(false);
48
+ onLoadSuccess?.(numPages);
49
+ };
50
+
51
+ const handleError = (err: Error) => {
52
+ setLoading(false);
53
+ setError(err);
54
+ onError?.(err);
55
+ };
56
+
57
+ if (error) {
58
+ return (
59
+ <div className={`flex items-center justify-center text-destructive text-sm ${className || ''}`}>
60
+ Failed to load PDF
61
+ </div>
62
+ );
63
+ }
64
+
65
+ return (
66
+ <div className={className}>
67
+ {loading && (
68
+ <LoadingSpinner className="py-4" size="md" />
69
+ )}
70
+ <Document
71
+ file={pdfUrl}
72
+ onLoadSuccess={handleLoadSuccess}
73
+ onLoadError={handleError}
74
+ loading={null}
75
+ >
76
+ <Page
77
+ pageNumber={pageNumber}
78
+ width={width}
79
+ renderTextLayer={renderTextLayer}
80
+ renderAnnotationLayer={renderAnnotationLayer}
81
+ loading={<LoadingSpinner className="py-4" size="sm" />}
82
+ />
83
+ </Document>
84
+ </div>
85
+ );
86
+ }
87
+
88
+ // Page dimensions from PDF
89
+ interface PageDimensions {
90
+ width: number;
91
+ height: number;
92
+ aspectRatio: number;
93
+ }
94
+
95
+ // PDF document proxy type
96
+ interface PDFDocumentProxy {
97
+ numPages: number;
98
+ getPage: (pageNum: number) => Promise<{ getViewport: (options: { scale: number }) => { width: number; height: number } }>;
99
+ }
100
+
101
+ // Context for sharing PDF state
102
+ interface SharedPdfContextValue {
103
+ pdfUrl: string;
104
+ numPages: number;
105
+ loading: boolean;
106
+ error: Error | null;
107
+ pageDimensions: PageDimensions | null;
108
+ }
109
+
110
+ const SharedPdfContext = createContext<SharedPdfContextValue | null>(null);
111
+
112
+ interface SharedPdfProviderProps {
113
+ pdfUrl: string;
114
+ urlLoading?: boolean;
115
+ children: (renderPage: (pageNumber: number, width?: number) => React.ReactNode) => React.ReactNode;
116
+ onLoadSuccess?: (numPages: number) => void;
117
+ }
118
+
119
+ /**
120
+ * Provider that loads a PDF once using a single Document component.
121
+ * Children receive a renderPage function to render pages inside the Document.
122
+ */
123
+ export function SharedPdfProvider({ pdfUrl, urlLoading = false, children, onLoadSuccess }: SharedPdfProviderProps) {
124
+ const [numPages, setNumPages] = useState(0);
125
+ const [loading, setLoading] = useState(true);
126
+ const [error, setError] = useState<Error | null>(null);
127
+ const [pageDimensions, setPageDimensions] = useState<PageDimensions | null>(null);
128
+
129
+ const handleLoadSuccess = async (pdf: PDFDocumentProxy) => {
130
+ setNumPages(pdf.numPages);
131
+ onLoadSuccess?.(pdf.numPages);
132
+
133
+ try {
134
+ const page = await pdf.getPage(1);
135
+ const viewport = page.getViewport({ scale: 1 });
136
+ setPageDimensions({
137
+ width: viewport.width,
138
+ height: viewport.height,
139
+ aspectRatio: viewport.width / viewport.height
140
+ });
141
+ } catch (err) {
142
+ console.error('Failed to get page dimensions:', err);
143
+ }
144
+
145
+ setLoading(false);
146
+ };
147
+
148
+ const handleError = (err: Error) => {
149
+ setLoading(false);
150
+ setError(err);
151
+ };
152
+
153
+ const isLoading = urlLoading || (pdfUrl ? loading : true);
154
+
155
+ const value: SharedPdfContextValue = {
156
+ pdfUrl,
157
+ numPages,
158
+ loading: isLoading,
159
+ error,
160
+ pageDimensions
161
+ };
162
+
163
+ // Render function that children use to render pages
164
+ const renderPage = (pageNumber: number, width?: number) => (
165
+ <Page
166
+ key={pageNumber}
167
+ pageNumber={pageNumber}
168
+ width={width}
169
+ renderTextLayer={false}
170
+ renderAnnotationLayer={false}
171
+ loading={<LoadingSpinner className="py-4" size="sm" />}
172
+ />
173
+ );
174
+
175
+ if (error) {
176
+ return (
177
+ <div className="flex items-center justify-center text-destructive text-sm py-4">
178
+ Failed to load PDF
179
+ </div>
180
+ );
181
+ }
182
+
183
+ return (
184
+ <SharedPdfContext.Provider value={value}>
185
+ {pdfUrl ? (
186
+ <Document
187
+ file={pdfUrl}
188
+ onLoadSuccess={handleLoadSuccess}
189
+ onLoadError={handleError}
190
+ loading={<LoadingSpinner className="py-4" size="md" />}
191
+ >
192
+ {children(renderPage)}
193
+ </Document>
194
+ ) : (
195
+ <LoadingSpinner className="py-4" size="md" />
196
+ )}
197
+ </SharedPdfContext.Provider>
198
+ );
199
+ }
200
+
201
+ export function useSharedPdf() {
202
+ return useContext(SharedPdfContext);
203
+ }
204
+
205
+ // A4 portrait aspect ratio
206
+ const A4_ASPECT_RATIO = 210 / 297;
207
+
208
+ interface SimplePdfPageProps {
209
+ pageNumber: number;
210
+ width?: number;
211
+ className?: string;
212
+ }
213
+
214
+ /**
215
+ * Simple wrapper for a PDF page that adds styling.
216
+ * Must be used inside SharedPdfProvider's children render function.
217
+ */
218
+ export function SimplePdfPage({ pageNumber, width, className }: SimplePdfPageProps) {
219
+ const context = useSharedPdf();
220
+ const aspectRatio = context?.pageDimensions?.aspectRatio ?? A4_ASPECT_RATIO;
221
+ const placeholderHeight = width ? Math.round(width / aspectRatio) : 200;
222
+
223
+ if (context?.loading) {
224
+ return (
225
+ <div
226
+ className={`flex items-center justify-center bg-muted ${className || ''}`}
227
+ style={{ height: placeholderHeight, width: width || '100%' }}
228
+ >
229
+ <LoadingSpinner size="md" />
230
+ </div>
231
+ );
232
+ }
233
+
234
+ return (
235
+ <div className={className}>
236
+ <Page
237
+ pageNumber={pageNumber}
238
+ width={width}
239
+ renderTextLayer={false}
240
+ renderAnnotationLayer={false}
241
+ loading={<LoadingSpinner className="py-4" size="sm" />}
242
+ />
243
+ </div>
244
+ );
245
+ }
246
+
247
+ interface PdfThumbnailListProps {
248
+ pdfUrl: string;
249
+ urlLoading?: boolean;
250
+ pageCount: number;
251
+ currentPage: number;
252
+ thumbnailWidth?: number;
253
+ onPageSelect: (pageNumber: number) => void;
254
+ renderThumbnail: (props: {
255
+ pageNumber: number;
256
+ isSelected: boolean;
257
+ pageElement: React.ReactNode;
258
+ onSelect: () => void;
259
+ }) => React.ReactNode;
260
+ /** Optional ref to the scroll container. If not provided, will search for scrollable ancestor. */
261
+ scrollContainerRef?: React.RefObject<HTMLElement | null>;
262
+ /** Callback when aspect ratio is determined from the PDF. Useful for synchronizing placeholder sizing. */
263
+ onAspectRatioChange?: (aspectRatio: number) => void;
264
+ /** Callback when item height is calculated. Useful for scroll position calculations. */
265
+ onItemHeightChange?: (itemHeight: number) => void;
266
+ /** Custom function to calculate item height. Receives placeholder height and should return total item height. */
267
+ calculateItemHeight?: (placeholderHeight: number) => number;
268
+ /** Callback when actual page count is determined from the PDF. Useful when initial count is estimated. */
269
+ onPageCountChange?: (pageCount: number) => void;
270
+ }
271
+
272
+ /**
273
+ * Virtualized PDF thumbnail that only renders the Page when visible.
274
+ * Uses IntersectionObserver for efficient visibility detection.
275
+ */
276
+ function VirtualizedThumbnail({
277
+ pageNumber,
278
+ width,
279
+ isSelected,
280
+ onSelect,
281
+ renderThumbnail,
282
+ aspectRatio = A4_ASPECT_RATIO,
283
+ rootMargin = '200px 0px'
284
+ }: {
285
+ pageNumber: number;
286
+ width?: number;
287
+ isSelected: boolean;
288
+ onSelect: () => void;
289
+ renderThumbnail: PdfThumbnailListProps['renderThumbnail'];
290
+ aspectRatio?: number;
291
+ rootMargin?: string;
292
+ }) {
293
+ const containerRef = useRef<HTMLDivElement>(null);
294
+ const [hasBeenVisible, setHasBeenVisible] = useState(false);
295
+
296
+ // Set up intersection observer
297
+ useEffect(() => {
298
+ const container = containerRef.current;
299
+ if (!container) return;
300
+
301
+ const observer = new IntersectionObserver(
302
+ (entries) => {
303
+ const entry = entries[0];
304
+ if (entry?.isIntersecting) {
305
+ setHasBeenVisible(true);
306
+ }
307
+ },
308
+ { rootMargin, threshold: 0 }
309
+ );
310
+
311
+ observer.observe(container);
312
+ return () => observer.disconnect();
313
+ }, [rootMargin]);
314
+
315
+ const placeholderHeight = width ? Math.round(width / aspectRatio) : 200;
316
+
317
+ // Only render the actual Page component if visible or has been visible
318
+ // Once rendered, keep it rendered to preserve the canvas
319
+ const shouldRenderPage = hasBeenVisible;
320
+
321
+ const pageElement = shouldRenderPage ? (
322
+ <Page
323
+ pageNumber={pageNumber}
324
+ width={width}
325
+ renderTextLayer={false}
326
+ renderAnnotationLayer={false}
327
+ loading={
328
+ <div
329
+ className="flex items-center justify-center bg-muted"
330
+ style={{ height: placeholderHeight }}
331
+ >
332
+ <LoadingSpinner size="sm" />
333
+ </div>
334
+ }
335
+ />
336
+ ) : (
337
+ <div
338
+ className="flex items-center justify-center bg-muted"
339
+ style={{ height: placeholderHeight, width: width || '100%' }}
340
+ >
341
+ <span className="text-muted-foreground text-xs">{pageNumber}</span>
342
+ </div>
343
+ );
344
+
345
+ return (
346
+ <div ref={containerRef}>
347
+ {renderThumbnail({
348
+ pageNumber,
349
+ isSelected,
350
+ pageElement,
351
+ onSelect
352
+ })}
353
+ </div>
354
+ );
355
+ }
356
+
357
+ /**
358
+ * Renders a list of PDF page thumbnails using a single Document.
359
+ * Uses windowed virtualization for better performance with large PDFs.
360
+ * Only renders components for pages within a window around the current scroll position.
361
+ */
362
+ // Helper to find the scrollable ancestor element
363
+ function findScrollableAncestor(element: HTMLElement | null): HTMLElement | null {
364
+ if (!element) return null;
365
+
366
+ let current = element.parentElement;
367
+ while (current) {
368
+ const style = window.getComputedStyle(current);
369
+ const overflowY = style.overflowY;
370
+ if (overflowY === 'auto' || overflowY === 'scroll') {
371
+ return current;
372
+ }
373
+ current = current.parentElement;
374
+ }
375
+ return null;
376
+ }
377
+
378
+ export function PdfThumbnailList({
379
+ pdfUrl,
380
+ urlLoading = false,
381
+ pageCount,
382
+ currentPage,
383
+ thumbnailWidth,
384
+ onPageSelect,
385
+ renderThumbnail,
386
+ scrollContainerRef,
387
+ onAspectRatioChange,
388
+ onItemHeightChange,
389
+ calculateItemHeight,
390
+ onPageCountChange
391
+ }: PdfThumbnailListProps) {
392
+ const [error, setError] = useState<Error | null>(null);
393
+ const [visibleRange, setVisibleRange] = useState({ start: 0, end: Math.min(15, pageCount) });
394
+ // Start with null to indicate we haven't loaded the PDF yet
395
+ const [aspectRatio, setAspectRatio] = useState<number | null>(null);
396
+ const containerRef = useRef<HTMLDivElement>(null);
397
+
398
+ const handleError = useCallback((err: Error) => {
399
+ setError(err);
400
+ }, []);
401
+
402
+ // Get actual page dimensions and count from PDF on load
403
+ const handleLoadSuccess = useCallback(async (pdf: PDFDocumentProxy) => {
404
+ // Report actual page count from PDF
405
+ onPageCountChange?.(pdf.numPages);
406
+
407
+ try {
408
+ const page = await pdf.getPage(1);
409
+ const viewport = page.getViewport({ scale: 1 });
410
+ const ratio = viewport.width / viewport.height;
411
+ setAspectRatio(ratio);
412
+ onAspectRatioChange?.(ratio);
413
+ } catch (err) {
414
+ console.error('Failed to get page dimensions:', err);
415
+ // Fall back to A4 if we can't get dimensions
416
+ setAspectRatio(A4_ASPECT_RATIO);
417
+ onAspectRatioChange?.(A4_ASPECT_RATIO);
418
+ }
419
+ }, [onAspectRatioChange, onPageCountChange]);
420
+
421
+ // Use A4 as fallback if aspect ratio not yet determined
422
+ const effectiveAspectRatio = aspectRatio ?? A4_ASPECT_RATIO;
423
+
424
+ // Calculate placeholder height using actual aspect ratio from PDF
425
+ const placeholderHeight = thumbnailWidth ? Math.round(thumbnailWidth / effectiveAspectRatio) : 200;
426
+ // Total height per item - use custom calculator if provided, otherwise default formula
427
+ // Default: padding (p-2 = 8px top + 8px bottom) + page number text (~24px) + gap
428
+ const itemHeight = calculateItemHeight
429
+ ? calculateItemHeight(placeholderHeight)
430
+ : placeholderHeight + 16 + 24 + 8;
431
+
432
+ // Notify parent of item height changes for scroll calculations
433
+ useEffect(() => {
434
+ if (itemHeight > 0 && aspectRatio !== null) {
435
+ onItemHeightChange?.(itemHeight);
436
+ }
437
+ }, [itemHeight, aspectRatio, onItemHeightChange]);
438
+
439
+ // Window size: how many pages to render above and below visible area
440
+ const WINDOW_BUFFER = 5;
441
+
442
+ // Track scroll position to update visible range
443
+ useEffect(() => {
444
+ // Find the scroll container - either from prop or by searching ancestors
445
+ const container = scrollContainerRef?.current || findScrollableAncestor(containerRef.current);
446
+ if (!container) return;
447
+
448
+ const updateVisibleRange = () => {
449
+ const scrollTop = container.scrollTop;
450
+ const viewportHeight = container.clientHeight;
451
+
452
+ // Calculate which pages are visible
453
+ const firstVisible = Math.floor(scrollTop / itemHeight);
454
+ const lastVisible = Math.ceil((scrollTop + viewportHeight) / itemHeight);
455
+
456
+ // Add buffer around visible range
457
+ const start = Math.max(0, firstVisible - WINDOW_BUFFER);
458
+ const end = Math.min(pageCount, lastVisible + WINDOW_BUFFER);
459
+
460
+ setVisibleRange(prev => {
461
+ if (prev.start !== start || prev.end !== end) {
462
+ return { start, end };
463
+ }
464
+ return prev;
465
+ });
466
+ };
467
+
468
+ // Initial calculation
469
+ updateVisibleRange();
470
+
471
+ // Listen to scroll events
472
+ container.addEventListener('scroll', updateVisibleRange, { passive: true });
473
+ return () => container.removeEventListener('scroll', updateVisibleRange);
474
+ }, [itemHeight, pageCount, scrollContainerRef]);
475
+
476
+ if (error) {
477
+ return (
478
+ <div className="flex items-center justify-center text-destructive text-sm py-4">
479
+ Failed to load PDF
480
+ </div>
481
+ );
482
+ }
483
+
484
+ if (urlLoading || !pdfUrl) {
485
+ return <LoadingSpinner className="py-4" size="md" />;
486
+ }
487
+
488
+ // Calculate spacer heights for virtual scrolling
489
+ const topSpacerHeight = visibleRange.start * itemHeight;
490
+ const bottomSpacerHeight = (pageCount - visibleRange.end) * itemHeight;
491
+
492
+ // Only render the virtualized list once we have the actual aspect ratio
493
+ // This prevents the total scroll height from changing after initial render
494
+ const hasAspectRatio = aspectRatio !== null;
495
+
496
+ return (
497
+ <div ref={containerRef}>
498
+ <Document
499
+ file={pdfUrl}
500
+ onLoadSuccess={handleLoadSuccess}
501
+ onLoadError={handleError}
502
+ loading={<LoadingSpinner className="py-4" size="md" />}
503
+ >
504
+ {hasAspectRatio ? (
505
+ <>
506
+ {/* Top spacer for pages above visible window */}
507
+ {topSpacerHeight > 0 && (
508
+ <div style={{ height: topSpacerHeight }} />
509
+ )}
510
+
511
+ {/* Only render pages within the visible window */}
512
+ {Array.from({ length: visibleRange.end - visibleRange.start }, (_, index) => {
513
+ const pageNumber = visibleRange.start + index + 1;
514
+ return (
515
+ <div key={pageNumber} data-page-index={pageNumber - 1} style={{ height: itemHeight, overflow: 'hidden' }}>
516
+ <VirtualizedThumbnail
517
+ pageNumber={pageNumber}
518
+ width={thumbnailWidth}
519
+ isSelected={pageNumber === currentPage}
520
+ onSelect={() => onPageSelect(pageNumber)}
521
+ renderThumbnail={renderThumbnail}
522
+ aspectRatio={effectiveAspectRatio}
523
+ />
524
+ </div>
525
+ );
526
+ })}
527
+
528
+ {/* Bottom spacer for pages below visible window */}
529
+ {bottomSpacerHeight > 0 && (
530
+ <div style={{ height: bottomSpacerHeight }} />
531
+ )}
532
+ </>
533
+ ) : (
534
+ <LoadingSpinner className="py-4" size="md" />
535
+ )}
536
+ </Document>
537
+ </div>
538
+ );
539
+ }
540
+
541
+ interface PdfDocumentRendererProps {
542
+ pdfUrl: string;
543
+ pageNumber: number;
544
+ width?: number;
545
+ height?: number;
546
+ className?: string;
547
+ renderTextLayer?: boolean;
548
+ renderAnnotationLayer?: boolean;
549
+ onPageChange?: (pageNumber: number, totalPages: number) => void;
550
+ }
551
+
552
+ export function PdfDocumentRenderer({
553
+ pdfUrl,
554
+ pageNumber,
555
+ width,
556
+ height,
557
+ className,
558
+ renderTextLayer = false,
559
+ renderAnnotationLayer = false,
560
+ onPageChange
561
+ }: PdfDocumentRendererProps) {
562
+ const [numPages, setNumPages] = useState<number>(0);
563
+ const [loading, setLoading] = useState(true);
564
+ const [error, setError] = useState<Error | null>(null);
565
+
566
+ useEffect(() => {
567
+ if (numPages > 0) {
568
+ onPageChange?.(pageNumber, numPages);
569
+ }
570
+ }, [pageNumber, numPages, onPageChange]);
571
+
572
+ const handleLoadSuccess = ({ numPages: pages }: { numPages: number }) => {
573
+ setNumPages(pages);
574
+ setLoading(false);
575
+ };
576
+
577
+ const handleError = (err: Error) => {
578
+ setLoading(false);
579
+ setError(err);
580
+ };
581
+
582
+ if (error) {
583
+ return (
584
+ <div className={`flex items-center justify-center text-destructive ${className || ''}`}>
585
+ <span>Failed to load PDF: {error.message}</span>
586
+ </div>
587
+ );
588
+ }
589
+
590
+ return (
591
+ <div className={className}>
592
+ {loading && (
593
+ <LoadingSpinner className="py-8" size="lg" />
594
+ )}
595
+ <Document
596
+ file={pdfUrl}
597
+ onLoadSuccess={handleLoadSuccess}
598
+ onLoadError={handleError}
599
+ loading={null}
600
+ >
601
+ <Page
602
+ pageNumber={Math.min(pageNumber, numPages || 1)}
603
+ width={width}
604
+ height={height}
605
+ renderTextLayer={renderTextLayer}
606
+ renderAnnotationLayer={renderAnnotationLayer}
607
+ loading={<LoadingSpinner className="py-8" size="md" />}
608
+ />
609
+ </Document>
610
+ </div>
611
+ );
612
+ }