@elabs-ai/components-viewer 4.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 (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/dist/chunk-2NQ4RSJ3.js +18 -0
  4. package/dist/chunk-2NQ4RSJ3.js.map +1 -0
  5. package/dist/chunk-3U3MESBU.js +21 -0
  6. package/dist/chunk-3U3MESBU.js.map +1 -0
  7. package/dist/chunk-4OFQYALG.js +147 -0
  8. package/dist/chunk-4OFQYALG.js.map +1 -0
  9. package/dist/chunk-5OMONA2G.js +87 -0
  10. package/dist/chunk-5OMONA2G.js.map +1 -0
  11. package/dist/chunk-AORNMH77.js +58 -0
  12. package/dist/chunk-AORNMH77.js.map +1 -0
  13. package/dist/chunk-BCF3RWRL.js +18 -0
  14. package/dist/chunk-BCF3RWRL.js.map +1 -0
  15. package/dist/chunk-FELIAGKI.js +22 -0
  16. package/dist/chunk-FELIAGKI.js.map +1 -0
  17. package/dist/chunk-GGR7BNW4.js +18 -0
  18. package/dist/chunk-GGR7BNW4.js.map +1 -0
  19. package/dist/chunk-H3OAON3D.js +22 -0
  20. package/dist/chunk-H3OAON3D.js.map +1 -0
  21. package/dist/chunk-HMG2ERXH.js +17 -0
  22. package/dist/chunk-HMG2ERXH.js.map +1 -0
  23. package/dist/chunk-KOMDRWOU.js +21 -0
  24. package/dist/chunk-KOMDRWOU.js.map +1 -0
  25. package/dist/chunk-NMA57QZ7.js +117 -0
  26. package/dist/chunk-NMA57QZ7.js.map +1 -0
  27. package/dist/chunk-RGWDIQJR.js +26 -0
  28. package/dist/chunk-RGWDIQJR.js.map +1 -0
  29. package/dist/chunk-SLXRUYT3.js +91 -0
  30. package/dist/chunk-SLXRUYT3.js.map +1 -0
  31. package/dist/chunk-UJIJECEF.js +21 -0
  32. package/dist/chunk-UJIJECEF.js.map +1 -0
  33. package/dist/chunk-UL43NGUG.js +68 -0
  34. package/dist/chunk-UL43NGUG.js.map +1 -0
  35. package/dist/chunk-VO2273Z2.js +55 -0
  36. package/dist/chunk-VO2273Z2.js.map +1 -0
  37. package/dist/code-adapter-ADZ4UOGN.js +226 -0
  38. package/dist/code-adapter-ADZ4UOGN.js.map +1 -0
  39. package/dist/csv-adapter-6VU3FFVU.js +100 -0
  40. package/dist/csv-adapter-6VU3FFVU.js.map +1 -0
  41. package/dist/docx-adapter-5CQDHWTD.js +383 -0
  42. package/dist/docx-adapter-5CQDHWTD.js.map +1 -0
  43. package/dist/image-adapter-WOHZR24J.js +132 -0
  44. package/dist/image-adapter-WOHZR24J.js.map +1 -0
  45. package/dist/index.d.ts +1353 -0
  46. package/dist/index.js +1364 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/json-adapter-ZUW5GQHE.js +90 -0
  49. package/dist/json-adapter-ZUW5GQHE.js.map +1 -0
  50. package/dist/markdown-adapter-YC6WTBS4.js +214 -0
  51. package/dist/markdown-adapter-YC6WTBS4.js.map +1 -0
  52. package/dist/media-adapter-MCTB4GBH.js +78 -0
  53. package/dist/media-adapter-MCTB4GBH.js.map +1 -0
  54. package/dist/pdf-adapter-5PMKEXUD.js +424 -0
  55. package/dist/pdf-adapter-5PMKEXUD.js.map +1 -0
  56. package/dist/pptx-adapter-6GEQLS2Z.js +351 -0
  57. package/dist/pptx-adapter-6GEQLS2Z.js.map +1 -0
  58. package/dist/text-adapter-NFNWB5W3.js +81 -0
  59. package/dist/text-adapter-NFNWB5W3.js.map +1 -0
  60. package/dist/xlsx-adapter-CM2Y6AKQ.js +167 -0
  61. package/dist/xlsx-adapter-CM2Y6AKQ.js.map +1 -0
  62. package/package.json +103 -0
  63. package/src/adapters/code/code-adapter.test.tsx +221 -0
  64. package/src/adapters/code/code-adapter.tsx +272 -0
  65. package/src/adapters/code/code-language.test.ts +45 -0
  66. package/src/adapters/code/code-language.ts +100 -0
  67. package/src/adapters/code/code-manifest.ts +19 -0
  68. package/src/adapters/code/code-theme.test.ts +46 -0
  69. package/src/adapters/code/code-theme.ts +99 -0
  70. package/src/adapters/csv/csv-adapter.test.tsx +169 -0
  71. package/src/adapters/csv/csv-adapter.tsx +146 -0
  72. package/src/adapters/csv/csv-manifest.ts +20 -0
  73. package/src/adapters/docx/docx-adapter.test.tsx +192 -0
  74. package/src/adapters/docx/docx-adapter.tsx +328 -0
  75. package/src/adapters/docx/docx-manifest.ts +18 -0
  76. package/src/adapters/docx/docx-model.test.ts +181 -0
  77. package/src/adapters/docx/docx-model.ts +294 -0
  78. package/src/adapters/image/image-adapter.test.tsx +141 -0
  79. package/src/adapters/image/image-adapter.tsx +191 -0
  80. package/src/adapters/image/image-manifest.ts +15 -0
  81. package/src/adapters/index.ts +64 -0
  82. package/src/adapters/json/json-adapter.test.tsx +77 -0
  83. package/src/adapters/json/json-adapter.tsx +141 -0
  84. package/src/adapters/json/json-manifest.ts +11 -0
  85. package/src/adapters/markdown/markdown-adapter.test.tsx +228 -0
  86. package/src/adapters/markdown/markdown-adapter.tsx +353 -0
  87. package/src/adapters/markdown/markdown-manifest.ts +21 -0
  88. package/src/adapters/markdown/markdown-marks.test.ts +85 -0
  89. package/src/adapters/markdown/markdown-marks.ts +119 -0
  90. package/src/adapters/media/media-adapter.test.tsx +79 -0
  91. package/src/adapters/media/media-adapter.tsx +123 -0
  92. package/src/adapters/media/media-manifest.ts +17 -0
  93. package/src/adapters/office-fixture.ts +239 -0
  94. package/src/adapters/pdf/pdf-adapter.test.tsx +489 -0
  95. package/src/adapters/pdf/pdf-adapter.tsx +599 -0
  96. package/src/adapters/pdf/pdf-engine.ts +171 -0
  97. package/src/adapters/pdf/pdf-fixture.ts +27 -0
  98. package/src/adapters/pdf/pdf-manifest.ts +30 -0
  99. package/src/adapters/pdf/pdf-text.test.ts +171 -0
  100. package/src/adapters/pdf/pdf-text.ts +202 -0
  101. package/src/adapters/pptx/pptx-adapter.test.tsx +241 -0
  102. package/src/adapters/pptx/pptx-adapter.tsx +341 -0
  103. package/src/adapters/pptx/pptx-manifest.ts +19 -0
  104. package/src/adapters/pptx/pptx-model.test.ts +219 -0
  105. package/src/adapters/pptx/pptx-model.ts +248 -0
  106. package/src/adapters/text/text-adapter.test.tsx +125 -0
  107. package/src/adapters/text/text-adapter.tsx +117 -0
  108. package/src/adapters/text/text-manifest.ts +19 -0
  109. package/src/adapters/xlsx/xlsx-adapter.test.tsx +159 -0
  110. package/src/adapters/xlsx/xlsx-adapter.tsx +250 -0
  111. package/src/adapters/xlsx/xlsx-manifest.ts +26 -0
  112. package/src/components/grid-text.test.ts +68 -0
  113. package/src/components/grid-text.ts +77 -0
  114. package/src/components/marked-text.tsx +36 -0
  115. package/src/components/sheet-table.tsx +142 -0
  116. package/src/core/errors.ts +120 -0
  117. package/src/core/highlight-marks.test.ts +95 -0
  118. package/src/core/highlight-marks.ts +95 -0
  119. package/src/core/highlight-resolve.test.ts +208 -0
  120. package/src/core/highlight-resolve.ts +191 -0
  121. package/src/core/highlight.ts +125 -0
  122. package/src/core/registry.test.ts +172 -0
  123. package/src/core/registry.ts +188 -0
  124. package/src/core/scroll-host.ts +42 -0
  125. package/src/core/text-index.test.ts +95 -0
  126. package/src/core/text-index.ts +159 -0
  127. package/src/core/types.ts +285 -0
  128. package/src/core/use-highlight-scroll.ts +59 -0
  129. package/src/core/use-page-control.ts +47 -0
  130. package/src/core/use-paged-scroll.ts +194 -0
  131. package/src/core/use-viewport-size.ts +54 -0
  132. package/src/core/zoom.ts +56 -0
  133. package/src/file-viewer/file-viewer-context.tsx +239 -0
  134. package/src/file-viewer/file-viewer-find.tsx +204 -0
  135. package/src/file-viewer/file-viewer-pager.tsx +120 -0
  136. package/src/file-viewer/file-viewer-zoom.tsx +166 -0
  137. package/src/file-viewer/file-viewer.stories.tsx +947 -0
  138. package/src/file-viewer/file-viewer.test.tsx +947 -0
  139. package/src/file-viewer/file-viewer.tsx +1143 -0
  140. package/src/index.ts +157 -0
@@ -0,0 +1,1353 @@
1
+ import * as react from 'react';
2
+ import { ComponentType, ReactNode, HTMLAttributes, RefObject } from 'react';
3
+ import { DocumentAddress, MatchRange, DocumentRect, DocumentAddressKind, FileCategory, ResolvedFileSource, ProseHeadingLevel, FileKind, FileSource, NormalizedText, QuoteAddress } from '@elabs-ai/components-ui';
4
+
5
+ /**
6
+ * What the viewer is pointed AT — the vocabulary shared by citations and
7
+ * find-in-document.
8
+ *
9
+ * A {@link DocumentHighlight} is what a caller asks for; a
10
+ * {@link ResolvedHighlight} is what the viewer worked out and what an adapter's
11
+ * `Renderer` is handed. Keeping the two apart is what lets "we could not find
12
+ * that passage" be a rendered STATE rather than a silent no-op: the request
13
+ * survives even when the location does not, so the chrome still has something
14
+ * to name.
15
+ *
16
+ * The address vocabulary itself lives in `@elabs-ai/components-ui`
17
+ * (`DocumentAddress`) because the producer of a citation and this consumer are
18
+ * sibling packages that may not import each other. Everything here is
19
+ * adapter-protocol detail and stays in this package.
20
+ */
21
+
22
+ /**
23
+ * Where a highlight came from. Both paint identically — only which one is
24
+ * ACTIVE differs — but the origin decides who owns the list: citations are a
25
+ * controlled prop the app supplies, search matches are the viewer's own.
26
+ */
27
+ type HighlightSource = "citation" | "search";
28
+ /** A request to point the viewer at part of the open document. */
29
+ interface DocumentHighlight {
30
+ /**
31
+ * Stable per highlight. It is what `activeHighlightId` names and what React
32
+ * keys on, so a list that renumbers between renders must not renumber ids.
33
+ */
34
+ id: string;
35
+ /** Which part of the document. */
36
+ address: DocumentAddress;
37
+ /**
38
+ * Short human label — the answer's claim, the source's title. Announced when
39
+ * this highlight becomes active, so prefer something a listener can act on
40
+ * over "Citation 3".
41
+ */
42
+ label?: string;
43
+ /** Defaults to `"citation"`. */
44
+ source?: HighlightSource;
45
+ }
46
+ /**
47
+ * How a request turned out.
48
+ *
49
+ * `unsupported` is a CAPABILITY GAP, not a failure — the same distinction the
50
+ * error panel already draws. "This build can't locate a rect in a Word file"
51
+ * is news about what we shipped; it is not the reader's mistake, and it is not
52
+ * retryable.
53
+ */
54
+ type HighlightStatus = "pending" | "resolved" | "not-found" | "unsupported";
55
+ /** Why a passage was not found — the two are genuinely different news. */
56
+ type HighlightMissReason =
57
+ /** Searched the whole projection; the passage is not in it. */
58
+ "absent"
59
+ /** The projection is capped, and the passage may lie past the cap. */
60
+ | "truncated";
61
+ /** A request, plus where it landed. What every adapter `Renderer` receives. */
62
+ interface ResolvedHighlight {
63
+ id: string;
64
+ label?: string;
65
+ source: HighlightSource;
66
+ status: HighlightStatus;
67
+ /** The original request, so a renderer can honour a kind the shell cannot. */
68
+ address: DocumentAddress;
69
+ /** Whether the viewer is currently pointed at this one. */
70
+ active: boolean;
71
+ /**
72
+ * 1-based position among the highlights of the same `source` that resolved —
73
+ * the "3" in "3 of 12". Absent when this one did not resolve, so a miss never
74
+ * silently consumes a number the reader is counting through.
75
+ */
76
+ index?: number;
77
+ /** Offsets into `document.text`, once located. */
78
+ range?: MatchRange;
79
+ /**
80
+ * 1-based page, set only for a `rect` address — the one kind with no range to
81
+ * derive a position from.
82
+ *
83
+ * A `quote` or `range` deliberately leaves this empty even when the caller
84
+ * supplied a page hint: where the passage actually landed is knowable from the
85
+ * adapter's own index, and a stale hint used as an instruction would page the
86
+ * reader somewhere the mark is not.
87
+ */
88
+ page?: number;
89
+ /** Geometry, for a `rect` address. */
90
+ rects?: readonly DocumentRect[];
91
+ /** Only on `not-found`. */
92
+ reason?: HighlightMissReason;
93
+ }
94
+ /**
95
+ * The most matches find-in-document will paint.
96
+ *
97
+ * A one-letter query against a 2 MB log matches hundreds of thousands of times;
98
+ * every one of those is a DOM element. The cap keeps typing responsive, and the
99
+ * chrome says so rather than quietly showing a wrong total.
100
+ */
101
+ declare const FIND_MATCH_LIMIT = 2000;
102
+ /** The id for the nth (0-based) find match. */
103
+ declare function findMatchId(index: number): string;
104
+ /** Whether an id belongs to find-in-document rather than to a caller. */
105
+ declare function isFindMatchId(id: string): boolean;
106
+ /** Which address kinds an adapter honours. Absent means none — the safe default. */
107
+ type HighlightSupport = readonly DocumentAddressKind[];
108
+
109
+ /**
110
+ * The map from a document's flat text projection back into its own model.
111
+ *
112
+ * `AdapterDocument.text` is the address space every text-based highlight is
113
+ * expressed in — but a renderer does not draw a flat string. It draws Word
114
+ * blocks, PDF pages, spreadsheet cells, code lines. So "characters 812–847 of
115
+ * the projection" has to become "the second half of block 14" before anything
116
+ * can be painted, and only the adapter knows how.
117
+ *
118
+ * That translation is the same bookkeeping every time — running offsets, a
119
+ * separator between chunks, a lookup by position — so it is written once here
120
+ * and each adapter supplies only its own `ref` type: a block index, a page
121
+ * number, a `{ sheet, row, column }` triple. The builder is what guarantees the
122
+ * projection and the index can never disagree, because the projection is its
123
+ * output rather than a second thing assembled by hand.
124
+ */
125
+
126
+ /** One chunk of the projection, and what it came from. */
127
+ interface TextSpan<TRef> {
128
+ /** Half-open `[start, end)` into the projection's text. */
129
+ start: number;
130
+ end: number;
131
+ /** The adapter's own pointer back into its model. */
132
+ ref: TRef;
133
+ }
134
+ /** A flat text projection plus the way back into the model that produced it. */
135
+ interface TextIndex<TRef> {
136
+ text: string;
137
+ /** Sorted by `start`, non-overlapping. Gaps are the separators between chunks. */
138
+ spans: readonly TextSpan<TRef>[];
139
+ }
140
+ interface TextIndexBuilderOptions {
141
+ /**
142
+ * Written between chunks and owned by no span, so a highlight can never be
143
+ * reported as covering the joint between two blocks. `"\n"` by default.
144
+ */
145
+ separator?: string;
146
+ }
147
+ interface TextIndexBuilder<TRef> {
148
+ /**
149
+ * Append a chunk. Empty chunks are skipped — an empty span matches nothing.
150
+ *
151
+ * `separator` overrides the builder's default for THIS joint only, which is
152
+ * how a multi-level projection stays one index: a workbook joins cells with a
153
+ * tab, rows with a newline and sheets with a blank line, and stitching three
154
+ * separate indexes together afterwards is exactly the offset bookkeeping this
155
+ * module exists to do once.
156
+ */
157
+ push(chunk: string, ref: TRef, separator?: string): void;
158
+ build(): TextIndex<TRef>;
159
+ }
160
+ /** Assemble a projection and its index together, so they cannot drift apart. */
161
+ declare function createTextIndexBuilder<TRef>(options?: TextIndexBuilderOptions): TextIndexBuilder<TRef>;
162
+ /** A span a range touches, with the overlap expressed in the CHUNK's own offsets. */
163
+ interface SpanOverlap<TRef> {
164
+ span: TextSpan<TRef>;
165
+ /** Offset into the chunk, not into the projection. */
166
+ start: number;
167
+ end: number;
168
+ }
169
+ /**
170
+ * Every chunk a projection range touches, clipped to that chunk.
171
+ *
172
+ * Binary search rather than a scan: a highlighted sentence in a 5,000-cell
173
+ * spreadsheet touches one or two spans, and walking all 5,000 to find them —
174
+ * once per highlight, on every render — is what makes a find-as-you-type box
175
+ * stutter.
176
+ */
177
+ declare function spansForRange<TRef>(index: TextIndex<TRef>, [start, end]: MatchRange): SpanOverlap<TRef>[];
178
+ /**
179
+ * Where the `index`-th part of a joined string begins, given where the join
180
+ * begins.
181
+ *
182
+ * The counterpart of pushing a whole row as one chunk: the REF can be the row
183
+ * while the MARK is still the character, because a cell's offset is the sum of
184
+ * the cells before it plus their separators. Used by every renderer that draws
185
+ * one chunk in several pieces — a Word table row, a spreadsheet row, a
186
+ * paragraph's styled runs. `undefined` in, `undefined` out, so a caller with no
187
+ * index does not need a branch.
188
+ */
189
+ declare function chunkOffset(parts: readonly string[], index: number, start: number | undefined, separator: string): number | undefined;
190
+ /** The chunk containing `offset`, or `undefined` when it lands on a separator. */
191
+ declare function spanAt<TRef>(index: TextIndex<TRef>, offset: number): TextSpan<TRef> | undefined;
192
+
193
+ /**
194
+ * The adapter protocol — what a format plugin declares, what it produces, and
195
+ * what renders it.
196
+ *
197
+ * ## The two halves, and why they are split
198
+ *
199
+ * A registry entry is an eager {@link AdapterManifest} plus a lazy loader. The
200
+ * manifest is plain data available the moment the registry is created, so the
201
+ * chrome can decide which controls to show and whether a file is openable at all
202
+ * **without downloading a parser**. The loader is a `() => import(…)` that pulls
203
+ * the parser AND its renderer together, only once a file of that kind is opened.
204
+ *
205
+ * ## Adapters emit DATA, not HTML (ADR 0024 §3)
206
+ *
207
+ * An adapter's `load()` returns an {@link AdapterDocument} — a model — and ships
208
+ * a `Renderer` that draws it with brand-ui components. No adapter returns an
209
+ * HTML string, so no adapter can smuggle a colour, a font or a border past the
210
+ * token layer. This is the single decision that keeps the viewer themeable.
211
+ */
212
+
213
+ /**
214
+ * The adapter protocol version. Bumped only when the shapes below change in a
215
+ * way an existing adapter would not survive; an adapter declaring a different
216
+ * version is rejected with a named error rather than crashing at render.
217
+ */
218
+ declare const PROTOCOL_VERSION = 1;
219
+ /**
220
+ * A quarter-turn clockwise rotation applied to the whole document.
221
+ *
222
+ * Document-level, not per-page: turning one page of a scan and leaving the rest
223
+ * is an AUTHORING act (it changes the file), and this package reads files.
224
+ */
225
+ type DocumentRotation = 0 | 90 | 180 | 270;
226
+ /** Scale the content to a dimension of the viewport instead of to a fixed number. */
227
+ type ZoomFit = "fit-width" | "fit-page";
228
+ /** One page's size at scale 1, in CSS pixels. */
229
+ interface PageSize {
230
+ width: number;
231
+ height: number;
232
+ }
233
+ /**
234
+ * What the chrome asks a renderer to draw at.
235
+ *
236
+ * A fit mode is a REQUEST, not a scale: only the renderer knows how wide its own
237
+ * viewport is and how big the page is, so it resolves the request and reports the
238
+ * number back through {@link AdapterRendererProps.onZoomResolved}. Resolving it
239
+ * in the provider would mean the shell measuring a box it does not own.
240
+ */
241
+ type ZoomLevel = number | ZoomFit;
242
+ /** What the chrome may offer for a format, known before any parser is fetched. */
243
+ interface AdapterCapabilities {
244
+ /** The document is paginated — show the pager. */
245
+ pages?: boolean;
246
+ /** The rendered content can be scaled — show the zoom control. */
247
+ zoom?: boolean;
248
+ /** The rendered content can be rotated. */
249
+ rotate?: boolean;
250
+ /**
251
+ * The document exposes text that can be searched.
252
+ *
253
+ * An OVERRIDE, not the source of truth: the shell offers find-in-document
254
+ * whenever a format has both a text projection and a text-based
255
+ * {@link AdapterCapabilities.highlight} kind, and reads this only when an
256
+ * adapter says otherwise. Left as the source of truth it would deny the find
257
+ * box to PDF — the format readers most expect it on — purely because that
258
+ * manifest predates the feature and never declared the flag.
259
+ *
260
+ * It overrides in the OFF direction only, so a declared `true` is inert: it
261
+ * cannot grant find to a format with no text or no range-painting renderer,
262
+ * because the box would then have nothing to show for a match.
263
+ */
264
+ search?: boolean;
265
+ /**
266
+ * Which {@link DocumentAddress} kinds this adapter's `Renderer` can actually
267
+ * paint. Absent means none.
268
+ *
269
+ * On the EAGER manifest on purpose: an app can decide whether to show a
270
+ * "jump to the cited passage" affordance before it downloads pdf.js.
271
+ *
272
+ * It is a promise the renderer has to keep — `pnpm viewer-highlight:check`
273
+ * fails a manifest that declares a kind its adapter never exercises.
274
+ */
275
+ highlight?: readonly DocumentAddressKind[];
276
+ /** The document has an outline/table of contents for the sidebar. */
277
+ outline?: boolean;
278
+ /** The document can produce page thumbnails for the sidebar. */
279
+ thumbnails?: boolean;
280
+ /** A plain-text projection exists — enables the raw/source toggle and copy. */
281
+ text?: boolean;
282
+ }
283
+ /**
284
+ * What an adapter can open. At least one of `extensions` / `mediaTypes` /
285
+ * `categories` must be present, or nothing will ever route to it.
286
+ */
287
+ interface AdapterManifest {
288
+ /**
289
+ * Stable identity and the OVERRIDE key: registering a different loader under
290
+ * an existing id replaces it, which is how a consumer swaps a built-in.
291
+ */
292
+ id: string;
293
+ /** Must equal {@link PROTOCOL_VERSION}. */
294
+ protocol: number;
295
+ /**
296
+ * Tie-break when several adapters claim the same file. Built-ins are `0`;
297
+ * register above it to win, below it to act only as a fallback.
298
+ */
299
+ priority?: number;
300
+ /** Extensions claimed, lowercase and without the dot. The most specific match. */
301
+ extensions?: string[];
302
+ /** MIME types claimed. A trailing slash makes it a prefix (`"image/"`). */
303
+ mediaTypes?: string[];
304
+ /** Coarse categories claimed. The broadest match — a catch-all fallback. */
305
+ categories?: FileCategory[];
306
+ /** Which chrome controls apply. Absent keys are `false`. */
307
+ capabilities?: AdapterCapabilities;
308
+ /**
309
+ * Optional peer packages this adapter dynamically imports. Purely
310
+ * informational to the registry — it is what the "install this to open that"
311
+ * message names when the import fails to resolve.
312
+ */
313
+ requires?: string[];
314
+ }
315
+ /**
316
+ * An adapter's parsed output. Adapters EXTEND this with their own payload; the
317
+ * fields here are the parts the shell itself understands, so it can drive the
318
+ * pager, the search box and the raw toggle without knowing the format.
319
+ */
320
+ interface AdapterDocument {
321
+ /** Adapter-defined discriminant, matched by that adapter's own `Renderer`. */
322
+ readonly kind: string;
323
+ /** Page count for a paginated document. Omit for single-surface formats. */
324
+ readonly pageCount?: number;
325
+ /**
326
+ * Each page's size at scale 1, in document order.
327
+ *
328
+ * What lets a continuously-scrolling renderer reserve the right height for a
329
+ * page it has not drawn yet, so the scrollbar means something from the first
330
+ * frame instead of growing as pages arrive. May be SHORTER than
331
+ * {@link AdapterDocument.pageCount} — measuring 900 pages up front costs 900
332
+ * round-trips to a worker, so an adapter is free to describe the pages it
333
+ * already touched and let the renderer assume the rest match.
334
+ */
335
+ readonly pageSizes?: readonly PageSize[];
336
+ /** Plain-text projection, when the format has one. Powers search, copy and the raw view. */
337
+ readonly text?: string;
338
+ /**
339
+ * Whether {@link AdapterDocument.text} is CAPPED rather than complete.
340
+ *
341
+ * Changes what "we couldn't find that passage" means: absent from the whole
342
+ * document, or simply past the part we previewed. Those deserve different
343
+ * sentences, and only the adapter knows which one is true.
344
+ */
345
+ readonly textTruncated?: boolean;
346
+ /**
347
+ * The map from {@link AdapterDocument.text} back into this adapter's own
348
+ * model, so a character range can become a block, a page or a cell.
349
+ *
350
+ * The `ref` type is the adapter's business — its own `Renderer` is the only
351
+ * thing that reads it, which is why it is `unknown` here.
352
+ */
353
+ readonly textIndex?: TextIndex<unknown>;
354
+ }
355
+ /** Everything an adapter is given while loading a document. */
356
+ interface AdapterLoadContext {
357
+ /** Cancels the load — always honour it; a stale parse must not win a race. */
358
+ signal?: AbortSignal;
359
+ /** Report determinate progress in `0..1` where the format allows it. */
360
+ onProgress?: (fraction: number) => void;
361
+ }
362
+ /** A parser instance. One per document — adapters may hold per-document state. */
363
+ interface FileAdapter {
364
+ load(source: ResolvedFileSource, context: AdapterLoadContext): Promise<AdapterDocument>;
365
+ /** Release anything the document holds (workers, object URLs, decoded buffers). */
366
+ dispose?(): void;
367
+ }
368
+ /** Props every adapter `Renderer` receives. */
369
+ interface AdapterRendererProps {
370
+ /** The value this adapter's own `load()` returned — narrow it by `kind`. */
371
+ document: AdapterDocument;
372
+ /** The source, for identity, `alt` text and download actions. */
373
+ source: ResolvedFileSource;
374
+ className?: string;
375
+ /**
376
+ * The heading rung a document's own top-level heading renders at.
377
+ *
378
+ * A viewed file carries its OWN heading tree, and that tree has to be
379
+ * relative to the page hosting it: a README's `#` inside an app that already
380
+ * has an `<h1>` would otherwise put two `h1`s in a screen reader's flat
381
+ * heading list. `FileViewerContent` passes the provider's value; an adapter
382
+ * that renders headings offsets by it (`clampHeadingLevel(own + base - 1)`),
383
+ * and one that renders none ignores it. Same seam as
384
+ * `@elabs-ai/components-ai`'s `MarkdownView baseHeadingLevel`.
385
+ *
386
+ * Optional, so adding it is not a `PROTOCOL_VERSION` change.
387
+ */
388
+ baseHeadingLevel?: ProseHeadingLevel;
389
+ /**
390
+ * The parts of the document to mark, already located — citations from the
391
+ * app and the viewer's own find-in-document matches, in document order.
392
+ *
393
+ * The shell has done LOCATE (a quote is now a character range); the renderer
394
+ * does MAP and PAINT, because only it knows that offsets 812–847 are the
395
+ * second half of block 14. An adapter renders only the entries whose `status`
396
+ * is `"resolved"` and whose `address.kind` it declared, and ignores the rest
397
+ * — the shell is what tells the reader about a miss.
398
+ *
399
+ * Optional, so adding it is not a `PROTOCOL_VERSION` change.
400
+ */
401
+ highlights?: readonly ResolvedHighlight[];
402
+ /**
403
+ * Which highlight the viewer is pointed at. The renderer scrolls it into
404
+ * view, pages to it if the format paginates, and draws it distinctly.
405
+ *
406
+ * Also present as `active` on the entry itself; this is the shortcut for a
407
+ * renderer that only needs to know whether anything changed.
408
+ */
409
+ activeHighlightId?: string | null;
410
+ /**
411
+ * Which page the reader is on, 1-based — the provider's, not the renderer's.
412
+ *
413
+ * Page, zoom and rotation are PROVIDER state (ADR 0026) so a control can live
414
+ * anywhere: the shell's own toolbar, an app's page header, a deep link. While
415
+ * these were `useState` inside each renderer nothing outside the canvas could
416
+ * read or drive them.
417
+ *
418
+ * Uncontrolled when absent — a renderer used outside a `FileViewerProvider`
419
+ * keeps its own page, so it still works standalone. Optional, so adding it is
420
+ * not a `PROTOCOL_VERSION` change.
421
+ */
422
+ pageNumber?: number;
423
+ /**
424
+ * Report a page the RENDERER navigated to on its own — turning to a cited
425
+ * page, or (once pages scroll continuously) the page scrolled into view. The
426
+ * provider is what keeps the pager's number honest.
427
+ */
428
+ onPageChange?: (page: number) => void;
429
+ /** The scale, or a fit mode to resolve. Absent means the renderer's own default. */
430
+ zoom?: ZoomLevel;
431
+ /**
432
+ * Report the scale a {@link ZoomFit} actually resolved to.
433
+ *
434
+ * Only a renderer knows this — it is the one measuring its viewport. Without
435
+ * it the chrome cannot show a percentage for a fitted page, and the reader's
436
+ * next "zoom in" would step from the last fixed stop rather than from what is
437
+ * on screen. A renderer that never resolves a fit mode never calls it.
438
+ */
439
+ onZoomResolved?: (scale: number) => void;
440
+ /** Quarter-turns clockwise. Ignored by renderers whose manifest omits `rotate`. */
441
+ rotation?: DocumentRotation;
442
+ }
443
+ /**
444
+ * What an adapter module exports. `manifest` is re-declared here so the registry
445
+ * can verify the loaded module is the one the eager manifest promised.
446
+ */
447
+ interface AdapterModule {
448
+ manifest: AdapterManifest;
449
+ /** Fresh instance per document — never a shared singleton. */
450
+ create(): FileAdapter;
451
+ Renderer: ComponentType<AdapterRendererProps>;
452
+ }
453
+ /** A lazy adapter loader. Always `() => import("…")` so nothing heavy is eager. */
454
+ type AdapterLoader = () => Promise<AdapterModule | {
455
+ default: AdapterModule;
456
+ }>;
457
+
458
+ /**
459
+ * The adapter registry — how a file finds its renderer.
460
+ *
461
+ * Architecture adapted from [anyview](https://github.com/harshpreet931/anyview)
462
+ * (MIT). Four properties are what make it work, and all four are deliberate:
463
+ *
464
+ * 1. **Eager manifests, lazy loaders.** Routing and capability questions are
465
+ * answered from plain data; the parser is fetched only when a file of that
466
+ * kind is actually opened.
467
+ * 2. **Priority override.** A consumer replaces a built-in by registering a
468
+ * higher priority — no fork, no patch.
469
+ * 3. **A fresh adapter per document.** The module (and its parser) is cached;
470
+ * the instance is not, so per-document state cannot leak between files.
471
+ * 4. **A protocol guard.** A mismatched adapter fails with a named error at
472
+ * registration, not with an undefined property at render.
473
+ */
474
+
475
+ /**
476
+ * How specifically a manifest claimed a file. Higher is more specific.
477
+ * A plain object rather than a `const enum` — the repo builds with
478
+ * `isolatedModules`, which cannot inline one.
479
+ */
480
+ declare const MatchScore: {
481
+ readonly None: 0;
482
+ readonly Category: 1;
483
+ readonly MediaTypePrefix: 2;
484
+ readonly MediaType: 3;
485
+ readonly Extension: 4;
486
+ };
487
+ type MatchScore = (typeof MatchScore)[keyof typeof MatchScore];
488
+ /** A registry of file adapters. Create with {@link createRegistry}. */
489
+ interface ViewerRegistry {
490
+ /**
491
+ * Add an adapter, or replace one with the same `id`.
492
+ *
493
+ * Replacement is by id and unconditional — the caller asked for it. Priority
494
+ * governs which of several DIFFERENT adapters wins a file, not whether a
495
+ * registration takes effect.
496
+ *
497
+ * @throws {ViewerError} `protocol-mismatch` if the manifest targets another protocol.
498
+ */
499
+ register(manifest: AdapterManifest, loader: AdapterLoader): void;
500
+ /**
501
+ * Every registered manifest, highest priority first. Not the routing order —
502
+ * specificity is per-file, so only {@link detect} can rank for a given file.
503
+ */
504
+ manifests(): AdapterManifest[];
505
+ /** The manifest that should open this file, or `undefined` if none claims it. */
506
+ detect(kind: FileKind): AdapterManifest | undefined;
507
+ /** Convenience over {@link detect} for a name + MIME. */
508
+ detectByName(name: string, mediaType?: string): AdapterManifest | undefined;
509
+ /**
510
+ * Fetch an adapter module by id, caching the MODULE (never an instance).
511
+ *
512
+ * @throws {ViewerError} `unsupported-format` when the id is unknown,
513
+ * `parser-missing` when the module's optional peer is not installed,
514
+ * `protocol-mismatch` when the loaded module disagrees with its manifest.
515
+ */
516
+ load(id: string): Promise<AdapterModule>;
517
+ }
518
+ /** How well `manifest` claims `kind`. `MatchScore.None` means "not mine". */
519
+ declare function scoreManifest(manifest: AdapterManifest, kind: FileKind): MatchScore;
520
+ /**
521
+ * Create an empty registry.
522
+ *
523
+ * Empty on purpose: the built-ins live in `createDefaultRegistry()`
524
+ * (`src/adapters`), so a consumer who wants only their own adapters — or only
525
+ * images — never pulls in the rest.
526
+ */
527
+ declare function createRegistry(): ViewerRegistry;
528
+
529
+ interface FileViewerProviderProps {
530
+ /** The file to show. `undefined` is the empty state, not an error. */
531
+ source?: FileSource;
532
+ /**
533
+ * Adapters available to this viewer. Defaults to the built-ins.
534
+ * Pass your own to add a format, drop one, or override a built-in.
535
+ */
536
+ registry?: ViewerRegistry;
537
+ /**
538
+ * Force the not-ready state while a parent fetches the source itself.
539
+ * ORed with the viewer's own loading — a parent can add loading, never remove it.
540
+ */
541
+ loading?: boolean;
542
+ /**
543
+ * The rung a viewed document's own top-level heading renders at. Default `2`.
544
+ *
545
+ * A file carries its OWN heading tree, and that tree is only correct relative
546
+ * to the page hosting it: a README's `#` rendered as an `<h1>` inside an app
547
+ * that already has one puts two `h1`s in a screen reader's flat heading list,
548
+ * and the frame's `<section aria-label>` does not fix that — most screen
549
+ * readers list headings flat, not per landmark. The default assumes the
550
+ * common case, a viewer embedded BELOW the page's own heading; pass `1` when
551
+ * the viewer genuinely is the page.
552
+ *
553
+ * Adapters that render no headings ignore it. Same seam as
554
+ * `@elabs-ai/components-ai`'s `MarkdownView baseHeadingLevel`.
555
+ */
556
+ baseHeadingLevel?: ProseHeadingLevel;
557
+ /**
558
+ * The parts of the document to point at — an answer's citations, a search
559
+ * result's context, anything the app already knows about the file.
560
+ *
561
+ * A PROP rather than provider-only state because citations originate outside
562
+ * the viewer entirely: the chat pane that produced them usually lives in
563
+ * another route, and it owns which one the reader clicked. Pass
564
+ * `defaultHighlights` instead to let the viewer own them.
565
+ */
566
+ highlights?: readonly DocumentHighlight[];
567
+ /** Uncontrolled initial citations. Ignored when `highlights` is supplied. */
568
+ defaultHighlights?: readonly DocumentHighlight[];
569
+ onHighlightsChange?: (highlights: readonly DocumentHighlight[]) => void;
570
+ /**
571
+ * Which citation the viewer is pointed at. `null` is "none" explicitly;
572
+ * `undefined` is what selects uncontrolled mode, so the two are not
573
+ * interchangeable here.
574
+ */
575
+ activeHighlightId?: string | null;
576
+ defaultActiveHighlightId?: string | null;
577
+ onActiveHighlightChange?: (id: string | null) => void;
578
+ /**
579
+ * Which page the viewer is on, 1-based — controlled.
580
+ *
581
+ * A trio (`component-api.md`) rather than provider-only state because the page
582
+ * is routinely something the APP owns: a deep link to page 7, a URL the reader
583
+ * can share, a position restored from a "continue reading" record. Clamped to
584
+ * the document on read, so an out-of-range value degrades to the nearest real
585
+ * page instead of blanking the canvas.
586
+ */
587
+ pageNumber?: number;
588
+ /** Uncontrolled initial page. Ignored when `pageNumber` is supplied. */
589
+ defaultPageNumber?: number;
590
+ onPageNumberChange?: (page: number) => void;
591
+ /**
592
+ * The scale to draw at, or a fit mode — controlled. Persisting a reader's
593
+ * preferred zoom across files and sessions is the reason this is a prop.
594
+ */
595
+ zoom?: ZoomLevel;
596
+ /**
597
+ * Uncontrolled initial zoom. Ignored when `zoom` is supplied.
598
+ *
599
+ * Defaults to `"fit-width"`, not `1`: a viewer's job on open is to show the
600
+ * document, and a 4000px scan or an A4 page at 100% in a 600px pane shows its
601
+ * top-left corner. Every reader-facing PDF viewer opens fitted for the same
602
+ * reason. Pass `1` for true 100%.
603
+ */
604
+ defaultZoom?: ZoomLevel;
605
+ onZoomChange?: (zoom: ZoomLevel) => void;
606
+ /** Quarter-turns clockwise — controlled. */
607
+ rotation?: DocumentRotation;
608
+ /** Uncontrolled initial rotation. Ignored when `rotation` is supplied. Default `0`. */
609
+ defaultRotation?: DocumentRotation;
610
+ onRotationChange?: (rotation: DocumentRotation) => void;
611
+ children: ReactNode;
612
+ }
613
+ declare function FileViewerProvider({ source, registry: registryProp, loading, baseHeadingLevel, highlights: highlightsProp, defaultHighlights, onHighlightsChange, activeHighlightId: activeHighlightIdProp, defaultActiveHighlightId, onActiveHighlightChange, pageNumber: pageNumberProp, defaultPageNumber, onPageNumberChange, zoom: zoomProp, defaultZoom, onZoomChange, rotation: rotationProp, defaultRotation, onRotationChange, children, }: FileViewerProviderProps): react.JSX.Element;
614
+ type FileViewerFrameProps = HTMLAttributes<HTMLDivElement>;
615
+ /**
616
+ * The bordered surface the toolbar and content sit in, and the scope of the
617
+ * viewer's find shortcut.
618
+ *
619
+ * Ctrl/Cmd+F is handled HERE rather than on `document`: a page may hold several
620
+ * viewers, or a viewer beside an editor that has its own find, and a
621
+ * document-level listener would let whichever mounted last win. Bound to the
622
+ * frame, the shortcut belongs to whichever viewer the reader is actually inside,
623
+ * and the browser's own find is untouched everywhere else on the page.
624
+ */
625
+ declare const FileViewerFrame: react.ForwardRefExoticComponent<FileViewerFrameProps & react.RefAttributes<HTMLDivElement>>;
626
+ interface FileViewerToolbarProps extends HTMLAttributes<HTMLDivElement> {
627
+ /** Extra controls, placed after the built-in actions. */
628
+ actions?: ReactNode;
629
+ }
630
+ /**
631
+ * The identity row: glyph, name, actions.
632
+ *
633
+ * No `role="toolbar"` — that role promises roving-tabindex arrow-key navigation,
634
+ * which this row does not implement. The same decision `ViewToolbar` made, and
635
+ * the reason the P1 `Toolbar` primitive exists (ADR 0024 §5a).
636
+ *
637
+ * With no file it renders NOTHING: a row holding a generic glyph and a blank
638
+ * name reads as a broken render, not as chrome. A screen that needs a permanent
639
+ * header composes its own row around `FileViewerFrame` — that is what the parts
640
+ * are for.
641
+ */
642
+ declare const FileViewerToolbar: react.ForwardRefExoticComponent<FileViewerToolbarProps & react.RefAttributes<HTMLDivElement>>;
643
+ type FileViewerHighlightStatusProps = HTMLAttributes<HTMLDivElement>;
644
+ /**
645
+ * "We couldn't find that passage."
646
+ *
647
+ * A citation that fails to locate must not fail SILENTLY: the reader clicked a
648
+ * source link and got a document that looks untouched, with no way to tell
649
+ * whether the viewer is broken, the passage moved, or they mis-clicked. The
650
+ * request survives resolution precisely so this line has something to say.
651
+ *
652
+ * Three different pieces of news, three sentences:
653
+ * - `not-found` / `absent` — searched the whole projection; it is not in it.
654
+ * - `not-found` / `truncated` — the projection is capped, so it may lie past it.
655
+ * - `unsupported` — a CAPABILITY GAP: this build cannot point at part of that
656
+ * format. Not the reader's fault and not retryable, same call the error panel
657
+ * already makes for `unsupported-format`.
658
+ *
659
+ * Search misses are excluded: the find bar already counts its own matches, and
660
+ * "No matches" there says it better than a second line here would.
661
+ */
662
+ declare const FileViewerHighlightStatus: react.ForwardRefExoticComponent<FileViewerHighlightStatusProps & react.RefAttributes<HTMLDivElement>>;
663
+ /**
664
+ * A layout-shaped skeleton, not a spinner: it occupies the box the real content
665
+ * will, so nothing shifts when the file arrives (`loading-states.md`).
666
+ * `aria-hidden` because `Skeleton` is decorative — the single live region on the
667
+ * wrapper is what AT hears.
668
+ */
669
+ declare const FileViewerSkeleton: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>;
670
+ declare const FileViewerError: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>;
671
+ declare const FileViewerEmpty: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>;
672
+ type FileViewerContentProps = HTMLAttributes<HTMLDivElement>;
673
+ /**
674
+ * The state switch: empty · loading · error · the adapter's own renderer.
675
+ *
676
+ * The adapter supplies its `Renderer` alongside its parser, so this component
677
+ * never grows a per-format `switch` — that is what keeps formats additive.
678
+ *
679
+ * **This is THE scroll boundary** for any adapter that does not manage its own
680
+ * viewport. Two nested `overflow-auto` boxes do not compose: the inner one clips
681
+ * while the outer one's padding stays put, so a long document ends flush against
682
+ * a band of whitespace and reads as a failed render rather than as "scroll for
683
+ * more". Adapters whose content simply flows (text, code, markdown, Word) let
684
+ * this scroll; the ones with a fixed sub-control and a scrolling body of their
685
+ * own — PDF pages, PowerPoint slides, a sheet under its tab bar — keep theirs
686
+ * and label it the same way.
687
+ *
688
+ * It is also a **focusable, named region**: a pane that scrolls but contains
689
+ * nothing focusable cannot be reached from a keyboard at all (WCAG 2.1.1), and
690
+ * a plain-text file contains nothing focusable by definition.
691
+ */
692
+ declare const FileViewerContent: react.ForwardRefExoticComponent<FileViewerContentProps & react.RefAttributes<HTMLDivElement>>;
693
+ interface FileViewerProps extends Omit<FileViewerProviderProps, "children">, Omit<HTMLAttributes<HTMLDivElement>, "children"> {
694
+ /** Replace the default composition. Rendered inside the provider AND the frame. */
695
+ children?: ReactNode;
696
+ }
697
+ /**
698
+ * The default composition — provider + frame + toolbar + content.
699
+ *
700
+ * Reach for the parts when you need a different arrangement; this covers the
701
+ * common case in one element.
702
+ */
703
+ declare const FileViewer: react.ForwardRefExoticComponent<FileViewerProps & react.RefAttributes<HTMLDivElement>>;
704
+
705
+ /** Whether a keyboard event is the platform's find shortcut. */
706
+ declare function isFindShortcut(event: {
707
+ key: string;
708
+ metaKey: boolean;
709
+ ctrlKey: boolean;
710
+ }): boolean;
711
+ type FileViewerFindProps = HTMLAttributes<HTMLDivElement>;
712
+ /**
713
+ * The search row. Renders nothing until `actions.openFind()` (or Ctrl/Cmd+F on
714
+ * the frame) opens it, and nothing at all for a document whose adapter cannot
715
+ * paint a range — an affordance that could never highlight anything is worse
716
+ * than no affordance at all.
717
+ */
718
+ declare const FileViewerFind: react.ForwardRefExoticComponent<FileViewerFindProps & react.RefAttributes<HTMLDivElement>>;
719
+
720
+ type FileViewerPagerProps = HTMLAttributes<HTMLDivElement>;
721
+ declare const FileViewerPager: react.ForwardRefExoticComponent<FileViewerPagerProps & react.RefAttributes<HTMLDivElement>>;
722
+
723
+ type FileViewerZoomProps = HTMLAttributes<HTMLDivElement>;
724
+ /**
725
+ * Zoom out · the current level · zoom in.
726
+ *
727
+ * The middle control is a `Select` rather than a read-out because the stops are
728
+ * the API: a reader who wants 200% should not have to press "+" four times, and
729
+ * the two fit modes have no number to press towards at all.
730
+ *
731
+ * Not a `role="toolbar"` — see `FileViewerPager`. It sits in the same row, and
732
+ * one row that is half roving-tabindex and half ordinary tab stops is worse for
733
+ * a keyboard reader than one that is consistently ordinary.
734
+ */
735
+ declare const FileViewerZoom: react.ForwardRefExoticComponent<FileViewerZoomProps & react.RefAttributes<HTMLDivElement>>;
736
+ type FileViewerRotateProps = HTMLAttributes<HTMLButtonElement>;
737
+ /**
738
+ * One button, one quarter-turn clockwise.
739
+ *
740
+ * Document-level and clockwise-only on purpose: a counter-clockwise button is a
741
+ * second control for something three presses of this one already do, and
742
+ * per-page rotation would change the file, which is authoring rather than
743
+ * viewing. `actions.rotate(-1)` is there for an app that wants the other one.
744
+ */
745
+ declare const FileViewerRotate: react.ForwardRefExoticComponent<FileViewerRotateProps & react.RefAttributes<HTMLButtonElement>>;
746
+
747
+ /**
748
+ * Typed failures, so the UI can decide what to SHOW without string-matching a
749
+ * message.
750
+ *
751
+ * The `message` on these errors is developer-facing — it goes to the console and
752
+ * to bug reports. Everything the user reads is looked up from {@link ViewerErrorCode}
753
+ * through `t()` (ADR 0017), which is why the code, not the prose, is the contract.
754
+ */
755
+ /** Why a file could not be shown. */
756
+ type ViewerErrorCode =
757
+ /** No registered adapter claims this file. */
758
+ "unsupported-format"
759
+ /** An adapter was built against a different registry protocol. */
760
+ | "protocol-mismatch"
761
+ /** The adapter exists but its optional peer dependency is not installed. */
762
+ | "parser-missing"
763
+ /** The bytes could not be obtained (network, permissions, a revoked blob). */
764
+ | "read-failed"
765
+ /** The bytes arrived but the parser rejected them. */
766
+ | "parse-failed"
767
+ /** The load was cancelled — a new source, or an unmounted view. */
768
+ | "aborted";
769
+ interface ViewerErrorOptions extends ErrorOptions {
770
+ /** The file this is about, for the developer-facing message. */
771
+ fileName?: string;
772
+ /** For `parser-missing`: the packages a consumer has to install. */
773
+ packages?: string[];
774
+ }
775
+ /** A failure a `FileViewer` can render, carrying a machine-readable {@link ViewerErrorCode}. */
776
+ declare class ViewerError extends Error {
777
+ readonly code: ViewerErrorCode;
778
+ readonly fileName?: string;
779
+ readonly packages?: string[];
780
+ constructor(code: ViewerErrorCode, message: string, options?: ViewerErrorOptions);
781
+ }
782
+ /** True for a {@link ViewerError}, without relying on `instanceof` across bundles. */
783
+ declare function isViewerError(value: unknown): value is ViewerError;
784
+ /** True when the failure is a cancellation rather than something to report. */
785
+ declare function isAbort(value: unknown): boolean;
786
+ /**
787
+ * Wrap an unknown throw as a {@link ViewerError}, preserving an existing one and
788
+ * mapping an abort to the `aborted` code so callers do not report cancellations
789
+ * as failures.
790
+ */
791
+ declare function toViewerError(value: unknown, fallbackCode: ViewerErrorCode, options?: ViewerErrorOptions): ViewerError;
792
+ /**
793
+ * A dynamic `import()` of an OPTIONAL peer that is not installed.
794
+ *
795
+ * Bundlers and Node report this differently (`ERR_MODULE_NOT_FOUND`,
796
+ * `Cannot find module`, `Failed to resolve module specifier`, Vite's
797
+ * `Failed to fetch dynamically imported module`), so match on the shapes rather
798
+ * than one runtime's wording. A false positive here is cheap — the user is told
799
+ * to install a package instead of seeing a raw parse error — and a false
800
+ * negative is the anyview failure mode this exists to avoid: an unhelpful
801
+ * module-resolution stack trace where a "install papaparse to open CSV files"
802
+ * message belongs.
803
+ */
804
+ declare function isModuleNotFound(value: unknown): boolean;
805
+ /**
806
+ * The `parser-missing` error, worded the same wherever the peer turns out to be
807
+ * absent.
808
+ *
809
+ * There are TWO such places, which is why this is a function and not a literal
810
+ * at one call site. The registry catches it when the adapter MODULE cannot be
811
+ * fetched; `FileViewerProvider` catches it when the module loaded fine and the
812
+ * adapter's own `await import("mammoth")` — inside `load()`, where every parser
813
+ * engine is actually reached — rejects. Without the second, a consumer who
814
+ * skipped an optional peer was told their file was damaged and offered a retry
815
+ * that could never work.
816
+ */
817
+ declare function parserMissingError(adapterId: string, packages: string[], options?: ViewerErrorOptions): ViewerError;
818
+
819
+ /**
820
+ * Where a file is in its journey to the screen.
821
+ *
822
+ * `loading` means "no renderable content yet" — the canonical signal from
823
+ * `.claude/rules/loading-states.md`, rendered as a layout-shaped skeleton.
824
+ * There is no separate `isStreaming`: a file arrives settled or not at all.
825
+ */
826
+ type FileViewerStatus = "empty" | "loading" | "ready" | "error";
827
+ /**
828
+ * The LOAD half of the state — everything the fetch-and-parse effect owns.
829
+ *
830
+ * Split from the highlight half because the two have different lifetimes: this
831
+ * one is replaced wholesale each time a file is opened, while the citations
832
+ * pointing into it are a prop the app controls and outlive any single parse.
833
+ * Folding them together would mean every `setState` in the load effect had to
834
+ * remember to carry the highlights forward.
835
+ */
836
+ interface FileViewerLoadState {
837
+ status: FileViewerStatus;
838
+ /** The resolved source, available as soon as there IS one — before any read. */
839
+ source?: ResolvedFileSource;
840
+ /** The adapter's parsed output. Only in `ready`. */
841
+ document?: AdapterDocument;
842
+ /** The adapter module that produced it, for its `Renderer`. Only in `ready`. */
843
+ adapter?: AdapterModule;
844
+ /** What the chrome may offer. Known from the manifest BEFORE the parser loads. */
845
+ capabilities: AdapterCapabilities;
846
+ /** Only in `error`. Always a `ViewerError`, so `code` can drive the message. */
847
+ error?: ViewerError;
848
+ }
849
+ /**
850
+ * How the open document is being LOOKED at — which page, at what scale, turned
851
+ * which way.
852
+ *
853
+ * Separate from the load state because it survives nothing and owns nothing: it
854
+ * is pure view, reset (page, rotation) or carried (zoom) when a new file opens.
855
+ * It lives in the provider rather than inside each adapter's `Renderer` so a
856
+ * control can sit anywhere — the shell toolbar, an app's own page header, a
857
+ * deep link — instead of only inside the canvas (ADR 0026).
858
+ */
859
+ interface FileViewerViewState {
860
+ /** 1-based. `1` for a format that does not paginate. */
861
+ pageNumber: number;
862
+ /** `0` until a paginated document is ready, and for formats with no pages. */
863
+ pageCount: number;
864
+ /** What was ASKED for: a fixed scale, or a fit mode the renderer resolves. */
865
+ zoom: ZoomLevel;
866
+ /**
867
+ * What that resolved to, as a number — what the zoom control shows and what
868
+ * `zoomIn`/`zoomOut` step from. Equal to `zoom` whenever `zoom` is a number.
869
+ */
870
+ effectiveZoom: number;
871
+ /** Quarter-turns clockwise. Reset to `0` when a different file is opened. */
872
+ rotation: DocumentRotation;
873
+ }
874
+ interface FileViewerState extends FileViewerLoadState, FileViewerViewState {
875
+ /**
876
+ * The parts of the document to point at, as REQUESTED. What was actually
877
+ * located is `meta.resolvedHighlights` — the request survives a miss so the
878
+ * chrome can say "we couldn't find that passage" instead of showing nothing.
879
+ */
880
+ highlights: readonly DocumentHighlight[];
881
+ /** Which one the viewer is pointed at. `null` is "none", explicitly. */
882
+ activeHighlightId: string | null;
883
+ find: FileViewerFindState;
884
+ }
885
+ /** What find-in-document is doing right now. */
886
+ interface FileViewerFindState {
887
+ /** Whether the search box is showing. */
888
+ open: boolean;
889
+ query: string;
890
+ caseSensitive: boolean;
891
+ /** How many matches the current query has. */
892
+ matches: number;
893
+ /** Whether that count hit `FIND_MATCH_LIMIT` and is therefore a floor. */
894
+ truncated: boolean;
895
+ /**
896
+ * Which match is current, 0-based — the "3" in "3 of 12", minus one.
897
+ *
898
+ * Deliberately NOT the same knob as `activeHighlightId`. Find is the viewer's
899
+ * own, and an app that controls `activeHighlightId` to drive citations would
900
+ * otherwise have to also honour every keystroke of a search it never asked
901
+ * for, or silently break next/previous.
902
+ */
903
+ activeIndex: number;
904
+ }
905
+ interface FileViewerActions {
906
+ /** Re-run the load. The retry action on the error state. */
907
+ reload: () => void;
908
+ /**
909
+ * Replace the citations. While `highlights` is controlled this writes no local
910
+ * state — but it still calls `onHighlightsChange`, so the owner can accept the
911
+ * request. Mirroring the platform: a controlled input reports, it does not
912
+ * self-update.
913
+ */
914
+ setHighlights: (highlights: readonly DocumentHighlight[]) => void;
915
+ /** Point the viewer at one highlight, or at none. */
916
+ setActiveHighlight: (id: string | null) => void;
917
+ /** Move to the next/previous CITATION in document order, wrapping around. */
918
+ nextHighlight: () => void;
919
+ previousHighlight: () => void;
920
+ openFind: () => void;
921
+ closeFind: () => void;
922
+ setFindQuery: (query: string) => void;
923
+ setFindCaseSensitive: (caseSensitive: boolean) => void;
924
+ /** Move to the next/previous SEARCH match, wrapping around. */
925
+ nextFindMatch: () => void;
926
+ previousFindMatch: () => void;
927
+ /**
928
+ * Turn to a page, 1-based. Clamped to the document — an out-of-range page is
929
+ * a caller's arithmetic slip, not a reason to blank the canvas.
930
+ */
931
+ goToPage: (page: number) => void;
932
+ /** Turn one page. Both stop at the ends rather than wrapping: a document is not a carousel. */
933
+ nextPage: () => void;
934
+ previousPage: () => void;
935
+ /** Draw at a fixed scale, or hand the renderer a fit mode to resolve. */
936
+ setZoom: (zoom: ZoomLevel) => void;
937
+ /**
938
+ * Step to the next stop above/below what is currently ON SCREEN — so zooming
939
+ * in from a fitted page continues from the fitted scale, not from wherever the
940
+ * fixed ladder was last parked.
941
+ */
942
+ zoomIn: () => void;
943
+ zoomOut: () => void;
944
+ setRotation: (rotation: DocumentRotation) => void;
945
+ /** Turn the document a quarter-turn: `1` clockwise, `-1` counter-clockwise. */
946
+ rotate: (quarterTurns: 1 | -1) => void;
947
+ /**
948
+ * The renderer's report channel for {@link FileViewerViewState.effectiveZoom}
949
+ * — not for app code. `FileViewerContent` wires it to the adapter's
950
+ * `onZoomResolved`, the same way `registerFind` is wired to a part rather than
951
+ * called by a consumer.
952
+ */
953
+ reportZoom: (scale: number) => void;
954
+ /**
955
+ * Tell the viewer a find part is mounted; call the returned function on
956
+ * unmount. `FileViewerFind` does this for you — it exists so the frame knows
957
+ * whether taking Ctrl/Cmd+F off the browser leads anywhere.
958
+ */
959
+ registerFind: () => () => void;
960
+ }
961
+ interface FileViewerContextValue {
962
+ state: FileViewerState;
963
+ actions: FileViewerActions;
964
+ registry: ViewerRegistry;
965
+ meta: {
966
+ /**
967
+ * The rung a viewed document's own top-level heading renders at. Passed to
968
+ * every adapter `Renderer`; see `AdapterRendererProps.baseHeadingLevel`.
969
+ */
970
+ baseHeadingLevel: ProseHeadingLevel;
971
+ /**
972
+ * Citations and find matches, LOCATED, in document order, numbered — what
973
+ * an adapter `Renderer` is handed and what the chrome counts.
974
+ */
975
+ resolvedHighlights: readonly ResolvedHighlight[];
976
+ /**
977
+ * Which highlight the viewer is EFFECTIVELY pointed at, and what an adapter
978
+ * `Renderer` receives as `activeHighlightId`.
979
+ *
980
+ * Not the same knob as `state.activeHighlightId`: that one is the citation
981
+ * the app controls, while the reader stepping through find matches is also
982
+ * "current". Find outranks the citation while its box is open and matching,
983
+ * so navigation and scrolling follow whichever the reader is actually moving.
984
+ */
985
+ currentHighlightId: string | null;
986
+ /** Which address kinds this document's adapter declared it can paint. */
987
+ highlightSupport: HighlightSupport;
988
+ /**
989
+ * Whether find-in-document applies to the open document. Derived, not read
990
+ * straight off the manifest — see `AdapterCapabilities.search`.
991
+ */
992
+ canFind: boolean;
993
+ /**
994
+ * Whether a `FileViewerFind` part is composed into this viewer.
995
+ *
996
+ * Separate from {@link canFind}, which only says the ADAPTER could paint a
997
+ * match. The frame needs both before it takes Ctrl/Cmd+F away from the
998
+ * browser: intercepting the shortcut with nowhere to type leaves a keyboard
999
+ * reader with no find at all.
1000
+ */
1001
+ hasFind: boolean;
1002
+ };
1003
+ }
1004
+ /**
1005
+ * Read the viewer state. Throws outside a provider — a part that silently
1006
+ * rendered nothing would be far harder to diagnose than a named error.
1007
+ */
1008
+ declare function useFileViewer(): FileViewerContextValue;
1009
+
1010
+ /**
1011
+ * The zoom vocabulary — the stops, and how the chrome moves between them.
1012
+ *
1013
+ * Lives in `core/` rather than in the PDF adapter (where the stops started)
1014
+ * because zoom is now provider state: the shell's zoom control, the provider's
1015
+ * `zoomIn`/`zoomOut` and every adapter that scales its content all have to agree
1016
+ * on the same ladder, or "125%" in the toolbar and the scale the page rendered
1017
+ * at drift apart.
1018
+ */
1019
+
1020
+ /**
1021
+ * Zoom stops, in document scale.
1022
+ *
1023
+ * Discrete rather than continuous: a slider is imprecise with a pointer and
1024
+ * impossible with a keyboard at this size, and "somewhere near 137%" is not a
1025
+ * state a reader ever wants to be in. The ladder is the one from the original
1026
+ * PDF pager, unchanged, so existing screens land on the same numbers.
1027
+ */
1028
+ declare const VIEWER_ZOOM_STEPS: readonly [0.5, 0.75, 1, 1.25, 1.5, 2, 3];
1029
+ /** 100% — the scale a document opens at. */
1030
+ declare const DEFAULT_ZOOM = 1;
1031
+ /** Whether a zoom level is a fit mode rather than a fixed scale. */
1032
+ declare function isZoomFit(zoom: ZoomLevel): zoom is ZoomFit;
1033
+ /**
1034
+ * The next stop above or below `from`.
1035
+ *
1036
+ * Takes an arbitrary scale, not an index, because a fit mode resolves to
1037
+ * whatever the viewport made it (1.37, say) and the reader's next "zoom in" has
1038
+ * to land on a real stop above that — an index-based pager would jump back to
1039
+ * wherever the ladder was last parked.
1040
+ */
1041
+ declare function stepZoom(from: number, delta: 1 | -1): number;
1042
+ /** Whether there is a stop left in that direction — what disables the button. */
1043
+ declare function canStepZoom(from: number, delta: 1 | -1): boolean;
1044
+
1045
+ /**
1046
+ * Turning a request into a location — the LOCATE step, and the only step that
1047
+ * runs outside the adapter.
1048
+ *
1049
+ * The funnel is three stages with three homes. **Locate** (here) answers "where
1050
+ * in the text projection is this?" and produces character offsets. **Map** (the
1051
+ * adapter's renderer) turns those offsets into its own model — block 14, page 3,
1052
+ * cell B7. **Paint** (also the renderer) draws it and scrolls to it.
1053
+ *
1054
+ * Locate lives in the shell rather than in each adapter because its OUTCOME is
1055
+ * chrome state, not pixels: "3 of 12", "we couldn't find that passage", "this
1056
+ * build can't locate a box in a Word file". Every adapter would otherwise write
1057
+ * that logic again, slightly differently, and the shell would have no way to
1058
+ * count what it is showing. It is also pure — no DOM, no engine — so it is
1059
+ * testable without jsdom.
1060
+ */
1061
+
1062
+ /**
1063
+ * Find a quoted passage in a normalized projection and map it back to raw
1064
+ * offsets.
1065
+ *
1066
+ * Both sides are folded first (whitespace, quote glyphs, case), because a
1067
+ * citation is re-typed by a model or extracted by a different tool and will
1068
+ * essentially never be byte-identical to what our parser produced.
1069
+ *
1070
+ * Ambiguity is resolved by the caller's own hints and never guessed at: an
1071
+ * explicit `occurrence` wins, then proximity to `near.offset`, then the first
1072
+ * match. Returning the first match silently would put a citation on the wrong
1073
+ * paragraph of a document that repeats a heading.
1074
+ */
1075
+ declare function locateQuote(normalized: NormalizedText, address: QuoteAddress): MatchRange | undefined;
1076
+ interface HighlightResolveContext {
1077
+ /**
1078
+ * The document's text projection, folded once with its offset map. Absent
1079
+ * when the format has no text projection at all.
1080
+ *
1081
+ * Pre-folded rather than raw because find-in-document re-resolves on every
1082
+ * keystroke, and folding a 2 MB projection per keystroke is what turns a
1083
+ * search box into a stutter.
1084
+ */
1085
+ normalized?: NormalizedText;
1086
+ /** Length of the RAW projection, for clamping `range` addresses. */
1087
+ textLength?: number;
1088
+ /** Whether that projection is capped, which changes what a miss MEANS. */
1089
+ truncated?: boolean;
1090
+ /** Which address kinds this document's adapter honours. */
1091
+ supported: HighlightSupport;
1092
+ /** The highlight the viewer is currently pointed at. */
1093
+ activeId?: string | null;
1094
+ }
1095
+ /**
1096
+ * Resolve every request, in document order, numbered per source.
1097
+ *
1098
+ * Document order rather than the caller's, because "next match" has to mean the
1099
+ * next one down the page — an app listing citations in relevance order would
1100
+ * otherwise send the reader jumping backwards. Numbering is per `source` so the
1101
+ * find box's "3 of 12" counts search matches only, and never the citations
1102
+ * painted beside them.
1103
+ */
1104
+ declare function resolveHighlights(highlights: readonly DocumentHighlight[], context: HighlightResolveContext): ResolvedHighlight[];
1105
+
1106
+ /**
1107
+ * The MAP + PAINT half of the highlight funnel, for adapters whose document IS
1108
+ * its own text projection — plain text, code, and anything else that renders
1109
+ * `document.text` verbatim.
1110
+ *
1111
+ * The shell has already done LOCATE (every quote is now a character range). All
1112
+ * that is left is to hand those ranges to `MatchHighlight` and say which one is
1113
+ * current. An adapter with a richer model (pages, blocks, cells) maps the same
1114
+ * ranges through its own `textIndex` instead and does not use this.
1115
+ */
1116
+
1117
+ /** What `MatchHighlight` takes: merged ranges, plus which merged mark is current. */
1118
+ interface MarkRanges {
1119
+ /** Clamped, sorted and merged — the same ranges `MatchHighlight` will paint. */
1120
+ readonly ranges: readonly MatchRange[];
1121
+ /** Index into {@link MarkRanges.ranges}, or `-1` when none is current. */
1122
+ readonly activeIndex: number;
1123
+ }
1124
+ /**
1125
+ * Fold resolved highlights into the ranges a `<mark>` layer paints.
1126
+ *
1127
+ * Merging happens HERE rather than being left to `MatchHighlight`, because the
1128
+ * two would otherwise disagree about what "the third mark" is: overlapping
1129
+ * citations collapse into one mark, and an `activeIndex` counted against the
1130
+ * unmerged list would then point at the wrong one — or past the end. The active
1131
+ * entry is located by which merged range CONTAINS its start, so it stays right
1132
+ * no matter how many requests collapsed into that mark.
1133
+ *
1134
+ * Non-`range` addresses and unresolved entries are skipped: an adapter paints
1135
+ * what it can and the shell is what tells the reader about the rest.
1136
+ */
1137
+ declare function toMarkRanges(highlights: readonly ResolvedHighlight[] | undefined, textLength: number): MarkRanges;
1138
+ /**
1139
+ * Re-express document ranges relative to one slice of the document — a code
1140
+ * token, a table cell, a block of prose.
1141
+ *
1142
+ * A renderer that draws its text in pieces cannot hand whole-document offsets to
1143
+ * a `<mark>` layer that only knows about the piece in front of it. Ranges are
1144
+ * clipped to the slice and rebased to 0; the ones that miss it entirely drop
1145
+ * out, which is also how a caller learns there is nothing to mark here (an empty
1146
+ * `ranges`). A range spanning several slices survives in each of them, so a
1147
+ * citation crossing a line break stays one visual run.
1148
+ */
1149
+ declare function localizeRanges({ ranges, activeIndex }: MarkRanges, start: number, end: number): MarkRanges;
1150
+
1151
+ /**
1152
+ * Bring the current highlight into view.
1153
+ *
1154
+ * Pointing at a passage is only half the job — a mark 4,000 lines down that
1155
+ * nobody scrolls to is the same as no mark at all. Every text-ish renderer needs
1156
+ * this, so it is written once here rather than per adapter.
1157
+ *
1158
+ * The current highlight is found by the stable `data-slot` selector its painter
1159
+ * emits, not by a ref: both painters produce their elements from a list — marks
1160
+ * inside `MatchHighlight`, boxes inside a page overlay — so the renderer has no
1161
+ * handle on the individual element and should not grow one just for scrolling.
1162
+ */
1163
+
1164
+ /**
1165
+ * How the current highlight identifies itself in the DOM, whichever way it was
1166
+ * painted: a `<mark>` in flowing text, a box over a page raster, or a whole
1167
+ * block plated because the format can only be addressed that coarsely.
1168
+ */
1169
+ declare const ACTIVE_HIGHLIGHT_SELECTOR: string;
1170
+ /**
1171
+ * Scroll the current highlight inside `containerRef` into view whenever the
1172
+ * viewer is pointed somewhere new.
1173
+ *
1174
+ * `block: "center"` rather than `"nearest"`: a cited passage should land where a
1175
+ * reader looks, with its surrounding context visible, not flush against the top
1176
+ * edge of the pane. Smooth scrolling is skipped under a reduced-motion
1177
+ * preference — an imperative scroll is invisible to the CSS media query, so the
1178
+ * check has to be explicit.
1179
+ *
1180
+ * `paintKey` is anything whose change means the highlights were repainted — a
1181
+ * page's box list, a re-tokenized document. A renderer that paints
1182
+ * asynchronously (a PDF fetches the page's text after the raster) has no element
1183
+ * to scroll to at the moment the active id changes, so it passes the thing it
1184
+ * was waiting for and the scroll runs once that arrives.
1185
+ */
1186
+ declare function useScrollActiveHighlightIntoView(containerRef: RefObject<HTMLElement | null>, activeHighlightId: string | null | undefined, paintKey?: unknown): void;
1187
+
1188
+ /**
1189
+ * Eager, data-only (see `image-manifest.ts`).
1190
+ *
1191
+ * Claims EXTENSIONS only — never the `code` category. That is what keeps the
1192
+ * plain-text adapter as the backstop: a `.log`, a `.env` or an unknown
1193
+ * extension still opens as text instead of failing to find a grammar, and a
1194
+ * consumer who never installs Shiki loses highlighting rather than the file.
1195
+ */
1196
+ declare const codeManifest: AdapterManifest;
1197
+
1198
+ /**
1199
+ * Eager, data-only (see `image-manifest.ts`).
1200
+ *
1201
+ * `requires` names the OPTIONAL peer this adapter dynamically imports. It is
1202
+ * what the "install this to open that" message names when the import cannot
1203
+ * resolve — the registry never installs anything itself.
1204
+ */
1205
+ declare const csvManifest: AdapterManifest;
1206
+
1207
+ /**
1208
+ * Word documents. `.doc` (the pre-2007 binary format) is deliberately absent:
1209
+ * mammoth reads OOXML only, and claiming an extension we cannot open would trade
1210
+ * an honest "can't preview this file type" panel for a parse error.
1211
+ */
1212
+ declare const docxManifest: AdapterManifest;
1213
+
1214
+ /**
1215
+ * Eager, data-only (see `image-manifest.ts`).
1216
+ *
1217
+ * `.mdx` is deliberately NOT claimed: it is JavaScript wearing markdown, and
1218
+ * rendering it would mean evaluating a foreign file's code — the one thing a
1219
+ * viewer must never do. An `.mdx` falls through to the plain-text adapter.
1220
+ */
1221
+ declare const markdownManifest: AdapterManifest;
1222
+
1223
+ /**
1224
+ * Eager, data-only. Split from the adapter module so `createDefaultRegistry`
1225
+ * can answer "can this be opened, and what controls apply" without pulling the
1226
+ * renderer into the entry chunk.
1227
+ */
1228
+ declare const imageManifest: AdapterManifest;
1229
+
1230
+ /** Eager, data-only (see `image-manifest.ts`). */
1231
+ declare const jsonManifest: AdapterManifest;
1232
+
1233
+ /**
1234
+ * Eager, data-only.
1235
+ *
1236
+ * No `requires`: the browser is the decoder, so this adapter has no optional
1237
+ * peer and can never report `parser-missing`. A codec the browser cannot play is
1238
+ * a different failure — the `<video>` element's own `error` event — and it is
1239
+ * reported where it happens, in the renderer.
1240
+ */
1241
+ declare const mediaManifest: AdapterManifest;
1242
+
1243
+ /**
1244
+ * Eager, data-only. Split from the adapter module so `createDefaultRegistry`
1245
+ * can answer "can this be opened, and what controls apply" without pulling
1246
+ * pdf.js — a ~1 MB engine — into the entry chunk.
1247
+ *
1248
+ * `requires` is what the "install this" message names when `pdfjs-dist` is not
1249
+ * present: the adapter is registered either way, so the reader is told what is
1250
+ * missing instead of being told the file type is unsupported.
1251
+ *
1252
+ * The only adapter that declares `rect`: a PDF page has a fixed coordinate
1253
+ * space, so a producer that knows where a passage sits on the page — an OCR
1254
+ * pipeline, a layout-aware chunker — can address it geometrically and skip the
1255
+ * text projection entirely.
1256
+ */
1257
+ declare const pdfManifest: AdapterManifest;
1258
+
1259
+ /**
1260
+ * PowerPoint decks. `.ppt` (the pre-2007 binary format) is not a zip at all, so
1261
+ * it is deliberately absent rather than claimed and then failed on.
1262
+ *
1263
+ * `requires: ["jszip"]` — the deck is unzipped here and the XML is parsed with
1264
+ * the platform's own `DOMParser`, so there is no PowerPoint library to install.
1265
+ */
1266
+ declare const pptxManifest: AdapterManifest;
1267
+
1268
+ /**
1269
+ * Eager, data-only (see `image-manifest.ts`).
1270
+ *
1271
+ * Claims only broad CATEGORIES, so any adapter naming an extension or an exact
1272
+ * MIME outranks it automatically — this is the "readable as text" backstop.
1273
+ */
1274
+ declare const textManifest: AdapterManifest;
1275
+
1276
+ /**
1277
+ * Excel workbooks. Data-only, eager — see `core/types.ts` for why the manifest
1278
+ * and the loader are split.
1279
+ *
1280
+ * `.csv` is deliberately NOT claimed here even though SheetJS can read it: the
1281
+ * `csv` adapter is a better answer for a delimited text file (it detects the
1282
+ * delimiter and needs a far smaller parser), and claiming it would make which
1283
+ * adapter wins depend on registration order.
1284
+ */
1285
+ declare const xlsxManifest: AdapterManifest;
1286
+
1287
+ /**
1288
+ * The built-in adapters, and the registry that has them all.
1289
+ *
1290
+ * Every entry is `manifest` (eager, plain data) + `() => import(…)` (lazy). The
1291
+ * manifests are imported statically ON PURPOSE — they are a few dozen bytes of
1292
+ * data each and answer "can this be opened, and what controls apply" with no
1293
+ * network. The parsers and renderers behind them are not.
1294
+ *
1295
+ * `pnpm heavy-deps:check` enforces the split: a static import of `papaparse`
1296
+ * (or, from P1, `pdfjs-dist`) fails CI, because those are optional peers — a
1297
+ * static edge does not merely bloat a chunk, it makes the package unresolvable
1298
+ * for every consumer that did not install that parser.
1299
+ */
1300
+
1301
+ /**
1302
+ * A registry with every built-in adapter registered.
1303
+ *
1304
+ * Call it per app (or per view) rather than sharing one module-level instance,
1305
+ * so one screen's `register()` override cannot leak into another's.
1306
+ */
1307
+ declare function createDefaultRegistry(): ViewerRegistry;
1308
+
1309
+ /**
1310
+ * The pdf.js seam — one place that knows the engine exists.
1311
+ *
1312
+ * Everything here is deliberately NOT React. Splitting the engine wiring from
1313
+ * the renderer keeps two things true: the dynamic `import("pdfjs-dist")` has a
1314
+ * single call site (so `heavy-deps:check` has one edge to police), and an app
1315
+ * can configure the worker before any file is ever opened.
1316
+ *
1317
+ * ## Why a worker is not optional
1318
+ *
1319
+ * pdf.js parses and rasterizes on a worker thread; without one it falls back to
1320
+ * doing that on the main thread, and a 40-page document freezes the tab. The
1321
+ * default below resolves the worker that ships INSIDE `pdfjs-dist`, via
1322
+ * `new URL(…, import.meta.url)` — the form every modern bundler understands, so
1323
+ * it works with no configuration in Vite/webpack/Next.
1324
+ *
1325
+ * ## Why an app may still have to configure it
1326
+ *
1327
+ * A CSP without `worker-src blob:` — or a bundler that cannot see through the
1328
+ * `new URL` form — needs the worker served as a real asset instead. Hence
1329
+ * {@link configurePdfEngine}: set `workerSrc` to a URL your app serves, and the
1330
+ * default is never consulted. `cMapUrl` / `standardFontDataUrl` are the same
1331
+ * story for CJK text and non-embedded fonts, which pdf.js fetches on demand.
1332
+ */
1333
+ /** Engine wiring an app can override. Every field is optional. */
1334
+ interface PdfEngineConfig {
1335
+ /** URL of `pdf.worker.min.mjs`. Defaults to the copy inside `pdfjs-dist`. */
1336
+ workerSrc?: string;
1337
+ /** Directory of pdf.js `cmaps/`, for documents with CJK text. */
1338
+ cMapUrl?: string;
1339
+ /** Directory of pdf.js `standard_fonts/`, for documents that embed no fonts. */
1340
+ standardFontDataUrl?: string;
1341
+ }
1342
+ /**
1343
+ * Point the PDF adapter at your own copies of the pdf.js assets.
1344
+ *
1345
+ * Call once at app start, before a PDF is opened. Calling it later is harmless
1346
+ * but only affects documents opened afterwards — pdf.js reads the worker
1347
+ * setting when a document is created.
1348
+ */
1349
+ declare function configurePdfEngine(next: PdfEngineConfig): void;
1350
+ /** What the adapter will actually use, after any {@link configurePdfEngine} call. */
1351
+ declare function getPdfEngineConfig(): Readonly<PdfEngineConfig>;
1352
+
1353
+ export { ACTIVE_HIGHLIGHT_SELECTOR, type AdapterCapabilities, type AdapterDocument, type AdapterLoadContext, type AdapterLoader, type AdapterManifest, type AdapterModule, type AdapterRendererProps, DEFAULT_ZOOM, type DocumentHighlight, type DocumentRotation, FIND_MATCH_LIMIT, type FileAdapter, FileViewer, type FileViewerActions, FileViewerContent, type FileViewerContentProps, type FileViewerContextValue, FileViewerEmpty, FileViewerError, FileViewerFind, type FileViewerFindProps, type FileViewerFindState, FileViewerFrame, type FileViewerFrameProps, FileViewerHighlightStatus, type FileViewerHighlightStatusProps, type FileViewerLoadState, FileViewerPager, type FileViewerPagerProps, type FileViewerProps, FileViewerProvider, type FileViewerProviderProps, FileViewerRotate, type FileViewerRotateProps, FileViewerSkeleton, type FileViewerState, type FileViewerStatus, FileViewerToolbar, type FileViewerToolbarProps, type FileViewerViewState, FileViewerZoom, type FileViewerZoomProps, type HighlightMissReason, type HighlightResolveContext, type HighlightSource, type HighlightStatus, type HighlightSupport, type MarkRanges, PROTOCOL_VERSION, type PdfEngineConfig, type ResolvedHighlight, type SpanOverlap, type TextIndex, type TextIndexBuilder, type TextIndexBuilderOptions, type TextSpan, VIEWER_ZOOM_STEPS, ViewerError, type ViewerErrorCode, type ViewerErrorOptions, type ViewerRegistry, type ZoomFit, type ZoomLevel, canStepZoom, chunkOffset, codeManifest, configurePdfEngine, createDefaultRegistry, createRegistry, createTextIndexBuilder, csvManifest, docxManifest, findMatchId, getPdfEngineConfig, imageManifest, isAbort, isFindMatchId, isFindShortcut, isModuleNotFound, isViewerError, isZoomFit, jsonManifest, localizeRanges, locateQuote, markdownManifest, mediaManifest, parserMissingError, pdfManifest, pptxManifest, resolveHighlights, scoreManifest, spanAt, spansForRange, stepZoom, textManifest, toMarkRanges, toViewerError, useFileViewer, useScrollActiveHighlightIntoView, xlsxManifest };