@elabs-ai/components-viewer 4.2.0 → 5.0.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 (204) hide show
  1. package/dist/{code-adapter-ADZ4UOGN.js → adapters/code/code-adapter.js} +15 -64
  2. package/dist/adapters/code/code-adapter.js.map +1 -0
  3. package/dist/adapters/code/code-language.js +10 -0
  4. package/dist/adapters/code/code-language.js.map +1 -0
  5. package/dist/adapters/code/code-manifest.js +10 -0
  6. package/dist/adapters/code/code-manifest.js.map +1 -0
  7. package/dist/adapters/code/code-theme.js +8 -0
  8. package/dist/adapters/code/code-theme.js.map +1 -0
  9. package/dist/{csv-adapter-7HOFZ5Z6.js → adapters/csv/csv-adapter.js} +13 -9
  10. package/dist/adapters/csv/csv-adapter.js.map +1 -0
  11. package/dist/adapters/csv/csv-manifest.js +9 -0
  12. package/dist/adapters/csv/csv-manifest.js.map +1 -0
  13. package/dist/{docx-adapter-5CQDHWTD.js → adapters/docx/docx-adapter.js} +17 -175
  14. package/dist/adapters/docx/docx-adapter.js.map +1 -0
  15. package/dist/adapters/docx/docx-manifest.js +9 -0
  16. package/dist/adapters/docx/docx-manifest.js.map +1 -0
  17. package/dist/adapters/docx/docx-model.js +21 -0
  18. package/dist/adapters/docx/docx-model.js.map +1 -0
  19. package/dist/{image-adapter-WOHZR24J.js → adapters/image/image-adapter.js} +4 -3
  20. package/dist/adapters/image/image-adapter.js.map +1 -0
  21. package/dist/adapters/image/image-manifest.js +9 -0
  22. package/dist/adapters/image/image-manifest.js.map +1 -0
  23. package/dist/adapters/index.js +56 -0
  24. package/dist/adapters/index.js.map +1 -0
  25. package/dist/{json-adapter-ZUW5GQHE.js → adapters/json/json-adapter.js} +4 -3
  26. package/dist/adapters/json/json-adapter.js.map +1 -0
  27. package/dist/adapters/json/json-manifest.js +9 -0
  28. package/dist/adapters/json/json-manifest.js.map +1 -0
  29. package/dist/{markdown-adapter-YC6WTBS4.js → adapters/markdown/markdown-adapter.js} +14 -36
  30. package/dist/adapters/markdown/markdown-adapter.js.map +1 -0
  31. package/dist/adapters/markdown/markdown-manifest.js +9 -0
  32. package/dist/adapters/markdown/markdown-manifest.js.map +1 -0
  33. package/dist/adapters/markdown/markdown-marks.js +10 -0
  34. package/dist/adapters/markdown/markdown-marks.js.map +1 -0
  35. package/dist/{media-adapter-MCTB4GBH.js → adapters/media/media-adapter.js} +4 -3
  36. package/dist/adapters/media/media-adapter.js.map +1 -0
  37. package/dist/adapters/media/media-manifest.js +9 -0
  38. package/dist/adapters/media/media-manifest.js.map +1 -0
  39. package/dist/{pdf-adapter-5PMKEXUD.js → adapters/pdf/pdf-adapter.js} +31 -81
  40. package/dist/adapters/pdf/pdf-adapter.js.map +1 -0
  41. package/dist/adapters/pdf/pdf-engine.js +14 -0
  42. package/dist/adapters/pdf/pdf-engine.js.map +1 -0
  43. package/dist/adapters/pdf/pdf-manifest.js +9 -0
  44. package/dist/adapters/pdf/pdf-manifest.js.map +1 -0
  45. package/dist/adapters/pdf/pdf-text.js +20 -0
  46. package/dist/adapters/pdf/pdf-text.js.map +1 -0
  47. package/dist/{pptx-adapter-BONWFHYT.js → adapters/pptx/pptx-adapter.js} +34 -135
  48. package/dist/adapters/pptx/pptx-adapter.js.map +1 -0
  49. package/dist/adapters/pptx/pptx-manifest.js +9 -0
  50. package/dist/adapters/pptx/pptx-manifest.js.map +1 -0
  51. package/dist/adapters/pptx/pptx-model.js +37 -0
  52. package/dist/adapters/pptx/pptx-model.js.map +1 -0
  53. package/dist/{text-adapter-NFNWB5W3.js → adapters/text/text-adapter.js} +8 -5
  54. package/dist/adapters/text/text-adapter.js.map +1 -0
  55. package/dist/adapters/text/text-manifest.js +9 -0
  56. package/dist/adapters/text/text-manifest.js.map +1 -0
  57. package/dist/{xlsx-adapter-O3XIVLFC.js → adapters/xlsx/xlsx-adapter.js} +12 -8
  58. package/dist/adapters/xlsx/xlsx-adapter.js.map +1 -0
  59. package/dist/adapters/xlsx/xlsx-manifest.js +9 -0
  60. package/dist/adapters/xlsx/xlsx-manifest.js.map +1 -0
  61. package/dist/chunk-3HKQFFZW.js +31 -0
  62. package/dist/chunk-3HKQFFZW.js.map +1 -0
  63. package/dist/chunk-5VJWILZ7.js +182 -0
  64. package/dist/chunk-5VJWILZ7.js.map +1 -0
  65. package/dist/{chunk-UL43NGUG.js → chunk-5YE2MUS2.js} +1 -1
  66. package/dist/{chunk-5OMONA2G.js → chunk-6FVJ4D7W.js} +3 -15
  67. package/dist/chunk-6FVJ4D7W.js.map +1 -0
  68. package/dist/{chunk-RGWDIQJR.js → chunk-7EASJBQM.js} +2 -2
  69. package/dist/chunk-7PGGKQVY.js +26 -0
  70. package/dist/chunk-7PGGKQVY.js.map +1 -0
  71. package/dist/{chunk-XBOR2YXS.js → chunk-AK7F3IZE.js} +9 -30
  72. package/dist/chunk-AK7F3IZE.js.map +1 -0
  73. package/dist/chunk-AOYEL7O3.js +34 -0
  74. package/dist/chunk-AOYEL7O3.js.map +1 -0
  75. package/dist/chunk-BDUFH66C.js +61 -0
  76. package/dist/chunk-BDUFH66C.js.map +1 -0
  77. package/dist/chunk-BWMXMHVZ.js +107 -0
  78. package/dist/chunk-BWMXMHVZ.js.map +1 -0
  79. package/dist/chunk-CIT2HHRI.js +9 -0
  80. package/dist/chunk-CIT2HHRI.js.map +1 -0
  81. package/dist/chunk-CLOI4HBA.js +18 -0
  82. package/dist/chunk-CLOI4HBA.js.map +1 -0
  83. package/dist/chunk-CMYBLW7D.js +21 -0
  84. package/dist/chunk-CMYBLW7D.js.map +1 -0
  85. package/dist/chunk-CO2VJRSU.js +18 -0
  86. package/dist/chunk-CO2VJRSU.js.map +1 -0
  87. package/dist/{chunk-3U3MESBU.js → chunk-EA72HA4I.js} +2 -2
  88. package/dist/{chunk-KOMDRWOU.js → chunk-FDT4QOIG.js} +2 -2
  89. package/dist/chunk-FQHZEQUH.js +90 -0
  90. package/dist/chunk-FQHZEQUH.js.map +1 -0
  91. package/dist/chunk-G27UJZWH.js +82 -0
  92. package/dist/chunk-G27UJZWH.js.map +1 -0
  93. package/dist/{chunk-BCF3RWRL.js → chunk-GH4UBYST.js} +2 -2
  94. package/dist/{chunk-2NQ4RSJ3.js → chunk-GNBD53AT.js} +2 -2
  95. package/dist/{chunk-VO2273Z2.js → chunk-HOQZ7OPH.js} +2 -18
  96. package/dist/chunk-HOQZ7OPH.js.map +1 -0
  97. package/dist/{chunk-GGR7BNW4.js → chunk-HUUJIFFT.js} +2 -2
  98. package/dist/{chunk-FELIAGKI.js → chunk-I5XD2NJ6.js} +2 -2
  99. package/dist/chunk-IB4N72FA.js +21 -0
  100. package/dist/chunk-IB4N72FA.js.map +1 -0
  101. package/dist/{chunk-4OFQYALG.js → chunk-IQ6Z4X3K.js} +8 -68
  102. package/dist/chunk-IQ6Z4X3K.js.map +1 -0
  103. package/dist/{chunk-AORNMH77.js → chunk-JHFC2KBI.js} +1 -5
  104. package/dist/chunk-JHFC2KBI.js.map +1 -0
  105. package/dist/chunk-LRPZ373O.js +93 -0
  106. package/dist/chunk-LRPZ373O.js.map +1 -0
  107. package/dist/chunk-M77ICVWP.js +24 -0
  108. package/dist/chunk-M77ICVWP.js.map +1 -0
  109. package/dist/chunk-MJUH32ZK.js +24 -0
  110. package/dist/chunk-MJUH32ZK.js.map +1 -0
  111. package/dist/chunk-PAIVK4N6.js +162 -0
  112. package/dist/chunk-PAIVK4N6.js.map +1 -0
  113. package/dist/chunk-PKYDKXCO.js +129 -0
  114. package/dist/chunk-PKYDKXCO.js.map +1 -0
  115. package/dist/chunk-QIPKBOZJ.js +59 -0
  116. package/dist/chunk-QIPKBOZJ.js.map +1 -0
  117. package/dist/chunk-SASY77ZL.js +29 -0
  118. package/dist/chunk-SASY77ZL.js.map +1 -0
  119. package/dist/{chunk-SLXRUYT3.js → chunk-SBG7WPGO.js} +2 -44
  120. package/dist/chunk-SBG7WPGO.js.map +1 -0
  121. package/dist/chunk-T4Y4YRHJ.js +733 -0
  122. package/dist/chunk-T4Y4YRHJ.js.map +1 -0
  123. package/dist/chunk-TLCNGQKL.js +37 -0
  124. package/dist/chunk-TLCNGQKL.js.map +1 -0
  125. package/dist/chunk-US5KT3TR.js +141 -0
  126. package/dist/chunk-US5KT3TR.js.map +1 -0
  127. package/dist/{chunk-HMG2ERXH.js → chunk-YA72QE57.js} +2 -2
  128. package/dist/{chunk-H3OAON3D.js → chunk-YWHFMCS4.js} +2 -2
  129. package/dist/{chunk-UJIJECEF.js → chunk-ZAVXZRIO.js} +2 -2
  130. package/dist/components/grid-text.js +19 -0
  131. package/dist/components/grid-text.js.map +1 -0
  132. package/dist/components/marked-text.js +10 -0
  133. package/dist/components/marked-text.js.map +1 -0
  134. package/dist/components/sheet-table.js +14 -0
  135. package/dist/components/sheet-table.js.map +1 -0
  136. package/dist/core/errors.js +18 -0
  137. package/dist/core/errors.js.map +1 -0
  138. package/dist/core/highlight-marks.js +10 -0
  139. package/dist/core/highlight-marks.js.map +1 -0
  140. package/dist/core/highlight-resolve.js +10 -0
  141. package/dist/core/highlight-resolve.js.map +1 -0
  142. package/dist/core/highlight.js +12 -0
  143. package/dist/core/highlight.js.map +1 -0
  144. package/dist/core/registry.js +12 -0
  145. package/dist/core/registry.js.map +1 -0
  146. package/dist/core/scroll-host.js +10 -0
  147. package/dist/core/scroll-host.js.map +1 -0
  148. package/dist/core/text-index.js +14 -0
  149. package/dist/core/text-index.js.map +1 -0
  150. package/dist/core/types.js +8 -0
  151. package/dist/core/types.js.map +1 -0
  152. package/dist/core/use-highlight-scroll.js +11 -0
  153. package/dist/core/use-highlight-scroll.js.map +1 -0
  154. package/dist/core/use-page-control.js +9 -0
  155. package/dist/core/use-page-control.js.map +1 -0
  156. package/dist/core/use-paged-scroll.js +10 -0
  157. package/dist/core/use-paged-scroll.js.map +1 -0
  158. package/dist/core/use-viewport-size.js +10 -0
  159. package/dist/core/use-viewport-size.js.map +1 -0
  160. package/dist/core/zoom.js +16 -0
  161. package/dist/core/zoom.js.map +1 -0
  162. package/dist/file-viewer/file-viewer-context.js +11 -0
  163. package/dist/file-viewer/file-viewer-context.js.map +1 -0
  164. package/dist/file-viewer/file-viewer-find.js +13 -0
  165. package/dist/file-viewer/file-viewer-find.js.map +1 -0
  166. package/dist/file-viewer/file-viewer-pager.js +10 -0
  167. package/dist/file-viewer/file-viewer-pager.js.map +1 -0
  168. package/dist/file-viewer/file-viewer-zoom.js +13 -0
  169. package/dist/file-viewer/file-viewer-zoom.js.map +1 -0
  170. package/dist/file-viewer/file-viewer.js +48 -0
  171. package/dist/file-viewer/file-viewer.js.map +1 -0
  172. package/dist/index.js +78 -1274
  173. package/dist/index.js.map +1 -1
  174. package/package.json +5 -5
  175. package/src/file-viewer/file-viewer-recipes.stories.tsx +406 -0
  176. package/src/file-viewer/file-viewer.stories.tsx +2 -2
  177. package/dist/chunk-4OFQYALG.js.map +0 -1
  178. package/dist/chunk-5OMONA2G.js.map +0 -1
  179. package/dist/chunk-AORNMH77.js.map +0 -1
  180. package/dist/chunk-SLXRUYT3.js.map +0 -1
  181. package/dist/chunk-VO2273Z2.js.map +0 -1
  182. package/dist/chunk-XBOR2YXS.js.map +0 -1
  183. package/dist/code-adapter-ADZ4UOGN.js.map +0 -1
  184. package/dist/csv-adapter-7HOFZ5Z6.js.map +0 -1
  185. package/dist/docx-adapter-5CQDHWTD.js.map +0 -1
  186. package/dist/image-adapter-WOHZR24J.js.map +0 -1
  187. package/dist/json-adapter-ZUW5GQHE.js.map +0 -1
  188. package/dist/markdown-adapter-YC6WTBS4.js.map +0 -1
  189. package/dist/media-adapter-MCTB4GBH.js.map +0 -1
  190. package/dist/pdf-adapter-5PMKEXUD.js.map +0 -1
  191. package/dist/pptx-adapter-BONWFHYT.js.map +0 -1
  192. package/dist/text-adapter-NFNWB5W3.js.map +0 -1
  193. package/dist/xlsx-adapter-O3XIVLFC.js.map +0 -1
  194. /package/dist/{chunk-UL43NGUG.js.map → chunk-5YE2MUS2.js.map} +0 -0
  195. /package/dist/{chunk-RGWDIQJR.js.map → chunk-7EASJBQM.js.map} +0 -0
  196. /package/dist/{chunk-3U3MESBU.js.map → chunk-EA72HA4I.js.map} +0 -0
  197. /package/dist/{chunk-KOMDRWOU.js.map → chunk-FDT4QOIG.js.map} +0 -0
  198. /package/dist/{chunk-BCF3RWRL.js.map → chunk-GH4UBYST.js.map} +0 -0
  199. /package/dist/{chunk-2NQ4RSJ3.js.map → chunk-GNBD53AT.js.map} +0 -0
  200. /package/dist/{chunk-GGR7BNW4.js.map → chunk-HUUJIFFT.js.map} +0 -0
  201. /package/dist/{chunk-FELIAGKI.js.map → chunk-I5XD2NJ6.js.map} +0 -0
  202. /package/dist/{chunk-HMG2ERXH.js.map → chunk-YA72QE57.js.map} +0 -0
  203. /package/dist/{chunk-H3OAON3D.js.map → chunk-YWHFMCS4.js.map} +0 -0
  204. /package/dist/{chunk-UJIJECEF.js.map → chunk-ZAVXZRIO.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,1311 +1,115 @@
1
1
  "use client";
2
2
  import {
3
- pptxManifest
4
- } from "./chunk-KOMDRWOU.js";
3
+ FileViewer,
4
+ FileViewerContent,
5
+ FileViewerEmpty,
6
+ FileViewerError,
7
+ FileViewerFrame,
8
+ FileViewerHighlightStatus,
9
+ FileViewerProvider,
10
+ FileViewerSkeleton,
11
+ FileViewerToolbar
12
+ } from "./chunk-T4Y4YRHJ.js";
5
13
  import {
6
- markdownManifest
7
- } from "./chunk-H3OAON3D.js";
14
+ FileViewerFind,
15
+ isFindShortcut
16
+ } from "./chunk-PAIVK4N6.js";
8
17
  import {
9
- codeManifest
10
- } from "./chunk-5OMONA2G.js";
18
+ FileViewerPager
19
+ } from "./chunk-LRPZ373O.js";
11
20
  import {
12
- textManifest
13
- } from "./chunk-UJIJECEF.js";
21
+ FileViewerRotate,
22
+ FileViewerZoom
23
+ } from "./chunk-PKYDKXCO.js";
14
24
  import {
15
- imageManifest
16
- } from "./chunk-GGR7BNW4.js";
25
+ useFileViewer
26
+ } from "./chunk-CLOI4HBA.js";
17
27
  import {
18
- jsonManifest
19
- } from "./chunk-BCF3RWRL.js";
28
+ locateQuote,
29
+ resolveHighlights
30
+ } from "./chunk-FQHZEQUH.js";
20
31
  import {
21
- csvManifest
22
- } from "./chunk-3U3MESBU.js";
32
+ FIND_MATCH_LIMIT,
33
+ findMatchId,
34
+ isFindMatchId
35
+ } from "./chunk-CO2VJRSU.js";
23
36
  import {
24
37
  DEFAULT_ZOOM,
25
38
  VIEWER_ZOOM_STEPS,
26
39
  canStepZoom,
27
- configurePdfEngine,
28
- getPdfEngineConfig,
29
40
  isZoomFit,
30
- pdfManifest,
31
41
  stepZoom
32
- } from "./chunk-SLXRUYT3.js";
42
+ } from "./chunk-SASY77ZL.js";
33
43
  import {
34
- mediaManifest
35
- } from "./chunk-HMG2ERXH.js";
44
+ configurePdfEngine,
45
+ getPdfEngineConfig
46
+ } from "./chunk-SBG7WPGO.js";
36
47
  import {
37
- docxManifest
38
- } from "./chunk-FELIAGKI.js";
48
+ createDefaultRegistry
49
+ } from "./chunk-QIPKBOZJ.js";
50
+ import {
51
+ createRegistry,
52
+ scoreManifest
53
+ } from "./chunk-BWMXMHVZ.js";
54
+ import {
55
+ pptxManifest
56
+ } from "./chunk-FDT4QOIG.js";
57
+ import {
58
+ textManifest
59
+ } from "./chunk-ZAVXZRIO.js";
39
60
  import {
40
61
  xlsxManifest
41
- } from "./chunk-RGWDIQJR.js";
62
+ } from "./chunk-7EASJBQM.js";
63
+ import {
64
+ mediaManifest
65
+ } from "./chunk-YA72QE57.js";
66
+ import {
67
+ pdfManifest
68
+ } from "./chunk-M77ICVWP.js";
69
+ import {
70
+ imageManifest
71
+ } from "./chunk-HUUJIFFT.js";
72
+ import {
73
+ jsonManifest
74
+ } from "./chunk-GH4UBYST.js";
75
+ import {
76
+ markdownManifest
77
+ } from "./chunk-YWHFMCS4.js";
78
+ import {
79
+ codeManifest
80
+ } from "./chunk-CMYBLW7D.js";
81
+ import "./chunk-6FVJ4D7W.js";
82
+ import {
83
+ csvManifest
84
+ } from "./chunk-EA72HA4I.js";
85
+ import {
86
+ docxManifest
87
+ } from "./chunk-I5XD2NJ6.js";
42
88
  import {
43
89
  chunkOffset,
44
90
  createTextIndexBuilder,
45
91
  spanAt,
46
92
  spansForRange
47
- } from "./chunk-UL43NGUG.js";
93
+ } from "./chunk-5YE2MUS2.js";
48
94
  import {
49
95
  ACTIVE_HIGHLIGHT_SELECTOR,
50
- localizeRanges,
51
- toMarkRanges,
52
96
  useScrollActiveHighlightIntoView
53
- } from "./chunk-VO2273Z2.js";
97
+ } from "./chunk-IB4N72FA.js";
98
+ import {
99
+ localizeRanges,
100
+ toMarkRanges
101
+ } from "./chunk-HOQZ7OPH.js";
54
102
  import {
55
- PROTOCOL_VERSION,
56
103
  ViewerError,
57
104
  isAbort,
58
105
  isModuleNotFound,
59
106
  isViewerError,
60
107
  parserMissingError,
61
108
  toViewerError
62
- } from "./chunk-AORNMH77.js";
63
-
64
- // src/file-viewer/file-viewer.tsx
109
+ } from "./chunk-JHFC2KBI.js";
65
110
  import {
66
- Button,
67
- cn as cn4,
68
- downloadBlob,
69
- fileIconFor,
70
- IconButton as IconButton4,
71
- normalizeFileSource,
72
- normalizeQuoteTextWithOffsets,
73
- queryToRanges,
74
- resolveFileKind as resolveFileKind2,
75
- Separator as Separator2,
76
- Skeleton,
77
- StatePanel,
78
- Text,
79
- useLocale as useLocale4
80
- } from "@elabs-ai/components-ui";
81
- import { DownloadIcon, EyeOffIcon, SearchXIcon } from "lucide-react";
82
- import {
83
- forwardRef as forwardRef4,
84
- useCallback,
85
- useDeferredValue,
86
- useEffect as useEffect2,
87
- useMemo,
88
- useRef as useRef2,
89
- useState as useState2
90
- } from "react";
91
-
92
- // src/core/registry.ts
93
- import { resolveFileKind } from "@elabs-ai/components-ui";
94
- var MatchScore = {
95
- None: 0,
96
- Category: 1,
97
- MediaTypePrefix: 2,
98
- MediaType: 3,
99
- Extension: 4
100
- };
101
- function scoreManifest(manifest, kind) {
102
- if (kind.extension && manifest.extensions?.includes(kind.extension)) {
103
- return MatchScore.Extension;
104
- }
105
- for (const declared of manifest.mediaTypes ?? []) {
106
- if (declared.endsWith("/")) {
107
- if (kind.mediaType.startsWith(declared)) return MatchScore.MediaTypePrefix;
108
- } else if (declared === kind.mediaType) {
109
- return MatchScore.MediaType;
110
- }
111
- }
112
- if (manifest.categories?.includes(kind.category)) return MatchScore.Category;
113
- return MatchScore.None;
114
- }
115
- function unwrap(loaded) {
116
- return "default" in loaded && loaded.default ? loaded.default : loaded;
117
- }
118
- function assertProtocol(manifest, where) {
119
- if (manifest.protocol !== PROTOCOL_VERSION) {
120
- throw new ViewerError(
121
- "protocol-mismatch",
122
- `Adapter "${manifest.id}" targets viewer protocol ${String(manifest.protocol)}, but this build speaks ${String(PROTOCOL_VERSION)} (${where}).`
123
- );
124
- }
125
- }
126
- function createRegistry() {
127
- const entries = /* @__PURE__ */ new Map();
128
- const modules = /* @__PURE__ */ new Map();
129
- function ordered() {
130
- return [...entries.values()].sort(
131
- (a, b) => (b.manifest.priority ?? 0) - (a.manifest.priority ?? 0)
132
- );
133
- }
134
- function detect(kind) {
135
- let best;
136
- for (const { manifest } of entries.values()) {
137
- const score = scoreManifest(manifest, kind);
138
- if (score === MatchScore.None) continue;
139
- const priority = manifest.priority ?? 0;
140
- const wins = !best || priority > best.priority || priority === best.priority && score > best.score;
141
- if (wins) best = { manifest, score, priority };
142
- }
143
- return best?.manifest;
144
- }
145
- return {
146
- register(manifest, loader) {
147
- assertProtocol(manifest, "registration");
148
- entries.set(manifest.id, { manifest, loader });
149
- modules.delete(manifest.id);
150
- },
151
- manifests() {
152
- return ordered().map((entry) => entry.manifest);
153
- },
154
- detect,
155
- detectByName(name, mediaType) {
156
- return detect(resolveFileKind(name, mediaType));
157
- },
158
- async load(id) {
159
- const entry = entries.get(id);
160
- if (!entry) {
161
- throw new ViewerError("unsupported-format", `No adapter is registered for "${id}".`);
162
- }
163
- let pending = modules.get(id);
164
- if (!pending) {
165
- pending = entry.loader().then((loaded) => {
166
- const adapterModule = unwrap(loaded);
167
- assertProtocol(adapterModule.manifest, `module "${id}"`);
168
- return adapterModule;
169
- }).catch((error) => {
170
- modules.delete(id);
171
- if (isModuleNotFound(error)) {
172
- throw parserMissingError(id, entry.manifest.requires ?? [], { cause: error });
173
- }
174
- throw toViewerError(error, "parse-failed");
175
- });
176
- modules.set(id, pending);
177
- }
178
- return pending;
179
- }
180
- };
181
- }
182
-
183
- // src/adapters/index.ts
184
- function createDefaultRegistry() {
185
- const registry = createRegistry();
186
- registry.register(imageManifest, () => import("./image-adapter-WOHZR24J.js"));
187
- registry.register(jsonManifest, () => import("./json-adapter-ZUW5GQHE.js"));
188
- registry.register(csvManifest, () => import("./csv-adapter-7HOFZ5Z6.js"));
189
- registry.register(pdfManifest, () => import("./pdf-adapter-5PMKEXUD.js"));
190
- registry.register(mediaManifest, () => import("./media-adapter-MCTB4GBH.js"));
191
- registry.register(docxManifest, () => import("./docx-adapter-5CQDHWTD.js"));
192
- registry.register(xlsxManifest, () => import("./xlsx-adapter-O3XIVLFC.js"));
193
- registry.register(pptxManifest, () => import("./pptx-adapter-BONWFHYT.js"));
194
- registry.register(markdownManifest, () => import("./markdown-adapter-YC6WTBS4.js"));
195
- registry.register(codeManifest, () => import("./code-adapter-ADZ4UOGN.js"));
196
- registry.register(textManifest, () => import("./text-adapter-NFNWB5W3.js"));
197
- return registry;
198
- }
199
-
200
- // src/core/highlight.ts
201
- var FIND_MATCH_LIMIT = 2e3;
202
- var FIND_ID_PREFIX = "find:";
203
- function findMatchId(index) {
204
- return `${FIND_ID_PREFIX}${String(index)}`;
205
- }
206
- function isFindMatchId(id) {
207
- return id.startsWith(FIND_ID_PREFIX);
208
- }
209
-
210
- // src/core/highlight-resolve.ts
211
- import {
212
- normalizeQuoteText
213
- } from "@elabs-ai/components-ui";
214
- function locateQuote(normalized, address) {
215
- const needle = normalizeQuoteText(address.text);
216
- if (needle.length === 0) return void 0;
217
- const starts = [];
218
- for (let at = normalized.text.indexOf(needle); at !== -1; ) {
219
- starts.push(at);
220
- at = normalized.text.indexOf(needle, at + 1);
221
- }
222
- if (starts.length === 0) return void 0;
223
- const toRange = (start) => [
224
- normalized.offsets[start],
225
- normalized.offsets[start + needle.length]
226
- ];
227
- if (address.occurrence !== void 0) {
228
- const chosen = starts[address.occurrence - 1];
229
- return chosen === void 0 ? void 0 : toRange(chosen);
230
- }
231
- const near = address.near?.offset;
232
- if (near !== void 0) {
233
- let best = starts[0];
234
- let bestDistance = Number.POSITIVE_INFINITY;
235
- for (const start of starts) {
236
- const distance = Math.abs(normalized.offsets[start] - near);
237
- if (distance < bestDistance) {
238
- bestDistance = distance;
239
- best = start;
240
- }
241
- }
242
- return toRange(best);
243
- }
244
- return toRange(starts[0]);
245
- }
246
- function positionOf(highlight) {
247
- if (highlight.range) return highlight.range[0];
248
- if (highlight.page !== void 0) return highlight.page;
249
- return Number.POSITIVE_INFINITY;
250
- }
251
- function resolveOne(highlight, context) {
252
- const source = highlight.source ?? "citation";
253
- const address = highlight.address;
254
- const base = { id: highlight.id, label: highlight.label, source, address, active: false };
255
- if (!context.supported.includes(address.kind)) {
256
- return { ...base, status: "unsupported" };
257
- }
258
- if (address.kind === "rect") {
259
- return { ...base, status: "resolved", page: address.page, rects: address.rects };
260
- }
261
- const miss = () => ({
262
- ...base,
263
- status: "not-found",
264
- reason: context.truncated ? "truncated" : "absent"
265
- });
266
- if (address.kind === "range") {
267
- const length = context.textLength;
268
- if (length === void 0) return miss();
269
- const start = Math.max(0, Math.min(address.start, length));
270
- const end = Math.max(start, Math.min(address.end, length));
271
- if (end === start) return miss();
272
- return { ...base, status: "resolved", range: [start, end] };
273
- }
274
- if (!context.normalized) return miss();
275
- const range = locateQuote(context.normalized, address);
276
- if (!range) return miss();
277
- return { ...base, status: "resolved", range };
278
- }
279
- function resolveHighlights(highlights, context) {
280
- const resolved = highlights.map((highlight) => resolveOne(highlight, context)).sort((a, b) => positionOf(a) - positionOf(b));
281
- const counters = /* @__PURE__ */ new Map();
282
- return resolved.map((highlight) => {
283
- const next = highlight.status === "resolved" ? (counters.get(highlight.source) ?? 0) + 1 : void 0;
284
- if (next !== void 0) counters.set(highlight.source, next);
285
- return {
286
- ...highlight,
287
- index: next,
288
- active: context.activeId != null && context.activeId === highlight.id
289
- };
290
- });
291
- }
292
-
293
- // src/file-viewer/file-viewer-find.tsx
294
- import {
295
- cn,
296
- IconButton,
297
- InputGroup,
298
- InputGroupAddon,
299
- InputGroupButton,
300
- InputGroupInput,
301
- Separator,
302
- useLocale
303
- } from "@elabs-ai/components-ui";
304
- import { CaseSensitiveIcon, ChevronDownIcon, ChevronUpIcon, SearchIcon, XIcon } from "lucide-react";
305
- import {
306
- forwardRef,
307
- useEffect,
308
- useRef
309
- } from "react";
310
-
311
- // src/file-viewer/file-viewer-context.tsx
312
- import { createContext, use } from "react";
313
- var FileViewerContext = createContext(null);
314
- function useFileViewer() {
315
- const value = use(FileViewerContext);
316
- if (!value) {
317
- throw new Error("useFileViewer must be used inside a <FileViewerProvider> (or <FileViewer>).");
318
- }
319
- return value;
320
- }
321
-
322
- // src/file-viewer/file-viewer-find.tsx
323
- import { jsx, jsxs } from "react/jsx-runtime";
324
- function isFindShortcut(event) {
325
- return event.key.toLowerCase() === "f" && (event.metaKey || event.ctrlKey);
326
- }
327
- var FileViewerFind = forwardRef(
328
- function FileViewerFind2({ className, ...props }, ref) {
329
- const { state, actions, meta } = useFileViewer();
330
- const { t, formatNumber } = useLocale();
331
- const input = useRef(null);
332
- const find = state.find;
333
- const open = find.open && meta.canFind;
334
- const { registerFind } = actions;
335
- useEffect(() => registerFind(), [registerFind]);
336
- useEffect(() => {
337
- if (open) input.current?.focus();
338
- }, [open]);
339
- if (!open) return null;
340
- const hasQuery = find.query.length > 0;
341
- const hasMatches = find.matches > 0;
342
- const onKeyDown = (event) => {
343
- if (event.key === "Escape") {
344
- event.preventDefault();
345
- event.stopPropagation();
346
- actions.closeFind();
347
- return;
348
- }
349
- if (event.key === "Enter") {
350
- event.preventDefault();
351
- if (event.shiftKey) actions.previousFindMatch();
352
- else actions.nextFindMatch();
353
- }
354
- };
355
- return /* @__PURE__ */ jsxs(
356
- "div",
357
- {
358
- ref,
359
- "data-slot": "file-viewer-find",
360
- role: "search",
361
- "aria-label": t("viewer.find.label"),
362
- className: cn(
363
- // The divider is the only cue between this row and the content below
364
- // it — same fill, no elevation change (WCAG 1.4.11, strong rung).
365
- "border-border-strong flex shrink-0 items-center gap-2 border-b px-3 py-2",
366
- className
367
- ),
368
- ...props,
369
- children: [
370
- /* @__PURE__ */ jsxs(InputGroup, { className: "h-8 max-w-72", children: [
371
- /* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(SearchIcon, { "aria-hidden": "true" }) }),
372
- /* @__PURE__ */ jsx(
373
- InputGroupInput,
374
- {
375
- ref: input,
376
- type: "text",
377
- value: find.query,
378
- "aria-label": t("viewer.find.label"),
379
- placeholder: t("viewer.find.placeholder"),
380
- autoComplete: "off",
381
- spellCheck: false,
382
- onChange: (event) => actions.setFindQuery(event.target.value),
383
- onKeyDown
384
- }
385
- ),
386
- /* @__PURE__ */ jsx(InputGroupAddon, { align: "inline-end", children: /* @__PURE__ */ jsx(
387
- InputGroupButton,
388
- {
389
- size: "icon-xs",
390
- "aria-pressed": find.caseSensitive,
391
- "aria-label": t("viewer.find.caseSensitive"),
392
- title: t("viewer.find.caseSensitive"),
393
- onClick: () => actions.setFindCaseSensitive(!find.caseSensitive),
394
- children: /* @__PURE__ */ jsx(CaseSensitiveIcon, { "aria-hidden": "true" })
395
- }
396
- ) })
397
- ] }),
398
- /* @__PURE__ */ jsxs(
399
- "span",
400
- {
401
- role: "status",
402
- "aria-live": "polite",
403
- className: "text-meta text-muted-foreground min-w-0 flex-1 truncate tabular-nums",
404
- children: [
405
- !hasQuery ? null : hasMatches ? t("viewer.find.count", {
406
- index: formatNumber(find.activeIndex + 1),
407
- total: formatNumber(Math.min(find.matches, FIND_MATCH_LIMIT))
408
- }) : t("viewer.find.none"),
409
- find.truncated ? ` ${t("viewer.find.capped", {
410
- limit: formatNumber(FIND_MATCH_LIMIT),
411
- total: formatNumber(find.matches)
412
- })}` : null
413
- ]
414
- }
415
- ),
416
- /* @__PURE__ */ jsx(
417
- IconButton,
418
- {
419
- variant: "ghost",
420
- size: "icon-sm",
421
- "aria-disabled": !hasMatches,
422
- label: t("viewer.find.previous"),
423
- icon: /* @__PURE__ */ jsx(ChevronUpIcon, { "aria-hidden": "true" }),
424
- onClick: actions.previousFindMatch
425
- }
426
- ),
427
- /* @__PURE__ */ jsx(
428
- IconButton,
429
- {
430
- variant: "ghost",
431
- size: "icon-sm",
432
- "aria-disabled": !hasMatches,
433
- label: t("viewer.find.next"),
434
- icon: /* @__PURE__ */ jsx(ChevronDownIcon, { "aria-hidden": "true" }),
435
- onClick: actions.nextFindMatch
436
- }
437
- ),
438
- /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-4" }),
439
- /* @__PURE__ */ jsx(
440
- IconButton,
441
- {
442
- variant: "ghost",
443
- size: "icon-sm",
444
- label: t("viewer.find.close"),
445
- icon: /* @__PURE__ */ jsx(XIcon, { "aria-hidden": "true" }),
446
- onClick: actions.closeFind
447
- }
448
- )
449
- ]
450
- }
451
- );
452
- }
453
- );
454
-
455
- // src/file-viewer/file-viewer-pager.tsx
456
- import { cn as cn2, IconButton as IconButton2, Input, useLocale as useLocale2 } from "@elabs-ai/components-ui";
457
- import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
458
- import { forwardRef as forwardRef2, useState } from "react";
459
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
460
- var FileViewerPager = forwardRef2(
461
- function FileViewerPager2({ className, ...props }, ref) {
462
- const { state, actions } = useFileViewer();
463
- const { t, formatNumber } = useLocale2();
464
- const [draft, setDraft] = useState(null);
465
- const { pageNumber, pageCount } = state;
466
- if (!state.capabilities.pages || pageCount === 0) return null;
467
- const commit = () => {
468
- if (draft === null) return;
469
- const parsed = Number.parseInt(draft, 10);
470
- setDraft(null);
471
- if (Number.isNaN(parsed)) return;
472
- actions.goToPage(parsed);
473
- };
474
- return /* @__PURE__ */ jsxs2(
475
- "div",
476
- {
477
- ref,
478
- "data-slot": "file-viewer-pager",
479
- role: "group",
480
- "aria-label": t("viewer.pager.controls"),
481
- className: cn2("flex shrink-0 items-center gap-1", className),
482
- ...props,
483
- children: [
484
- /* @__PURE__ */ jsx2(
485
- IconButton2,
486
- {
487
- variant: "ghost",
488
- size: "icon-sm",
489
- label: t("viewer.pager.previous"),
490
- icon: /* @__PURE__ */ jsx2(ChevronLeftIcon, { "aria-hidden": "true" }),
491
- disabled: pageNumber <= 1,
492
- onClick: actions.previousPage
493
- }
494
- ),
495
- /* @__PURE__ */ jsx2(
496
- Input,
497
- {
498
- type: "text",
499
- inputMode: "numeric",
500
- autoComplete: "off",
501
- spellCheck: false,
502
- "aria-label": t("viewer.pager.pageNumber"),
503
- value: draft ?? String(pageNumber),
504
- onChange: (event) => setDraft(event.target.value),
505
- onBlur: commit,
506
- onKeyDown: (event) => {
507
- if (event.key === "Enter") {
508
- event.preventDefault();
509
- commit();
510
- } else if (event.key === "Escape") {
511
- setDraft(null);
512
- }
513
- },
514
- className: "h-7 w-12 px-1 text-center tabular-nums"
515
- }
516
- ),
517
- /* @__PURE__ */ jsx2("span", { className: "text-meta text-muted-foreground whitespace-nowrap tabular-nums", children: t("viewer.pager.of", { total: formatNumber(pageCount) }) }),
518
- /* @__PURE__ */ jsx2(
519
- IconButton2,
520
- {
521
- variant: "ghost",
522
- size: "icon-sm",
523
- label: t("viewer.pager.next"),
524
- icon: /* @__PURE__ */ jsx2(ChevronRightIcon, { "aria-hidden": "true" }),
525
- disabled: pageNumber >= pageCount,
526
- onClick: actions.nextPage
527
- }
528
- ),
529
- /* @__PURE__ */ jsx2("span", { role: "status", "aria-live": "polite", className: "sr-only", children: t("viewer.pager.status", {
530
- page: formatNumber(pageNumber),
531
- total: formatNumber(pageCount)
532
- }) })
533
- ]
534
- }
535
- );
536
- }
537
- );
538
-
539
- // src/file-viewer/file-viewer-zoom.tsx
540
- import {
541
- cn as cn3,
542
- IconButton as IconButton3,
543
- Select,
544
- SelectContent,
545
- SelectItem,
546
- SelectSeparator,
547
- SelectTrigger,
548
- SelectValue,
549
- useLocale as useLocale3
550
- } from "@elabs-ai/components-ui";
551
- import { RotateCwIcon, ZoomInIcon, ZoomOutIcon } from "lucide-react";
552
- import { forwardRef as forwardRef3 } from "react";
553
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
554
- var FileViewerZoom = forwardRef3(
555
- function FileViewerZoom2({ className, ...props }, ref) {
556
- const { state, actions } = useFileViewer();
557
- const { t, formatNumber } = useLocale3();
558
- if (!state.capabilities.zoom) return null;
559
- const { zoom, effectiveZoom } = state;
560
- const percent = (scale) => formatNumber(scale, { style: "percent", maximumFractionDigits: 0 });
561
- return /* @__PURE__ */ jsxs3(
562
- "div",
563
- {
564
- ref,
565
- "data-slot": "file-viewer-zoom",
566
- role: "group",
567
- "aria-label": t("viewer.zoom.controls"),
568
- className: cn3("flex shrink-0 items-center gap-1", className),
569
- ...props,
570
- children: [
571
- /* @__PURE__ */ jsx3(
572
- IconButton3,
573
- {
574
- variant: "ghost",
575
- size: "icon-sm",
576
- label: t("viewer.zoom.out"),
577
- icon: /* @__PURE__ */ jsx3(ZoomOutIcon, { "aria-hidden": "true" }),
578
- disabled: !canStepZoom(effectiveZoom, -1),
579
- onClick: actions.zoomOut
580
- }
581
- ),
582
- /* @__PURE__ */ jsxs3(
583
- Select,
584
- {
585
- value: zoomToValue(zoom),
586
- onValueChange: (value) => actions.setZoom(valueToZoom(value)),
587
- children: [
588
- /* @__PURE__ */ jsx3(
589
- SelectTrigger,
590
- {
591
- size: "sm",
592
- "aria-label": t("viewer.zoom.level"),
593
- className: "h-7 w-24 gap-1 px-2",
594
- children: /* @__PURE__ */ jsx3(SelectValue, { children: isZoomFit(zoom) ? t(zoom === "fit-width" ? "viewer.zoom.fitWidth" : "viewer.zoom.fitPage") : percent(zoom) })
595
- }
596
- ),
597
- /* @__PURE__ */ jsxs3(SelectContent, { children: [
598
- /* @__PURE__ */ jsx3(SelectItem, { value: "fit-width", children: t("viewer.zoom.fitWidth") }),
599
- /* @__PURE__ */ jsx3(SelectItem, { value: "fit-page", children: t("viewer.zoom.fitPage") }),
600
- /* @__PURE__ */ jsx3(SelectSeparator, {}),
601
- VIEWER_ZOOM_STEPS.map((step) => /* @__PURE__ */ jsx3(SelectItem, { value: String(step), children: percent(step) }, step))
602
- ] })
603
- ]
604
- }
605
- ),
606
- /* @__PURE__ */ jsx3(
607
- IconButton3,
608
- {
609
- variant: "ghost",
610
- size: "icon-sm",
611
- label: t("viewer.zoom.in"),
612
- icon: /* @__PURE__ */ jsx3(ZoomInIcon, { "aria-hidden": "true" }),
613
- disabled: !canStepZoom(effectiveZoom, 1),
614
- onClick: actions.zoomIn
615
- }
616
- ),
617
- /* @__PURE__ */ jsx3("span", { role: "status", "aria-live": "polite", className: "sr-only", children: t("viewer.zoom.status", { level: percent(effectiveZoom) }) })
618
- ]
619
- }
620
- );
621
- }
622
- );
623
- function zoomToValue(zoom) {
624
- return isZoomFit(zoom) ? zoom : String(zoom);
625
- }
626
- function valueToZoom(value) {
627
- if (value === "fit-width" || value === "fit-page") return value;
628
- const parsed = Number.parseFloat(value);
629
- return Number.isNaN(parsed) ? 1 : parsed;
630
- }
631
- var FileViewerRotate = forwardRef3(
632
- function FileViewerRotate2({ className, ...props }, ref) {
633
- const { state, actions } = useFileViewer();
634
- const { t } = useLocale3();
635
- if (!state.capabilities.rotate) return null;
636
- return /* @__PURE__ */ jsx3(
637
- IconButton3,
638
- {
639
- ref,
640
- "data-slot": "file-viewer-rotate",
641
- variant: "ghost",
642
- size: "icon-sm",
643
- label: t("viewer.rotate"),
644
- icon: /* @__PURE__ */ jsx3(RotateCwIcon, { "aria-hidden": "true" }),
645
- className: cn3("shrink-0", className),
646
- onClick: () => actions.rotate(1),
647
- ...props
648
- }
649
- );
650
- }
651
- );
652
-
653
- // src/file-viewer/file-viewer.tsx
654
- import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
655
- var NO_HIGHLIGHTS = [];
656
- var NO_RESOLVED = [];
657
- var NO_SUPPORT = [];
658
- var NO_MATCHES = { highlights: NO_HIGHLIGHTS, total: 0, truncated: false };
659
- function FileViewerProvider({
660
- source,
661
- registry: registryProp,
662
- loading = false,
663
- baseHeadingLevel = 2,
664
- highlights: highlightsProp,
665
- defaultHighlights,
666
- onHighlightsChange,
667
- activeHighlightId: activeHighlightIdProp,
668
- defaultActiveHighlightId = null,
669
- onActiveHighlightChange,
670
- pageNumber: pageNumberProp,
671
- defaultPageNumber = 1,
672
- onPageNumberChange,
673
- zoom: zoomProp,
674
- defaultZoom = "fit-width",
675
- onZoomChange,
676
- rotation: rotationProp,
677
- defaultRotation = 0,
678
- onRotationChange,
679
- children
680
- }) {
681
- const fallbackRegistry = useMemo(() => createDefaultRegistry(), []);
682
- const registry = registryProp ?? fallbackRegistry;
683
- const [attempt, setAttempt] = useState2(0);
684
- const [state, setState] = useState2({ status: "empty", capabilities: {} });
685
- const resolved = useMemo(() => source ? normalizeFileSource(source) : void 0, [source]);
686
- useEffect2(() => {
687
- if (!resolved) {
688
- setState({ status: "empty", capabilities: {} });
689
- return;
690
- }
691
- const controller = new AbortController();
692
- let instance;
693
- let cancelled = false;
694
- const kind = resolveFileKind2(resolved.name, resolved.mediaType);
695
- const manifest = registry.detect(kind);
696
- setState({
697
- status: "loading",
698
- source: resolved,
699
- capabilities: manifest?.capabilities ?? {}
700
- });
701
- void (async () => {
702
- try {
703
- if (!manifest) {
704
- throw new ViewerError("unsupported-format", `No adapter can open "${resolved.name}".`, {
705
- fileName: resolved.name
706
- });
707
- }
708
- const adapter = await registry.load(manifest.id);
709
- const parser = adapter.create();
710
- instance = parser;
711
- const document = await parser.load(resolved, { signal: controller.signal });
712
- if (cancelled) return;
713
- setState({
714
- status: "ready",
715
- source: resolved,
716
- document,
717
- adapter,
718
- capabilities: manifest.capabilities ?? {}
719
- });
720
- } catch (error) {
721
- if (cancelled || isAbort(error)) return;
722
- const missingPeer = isModuleNotFound(error) && manifest ? parserMissingError(manifest.id, manifest.requires ?? [], {
723
- fileName: resolved.name,
724
- cause: error
725
- }) : void 0;
726
- setState({
727
- status: "error",
728
- source: resolved,
729
- capabilities: manifest?.capabilities ?? {},
730
- error: missingPeer ?? toViewerError(error, "parse-failed", { fileName: resolved.name })
731
- });
732
- }
733
- })();
734
- return () => {
735
- cancelled = true;
736
- controller.abort();
737
- instance?.dispose?.();
738
- resolved.revoke();
739
- };
740
- }, [resolved, registry, attempt]);
741
- const highlightsControlled = highlightsProp !== void 0;
742
- const [ownHighlights, setOwnHighlights] = useState2(defaultHighlights ?? NO_HIGHLIGHTS);
743
- const highlights = highlightsControlled ? highlightsProp : ownHighlights;
744
- const setHighlights = useCallback(
745
- (next) => {
746
- if (!highlightsControlled) setOwnHighlights(next);
747
- onHighlightsChange?.(next);
748
- },
749
- [highlightsControlled, onHighlightsChange]
750
- );
751
- const activeControlled = activeHighlightIdProp !== void 0;
752
- const [ownActiveId, setOwnActiveId] = useState2(defaultActiveHighlightId);
753
- const activeHighlightId = activeControlled ? activeHighlightIdProp : ownActiveId;
754
- const setActiveHighlight = useCallback(
755
- (id) => {
756
- if (!activeControlled) setOwnActiveId(id);
757
- onActiveHighlightChange?.(id);
758
- },
759
- [activeControlled, onActiveHighlightChange]
760
- );
761
- const pageCount = state.document?.pageCount ?? 0;
762
- const pageControlled = pageNumberProp !== void 0;
763
- const [ownPage, setOwnPage] = useState2(defaultPageNumber);
764
- const rawPage = pageControlled ? pageNumberProp : ownPage;
765
- const pageNumber = pageCount > 0 ? Math.min(Math.max(1, rawPage), pageCount) : Math.max(1, rawPage);
766
- const goToPage = useCallback(
767
- (page) => {
768
- const next = Math.max(1, Math.round(page));
769
- if (!pageControlled) setOwnPage(next);
770
- onPageNumberChange?.(next);
771
- },
772
- [pageControlled, onPageNumberChange]
773
- );
774
- const zoomControlled = zoomProp !== void 0;
775
- const [ownZoom, setOwnZoom] = useState2(defaultZoom);
776
- const zoom = zoomControlled ? zoomProp : ownZoom;
777
- const setZoom = useCallback(
778
- (next) => {
779
- if (!zoomControlled) setOwnZoom(next);
780
- onZoomChange?.(next);
781
- },
782
- [zoomControlled, onZoomChange]
783
- );
784
- const [reportedZoom, setReportedZoom] = useState2(DEFAULT_ZOOM);
785
- const effectiveZoom = typeof zoom === "number" ? zoom : reportedZoom;
786
- const rotationControlled = rotationProp !== void 0;
787
- const [ownRotation, setOwnRotation] = useState2(defaultRotation);
788
- const rotation = rotationControlled ? rotationProp : ownRotation;
789
- const setRotation = useCallback(
790
- (next) => {
791
- if (!rotationControlled) setOwnRotation(next);
792
- onRotationChange?.(next);
793
- },
794
- [rotationControlled, onRotationChange]
795
- );
796
- const previousSource = useRef2(resolved);
797
- useEffect2(() => {
798
- if (previousSource.current === resolved) return;
799
- previousSource.current = resolved;
800
- setOwnPage(1);
801
- setOwnRotation(0);
802
- }, [resolved]);
803
- const [find, setFind] = useState2({
804
- open: false,
805
- query: "",
806
- caseSensitive: false,
807
- activeIndex: 0
808
- });
809
- const [findParts, setFindParts] = useState2(0);
810
- const registerFind = useCallback(() => {
811
- setFindParts((count) => count + 1);
812
- return () => setFindParts((count) => count - 1);
813
- }, []);
814
- const text = state.document?.text;
815
- const capabilities = state.capabilities;
816
- const support = capabilities.highlight ?? NO_SUPPORT;
817
- const canFind = (capabilities.search ?? true) && text !== void 0 && support.includes("range");
818
- const query = useDeferredValue(find.query);
819
- const findMatches = useMemo(() => {
820
- if (!find.open || !canFind || text === void 0 || query.length === 0) return NO_MATCHES;
821
- const ranges = queryToRanges(text, query, find.caseSensitive);
822
- return {
823
- highlights: ranges.slice(0, FIND_MATCH_LIMIT).map(([start, end], index) => ({
824
- id: findMatchId(index),
825
- address: { kind: "range", start, end },
826
- source: "search"
827
- })),
828
- total: ranges.length,
829
- truncated: ranges.length > FIND_MATCH_LIMIT
830
- };
831
- }, [find.open, find.caseSensitive, canFind, text, query]);
832
- const normalized = useMemo(
833
- () => text === void 0 ? void 0 : normalizeQuoteTextWithOffsets(text),
834
- [text]
835
- );
836
- const activeFindId = find.open && findMatches.highlights.length > 0 ? findMatchId(find.activeIndex) : void 0;
837
- const currentHighlightId = activeFindId ?? activeHighlightId;
838
- const resolvedHighlights = useMemo(() => {
839
- if (highlights.length === 0 && findMatches.highlights.length === 0) return NO_RESOLVED;
840
- return resolveHighlights([...highlights, ...findMatches.highlights], {
841
- normalized,
842
- textLength: text?.length,
843
- truncated: state.document?.textTruncated,
844
- supported: support,
845
- activeId: currentHighlightId
846
- });
847
- }, [
848
- highlights,
849
- findMatches.highlights,
850
- normalized,
851
- text?.length,
852
- state.document?.textTruncated,
853
- support,
854
- currentHighlightId
855
- ]);
856
- const stepCitation = useCallback(
857
- (delta) => {
858
- const list = resolvedHighlights.filter(
859
- (highlight) => highlight.source === "citation" && highlight.status === "resolved"
860
- );
861
- if (list.length === 0) return;
862
- const at = list.findIndex((highlight) => highlight.id === activeHighlightId);
863
- const to = at === -1 ? delta === 1 ? 0 : list.length - 1 : (at + delta + list.length) % list.length;
864
- setActiveHighlight(list[to].id);
865
- },
866
- [resolvedHighlights, activeHighlightId, setActiveHighlight]
867
- );
868
- const stepFind = useCallback(
869
- (delta) => {
870
- const count = findMatches.highlights.length;
871
- if (count === 0) return;
872
- setFind((current) => ({
873
- ...current,
874
- activeIndex: (current.activeIndex + delta + count) % count
875
- }));
876
- },
877
- [findMatches.highlights.length]
878
- );
879
- const findState = useMemo(
880
- () => ({
881
- open: find.open,
882
- query: find.query,
883
- caseSensitive: find.caseSensitive,
884
- matches: findMatches.total,
885
- truncated: findMatches.truncated,
886
- activeIndex: find.activeIndex
887
- }),
888
- [find, findMatches.total, findMatches.truncated]
889
- );
890
- const actions = useMemo(
891
- () => ({
892
- reload: () => setAttempt((n) => n + 1),
893
- setHighlights,
894
- setActiveHighlight,
895
- nextHighlight: () => stepCitation(1),
896
- previousHighlight: () => stepCitation(-1),
897
- openFind: () => setFind((current) => ({ ...current, open: true })),
898
- // The query survives a close, so re-opening resumes where the reader was;
899
- // only the box goes away.
900
- closeFind: () => setFind((current) => ({ ...current, open: false })),
901
- setFindQuery: (next) => setFind((current) => ({ ...current, query: next, activeIndex: 0 })),
902
- setFindCaseSensitive: (next) => setFind((current) => ({ ...current, caseSensitive: next, activeIndex: 0 })),
903
- nextFindMatch: () => stepFind(1),
904
- previousFindMatch: () => stepFind(-1),
905
- goToPage,
906
- // Stop at the ends rather than wrap: a document is not a carousel, and a
907
- // reader who holds "next" past the last page expects to stay there.
908
- nextPage: () => goToPage(Math.min(pageNumber + 1, pageCount || pageNumber + 1)),
909
- previousPage: () => goToPage(pageNumber - 1),
910
- setZoom,
911
- // Stepping from `effectiveZoom`, not from `zoom`: after a fit the ladder
912
- // has to continue from what is actually on screen.
913
- zoomIn: () => setZoom(stepZoom(effectiveZoom, 1)),
914
- zoomOut: () => setZoom(stepZoom(effectiveZoom, -1)),
915
- setRotation,
916
- rotate: (quarterTurns) => setRotation(((rotation + quarterTurns * 90) % 360 + 360) % 360),
917
- reportZoom: setReportedZoom,
918
- registerFind
919
- }),
920
- [
921
- setHighlights,
922
- setActiveHighlight,
923
- stepCitation,
924
- stepFind,
925
- registerFind,
926
- goToPage,
927
- pageNumber,
928
- pageCount,
929
- setZoom,
930
- effectiveZoom,
931
- setRotation,
932
- rotation
933
- ]
934
- );
935
- const value = useMemo(
936
- () => ({
937
- state: {
938
- // A parent's `loading` can add the not-ready state but never clear a
939
- // real error — losing a failure to a stale prop is worse than a late
940
- // spinner.
941
- ...loading && state.status !== "error" ? { ...state, status: "loading" } : state,
942
- highlights,
943
- activeHighlightId,
944
- find: findState,
945
- pageNumber,
946
- pageCount,
947
- zoom,
948
- effectiveZoom,
949
- rotation
950
- },
951
- actions,
952
- registry,
953
- meta: {
954
- baseHeadingLevel,
955
- resolvedHighlights,
956
- currentHighlightId,
957
- highlightSupport: support,
958
- canFind,
959
- hasFind: findParts > 0
960
- }
961
- }),
962
- [
963
- state,
964
- loading,
965
- registry,
966
- baseHeadingLevel,
967
- highlights,
968
- activeHighlightId,
969
- findState,
970
- actions,
971
- resolvedHighlights,
972
- currentHighlightId,
973
- support,
974
- canFind,
975
- findParts,
976
- pageNumber,
977
- pageCount,
978
- zoom,
979
- effectiveZoom,
980
- rotation
981
- ]
982
- );
983
- return /* @__PURE__ */ jsx4(FileViewerContext, { value, children });
984
- }
985
- var FileViewerFrame = forwardRef4(
986
- function FileViewerFrame2({ className, children, onKeyDown, ...props }, ref) {
987
- const { state, actions, meta } = useFileViewer();
988
- const { t } = useLocale4();
989
- const frame = useRef2(null);
990
- const wasOpen = useRef2(false);
991
- const findOpen = state.find.open && meta.canFind;
992
- useEffect2(() => {
993
- if (wasOpen.current && !findOpen) {
994
- frame.current?.querySelector('[data-slot="file-viewer-content"]')?.focus({ preventScroll: true });
995
- }
996
- wasOpen.current = findOpen;
997
- }, [findOpen]);
998
- return /* @__PURE__ */ jsx4(
999
- "section",
1000
- {
1001
- ref: (node) => {
1002
- frame.current = node;
1003
- if (typeof ref === "function") ref(node);
1004
- else if (ref) ref.current = node;
1005
- },
1006
- "data-slot": "file-viewer",
1007
- "aria-label": t("viewer.label"),
1008
- className: cn4(
1009
- "bg-card text-card-foreground border-border flex h-full min-h-0 flex-col overflow-hidden rounded-lg border shadow-sm",
1010
- className
1011
- ),
1012
- onKeyDown: (event) => {
1013
- onKeyDown?.(event);
1014
- if (!event.defaultPrevented && meta.canFind && meta.hasFind && isFindShortcut(event)) {
1015
- event.preventDefault();
1016
- actions.openFind();
1017
- }
1018
- },
1019
- ...props,
1020
- children
1021
- }
1022
- );
1023
- }
1024
- );
1025
- var FileViewerToolbar = forwardRef4(
1026
- function FileViewerToolbar2({ className, actions, children, ...props }, ref) {
1027
- const { state } = useFileViewer();
1028
- const { t } = useLocale4();
1029
- const source = state.source;
1030
- const Glyph = fileIconFor(source?.name ?? "", source?.mediaType);
1031
- const [downloadFailed, setDownloadFailed] = useState2(false);
1032
- if (!source) return null;
1033
- const download = () => {
1034
- setDownloadFailed(false);
1035
- void source.bytes().then((bytes) => {
1036
- downloadBlob(new Blob([bytes], { type: source.mediaType }), source.name);
1037
- }).catch((error) => {
1038
- console.error("FileViewer: download failed", error);
1039
- setDownloadFailed(true);
1040
- });
1041
- };
1042
- return /* @__PURE__ */ jsxs4(
1043
- "div",
1044
- {
1045
- ref,
1046
- "data-slot": "file-viewer-toolbar",
1047
- className: cn4(
1048
- // The divider is the ONLY cue between toolbar and content (same fill,
1049
- // no elevation change) — WCAG 1.4.11, so the strong rung.
1050
- "border-border-strong flex shrink-0 items-center gap-2 border-b px-3 py-2",
1051
- className
1052
- ),
1053
- ...props,
1054
- children: [
1055
- /* @__PURE__ */ jsx4(Glyph, { "aria-hidden": "true", className: "text-muted-foreground size-4 shrink-0" }),
1056
- /* @__PURE__ */ jsx4(Text, { className: "min-w-0 flex-1 truncate", title: source.name, children: source.name }),
1057
- children,
1058
- actions,
1059
- downloadFailed ? /* @__PURE__ */ jsx4(Text, { role: "alert", className: "text-destructive-text shrink-0 truncate", children: t("viewer.error.readFailedBody", { name: source.name, format: source.mediaType }) }) : null,
1060
- /* @__PURE__ */ jsx4(Separator2, { orientation: "vertical", className: "h-4" }),
1061
- /* @__PURE__ */ jsx4(
1062
- IconButton4,
1063
- {
1064
- variant: "ghost",
1065
- label: t("viewer.download", { name: source.name }),
1066
- icon: /* @__PURE__ */ jsx4(DownloadIcon, { "aria-hidden": "true" }),
1067
- onClick: download
1068
- }
1069
- )
1070
- ]
1071
- }
1072
- );
1073
- }
1074
- );
1075
- var FileViewerHighlightStatus = forwardRef4(
1076
- function FileViewerHighlightStatus2({ className, ...props }, ref) {
1077
- const { state, meta } = useFileViewer();
1078
- const { t } = useLocale4();
1079
- const missed = meta.resolvedHighlights.filter(
1080
- (highlight) => highlight.source === "citation" && (highlight.status === "not-found" || highlight.status === "unsupported")
1081
- );
1082
- if (missed.length === 0) return null;
1083
- const first = missed[0];
1084
- const message = first.status === "unsupported" ? t("viewer.highlight.unsupported", {
1085
- format: state.source?.extension.toUpperCase() || ""
1086
- }) : first.reason === "truncated" ? t("viewer.highlight.notFoundTruncated") : t("viewer.highlight.notFound");
1087
- return /* @__PURE__ */ jsxs4(
1088
- "div",
1089
- {
1090
- ref,
1091
- "data-slot": "file-viewer-highlight-status",
1092
- role: "status",
1093
- "aria-live": "polite",
1094
- className: cn4(
1095
- // Information, not a failure — a neutral muted row, never the
1096
- // destructive tone. The divider is the sole cue between it and the
1097
- // content below (WCAG 1.4.11, strong rung).
1098
- "border-border-strong text-muted-foreground flex shrink-0 items-center gap-2 border-b px-3 py-2",
1099
- className
1100
- ),
1101
- ...props,
1102
- children: [
1103
- /* @__PURE__ */ jsx4(SearchXIcon, { "aria-hidden": "true", className: "size-4 shrink-0" }),
1104
- /* @__PURE__ */ jsx4("span", { className: "text-meta min-w-0 flex-1 truncate", children: message })
1105
- ]
1106
- }
1107
- );
1108
- }
1109
- );
1110
- var FileViewerSkeleton = forwardRef4(
1111
- function FileViewerSkeleton2({ className, ...props }, ref) {
1112
- const { state } = useFileViewer();
1113
- const { t } = useLocale4();
1114
- return /* @__PURE__ */ jsxs4(
1115
- "div",
1116
- {
1117
- ref,
1118
- "data-slot": "file-viewer-skeleton",
1119
- role: "status",
1120
- "aria-live": "polite",
1121
- className: cn4("flex h-full flex-col gap-3 p-4", className),
1122
- ...props,
1123
- children: [
1124
- /* @__PURE__ */ jsx4("span", { className: "sr-only", children: t("viewer.loading", { name: state.source?.name ?? "" }) }),
1125
- /* @__PURE__ */ jsx4(Skeleton, { "aria-hidden": "true", className: "h-4 w-2/5" }),
1126
- /* @__PURE__ */ jsx4(Skeleton, { "aria-hidden": "true", className: "h-4 w-4/5" }),
1127
- /* @__PURE__ */ jsx4(Skeleton, { "aria-hidden": "true", className: "h-4 w-3/5" }),
1128
- /* @__PURE__ */ jsx4(Skeleton, { "aria-hidden": "true", className: "min-h-24 flex-1" })
1129
- ]
1130
- }
1131
- );
1132
- }
1133
- );
1134
- var ERROR_MESSAGES = {
1135
- "unsupported-format": {
1136
- title: "viewer.error.unsupportedFormat",
1137
- body: "viewer.error.unsupportedFormatBody"
1138
- },
1139
- "parser-missing": {
1140
- title: "viewer.error.parserMissing",
1141
- body: "viewer.error.parserMissingBody"
1142
- },
1143
- "read-failed": { title: "viewer.error.readFailed", body: "viewer.error.readFailedBody" },
1144
- "parse-failed": { title: "viewer.error.parseFailed", body: "viewer.error.parseFailedBody" },
1145
- // Neither should ever reach the UI: a protocol mismatch throws at registration
1146
- // and an abort is swallowed as a superseded load. Mapped anyway so the panel
1147
- // can never render an empty title.
1148
- "protocol-mismatch": { title: "viewer.error.parseFailed", body: "viewer.error.parseFailedBody" },
1149
- aborted: { title: "viewer.error.readFailed", body: "viewer.error.readFailedBody" }
1150
- };
1151
- var CAPABILITY_GAPS = /* @__PURE__ */ new Set(["unsupported-format", "parser-missing"]);
1152
- var FileViewerError = forwardRef4(
1153
- function FileViewerError2({ className, ...props }, ref) {
1154
- const { state } = useFileViewer();
1155
- const { t } = useLocale4();
1156
- const error = state.error;
1157
- if (!error) return null;
1158
- const message = ERROR_MESSAGES[error.code];
1159
- const vars = {
1160
- name: state.source?.name ?? "",
1161
- format: state.source?.extension.toUpperCase() || t("viewer.error.parseFailed"),
1162
- packages: error.packages?.join(", ") ?? ""
1163
- };
1164
- const isGap = CAPABILITY_GAPS.has(error.code);
1165
- return /* @__PURE__ */ jsx4(
1166
- "div",
1167
- {
1168
- ref,
1169
- "data-slot": "file-viewer-error",
1170
- className: cn4("flex min-h-full flex-col justify-center p-4", className),
1171
- ...isGap ? { role: "status", "aria-live": "polite" } : {},
1172
- ...props,
1173
- children: /* @__PURE__ */ jsx4(
1174
- StatePanel,
1175
- {
1176
- kind: isGap ? "empty" : "error",
1177
- className: isGap ? "border-solid" : void 0,
1178
- icon: isGap ? /* @__PURE__ */ jsx4(EyeOffIcon, { "aria-hidden": "true" }) : void 0,
1179
- title: t(message.title),
1180
- description: t(message.body, vars),
1181
- actions: error.code === "read-failed" || error.code === "parse-failed" ? /* @__PURE__ */ jsx4(RetryButton, {}) : void 0
1182
- }
1183
- )
1184
- }
1185
- );
1186
- }
1187
- );
1188
- function RetryButton() {
1189
- const { actions } = useFileViewer();
1190
- const { t } = useLocale4();
1191
- return /* @__PURE__ */ jsx4(Button, { variant: "outline", size: "sm", onClick: actions.reload, children: t("viewer.retry") });
1192
- }
1193
- var FileViewerEmpty = forwardRef4(
1194
- function FileViewerEmpty2({ className, ...props }, ref) {
1195
- const { t } = useLocale4();
1196
- return /* @__PURE__ */ jsx4(
1197
- "div",
1198
- {
1199
- ref,
1200
- "data-slot": "file-viewer-empty",
1201
- className: cn4("flex min-h-full flex-col justify-center p-4", className),
1202
- ...props,
1203
- children: /* @__PURE__ */ jsx4(StatePanel, { kind: "empty", title: t("viewer.empty"), description: t("viewer.emptyBody") })
1204
- }
1205
- );
1206
- }
1207
- );
1208
- var FileViewerContent = forwardRef4(
1209
- function FileViewerContent2({ className, ...props }, ref) {
1210
- const { state, actions, meta } = useFileViewer();
1211
- const { t } = useLocale4();
1212
- return /* @__PURE__ */ jsxs4(
1213
- "div",
1214
- {
1215
- ref,
1216
- "data-slot": "file-viewer-content",
1217
- role: "region",
1218
- "aria-label": t("viewer.content"),
1219
- tabIndex: 0,
1220
- className: cn4("focus-ring min-h-0 flex-1 overflow-auto p-4", className),
1221
- ...props,
1222
- children: [
1223
- state.status === "empty" && /* @__PURE__ */ jsx4(FileViewerEmpty, {}),
1224
- state.status === "loading" && /* @__PURE__ */ jsx4(FileViewerSkeleton, {}),
1225
- state.status === "error" && /* @__PURE__ */ jsx4(FileViewerError, {}),
1226
- state.status === "ready" && state.adapter && state.document && state.source && /* @__PURE__ */ jsx4(
1227
- state.adapter.Renderer,
1228
- {
1229
- document: state.document,
1230
- source: state.source,
1231
- baseHeadingLevel: meta.baseHeadingLevel,
1232
- highlights: meta.resolvedHighlights,
1233
- activeHighlightId: meta.currentHighlightId,
1234
- pageNumber: state.pageNumber,
1235
- onPageChange: actions.goToPage,
1236
- zoom: state.zoom,
1237
- onZoomResolved: actions.reportZoom,
1238
- rotation: state.rotation
1239
- }
1240
- )
1241
- ]
1242
- }
1243
- );
1244
- }
1245
- );
1246
- var FileViewer = forwardRef4(function FileViewer2({
1247
- // Every provider prop is destructured BY NAME, and the rest goes to the
1248
- // frame. Peeling off only some of them and spreading the remainder was a
1249
- // silent bug: `baseHeadingLevel` type-checked, never reached the provider,
1250
- // and landed on the `<section>` as an unknown DOM attribute. Adding a
1251
- // provider prop above without adding it here re-creates that exactly.
1252
- source,
1253
- registry,
1254
- loading,
1255
- baseHeadingLevel,
1256
- highlights,
1257
- defaultHighlights,
1258
- onHighlightsChange,
1259
- activeHighlightId,
1260
- defaultActiveHighlightId,
1261
- onActiveHighlightChange,
1262
- pageNumber,
1263
- defaultPageNumber,
1264
- onPageNumberChange,
1265
- zoom,
1266
- defaultZoom,
1267
- onZoomChange,
1268
- rotation,
1269
- defaultRotation,
1270
- onRotationChange,
1271
- children,
1272
- ...props
1273
- }, ref) {
1274
- return /* @__PURE__ */ jsx4(
1275
- FileViewerProvider,
1276
- {
1277
- source,
1278
- registry,
1279
- loading,
1280
- baseHeadingLevel,
1281
- highlights,
1282
- defaultHighlights,
1283
- onHighlightsChange,
1284
- activeHighlightId,
1285
- defaultActiveHighlightId,
1286
- onActiveHighlightChange,
1287
- pageNumber,
1288
- defaultPageNumber,
1289
- onPageNumberChange,
1290
- zoom,
1291
- defaultZoom,
1292
- onZoomChange,
1293
- rotation,
1294
- defaultRotation,
1295
- onRotationChange,
1296
- children: /* @__PURE__ */ jsx4(FileViewerFrame, { ref, ...props, children: children ?? /* @__PURE__ */ jsxs4(Fragment, { children: [
1297
- /* @__PURE__ */ jsxs4(FileViewerToolbar, { children: [
1298
- /* @__PURE__ */ jsx4(FileViewerPager, {}),
1299
- /* @__PURE__ */ jsx4(FileViewerZoom, {}),
1300
- /* @__PURE__ */ jsx4(FileViewerRotate, {})
1301
- ] }),
1302
- /* @__PURE__ */ jsx4(FileViewerFind, {}),
1303
- /* @__PURE__ */ jsx4(FileViewerHighlightStatus, {}),
1304
- /* @__PURE__ */ jsx4(FileViewerContent, {})
1305
- ] }) })
1306
- }
1307
- );
1308
- });
111
+ PROTOCOL_VERSION
112
+ } from "./chunk-CIT2HHRI.js";
1309
113
  export {
1310
114
  ACTIVE_HIGHLIGHT_SELECTOR,
1311
115
  DEFAULT_ZOOM,