react-pdf-highlight-viewer 1.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/README.md +345 -0
- package/dist/components/PdfHighlighter.d.ts +16 -0
- package/dist/components/__tests__/PdfHighlighter.test.d.ts +1 -0
- package/dist/components/__tests__/highlighting.test.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/react-pdf-highlight-viewer.css +1 -0
- package/dist/react-pdf-highlighter.es.js +372 -0
- package/dist/react-pdf-highlighter.umd.js +6 -0
- package/dist/setupTests.d.ts +0 -0
- package/dist/types.d.ts +71 -0
- package/dist/vite.svg +1 -0
- package/package.json +84 -0
package/README.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# React PDF Highlighter
|
|
2
|
+
|
|
3
|
+
A lightweight, production-ready React library for rendering PDFs with text highlighting support. Built on top of `react-pdf` with a simple, intuitive API.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
✨ **Simple API** - Just pass a PDF file and an array of highlights
|
|
8
|
+
🎨 **Customizable Colors** - Set default or per-highlight colors
|
|
9
|
+
🔤 **Case-Insensitive Matching** - Optional case-insensitive text matching
|
|
10
|
+
📱 **Responsive** - Automatically adapts to container width
|
|
11
|
+
⚡ **Performance Optimized** - Efficient DOM manipulation and memoization
|
|
12
|
+
🔄 **Dynamic Updates** - Add/remove highlights on the fly
|
|
13
|
+
📦 **Lightweight** - Minimal bundle size with peer dependencies
|
|
14
|
+
🎯 **TypeScript Support** - Full type definitions included
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install react-pdf-highlight-viewer react react-dom react-pdf
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Peer Dependencies
|
|
23
|
+
|
|
24
|
+
This library requires the following peer dependencies:
|
|
25
|
+
|
|
26
|
+
- `react` ^19.2.0
|
|
27
|
+
- `react-dom` ^19.2.0
|
|
28
|
+
- `react-pdf` ^10.3.0
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { PdfHighlighter } from 'react-pdf-highlight-viewer';
|
|
34
|
+
import 'react-pdf-highlight-viewer/dist/react-pdf-highlight-viewer.css';
|
|
35
|
+
|
|
36
|
+
function App() {
|
|
37
|
+
const highlights = [
|
|
38
|
+
{ pageNumber: 1, content: "important text" },
|
|
39
|
+
{ pageNumber: 2, content: "another highlight", color: "#ff0000" }
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<PdfHighlighter
|
|
44
|
+
file="https://example.com/document.pdf"
|
|
45
|
+
highlights={highlights}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API Reference
|
|
52
|
+
|
|
53
|
+
### `<PdfHighlighter />`
|
|
54
|
+
|
|
55
|
+
Main component for rendering PDFs with highlights.
|
|
56
|
+
|
|
57
|
+
#### Props
|
|
58
|
+
|
|
59
|
+
| Prop | Type | Default | Description |
|
|
60
|
+
|------|------|---------|-------------|
|
|
61
|
+
| `file` | `string \| File \| null` | **required** | PDF source (URL, data URI, or File object) |
|
|
62
|
+
| `highlights` | `Highlight[]` | `[]` | Array of text highlights to apply |
|
|
63
|
+
| `width` | `number` | `undefined` | Page width in pixels (responsive if not set) |
|
|
64
|
+
| `loading` | `React.ReactNode` | `<div>Loading PDF...</div>` | Custom loading component |
|
|
65
|
+
| `defaultHighlightColor` | `string` | `'#ffeb3b'` | Default highlight background color |
|
|
66
|
+
| `defaultCaseSensitive` | `boolean` | `true` | Default case sensitivity for all highlights |
|
|
67
|
+
| `pageProps` | `Omit<PageProps, ...>` | `undefined` | Additional props to pass to each Page component |
|
|
68
|
+
| `...documentProps` | `DocumentProps` | - | All other react-pdf Document props are supported |
|
|
69
|
+
|
|
70
|
+
### `Highlight` Interface
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
interface Highlight {
|
|
74
|
+
pageNumber: number; // 1-indexed page number
|
|
75
|
+
content: string; // Exact text to highlight
|
|
76
|
+
color?: string; // Optional custom color (overrides default)
|
|
77
|
+
caseSensitive?: boolean; // Whether to match case-sensitively (default: true)
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Usage Examples
|
|
82
|
+
|
|
83
|
+
### Basic Highlighting
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { PdfHighlighter } from 'npm-react-pdf-lib';
|
|
87
|
+
import 'npm-react-pdf-lib/dist/npm-react-pdf-lib.css';
|
|
88
|
+
|
|
89
|
+
function BasicExample() {
|
|
90
|
+
return (
|
|
91
|
+
<PdfHighlighter
|
|
92
|
+
file="/path/to/document.pdf"
|
|
93
|
+
highlights={[
|
|
94
|
+
{ pageNumber: 1, content: "Chapter 1" }
|
|
95
|
+
]}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Custom Colors
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
function ColorExample() {
|
|
105
|
+
return (
|
|
106
|
+
<PdfHighlighter
|
|
107
|
+
file="/document.pdf"
|
|
108
|
+
defaultHighlightColor="#ffeb3b" // Yellow default
|
|
109
|
+
highlights={[
|
|
110
|
+
{ pageNumber: 1, content: "Important", color: "#ff0000" }, // Red
|
|
111
|
+
{ pageNumber: 1, content: "Note", color: "#00ff00" }, // Green
|
|
112
|
+
{ pageNumber: 2, content: "Warning" } // Uses default yellow
|
|
113
|
+
]}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Case-Insensitive Matching
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
function CaseInsensitiveExample() {
|
|
123
|
+
return (
|
|
124
|
+
<PdfHighlighter
|
|
125
|
+
file="/document.pdf"
|
|
126
|
+
highlights={[
|
|
127
|
+
// Will match "Hello World", "HELLO WORLD", "hello world", etc.
|
|
128
|
+
{ pageNumber: 1, content: "hello world", caseSensitive: false },
|
|
129
|
+
|
|
130
|
+
// Case-sensitive (default behavior)
|
|
131
|
+
{ pageNumber: 1, content: "Exact Match" },
|
|
132
|
+
|
|
133
|
+
// Override default with custom color
|
|
134
|
+
{ pageNumber: 2, content: "sample", caseSensitive: false, color: "#00ff00" }
|
|
135
|
+
]}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Set default case-insensitivity for all highlights
|
|
141
|
+
function GlobalCaseInsensitive() {
|
|
142
|
+
return (
|
|
143
|
+
<PdfHighlighter
|
|
144
|
+
file="/document.pdf"
|
|
145
|
+
defaultCaseSensitive={false} // All highlights are case-insensitive by default
|
|
146
|
+
highlights={[
|
|
147
|
+
{ pageNumber: 1, content: "sample text" }, // Case-insensitive
|
|
148
|
+
{ pageNumber: 2, content: "another example" }, // Case-insensitive
|
|
149
|
+
// Override to be case-sensitive for this one
|
|
150
|
+
{ pageNumber: 3, content: "ExactCase", caseSensitive: true }
|
|
151
|
+
]}
|
|
152
|
+
/>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Using React-PDF Props
|
|
158
|
+
|
|
159
|
+
The component extends `react-pdf`'s `DocumentProps`, so you can pass any Document or Page props:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
function AdvancedExample() {
|
|
163
|
+
return (
|
|
164
|
+
<PdfHighlighter
|
|
165
|
+
file="/document.pdf"
|
|
166
|
+
highlights={[{ pageNumber: 1, content: "highlight me" }]}
|
|
167
|
+
|
|
168
|
+
// Document props (from react-pdf)
|
|
169
|
+
onLoadSuccess={(pdf) => console.log('Loaded', pdf.numPages, 'pages')}
|
|
170
|
+
onLoadError={(error) => console.error('Load error:', error)}
|
|
171
|
+
onSourceSuccess={() => console.log('Source loaded')}
|
|
172
|
+
|
|
173
|
+
// Page props (applied to all pages)
|
|
174
|
+
pageProps={{
|
|
175
|
+
scale: 1.5, // Zoom level
|
|
176
|
+
renderTextLayer: true, // Enable text selection
|
|
177
|
+
renderAnnotationLayer: false, // Disable annotations
|
|
178
|
+
renderMode: 'canvas' // Render mode: 'canvas' | 'svg'
|
|
179
|
+
}}
|
|
180
|
+
/>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Dynamic Highlights
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
function DynamicExample() {
|
|
189
|
+
const [highlights, setHighlights] = useState([]);
|
|
190
|
+
|
|
191
|
+
const addHighlight = (text) => {
|
|
192
|
+
setHighlights(prev => [...prev, {
|
|
193
|
+
pageNumber: 1,
|
|
194
|
+
content: text
|
|
195
|
+
}]);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const clearHighlights = () => setHighlights([]);
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div>
|
|
202
|
+
<button onClick={() => addHighlight("search term")}>
|
|
203
|
+
Add Highlight
|
|
204
|
+
</button>
|
|
205
|
+
<button onClick={clearHighlights}>
|
|
206
|
+
Clear All
|
|
207
|
+
</button>
|
|
208
|
+
<PdfHighlighter
|
|
209
|
+
file="/document.pdf"
|
|
210
|
+
highlights={highlights}
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### With File Upload
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
function FileUploadExample() {
|
|
221
|
+
const [file, setFile] = useState(null);
|
|
222
|
+
|
|
223
|
+
const handleFileChange = (e) => {
|
|
224
|
+
setFile(e.target.files[0]);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div>
|
|
229
|
+
<input type="file" accept=".pdf" onChange={handleFileChange} />
|
|
230
|
+
{file && (
|
|
231
|
+
<PdfHighlighter
|
|
232
|
+
file={file}
|
|
233
|
+
highlights={[{ pageNumber: 1, content: "example" }]}
|
|
234
|
+
/>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Responsive Width
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
function ResponsiveExample() {
|
|
245
|
+
return (
|
|
246
|
+
<div style={{ maxWidth: '800px', margin: '0 auto' }}>
|
|
247
|
+
{/* PDF will adapt to container width */}
|
|
248
|
+
<PdfHighlighter
|
|
249
|
+
file="/document.pdf"
|
|
250
|
+
highlights={[]}
|
|
251
|
+
/>
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Fixed Width
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
function FixedWidthExample() {
|
|
261
|
+
return (
|
|
262
|
+
<PdfHighlighter
|
|
263
|
+
file="/document.pdf"
|
|
264
|
+
width={600} // Fixed 600px width
|
|
265
|
+
highlights={[]}
|
|
266
|
+
/>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Important Notes
|
|
272
|
+
|
|
273
|
+
### Text Matching
|
|
274
|
+
|
|
275
|
+
By default, the `content` field must **exactly match** the PDF's internal text representation:
|
|
276
|
+
|
|
277
|
+
- ✅ Correct: `"Hello World"` (if PDF has this exact text)
|
|
278
|
+
- ✅ Also works: `"hello world"` with `caseSensitive: false`
|
|
279
|
+
- ❌ Wrong: `"Hello World"` (extra space - whitespace must still match exactly)
|
|
280
|
+
|
|
281
|
+
**Tips**:
|
|
282
|
+
- Use `caseSensitive: false` to ignore case differences
|
|
283
|
+
- Copy text directly from the PDF viewer to ensure exact matching
|
|
284
|
+
- Whitespace must always match exactly, even with case-insensitive mode
|
|
285
|
+
|
|
286
|
+
### Whitespace Issues
|
|
287
|
+
|
|
288
|
+
PDFs sometimes store text differently than it appears visually. If highlighting doesn't work:
|
|
289
|
+
|
|
290
|
+
1. Open browser DevTools
|
|
291
|
+
2. Inspect the PDF text layer
|
|
292
|
+
3. Copy the exact text from the DOM
|
|
293
|
+
4. Use that text in your `content` field
|
|
294
|
+
|
|
295
|
+
Example:
|
|
296
|
+
```tsx
|
|
297
|
+
// Visual text: "purus aliquam"
|
|
298
|
+
// PDF internal: "purusaliquam" (no space!)
|
|
299
|
+
|
|
300
|
+
// Use the internal representation:
|
|
301
|
+
{ pageNumber: 1, content: "purusaliquam" }
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Troubleshooting
|
|
305
|
+
|
|
306
|
+
### Highlights Not Appearing
|
|
307
|
+
|
|
308
|
+
1. **Check text matching**: Ensure `content` exactly matches PDF text
|
|
309
|
+
2. **Verify page number**: Page numbers are 1-indexed (first page = 1)
|
|
310
|
+
3. **Check console**: Look for errors or warnings
|
|
311
|
+
4. **Inspect DOM**: Verify text exists in `.react-pdf__Page__textContent`
|
|
312
|
+
|
|
313
|
+
### PDF Not Loading
|
|
314
|
+
|
|
315
|
+
1. **CORS issues**: Ensure PDF URL allows cross-origin requests
|
|
316
|
+
2. **File path**: Verify the file path or URL is correct
|
|
317
|
+
3. **Worker error**: The library uses a CDN-hosted PDF.js worker by default
|
|
318
|
+
|
|
319
|
+
### Performance Issues
|
|
320
|
+
|
|
321
|
+
For large PDFs with many highlights:
|
|
322
|
+
|
|
323
|
+
1. Limit visible pages (implement pagination)
|
|
324
|
+
2. Debounce highlight updates
|
|
325
|
+
3. Use `React.memo` on parent components
|
|
326
|
+
|
|
327
|
+
## Browser Support
|
|
328
|
+
|
|
329
|
+
- Chrome/Edge: ✅ Latest 2 versions
|
|
330
|
+
- Firefox: ✅ Latest 2 versions
|
|
331
|
+
- Safari: ✅ Latest 2 versions
|
|
332
|
+
|
|
333
|
+
## License
|
|
334
|
+
|
|
335
|
+
MIT
|
|
336
|
+
|
|
337
|
+
## Contributing
|
|
338
|
+
|
|
339
|
+
Contributions are welcome! Please open an issue or PR on GitHub.
|
|
340
|
+
|
|
341
|
+
## Credits
|
|
342
|
+
|
|
343
|
+
Built with:
|
|
344
|
+
- [react-pdf](https://github.com/wojtekmaj/react-pdf) - PDF rendering
|
|
345
|
+
- [PDF.js](https://mozilla.github.io/pdf.js/) - PDF parsing (Mozilla)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { PdfHighlighterProps } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* PdfHighlighter - A React component for rendering PDFs with text highlighting
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* <PdfHighlighter
|
|
9
|
+
* file="https://example.com/document.pdf"
|
|
10
|
+
* highlights={[
|
|
11
|
+
* { pageNumber: 1, content: "important text" }
|
|
12
|
+
* ]}
|
|
13
|
+
* />
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare const PdfHighlighter: React.FC<PdfHighlighterProps>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--react-pdf-annotation-layer: 1;--annotation-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>");--input-focus-border-color: Highlight;--input-focus-outline: 1px solid Canvas;--input-unfocused-border-color: transparent;--input-disabled-border-color: transparent;--input-hover-border-color: black;--link-outline: none}@media screen and (forced-colors:active){:root{--input-focus-border-color: CanvasText;--input-unfocused-border-color: ActiveText;--input-disabled-border-color: GrayText;--input-hover-border-color: Highlight;--link-outline: 1.5px solid LinkText}.annotationLayer .textWidgetAnnotation :is(input,textarea):required,.annotationLayer .choiceWidgetAnnotation select:required,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:required{outline:1.5px solid selectedItem}.annotationLayer .linkAnnotation:hover{-webkit-backdrop-filter:invert(100%);backdrop-filter:invert(100%)}}.annotationLayer{position:absolute;top:0;left:0;pointer-events:none;transform-origin:0 0;z-index:3}.annotationLayer[data-main-rotation="90"] .norotate{transform:rotate(270deg) translate(-100%)}.annotationLayer[data-main-rotation="180"] .norotate{transform:rotate(180deg) translate(-100%,-100%)}.annotationLayer[data-main-rotation="270"] .norotate{transform:rotate(90deg) translateY(-100%)}.annotationLayer canvas{position:absolute;width:100%;height:100%}.annotationLayer section{position:absolute;text-align:initial;pointer-events:auto;box-sizing:border-box;margin:0;transform-origin:0 0}.annotationLayer .linkAnnotation{outline:var(--link-outline)}.textLayer.selecting~.annotationLayer section{pointer-events:none}.annotationLayer :is(.linkAnnotation,.buttonWidgetAnnotation.pushButton)>a{position:absolute;font-size:1em;top:0;left:0;width:100%;height:100%}.annotationLayer :is(.linkAnnotation,.buttonWidgetAnnotation.pushButton)>a:hover{opacity:.2;background:#ff0;box-shadow:0 2px 10px #ff0}.annotationLayer .textAnnotation img{position:absolute;cursor:pointer;width:100%;height:100%;top:0;left:0}.annotationLayer .textWidgetAnnotation :is(input,textarea),.annotationLayer .choiceWidgetAnnotation select,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input{background-image:var(--annotation-unfocused-field-background);border:2px solid var(--input-unfocused-border-color);box-sizing:border-box;font:calc(9px * var(--total-scale-factor)) sans-serif;height:100%;margin:0;vertical-align:top;width:100%}.annotationLayer .textWidgetAnnotation :is(input,textarea):required,.annotationLayer .choiceWidgetAnnotation select:required,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:required{outline:1.5px solid red}.annotationLayer .choiceWidgetAnnotation select option{padding:0}.annotationLayer .buttonWidgetAnnotation.radioButton input{border-radius:50%}.annotationLayer .textWidgetAnnotation textarea{resize:none}.annotationLayer .textWidgetAnnotation :is(input,textarea)[disabled],.annotationLayer .choiceWidgetAnnotation select[disabled],.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input[disabled]{background:none;border:2px solid var(--input-disabled-border-color);cursor:not-allowed}.annotationLayer .textWidgetAnnotation :is(input,textarea):hover,.annotationLayer .choiceWidgetAnnotation select:hover,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:hover{border:2px solid var(--input-hover-border-color)}.annotationLayer .textWidgetAnnotation :is(input,textarea):hover,.annotationLayer .choiceWidgetAnnotation select:hover,.annotationLayer .buttonWidgetAnnotation.checkBox input:hover{border-radius:2px}.annotationLayer .textWidgetAnnotation :is(input,textarea):focus,.annotationLayer .choiceWidgetAnnotation select:focus{background:none;border:2px solid var(--input-focus-border-color);border-radius:2px;outline:var(--input-focus-outline)}.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) :focus{background-image:none;background-color:transparent}.annotationLayer .buttonWidgetAnnotation.checkBox :focus{border:2px solid var(--input-focus-border-color);border-radius:2px;outline:var(--input-focus-outline)}.annotationLayer .buttonWidgetAnnotation.radioButton :focus{border:2px solid var(--input-focus-border-color);outline:var(--input-focus-outline)}.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before,.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after,.annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before{background-color:CanvasText;content:"";display:block;position:absolute}.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before,.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after{height:80%;left:45%;width:1px}.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before{transform:rotate(45deg)}.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after{transform:rotate(-45deg)}.annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before{border-radius:50%;height:50%;left:30%;top:20%;width:50%}.annotationLayer .textWidgetAnnotation input.comb{font-family:monospace;padding-left:2px;padding-right:0}.annotationLayer .textWidgetAnnotation input.comb:focus{width:103%}.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input{appearance:none}.annotationLayer .popupTriggerArea{height:100%;width:100%}.annotationLayer .fileAttachmentAnnotation .popupTriggerArea{position:absolute}.annotationLayer .popupWrapper{position:absolute;font-size:calc(9px * var(--total-scale-factor));width:100%;min-width:calc(180px * var(--total-scale-factor));pointer-events:none}.annotationLayer .popup{position:absolute;max-width:calc(180px * var(--total-scale-factor));background-color:#ff9;box-shadow:0 calc(2px * var(--total-scale-factor)) calc(5px * var(--total-scale-factor)) #888;border-radius:calc(2px * var(--total-scale-factor));padding:calc(6px * var(--total-scale-factor));margin-left:calc(5px * var(--total-scale-factor));cursor:pointer;font:message-box;white-space:normal;word-wrap:break-word;pointer-events:auto}.annotationLayer .popup>*{font-size:calc(9px * var(--total-scale-factor))}.annotationLayer .popup h1{display:inline-block}.annotationLayer .popupDate{display:inline-block;margin-left:calc(5px * var(--total-scale-factor))}.annotationLayer .popupContent{border-top:1px solid rgba(51,51,51,1);margin-top:calc(2px * var(--total-scale-factor));padding-top:calc(2px * var(--total-scale-factor))}.annotationLayer .richText>*{white-space:pre-wrap;font-size:calc(9px * var(--total-scale-factor))}.annotationLayer .highlightAnnotation,.annotationLayer .underlineAnnotation,.annotationLayer .squigglyAnnotation,.annotationLayer .strikeoutAnnotation,.annotationLayer .freeTextAnnotation,.annotationLayer .lineAnnotation svg line,.annotationLayer .squareAnnotation svg rect,.annotationLayer .circleAnnotation svg ellipse,.annotationLayer .polylineAnnotation svg polyline,.annotationLayer .polygonAnnotation svg polygon,.annotationLayer .caretAnnotation,.annotationLayer .inkAnnotation svg polyline,.annotationLayer .stampAnnotation,.annotationLayer .fileAttachmentAnnotation{cursor:pointer}.annotationLayer section svg{position:absolute;width:100%;height:100%;top:0;left:0}.annotationLayer .annotationTextContent{position:absolute;width:100%;height:100%;opacity:0;color:transparent;-webkit-user-select:none;user-select:none;pointer-events:none}.annotationLayer .annotationTextContent span{width:100%;display:inline-block}:root{--react-pdf-text-layer: 1;--highlight-bg-color: rgba(180, 0, 170, 1);--highlight-selected-bg-color: rgba(0, 100, 0, 1)}@media screen and (forced-colors:active){:root{--highlight-bg-color: Highlight;--highlight-selected-bg-color: ButtonText}}[data-main-rotation="90"]{transform:rotate(90deg) translateY(-100%)}[data-main-rotation="180"]{transform:rotate(180deg) translate(-100%,-100%)}[data-main-rotation="270"]{transform:rotate(270deg) translate(-100%)}.textLayer{position:absolute;text-align:initial;inset:0;overflow:hidden;line-height:1;text-size-adjust:none;forced-color-adjust:none;transform-origin:0 0;z-index:2}.textLayer :is(span,br){color:transparent;position:absolute;white-space:pre;cursor:text;margin:0;transform-origin:0 0}.textLayer span.markedContent{top:0;height:0}.textLayer .highlight{margin:-1px;padding:1px;background-color:var(--highlight-bg-color);border-radius:4px}.textLayer .highlight.appended{position:initial}.textLayer .highlight.begin{border-radius:4px 0 0 4px}.textLayer .highlight.end{border-radius:0 4px 4px 0}.textLayer .highlight.middle{border-radius:0}.textLayer .highlight.selected{background-color:var(--highlight-selected-bg-color)}.textLayer br::selection{background:transparent}.textLayer .endOfContent{display:block;position:absolute;inset:100% 0 0;z-index:-1;cursor:default;-webkit-user-select:none;user-select:none}.textLayer.selecting .endOfContent{top:0}.hiddenCanvasElement{position:absolute;top:0;left:0;width:0;height:0;display:none}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import ae, { useState as te, useCallback as ne, useMemo as se, useRef as B, useEffect as Z } from "react";
|
|
2
|
+
import { pdfjs as Q, Document as ce, Page as le } from "react-pdf";
|
|
3
|
+
var W = { exports: {} }, Y = {};
|
|
4
|
+
var K;
|
|
5
|
+
function ue() {
|
|
6
|
+
if (K) return Y;
|
|
7
|
+
K = 1;
|
|
8
|
+
var c = /* @__PURE__ */ Symbol.for("react.transitional.element"), v = /* @__PURE__ */ Symbol.for("react.fragment");
|
|
9
|
+
function f(_, a, l) {
|
|
10
|
+
var u = null;
|
|
11
|
+
if (l !== void 0 && (u = "" + l), a.key !== void 0 && (u = "" + a.key), "key" in a) {
|
|
12
|
+
l = {};
|
|
13
|
+
for (var d in a)
|
|
14
|
+
d !== "key" && (l[d] = a[d]);
|
|
15
|
+
} else l = a;
|
|
16
|
+
return a = l.ref, {
|
|
17
|
+
$$typeof: c,
|
|
18
|
+
type: _,
|
|
19
|
+
key: u,
|
|
20
|
+
ref: a !== void 0 ? a : null,
|
|
21
|
+
props: l
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return Y.Fragment = v, Y.jsx = f, Y.jsxs = f, Y;
|
|
25
|
+
}
|
|
26
|
+
var $ = {};
|
|
27
|
+
var ee;
|
|
28
|
+
function ie() {
|
|
29
|
+
return ee || (ee = 1, process.env.NODE_ENV !== "production" && (function() {
|
|
30
|
+
function c(e) {
|
|
31
|
+
if (e == null) return null;
|
|
32
|
+
if (typeof e == "function")
|
|
33
|
+
return e.$$typeof === O ? null : e.displayName || e.name || null;
|
|
34
|
+
if (typeof e == "string") return e;
|
|
35
|
+
switch (e) {
|
|
36
|
+
case i:
|
|
37
|
+
return "Fragment";
|
|
38
|
+
case z:
|
|
39
|
+
return "Profiler";
|
|
40
|
+
case T:
|
|
41
|
+
return "StrictMode";
|
|
42
|
+
case h:
|
|
43
|
+
return "Suspense";
|
|
44
|
+
case V:
|
|
45
|
+
return "SuspenseList";
|
|
46
|
+
case R:
|
|
47
|
+
return "Activity";
|
|
48
|
+
}
|
|
49
|
+
if (typeof e == "object")
|
|
50
|
+
switch (typeof e.tag == "number" && console.error(
|
|
51
|
+
"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
|
|
52
|
+
), e.$$typeof) {
|
|
53
|
+
case I:
|
|
54
|
+
return "Portal";
|
|
55
|
+
case G:
|
|
56
|
+
return e.displayName || "Context";
|
|
57
|
+
case M:
|
|
58
|
+
return (e._context.displayName || "Context") + ".Consumer";
|
|
59
|
+
case J:
|
|
60
|
+
var r = e.render;
|
|
61
|
+
return e = e.displayName, e || (e = r.displayName || r.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e;
|
|
62
|
+
case A:
|
|
63
|
+
return r = e.displayName || null, r !== null ? r : c(e.type) || "Memo";
|
|
64
|
+
case p:
|
|
65
|
+
r = e._payload, e = e._init;
|
|
66
|
+
try {
|
|
67
|
+
return c(e(r));
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
function v(e) {
|
|
74
|
+
return "" + e;
|
|
75
|
+
}
|
|
76
|
+
function f(e) {
|
|
77
|
+
try {
|
|
78
|
+
v(e);
|
|
79
|
+
var r = !1;
|
|
80
|
+
} catch {
|
|
81
|
+
r = !0;
|
|
82
|
+
}
|
|
83
|
+
if (r) {
|
|
84
|
+
r = console;
|
|
85
|
+
var t = r.error, n = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
|
|
86
|
+
return t.call(
|
|
87
|
+
r,
|
|
88
|
+
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
|
|
89
|
+
n
|
|
90
|
+
), v(e);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function _(e) {
|
|
94
|
+
if (e === i) return "<>";
|
|
95
|
+
if (typeof e == "object" && e !== null && e.$$typeof === p)
|
|
96
|
+
return "<...>";
|
|
97
|
+
try {
|
|
98
|
+
var r = c(e);
|
|
99
|
+
return r ? "<" + r + ">" : "<...>";
|
|
100
|
+
} catch {
|
|
101
|
+
return "<...>";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function a() {
|
|
105
|
+
var e = j.A;
|
|
106
|
+
return e === null ? null : e.getOwner();
|
|
107
|
+
}
|
|
108
|
+
function l() {
|
|
109
|
+
return Error("react-stack-top-frame");
|
|
110
|
+
}
|
|
111
|
+
function u(e) {
|
|
112
|
+
if (w.call(e, "key")) {
|
|
113
|
+
var r = Object.getOwnPropertyDescriptor(e, "key").get;
|
|
114
|
+
if (r && r.isReactWarning) return !1;
|
|
115
|
+
}
|
|
116
|
+
return e.key !== void 0;
|
|
117
|
+
}
|
|
118
|
+
function d(e, r) {
|
|
119
|
+
function t() {
|
|
120
|
+
C || (C = !0, console.error(
|
|
121
|
+
"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
|
|
122
|
+
r
|
|
123
|
+
));
|
|
124
|
+
}
|
|
125
|
+
t.isReactWarning = !0, Object.defineProperty(e, "key", {
|
|
126
|
+
get: t,
|
|
127
|
+
configurable: !0
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function g() {
|
|
131
|
+
var e = c(this.type);
|
|
132
|
+
return N[e] || (N[e] = !0, console.error(
|
|
133
|
+
"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
|
|
134
|
+
)), e = this.props.ref, e !== void 0 ? e : null;
|
|
135
|
+
}
|
|
136
|
+
function x(e, r, t, n, q, H) {
|
|
137
|
+
var o = t.ref;
|
|
138
|
+
return e = {
|
|
139
|
+
$$typeof: U,
|
|
140
|
+
type: e,
|
|
141
|
+
key: r,
|
|
142
|
+
props: t,
|
|
143
|
+
_owner: n
|
|
144
|
+
}, (o !== void 0 ? o : null) !== null ? Object.defineProperty(e, "ref", {
|
|
145
|
+
enumerable: !1,
|
|
146
|
+
get: g
|
|
147
|
+
}) : Object.defineProperty(e, "ref", { enumerable: !1, value: null }), e._store = {}, Object.defineProperty(e._store, "validated", {
|
|
148
|
+
configurable: !1,
|
|
149
|
+
enumerable: !1,
|
|
150
|
+
writable: !0,
|
|
151
|
+
value: 0
|
|
152
|
+
}), Object.defineProperty(e, "_debugInfo", {
|
|
153
|
+
configurable: !1,
|
|
154
|
+
enumerable: !1,
|
|
155
|
+
writable: !0,
|
|
156
|
+
value: null
|
|
157
|
+
}), Object.defineProperty(e, "_debugStack", {
|
|
158
|
+
configurable: !1,
|
|
159
|
+
enumerable: !1,
|
|
160
|
+
writable: !0,
|
|
161
|
+
value: q
|
|
162
|
+
}), Object.defineProperty(e, "_debugTask", {
|
|
163
|
+
configurable: !1,
|
|
164
|
+
enumerable: !1,
|
|
165
|
+
writable: !0,
|
|
166
|
+
value: H
|
|
167
|
+
}), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
|
|
168
|
+
}
|
|
169
|
+
function k(e, r, t, n, q, H) {
|
|
170
|
+
var o = r.children;
|
|
171
|
+
if (o !== void 0)
|
|
172
|
+
if (n)
|
|
173
|
+
if (D(o)) {
|
|
174
|
+
for (n = 0; n < o.length; n++)
|
|
175
|
+
E(o[n]);
|
|
176
|
+
Object.freeze && Object.freeze(o);
|
|
177
|
+
} else
|
|
178
|
+
console.error(
|
|
179
|
+
"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
|
|
180
|
+
);
|
|
181
|
+
else E(o);
|
|
182
|
+
if (w.call(r, "key")) {
|
|
183
|
+
o = c(e);
|
|
184
|
+
var S = Object.keys(r).filter(function(oe) {
|
|
185
|
+
return oe !== "key";
|
|
186
|
+
});
|
|
187
|
+
n = 0 < S.length ? "{key: someKey, " + S.join(": ..., ") + ": ...}" : "{key: someKey}", b[o + n] || (S = 0 < S.length ? "{" + S.join(": ..., ") + ": ...}" : "{}", console.error(
|
|
188
|
+
`A props object containing a "key" prop is being spread into JSX:
|
|
189
|
+
let props = %s;
|
|
190
|
+
<%s {...props} />
|
|
191
|
+
React keys must be passed directly to JSX without using spread:
|
|
192
|
+
let props = %s;
|
|
193
|
+
<%s key={someKey} {...props} />`,
|
|
194
|
+
n,
|
|
195
|
+
o,
|
|
196
|
+
S,
|
|
197
|
+
o
|
|
198
|
+
), b[o + n] = !0);
|
|
199
|
+
}
|
|
200
|
+
if (o = null, t !== void 0 && (f(t), o = "" + t), u(r) && (f(r.key), o = "" + r.key), "key" in r) {
|
|
201
|
+
t = {};
|
|
202
|
+
for (var X in r)
|
|
203
|
+
X !== "key" && (t[X] = r[X]);
|
|
204
|
+
} else t = r;
|
|
205
|
+
return o && d(
|
|
206
|
+
t,
|
|
207
|
+
typeof e == "function" ? e.displayName || e.name || "Unknown" : e
|
|
208
|
+
), x(
|
|
209
|
+
e,
|
|
210
|
+
o,
|
|
211
|
+
t,
|
|
212
|
+
a(),
|
|
213
|
+
q,
|
|
214
|
+
H
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
function E(e) {
|
|
218
|
+
s(e) ? e._store && (e._store.validated = 1) : typeof e == "object" && e !== null && e.$$typeof === p && (e._payload.status === "fulfilled" ? s(e._payload.value) && e._payload.value._store && (e._payload.value._store.validated = 1) : e._store && (e._store.validated = 1));
|
|
219
|
+
}
|
|
220
|
+
function s(e) {
|
|
221
|
+
return typeof e == "object" && e !== null && e.$$typeof === U;
|
|
222
|
+
}
|
|
223
|
+
var m = ae, U = /* @__PURE__ */ Symbol.for("react.transitional.element"), I = /* @__PURE__ */ Symbol.for("react.portal"), i = /* @__PURE__ */ Symbol.for("react.fragment"), T = /* @__PURE__ */ Symbol.for("react.strict_mode"), z = /* @__PURE__ */ Symbol.for("react.profiler"), M = /* @__PURE__ */ Symbol.for("react.consumer"), G = /* @__PURE__ */ Symbol.for("react.context"), J = /* @__PURE__ */ Symbol.for("react.forward_ref"), h = /* @__PURE__ */ Symbol.for("react.suspense"), V = /* @__PURE__ */ Symbol.for("react.suspense_list"), A = /* @__PURE__ */ Symbol.for("react.memo"), p = /* @__PURE__ */ Symbol.for("react.lazy"), R = /* @__PURE__ */ Symbol.for("react.activity"), O = /* @__PURE__ */ Symbol.for("react.client.reference"), j = m.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, w = Object.prototype.hasOwnProperty, D = Array.isArray, y = console.createTask ? console.createTask : function() {
|
|
224
|
+
return null;
|
|
225
|
+
};
|
|
226
|
+
m = {
|
|
227
|
+
react_stack_bottom_frame: function(e) {
|
|
228
|
+
return e();
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
var C, N = {}, F = m.react_stack_bottom_frame.bind(
|
|
232
|
+
m,
|
|
233
|
+
l
|
|
234
|
+
)(), L = y(_(l)), b = {};
|
|
235
|
+
$.Fragment = i, $.jsx = function(e, r, t) {
|
|
236
|
+
var n = 1e4 > j.recentlyCreatedOwnerStacks++;
|
|
237
|
+
return k(
|
|
238
|
+
e,
|
|
239
|
+
r,
|
|
240
|
+
t,
|
|
241
|
+
!1,
|
|
242
|
+
n ? Error("react-stack-top-frame") : F,
|
|
243
|
+
n ? y(_(e)) : L
|
|
244
|
+
);
|
|
245
|
+
}, $.jsxs = function(e, r, t) {
|
|
246
|
+
var n = 1e4 > j.recentlyCreatedOwnerStacks++;
|
|
247
|
+
return k(
|
|
248
|
+
e,
|
|
249
|
+
r,
|
|
250
|
+
t,
|
|
251
|
+
!0,
|
|
252
|
+
n ? Error("react-stack-top-frame") : F,
|
|
253
|
+
n ? y(_(e)) : L
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
})()), $;
|
|
257
|
+
}
|
|
258
|
+
var re;
|
|
259
|
+
function fe() {
|
|
260
|
+
return re || (re = 1, process.env.NODE_ENV === "production" ? W.exports = ue() : W.exports = ie()), W.exports;
|
|
261
|
+
}
|
|
262
|
+
var P = fe();
|
|
263
|
+
Q.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${Q.version}/build/pdf.worker.min.mjs`;
|
|
264
|
+
const _e = ({
|
|
265
|
+
file: c,
|
|
266
|
+
highlights: v = [],
|
|
267
|
+
width: f,
|
|
268
|
+
loading: _,
|
|
269
|
+
defaultHighlightColor: a = "#ffeb3b",
|
|
270
|
+
defaultCaseSensitive: l = !0,
|
|
271
|
+
pageProps: u,
|
|
272
|
+
// Destructure all other DocumentProps to pass through
|
|
273
|
+
...d
|
|
274
|
+
}) => {
|
|
275
|
+
const [g, x] = te(0), k = ne(({ numPages: s }) => {
|
|
276
|
+
x(s);
|
|
277
|
+
}, []), E = se(
|
|
278
|
+
() => Array.from({ length: g }, (s, m) => m + 1),
|
|
279
|
+
[g]
|
|
280
|
+
);
|
|
281
|
+
return /* @__PURE__ */ P.jsx("div", { className: "pdf-highlighter-container", "data-testid": "pdf-highlighter-container", children: /* @__PURE__ */ P.jsx(
|
|
282
|
+
ce,
|
|
283
|
+
{
|
|
284
|
+
file: c,
|
|
285
|
+
onLoadSuccess: k,
|
|
286
|
+
loading: _ || /* @__PURE__ */ P.jsx("div", { children: "Loading PDF..." }),
|
|
287
|
+
...d,
|
|
288
|
+
children: E.map((s) => /* @__PURE__ */ P.jsx(
|
|
289
|
+
de,
|
|
290
|
+
{
|
|
291
|
+
pageNumber: s,
|
|
292
|
+
width: f,
|
|
293
|
+
highlights: v,
|
|
294
|
+
defaultColor: a,
|
|
295
|
+
defaultCaseSensitive: l,
|
|
296
|
+
pageProps: u
|
|
297
|
+
},
|
|
298
|
+
`page_${s}`
|
|
299
|
+
))
|
|
300
|
+
}
|
|
301
|
+
) });
|
|
302
|
+
}, de = ({
|
|
303
|
+
pageNumber: c,
|
|
304
|
+
width: v,
|
|
305
|
+
highlights: f,
|
|
306
|
+
defaultColor: _,
|
|
307
|
+
defaultCaseSensitive: a,
|
|
308
|
+
pageProps: l
|
|
309
|
+
}) => {
|
|
310
|
+
const u = B(null), [d, g] = te(!1), x = B(/* @__PURE__ */ new Map()), k = ne(() => {
|
|
311
|
+
setTimeout(() => {
|
|
312
|
+
g(!0);
|
|
313
|
+
}, 100);
|
|
314
|
+
}, []);
|
|
315
|
+
return Z(() => {
|
|
316
|
+
if (!d || !u.current) return;
|
|
317
|
+
const E = u.current.querySelector(".react-pdf__Page__textContent");
|
|
318
|
+
if (!E) return;
|
|
319
|
+
const s = Array.from(E.querySelectorAll('span[role="presentation"]'));
|
|
320
|
+
x.current.size === 0 && s.forEach((m) => {
|
|
321
|
+
x.current.set(m, m.textContent || "");
|
|
322
|
+
});
|
|
323
|
+
}, [d]), Z(() => {
|
|
324
|
+
if (!d || !u.current) return;
|
|
325
|
+
const E = u.current.querySelector(".react-pdf__Page__textContent");
|
|
326
|
+
if (!E) return;
|
|
327
|
+
const s = Array.from(E.querySelectorAll('span[role="presentation"]'));
|
|
328
|
+
if (s.length === 0 || (s.forEach((i) => {
|
|
329
|
+
const T = x.current.get(i);
|
|
330
|
+
T !== void 0 && (i.innerHTML = "", i.appendChild(document.createTextNode(T)));
|
|
331
|
+
}), !f || f.length === 0)) return;
|
|
332
|
+
const m = f.filter((i) => i.pageNumber === c);
|
|
333
|
+
if (m.length === 0) return;
|
|
334
|
+
const I = s.map((i) => i.textContent || "").join("");
|
|
335
|
+
m.forEach((i) => {
|
|
336
|
+
const { content: T, color: z, caseSensitive: M } = i;
|
|
337
|
+
if (!T) return;
|
|
338
|
+
const G = M !== void 0 ? M : a, J = z || _;
|
|
339
|
+
let h;
|
|
340
|
+
if (G)
|
|
341
|
+
h = I.indexOf(T);
|
|
342
|
+
else {
|
|
343
|
+
const p = I.toLowerCase(), R = T.toLowerCase();
|
|
344
|
+
h = p.indexOf(R);
|
|
345
|
+
}
|
|
346
|
+
if (h === -1) return;
|
|
347
|
+
const V = h + T.length;
|
|
348
|
+
let A = 0;
|
|
349
|
+
s.forEach((p) => {
|
|
350
|
+
const R = p.textContent || "", O = A, j = A + R.length, w = Math.max(h, O), D = Math.min(V, j);
|
|
351
|
+
if (w < D) {
|
|
352
|
+
const y = w - O, C = D - O, N = R.slice(0, y), F = R.slice(y, C), L = R.slice(C);
|
|
353
|
+
p.innerHTML = "", N && p.appendChild(document.createTextNode(N));
|
|
354
|
+
const b = document.createElement("mark");
|
|
355
|
+
b.style.backgroundColor = J, b.style.color = "#000", b.textContent = F, p.appendChild(b), L && p.appendChild(document.createTextNode(L));
|
|
356
|
+
}
|
|
357
|
+
A += R.length;
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
}, [d, f, c, _, a]), /* @__PURE__ */ P.jsx("div", { ref: u, "data-testid": `pdf-page-highlighter-${c}`, children: /* @__PURE__ */ P.jsx(
|
|
361
|
+
le,
|
|
362
|
+
{
|
|
363
|
+
pageNumber: c,
|
|
364
|
+
width: v,
|
|
365
|
+
onGetTextSuccess: k,
|
|
366
|
+
...l
|
|
367
|
+
}
|
|
368
|
+
) });
|
|
369
|
+
};
|
|
370
|
+
export {
|
|
371
|
+
_e as PdfHighlighter
|
|
372
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
(function(v,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("react"),require("react-pdf")):typeof define=="function"&&define.amd?define(["exports","react","react-pdf"],c):(v=typeof globalThis<"u"?globalThis:v||self,c(v.ReactPdfHighlighter={},v.React,v.ReactPDF))})(this,(function(v,c,F){"use strict";var W={exports:{}},A={};var K;function re(){if(K)return A;K=1;var l=Symbol.for("react.transitional.element"),g=Symbol.for("react.fragment");function d(E,a,i){var u=null;if(i!==void 0&&(u=""+i),a.key!==void 0&&(u=""+a.key),"key"in a){i={};for(var m in a)m!=="key"&&(i[m]=a[m])}else i=a;return a=i.ref,{$$typeof:l,type:E,key:u,ref:a!==void 0?a:null,props:i}}return A.Fragment=g,A.jsx=d,A.jsxs=d,A}var w={};var ee;function ne(){return ee||(ee=1,process.env.NODE_ENV!=="production"&&(function(){function l(e){if(e==null)return null;if(typeof e=="function")return e.$$typeof===N?null:e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case f:return"Fragment";case X:return"Profiler";case T:return"StrictMode";case b:return"Suspense";case Z:return"SuspenseList";case h:return"Activity"}if(typeof e=="object")switch(typeof e.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),e.$$typeof){case U:return"Portal";case q:return e.displayName||"Context";case H:return(e._context.displayName||"Context")+".Consumer";case B:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case C:return t=e.displayName||null,t!==null?t:l(e.type)||"Memo";case _:t=e._payload,e=e._init;try{return l(e(t))}catch{}}return null}function g(e){return""+e}function d(e){try{g(e);var t=!1}catch{t=!0}if(t){t=console;var r=t.error,n=typeof Symbol=="function"&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return r.call(t,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",n),g(e)}}function E(e){if(e===f)return"<>";if(typeof e=="object"&&e!==null&&e.$$typeof===_)return"<...>";try{var t=l(e);return t?"<"+t+">":"<...>"}catch{return"<...>"}}function a(){var e=L.A;return e===null?null:e.getOwner()}function i(){return Error("react-stack-top-frame")}function u(e){if(Y.call(e,"key")){var t=Object.getOwnPropertyDescriptor(e,"key").get;if(t&&t.isReactWarning)return!1}return e.key!==void 0}function m(e,t){function r(){I||(I=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",t))}r.isReactWarning=!0,Object.defineProperty(e,"key",{get:r,configurable:!0})}function S(){var e=l(this.type);return M[e]||(M[e]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),e=this.props.ref,e!==void 0?e:null}function x(e,t,r,n,J,Q){var o=r.ref;return e={$$typeof:V,type:e,key:t,props:r,_owner:n},(o!==void 0?o:null)!==null?Object.defineProperty(e,"ref",{enumerable:!1,get:S}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:J}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:Q}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function P(e,t,r,n,J,Q){var o=t.children;if(o!==void 0)if(n)if(z(o)){for(n=0;n<o.length;n++)R(o[n]);Object.freeze&&Object.freeze(o)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else R(o);if(Y.call(t,"key")){o=l(e);var O=Object.keys(t).filter(function(ce){return ce!=="key"});n=0<O.length?"{key: someKey, "+O.join(": ..., ")+": ...}":"{key: someKey}",y[o+n]||(O=0<O.length?"{"+O.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
2
|
+
let props = %s;
|
|
3
|
+
<%s {...props} />
|
|
4
|
+
React keys must be passed directly to JSX without using spread:
|
|
5
|
+
let props = %s;
|
|
6
|
+
<%s key={someKey} {...props} />`,n,o,O,o),y[o+n]=!0)}if(o=null,r!==void 0&&(d(r),o=""+r),u(t)&&(d(t.key),o=""+t.key),"key"in t){r={};for(var $ in t)$!=="key"&&(r[$]=t[$])}else r=t;return o&&m(r,typeof e=="function"?e.displayName||e.name||"Unknown":e),x(e,o,r,a(),J,Q)}function R(e){s(e)?e._store&&(e._store.validated=1):typeof e=="object"&&e!==null&&e.$$typeof===_&&(e._payload.status==="fulfilled"?s(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function s(e){return typeof e=="object"&&e!==null&&e.$$typeof===V}var p=c,V=Symbol.for("react.transitional.element"),U=Symbol.for("react.portal"),f=Symbol.for("react.fragment"),T=Symbol.for("react.strict_mode"),X=Symbol.for("react.profiler"),H=Symbol.for("react.consumer"),q=Symbol.for("react.context"),B=Symbol.for("react.forward_ref"),b=Symbol.for("react.suspense"),Z=Symbol.for("react.suspense_list"),C=Symbol.for("react.memo"),_=Symbol.for("react.lazy"),h=Symbol.for("react.activity"),N=Symbol.for("react.client.reference"),L=p.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,Y=Object.prototype.hasOwnProperty,z=Array.isArray,j=console.createTask?console.createTask:function(){return null};p={react_stack_bottom_frame:function(e){return e()}};var I,M={},G=p.react_stack_bottom_frame.bind(p,i)(),D=j(E(i)),y={};w.Fragment=f,w.jsx=function(e,t,r){var n=1e4>L.recentlyCreatedOwnerStacks++;return P(e,t,r,!1,n?Error("react-stack-top-frame"):G,n?j(E(e)):D)},w.jsxs=function(e,t,r){var n=1e4>L.recentlyCreatedOwnerStacks++;return P(e,t,r,!0,n?Error("react-stack-top-frame"):G,n?j(E(e)):D)}})()),w}var te;function oe(){return te||(te=1,process.env.NODE_ENV==="production"?W.exports=re():W.exports=ne()),W.exports}var k=oe();F.pdfjs.GlobalWorkerOptions.workerSrc=`https://unpkg.com/pdfjs-dist@${F.pdfjs.version}/build/pdf.worker.min.mjs`;const ae=({file:l,highlights:g=[],width:d,loading:E,defaultHighlightColor:a="#ffeb3b",defaultCaseSensitive:i=!0,pageProps:u,...m})=>{const[S,x]=c.useState(0),P=c.useCallback(({numPages:s})=>{x(s)},[]),R=c.useMemo(()=>Array.from({length:S},(s,p)=>p+1),[S]);return k.jsx("div",{className:"pdf-highlighter-container","data-testid":"pdf-highlighter-container",children:k.jsx(F.Document,{file:l,onLoadSuccess:P,loading:E||k.jsx("div",{children:"Loading PDF..."}),...m,children:R.map(s=>k.jsx(se,{pageNumber:s,width:d,highlights:g,defaultColor:a,defaultCaseSensitive:i,pageProps:u},`page_${s}`))})})},se=({pageNumber:l,width:g,highlights:d,defaultColor:E,defaultCaseSensitive:a,pageProps:i})=>{const u=c.useRef(null),[m,S]=c.useState(!1),x=c.useRef(new Map),P=c.useCallback(()=>{setTimeout(()=>{S(!0)},100)},[]);return c.useEffect(()=>{if(!m||!u.current)return;const R=u.current.querySelector(".react-pdf__Page__textContent");if(!R)return;const s=Array.from(R.querySelectorAll('span[role="presentation"]'));x.current.size===0&&s.forEach(p=>{x.current.set(p,p.textContent||"")})},[m]),c.useEffect(()=>{if(!m||!u.current)return;const R=u.current.querySelector(".react-pdf__Page__textContent");if(!R)return;const s=Array.from(R.querySelectorAll('span[role="presentation"]'));if(s.length===0||(s.forEach(f=>{const T=x.current.get(f);T!==void 0&&(f.innerHTML="",f.appendChild(document.createTextNode(T)))}),!d||d.length===0))return;const p=d.filter(f=>f.pageNumber===l);if(p.length===0)return;const U=s.map(f=>f.textContent||"").join("");p.forEach(f=>{const{content:T,color:X,caseSensitive:H}=f;if(!T)return;const q=H!==void 0?H:a,B=X||E;let b;if(q)b=U.indexOf(T);else{const _=U.toLowerCase(),h=T.toLowerCase();b=_.indexOf(h)}if(b===-1)return;const Z=b+T.length;let C=0;s.forEach(_=>{const h=_.textContent||"",N=C,L=C+h.length,Y=Math.max(b,N),z=Math.min(Z,L);if(Y<z){const j=Y-N,I=z-N,M=h.slice(0,j),G=h.slice(j,I),D=h.slice(I);_.innerHTML="",M&&_.appendChild(document.createTextNode(M));const y=document.createElement("mark");y.style.backgroundColor=B,y.style.color="#000",y.textContent=G,_.appendChild(y),D&&_.appendChild(document.createTextNode(D))}C+=h.length})})},[m,d,l,E,a]),k.jsx("div",{ref:u,"data-testid":`pdf-page-highlighter-${l}`,children:k.jsx(F.Page,{pageNumber:l,width:g,onGetTextSuccess:P,...i})})};v.PdfHighlighter=ae,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})}));
|
|
File without changes
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { DocumentProps, PageProps } from 'react-pdf';
|
|
2
|
+
/**
|
|
3
|
+
* Represents a text highlight in a PDF document
|
|
4
|
+
*/
|
|
5
|
+
export interface Highlight {
|
|
6
|
+
/**
|
|
7
|
+
* The page number where the highlight should appear (1-indexed)
|
|
8
|
+
*/
|
|
9
|
+
pageNumber: number;
|
|
10
|
+
/**
|
|
11
|
+
* The exact text content to highlight
|
|
12
|
+
* Note: Must match the PDF's internal text representation exactly (unless caseSensitive is false)
|
|
13
|
+
*/
|
|
14
|
+
content: string;
|
|
15
|
+
/**
|
|
16
|
+
* Optional custom background color for this highlight
|
|
17
|
+
* @default '#ffeb3b' (yellow)
|
|
18
|
+
*/
|
|
19
|
+
color?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to match text case-sensitively
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
caseSensitive?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Props for the PdfHighlighter component
|
|
28
|
+
* Extends react-pdf's DocumentProps to allow full customization
|
|
29
|
+
*/
|
|
30
|
+
export interface PdfHighlighterProps extends Omit<DocumentProps, 'children'> {
|
|
31
|
+
/**
|
|
32
|
+
* Source of the PDF file
|
|
33
|
+
* Can be a URL string, base64 data URI, or File object
|
|
34
|
+
* @example "https://example.com/document.pdf"
|
|
35
|
+
* @example "data:application/pdf;base64,..."
|
|
36
|
+
*/
|
|
37
|
+
file: string | File | null;
|
|
38
|
+
/**
|
|
39
|
+
* Array of text highlights to apply to the PDF
|
|
40
|
+
* @default []
|
|
41
|
+
*/
|
|
42
|
+
highlights?: Highlight[];
|
|
43
|
+
/**
|
|
44
|
+
* Width of the PDF pages in pixels
|
|
45
|
+
* If not provided, pages will be responsive
|
|
46
|
+
* @default undefined (responsive)
|
|
47
|
+
*/
|
|
48
|
+
width?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Custom loading component to display while PDF is loading
|
|
51
|
+
* @default <div>Loading PDF...</div>
|
|
52
|
+
*/
|
|
53
|
+
loading?: React.ReactNode;
|
|
54
|
+
/**
|
|
55
|
+
* Default highlight color for all highlights
|
|
56
|
+
* Individual highlights can override this with their own color
|
|
57
|
+
* @default '#ffeb3b' (yellow)
|
|
58
|
+
*/
|
|
59
|
+
defaultHighlightColor?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Default case sensitivity for all highlights
|
|
62
|
+
* Individual highlights can override this with their own caseSensitive property
|
|
63
|
+
* @default true
|
|
64
|
+
*/
|
|
65
|
+
defaultCaseSensitive?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Additional props to pass to each Page component
|
|
68
|
+
* Allows customization of page rendering (scale, renderMode, etc.)
|
|
69
|
+
*/
|
|
70
|
+
pageProps?: Omit<PageProps, 'pageNumber' | 'width' | 'onGetTextSuccess'>;
|
|
71
|
+
}
|
package/dist/vite.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-pdf-highlight-viewer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight React library for rendering PDFs with text highlighting support",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"pdf",
|
|
8
|
+
"highlight",
|
|
9
|
+
"viewer",
|
|
10
|
+
"react-pdf",
|
|
11
|
+
"pdf-highlighter",
|
|
12
|
+
"text-highlight",
|
|
13
|
+
"document-viewer"
|
|
14
|
+
],
|
|
15
|
+
"author": "Sivakkannan R",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/sivakannan/react-pdf-highlight-viewer"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/sivakannan/react-pdf-highlight-viewer/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/sivakannan/react-pdf-highlight-viewer#readme",
|
|
25
|
+
"private": false,
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/react-pdf-highlighter.umd.js",
|
|
28
|
+
"module": "./dist/react-pdf-highlighter.es.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/react-pdf-highlighter.es.js",
|
|
38
|
+
"require": "./dist/react-pdf-highlighter.umd.js"
|
|
39
|
+
},
|
|
40
|
+
"./dist/npm-react-pdf-lib.css": "./dist/npm-react-pdf-lib.css"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"dev": "vite",
|
|
44
|
+
"build": "tsc -b && vite build",
|
|
45
|
+
"lint": "eslint .",
|
|
46
|
+
"preview": "vite preview",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"test:coverage": "vitest run --coverage",
|
|
50
|
+
"test:ui": "vitest --ui",
|
|
51
|
+
"prepublishOnly": "npm run build"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"react": "^19.2.0",
|
|
55
|
+
"react-dom": "^19.2.0",
|
|
56
|
+
"react-pdf": "^10.3.0"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"prop-types": "^15.8.1"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@eslint/js": "^9.39.1",
|
|
63
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
64
|
+
"@testing-library/react": "^16.3.2",
|
|
65
|
+
"@testing-library/user-event": "^14.6.1",
|
|
66
|
+
"@types/node": "^24.10.1",
|
|
67
|
+
"@types/react": "^19.2.5",
|
|
68
|
+
"@types/react-dom": "^19.2.3",
|
|
69
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
70
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
71
|
+
"@vitest/ui": "^4.0.18",
|
|
72
|
+
"eslint": "^9.39.1",
|
|
73
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
74
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
75
|
+
"globals": "^16.5.0",
|
|
76
|
+
"happy-dom": "^20.5.3",
|
|
77
|
+
"jsdom": "^27.0.1",
|
|
78
|
+
"typescript": "~5.9.3",
|
|
79
|
+
"typescript-eslint": "^8.46.4",
|
|
80
|
+
"vite": "^7.2.4",
|
|
81
|
+
"vite-plugin-dts": "^4.5.4",
|
|
82
|
+
"vitest": "^4.0.18"
|
|
83
|
+
}
|
|
84
|
+
}
|