@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.
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/dist/chunk-2NQ4RSJ3.js +18 -0
- package/dist/chunk-2NQ4RSJ3.js.map +1 -0
- package/dist/chunk-3U3MESBU.js +21 -0
- package/dist/chunk-3U3MESBU.js.map +1 -0
- package/dist/chunk-4OFQYALG.js +147 -0
- package/dist/chunk-4OFQYALG.js.map +1 -0
- package/dist/chunk-5OMONA2G.js +87 -0
- package/dist/chunk-5OMONA2G.js.map +1 -0
- package/dist/chunk-AORNMH77.js +58 -0
- package/dist/chunk-AORNMH77.js.map +1 -0
- package/dist/chunk-BCF3RWRL.js +18 -0
- package/dist/chunk-BCF3RWRL.js.map +1 -0
- package/dist/chunk-FELIAGKI.js +22 -0
- package/dist/chunk-FELIAGKI.js.map +1 -0
- package/dist/chunk-GGR7BNW4.js +18 -0
- package/dist/chunk-GGR7BNW4.js.map +1 -0
- package/dist/chunk-H3OAON3D.js +22 -0
- package/dist/chunk-H3OAON3D.js.map +1 -0
- package/dist/chunk-HMG2ERXH.js +17 -0
- package/dist/chunk-HMG2ERXH.js.map +1 -0
- package/dist/chunk-KOMDRWOU.js +21 -0
- package/dist/chunk-KOMDRWOU.js.map +1 -0
- package/dist/chunk-NMA57QZ7.js +117 -0
- package/dist/chunk-NMA57QZ7.js.map +1 -0
- package/dist/chunk-RGWDIQJR.js +26 -0
- package/dist/chunk-RGWDIQJR.js.map +1 -0
- package/dist/chunk-SLXRUYT3.js +91 -0
- package/dist/chunk-SLXRUYT3.js.map +1 -0
- package/dist/chunk-UJIJECEF.js +21 -0
- package/dist/chunk-UJIJECEF.js.map +1 -0
- package/dist/chunk-UL43NGUG.js +68 -0
- package/dist/chunk-UL43NGUG.js.map +1 -0
- package/dist/chunk-VO2273Z2.js +55 -0
- package/dist/chunk-VO2273Z2.js.map +1 -0
- package/dist/code-adapter-ADZ4UOGN.js +226 -0
- package/dist/code-adapter-ADZ4UOGN.js.map +1 -0
- package/dist/csv-adapter-6VU3FFVU.js +100 -0
- package/dist/csv-adapter-6VU3FFVU.js.map +1 -0
- package/dist/docx-adapter-5CQDHWTD.js +383 -0
- package/dist/docx-adapter-5CQDHWTD.js.map +1 -0
- package/dist/image-adapter-WOHZR24J.js +132 -0
- package/dist/image-adapter-WOHZR24J.js.map +1 -0
- package/dist/index.d.ts +1353 -0
- package/dist/index.js +1364 -0
- package/dist/index.js.map +1 -0
- package/dist/json-adapter-ZUW5GQHE.js +90 -0
- package/dist/json-adapter-ZUW5GQHE.js.map +1 -0
- package/dist/markdown-adapter-YC6WTBS4.js +214 -0
- package/dist/markdown-adapter-YC6WTBS4.js.map +1 -0
- package/dist/media-adapter-MCTB4GBH.js +78 -0
- package/dist/media-adapter-MCTB4GBH.js.map +1 -0
- package/dist/pdf-adapter-5PMKEXUD.js +424 -0
- package/dist/pdf-adapter-5PMKEXUD.js.map +1 -0
- package/dist/pptx-adapter-6GEQLS2Z.js +351 -0
- package/dist/pptx-adapter-6GEQLS2Z.js.map +1 -0
- package/dist/text-adapter-NFNWB5W3.js +81 -0
- package/dist/text-adapter-NFNWB5W3.js.map +1 -0
- package/dist/xlsx-adapter-CM2Y6AKQ.js +167 -0
- package/dist/xlsx-adapter-CM2Y6AKQ.js.map +1 -0
- package/package.json +103 -0
- package/src/adapters/code/code-adapter.test.tsx +221 -0
- package/src/adapters/code/code-adapter.tsx +272 -0
- package/src/adapters/code/code-language.test.ts +45 -0
- package/src/adapters/code/code-language.ts +100 -0
- package/src/adapters/code/code-manifest.ts +19 -0
- package/src/adapters/code/code-theme.test.ts +46 -0
- package/src/adapters/code/code-theme.ts +99 -0
- package/src/adapters/csv/csv-adapter.test.tsx +169 -0
- package/src/adapters/csv/csv-adapter.tsx +146 -0
- package/src/adapters/csv/csv-manifest.ts +20 -0
- package/src/adapters/docx/docx-adapter.test.tsx +192 -0
- package/src/adapters/docx/docx-adapter.tsx +328 -0
- package/src/adapters/docx/docx-manifest.ts +18 -0
- package/src/adapters/docx/docx-model.test.ts +181 -0
- package/src/adapters/docx/docx-model.ts +294 -0
- package/src/adapters/image/image-adapter.test.tsx +141 -0
- package/src/adapters/image/image-adapter.tsx +191 -0
- package/src/adapters/image/image-manifest.ts +15 -0
- package/src/adapters/index.ts +64 -0
- package/src/adapters/json/json-adapter.test.tsx +77 -0
- package/src/adapters/json/json-adapter.tsx +141 -0
- package/src/adapters/json/json-manifest.ts +11 -0
- package/src/adapters/markdown/markdown-adapter.test.tsx +228 -0
- package/src/adapters/markdown/markdown-adapter.tsx +353 -0
- package/src/adapters/markdown/markdown-manifest.ts +21 -0
- package/src/adapters/markdown/markdown-marks.test.ts +85 -0
- package/src/adapters/markdown/markdown-marks.ts +119 -0
- package/src/adapters/media/media-adapter.test.tsx +79 -0
- package/src/adapters/media/media-adapter.tsx +123 -0
- package/src/adapters/media/media-manifest.ts +17 -0
- package/src/adapters/office-fixture.ts +239 -0
- package/src/adapters/pdf/pdf-adapter.test.tsx +489 -0
- package/src/adapters/pdf/pdf-adapter.tsx +599 -0
- package/src/adapters/pdf/pdf-engine.ts +171 -0
- package/src/adapters/pdf/pdf-fixture.ts +27 -0
- package/src/adapters/pdf/pdf-manifest.ts +30 -0
- package/src/adapters/pdf/pdf-text.test.ts +171 -0
- package/src/adapters/pdf/pdf-text.ts +202 -0
- package/src/adapters/pptx/pptx-adapter.test.tsx +241 -0
- package/src/adapters/pptx/pptx-adapter.tsx +341 -0
- package/src/adapters/pptx/pptx-manifest.ts +19 -0
- package/src/adapters/pptx/pptx-model.test.ts +219 -0
- package/src/adapters/pptx/pptx-model.ts +248 -0
- package/src/adapters/text/text-adapter.test.tsx +125 -0
- package/src/adapters/text/text-adapter.tsx +117 -0
- package/src/adapters/text/text-manifest.ts +19 -0
- package/src/adapters/xlsx/xlsx-adapter.test.tsx +159 -0
- package/src/adapters/xlsx/xlsx-adapter.tsx +250 -0
- package/src/adapters/xlsx/xlsx-manifest.ts +26 -0
- package/src/components/grid-text.test.ts +68 -0
- package/src/components/grid-text.ts +77 -0
- package/src/components/marked-text.tsx +36 -0
- package/src/components/sheet-table.tsx +142 -0
- package/src/core/errors.ts +120 -0
- package/src/core/highlight-marks.test.ts +95 -0
- package/src/core/highlight-marks.ts +95 -0
- package/src/core/highlight-resolve.test.ts +208 -0
- package/src/core/highlight-resolve.ts +191 -0
- package/src/core/highlight.ts +125 -0
- package/src/core/registry.test.ts +172 -0
- package/src/core/registry.ts +188 -0
- package/src/core/scroll-host.ts +42 -0
- package/src/core/text-index.test.ts +95 -0
- package/src/core/text-index.ts +159 -0
- package/src/core/types.ts +285 -0
- package/src/core/use-highlight-scroll.ts +59 -0
- package/src/core/use-page-control.ts +47 -0
- package/src/core/use-paged-scroll.ts +194 -0
- package/src/core/use-viewport-size.ts +54 -0
- package/src/core/zoom.ts +56 -0
- package/src/file-viewer/file-viewer-context.tsx +239 -0
- package/src/file-viewer/file-viewer-find.tsx +204 -0
- package/src/file-viewer/file-viewer-pager.tsx +120 -0
- package/src/file-viewer/file-viewer-zoom.tsx +166 -0
- package/src/file-viewer/file-viewer.stories.tsx +947 -0
- package/src/file-viewer/file-viewer.test.tsx +947 -0
- package/src/file-viewer/file-viewer.tsx +1143 -0
- package/src/index.ts +157 -0
package/dist/index.d.ts
ADDED
|
@@ -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 };
|