pdf-search-highlight 0.1.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/dist/PDFSearchViewer-8kMeXl51.d.cts +184 -0
- package/dist/PDFSearchViewer-8kMeXl51.d.ts +184 -0
- package/dist/chunk-M56VMBOL.js +587 -0
- package/dist/chunk-M56VMBOL.js.map +1 -0
- package/dist/chunk-OMRGA5I4.cjs +587 -0
- package/dist/chunk-OMRGA5I4.cjs.map +1 -0
- package/dist/core/index.cjs +23 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +184 -0
- package/dist/core/index.d.ts +184 -0
- package/dist/core/index.js +23 -0
- package/dist/core/index.js.map +1 -0
- package/dist/pdf-search-highlight.css +58 -0
- package/dist/react/index.cjs +166 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +123 -0
- package/dist/react/index.d.ts +123 -0
- package/dist/react/index.js +166 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { P as PageData, f as PDFSource, a as PDFSearchViewerOptions, C as ClassNames, S as SearchOptions, d as PDFSearchViewer$1 } from '../PDFSearchViewer-8kMeXl51.cjs';
|
|
2
|
+
export { c as SearchMatch } from '../PDFSearchViewer-8kMeXl51.cjs';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
import { CSSProperties } from 'react';
|
|
5
|
+
|
|
6
|
+
interface UsePDFRendererReturn {
|
|
7
|
+
/** Ref to attach to the container div */
|
|
8
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
9
|
+
/** Rendered page data (available after loading) */
|
|
10
|
+
pages: PageData[];
|
|
11
|
+
/** Number of pages */
|
|
12
|
+
pageCount: number;
|
|
13
|
+
/** Whether PDF is currently loading */
|
|
14
|
+
loading: boolean;
|
|
15
|
+
/** Load a PDF source */
|
|
16
|
+
loadPDF: (source: PDFSource) => Promise<PageData[]>;
|
|
17
|
+
/** Clean up renderer */
|
|
18
|
+
cleanup: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Hook for rendering PDF into a container div.
|
|
22
|
+
* Returns pages data that can be passed to useSearchController.
|
|
23
|
+
*
|
|
24
|
+
* ```tsx
|
|
25
|
+
* const { containerRef, pages, loadPDF } = usePDFRenderer(pdfjsLib);
|
|
26
|
+
*
|
|
27
|
+
* return <div ref={containerRef} style={{ height: '80vh', overflow: 'auto' }} />;
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function usePDFRenderer(pdfjsLib: any, options?: PDFSearchViewerOptions): UsePDFRendererReturn;
|
|
31
|
+
|
|
32
|
+
interface UseSearchControllerReturn {
|
|
33
|
+
/** Run a search query */
|
|
34
|
+
search: (query: string, options?: SearchOptions) => number;
|
|
35
|
+
/** Go to next match */
|
|
36
|
+
next: () => void;
|
|
37
|
+
/** Go to previous match */
|
|
38
|
+
prev: () => void;
|
|
39
|
+
/** Go to specific match index */
|
|
40
|
+
goTo: (index: number) => void;
|
|
41
|
+
/** Clear all highlights */
|
|
42
|
+
clear: () => void;
|
|
43
|
+
/** Current active match index (0-based), -1 if none */
|
|
44
|
+
current: number;
|
|
45
|
+
/** Total number of matches */
|
|
46
|
+
total: number;
|
|
47
|
+
}
|
|
48
|
+
interface UseSearchControllerOptions {
|
|
49
|
+
classNames?: Pick<ClassNames, 'highlight' | 'activeHighlight'>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Hook for search + highlight logic (headless).
|
|
53
|
+
* Pass pages from usePDFRenderer to connect them.
|
|
54
|
+
*
|
|
55
|
+
* ```tsx
|
|
56
|
+
* const { containerRef, pages, loadPDF } = usePDFRenderer(pdfjsLib);
|
|
57
|
+
* const { search, next, prev, current, total } = useSearchController(pages);
|
|
58
|
+
*
|
|
59
|
+
* return (
|
|
60
|
+
* <>
|
|
61
|
+
* <input onChange={e => search(e.target.value)} />
|
|
62
|
+
* <span>{current + 1}/{total}</span>
|
|
63
|
+
* <button onClick={prev}>Prev</button>
|
|
64
|
+
* <button onClick={next}>Next</button>
|
|
65
|
+
* <div ref={containerRef} />
|
|
66
|
+
* </>
|
|
67
|
+
* );
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function useSearchController(pages: PageData[], options?: UseSearchControllerOptions): UseSearchControllerReturn;
|
|
71
|
+
|
|
72
|
+
interface PDFSearchViewerProps {
|
|
73
|
+
/** pdfjs-dist library instance. Must be passed by consumer. */
|
|
74
|
+
pdfjsLib: any;
|
|
75
|
+
/** PDF source: URL string, File, ArrayBuffer, or Uint8Array. */
|
|
76
|
+
source?: PDFSource | null;
|
|
77
|
+
/** Search query string. Empty/undefined clears the search. */
|
|
78
|
+
searchQuery?: string;
|
|
79
|
+
/** Search options. */
|
|
80
|
+
searchOptions?: SearchOptions;
|
|
81
|
+
/** Viewer options (applied once on mount). */
|
|
82
|
+
viewerOptions?: PDFSearchViewerOptions;
|
|
83
|
+
/** Called when PDF finishes loading. */
|
|
84
|
+
onLoad?: (data: {
|
|
85
|
+
pageCount: number;
|
|
86
|
+
}) => void;
|
|
87
|
+
/** Called when search completes. */
|
|
88
|
+
onSearch?: (data: {
|
|
89
|
+
query: string;
|
|
90
|
+
total: number;
|
|
91
|
+
}) => void;
|
|
92
|
+
/** Called when active match changes. */
|
|
93
|
+
onMatchChange?: (data: {
|
|
94
|
+
current: number;
|
|
95
|
+
total: number;
|
|
96
|
+
}) => void;
|
|
97
|
+
/** Called on error. */
|
|
98
|
+
onError?: (data: {
|
|
99
|
+
error: Error;
|
|
100
|
+
context: string;
|
|
101
|
+
}) => void;
|
|
102
|
+
/** Additional className for the container div. */
|
|
103
|
+
className?: string;
|
|
104
|
+
/** Inline style for the container div. */
|
|
105
|
+
style?: CSSProperties;
|
|
106
|
+
}
|
|
107
|
+
interface PDFSearchViewerHandle {
|
|
108
|
+
/** Navigate to next match. Returns new index. */
|
|
109
|
+
nextMatch: () => number;
|
|
110
|
+
/** Navigate to previous match. Returns new index. */
|
|
111
|
+
prevMatch: () => number;
|
|
112
|
+
/** Clear all search highlights. */
|
|
113
|
+
clearSearch: () => void;
|
|
114
|
+
/** Get total match count. */
|
|
115
|
+
getMatchCount: () => number;
|
|
116
|
+
/** Get current match index. */
|
|
117
|
+
getCurrentMatchIndex: () => number;
|
|
118
|
+
/** Get underlying core instance. */
|
|
119
|
+
getCore: () => PDFSearchViewer$1 | null;
|
|
120
|
+
}
|
|
121
|
+
declare const PDFSearchViewer: react.ForwardRefExoticComponent<PDFSearchViewerProps & react.RefAttributes<PDFSearchViewerHandle>>;
|
|
122
|
+
|
|
123
|
+
export { ClassNames, PDFSearchViewer, type PDFSearchViewerHandle, PDFSearchViewerOptions, type PDFSearchViewerProps, PDFSource, PageData, SearchOptions, type UsePDFRendererReturn, type UseSearchControllerOptions, type UseSearchControllerReturn, usePDFRenderer, useSearchController };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { P as PageData, f as PDFSource, a as PDFSearchViewerOptions, C as ClassNames, S as SearchOptions, d as PDFSearchViewer$1 } from '../PDFSearchViewer-8kMeXl51.js';
|
|
2
|
+
export { c as SearchMatch } from '../PDFSearchViewer-8kMeXl51.js';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
import { CSSProperties } from 'react';
|
|
5
|
+
|
|
6
|
+
interface UsePDFRendererReturn {
|
|
7
|
+
/** Ref to attach to the container div */
|
|
8
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
9
|
+
/** Rendered page data (available after loading) */
|
|
10
|
+
pages: PageData[];
|
|
11
|
+
/** Number of pages */
|
|
12
|
+
pageCount: number;
|
|
13
|
+
/** Whether PDF is currently loading */
|
|
14
|
+
loading: boolean;
|
|
15
|
+
/** Load a PDF source */
|
|
16
|
+
loadPDF: (source: PDFSource) => Promise<PageData[]>;
|
|
17
|
+
/** Clean up renderer */
|
|
18
|
+
cleanup: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Hook for rendering PDF into a container div.
|
|
22
|
+
* Returns pages data that can be passed to useSearchController.
|
|
23
|
+
*
|
|
24
|
+
* ```tsx
|
|
25
|
+
* const { containerRef, pages, loadPDF } = usePDFRenderer(pdfjsLib);
|
|
26
|
+
*
|
|
27
|
+
* return <div ref={containerRef} style={{ height: '80vh', overflow: 'auto' }} />;
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function usePDFRenderer(pdfjsLib: any, options?: PDFSearchViewerOptions): UsePDFRendererReturn;
|
|
31
|
+
|
|
32
|
+
interface UseSearchControllerReturn {
|
|
33
|
+
/** Run a search query */
|
|
34
|
+
search: (query: string, options?: SearchOptions) => number;
|
|
35
|
+
/** Go to next match */
|
|
36
|
+
next: () => void;
|
|
37
|
+
/** Go to previous match */
|
|
38
|
+
prev: () => void;
|
|
39
|
+
/** Go to specific match index */
|
|
40
|
+
goTo: (index: number) => void;
|
|
41
|
+
/** Clear all highlights */
|
|
42
|
+
clear: () => void;
|
|
43
|
+
/** Current active match index (0-based), -1 if none */
|
|
44
|
+
current: number;
|
|
45
|
+
/** Total number of matches */
|
|
46
|
+
total: number;
|
|
47
|
+
}
|
|
48
|
+
interface UseSearchControllerOptions {
|
|
49
|
+
classNames?: Pick<ClassNames, 'highlight' | 'activeHighlight'>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Hook for search + highlight logic (headless).
|
|
53
|
+
* Pass pages from usePDFRenderer to connect them.
|
|
54
|
+
*
|
|
55
|
+
* ```tsx
|
|
56
|
+
* const { containerRef, pages, loadPDF } = usePDFRenderer(pdfjsLib);
|
|
57
|
+
* const { search, next, prev, current, total } = useSearchController(pages);
|
|
58
|
+
*
|
|
59
|
+
* return (
|
|
60
|
+
* <>
|
|
61
|
+
* <input onChange={e => search(e.target.value)} />
|
|
62
|
+
* <span>{current + 1}/{total}</span>
|
|
63
|
+
* <button onClick={prev}>Prev</button>
|
|
64
|
+
* <button onClick={next}>Next</button>
|
|
65
|
+
* <div ref={containerRef} />
|
|
66
|
+
* </>
|
|
67
|
+
* );
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function useSearchController(pages: PageData[], options?: UseSearchControllerOptions): UseSearchControllerReturn;
|
|
71
|
+
|
|
72
|
+
interface PDFSearchViewerProps {
|
|
73
|
+
/** pdfjs-dist library instance. Must be passed by consumer. */
|
|
74
|
+
pdfjsLib: any;
|
|
75
|
+
/** PDF source: URL string, File, ArrayBuffer, or Uint8Array. */
|
|
76
|
+
source?: PDFSource | null;
|
|
77
|
+
/** Search query string. Empty/undefined clears the search. */
|
|
78
|
+
searchQuery?: string;
|
|
79
|
+
/** Search options. */
|
|
80
|
+
searchOptions?: SearchOptions;
|
|
81
|
+
/** Viewer options (applied once on mount). */
|
|
82
|
+
viewerOptions?: PDFSearchViewerOptions;
|
|
83
|
+
/** Called when PDF finishes loading. */
|
|
84
|
+
onLoad?: (data: {
|
|
85
|
+
pageCount: number;
|
|
86
|
+
}) => void;
|
|
87
|
+
/** Called when search completes. */
|
|
88
|
+
onSearch?: (data: {
|
|
89
|
+
query: string;
|
|
90
|
+
total: number;
|
|
91
|
+
}) => void;
|
|
92
|
+
/** Called when active match changes. */
|
|
93
|
+
onMatchChange?: (data: {
|
|
94
|
+
current: number;
|
|
95
|
+
total: number;
|
|
96
|
+
}) => void;
|
|
97
|
+
/** Called on error. */
|
|
98
|
+
onError?: (data: {
|
|
99
|
+
error: Error;
|
|
100
|
+
context: string;
|
|
101
|
+
}) => void;
|
|
102
|
+
/** Additional className for the container div. */
|
|
103
|
+
className?: string;
|
|
104
|
+
/** Inline style for the container div. */
|
|
105
|
+
style?: CSSProperties;
|
|
106
|
+
}
|
|
107
|
+
interface PDFSearchViewerHandle {
|
|
108
|
+
/** Navigate to next match. Returns new index. */
|
|
109
|
+
nextMatch: () => number;
|
|
110
|
+
/** Navigate to previous match. Returns new index. */
|
|
111
|
+
prevMatch: () => number;
|
|
112
|
+
/** Clear all search highlights. */
|
|
113
|
+
clearSearch: () => void;
|
|
114
|
+
/** Get total match count. */
|
|
115
|
+
getMatchCount: () => number;
|
|
116
|
+
/** Get current match index. */
|
|
117
|
+
getCurrentMatchIndex: () => number;
|
|
118
|
+
/** Get underlying core instance. */
|
|
119
|
+
getCore: () => PDFSearchViewer$1 | null;
|
|
120
|
+
}
|
|
121
|
+
declare const PDFSearchViewer: react.ForwardRefExoticComponent<PDFSearchViewerProps & react.RefAttributes<PDFSearchViewerHandle>>;
|
|
122
|
+
|
|
123
|
+
export { ClassNames, PDFSearchViewer, type PDFSearchViewerHandle, PDFSearchViewerOptions, type PDFSearchViewerProps, PDFSource, PageData, SearchOptions, type UsePDFRendererReturn, type UseSearchControllerOptions, type UseSearchControllerReturn, usePDFRenderer, useSearchController };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PDFRenderer,
|
|
3
|
+
PDFSearchViewer,
|
|
4
|
+
SearchController
|
|
5
|
+
} from "../chunk-M56VMBOL.js";
|
|
6
|
+
|
|
7
|
+
// src/react/usePDFRenderer.ts
|
|
8
|
+
import { useRef, useEffect, useCallback, useState } from "react";
|
|
9
|
+
function usePDFRenderer(pdfjsLib, options = {}) {
|
|
10
|
+
const containerRef = useRef(null);
|
|
11
|
+
const rendererRef = useRef(null);
|
|
12
|
+
const [pages, setPages] = useState([]);
|
|
13
|
+
const [pageCount, setPageCount] = useState(0);
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
return () => {
|
|
17
|
+
rendererRef.current?.cleanup();
|
|
18
|
+
rendererRef.current = null;
|
|
19
|
+
};
|
|
20
|
+
}, []);
|
|
21
|
+
const getRenderer = useCallback(() => {
|
|
22
|
+
if (!containerRef.current) throw new Error("Container ref not attached");
|
|
23
|
+
if (!rendererRef.current) {
|
|
24
|
+
const r = new PDFRenderer(containerRef.current, options);
|
|
25
|
+
r.setPdfjsLib(pdfjsLib);
|
|
26
|
+
rendererRef.current = r;
|
|
27
|
+
}
|
|
28
|
+
return rendererRef.current;
|
|
29
|
+
}, [pdfjsLib, options]);
|
|
30
|
+
const loadPDF = useCallback(
|
|
31
|
+
async (source) => {
|
|
32
|
+
const renderer = getRenderer();
|
|
33
|
+
setLoading(true);
|
|
34
|
+
try {
|
|
35
|
+
const count = await renderer.loadDocument(source);
|
|
36
|
+
const p = await renderer.renderAllPages();
|
|
37
|
+
setPages(p);
|
|
38
|
+
setPageCount(count);
|
|
39
|
+
return p;
|
|
40
|
+
} finally {
|
|
41
|
+
setLoading(false);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
[getRenderer]
|
|
45
|
+
);
|
|
46
|
+
const cleanup = useCallback(() => {
|
|
47
|
+
rendererRef.current?.cleanup();
|
|
48
|
+
rendererRef.current = null;
|
|
49
|
+
setPages([]);
|
|
50
|
+
setPageCount(0);
|
|
51
|
+
}, []);
|
|
52
|
+
return { containerRef, pages, pageCount, loading, loadPDF, cleanup };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/react/useSearchController.ts
|
|
56
|
+
import { useRef as useRef2, useEffect as useEffect2, useCallback as useCallback2, useState as useState2 } from "react";
|
|
57
|
+
function useSearchController(pages, options = {}) {
|
|
58
|
+
const controllerRef = useRef2(null);
|
|
59
|
+
const [current, setCurrent] = useState2(-1);
|
|
60
|
+
const [total, setTotal] = useState2(0);
|
|
61
|
+
if (!controllerRef.current) {
|
|
62
|
+
controllerRef.current = new SearchController(options);
|
|
63
|
+
}
|
|
64
|
+
useEffect2(() => {
|
|
65
|
+
const ctrl = controllerRef.current;
|
|
66
|
+
ctrl.onChange = ({ current: c, total: t }) => {
|
|
67
|
+
setCurrent(c);
|
|
68
|
+
setTotal(t);
|
|
69
|
+
};
|
|
70
|
+
return () => {
|
|
71
|
+
ctrl.onChange = null;
|
|
72
|
+
};
|
|
73
|
+
}, []);
|
|
74
|
+
useEffect2(() => {
|
|
75
|
+
controllerRef.current.setPages(pages);
|
|
76
|
+
}, [pages]);
|
|
77
|
+
const search = useCallback2((query, opts) => {
|
|
78
|
+
return controllerRef.current.search(query, opts);
|
|
79
|
+
}, []);
|
|
80
|
+
const next = useCallback2(() => {
|
|
81
|
+
controllerRef.current.next();
|
|
82
|
+
}, []);
|
|
83
|
+
const prev = useCallback2(() => {
|
|
84
|
+
controllerRef.current.prev();
|
|
85
|
+
}, []);
|
|
86
|
+
const goTo = useCallback2((index) => {
|
|
87
|
+
controllerRef.current.goTo(index);
|
|
88
|
+
}, []);
|
|
89
|
+
const clear = useCallback2(() => {
|
|
90
|
+
controllerRef.current.clear();
|
|
91
|
+
}, []);
|
|
92
|
+
return { search, next, prev, goTo, clear, current, total };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/react/PDFSearchViewer.tsx
|
|
96
|
+
import {
|
|
97
|
+
useRef as useRef3,
|
|
98
|
+
useEffect as useEffect3,
|
|
99
|
+
useImperativeHandle,
|
|
100
|
+
forwardRef
|
|
101
|
+
} from "react";
|
|
102
|
+
import { jsx } from "react/jsx-runtime";
|
|
103
|
+
var PDFSearchViewer2 = forwardRef(function PDFSearchViewer3(props, ref) {
|
|
104
|
+
const {
|
|
105
|
+
pdfjsLib,
|
|
106
|
+
source,
|
|
107
|
+
searchQuery,
|
|
108
|
+
searchOptions,
|
|
109
|
+
viewerOptions,
|
|
110
|
+
onLoad,
|
|
111
|
+
onSearch,
|
|
112
|
+
onMatchChange,
|
|
113
|
+
onError,
|
|
114
|
+
className,
|
|
115
|
+
style
|
|
116
|
+
} = props;
|
|
117
|
+
const containerRef = useRef3(null);
|
|
118
|
+
const coreRef = useRef3(null);
|
|
119
|
+
const callbackRefs = useRef3({ onLoad, onSearch, onMatchChange, onError });
|
|
120
|
+
callbackRefs.current = { onLoad, onSearch, onMatchChange, onError };
|
|
121
|
+
useEffect3(() => {
|
|
122
|
+
if (!containerRef.current || !pdfjsLib) return;
|
|
123
|
+
const core = new PDFSearchViewer(
|
|
124
|
+
containerRef.current,
|
|
125
|
+
pdfjsLib,
|
|
126
|
+
viewerOptions ?? {}
|
|
127
|
+
);
|
|
128
|
+
core.on("load", (data) => callbackRefs.current.onLoad?.(data));
|
|
129
|
+
core.on("search", (data) => callbackRefs.current.onSearch?.(data));
|
|
130
|
+
core.on("matchchange", (data) => callbackRefs.current.onMatchChange?.(data));
|
|
131
|
+
core.on("error", (data) => callbackRefs.current.onError?.(data));
|
|
132
|
+
coreRef.current = core;
|
|
133
|
+
return () => {
|
|
134
|
+
core.destroy();
|
|
135
|
+
coreRef.current = null;
|
|
136
|
+
};
|
|
137
|
+
}, [pdfjsLib]);
|
|
138
|
+
useEffect3(() => {
|
|
139
|
+
if (!coreRef.current || !source) return;
|
|
140
|
+
coreRef.current.loadPDF(source).catch(() => {
|
|
141
|
+
});
|
|
142
|
+
}, [source]);
|
|
143
|
+
useEffect3(() => {
|
|
144
|
+
if (!coreRef.current) return;
|
|
145
|
+
if (searchQuery && searchQuery.trim().length > 0) {
|
|
146
|
+
coreRef.current.search(searchQuery, searchOptions);
|
|
147
|
+
} else {
|
|
148
|
+
coreRef.current.clearSearch();
|
|
149
|
+
}
|
|
150
|
+
}, [searchQuery, searchOptions]);
|
|
151
|
+
useImperativeHandle(ref, () => ({
|
|
152
|
+
nextMatch: () => coreRef.current?.nextMatch() ?? -1,
|
|
153
|
+
prevMatch: () => coreRef.current?.prevMatch() ?? -1,
|
|
154
|
+
clearSearch: () => coreRef.current?.clearSearch(),
|
|
155
|
+
getMatchCount: () => coreRef.current?.getMatchCount() ?? 0,
|
|
156
|
+
getCurrentMatchIndex: () => coreRef.current?.getCurrentMatchIndex() ?? -1,
|
|
157
|
+
getCore: () => coreRef.current
|
|
158
|
+
}));
|
|
159
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className, style });
|
|
160
|
+
});
|
|
161
|
+
export {
|
|
162
|
+
PDFSearchViewer2 as PDFSearchViewer,
|
|
163
|
+
usePDFRenderer,
|
|
164
|
+
useSearchController
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/usePDFRenderer.ts","../../src/react/useSearchController.ts","../../src/react/PDFSearchViewer.tsx"],"sourcesContent":["import { useRef, useEffect, useCallback, useState } from 'react';\nimport { PDFRenderer } from '../core/PDFRenderer';\nimport type { PDFSearchViewerOptions, PageData, PDFSource } from '../core';\n\nexport interface UsePDFRendererReturn {\n /** Ref to attach to the container div */\n containerRef: React.RefObject<HTMLDivElement | null>;\n /** Rendered page data (available after loading) */\n pages: PageData[];\n /** Number of pages */\n pageCount: number;\n /** Whether PDF is currently loading */\n loading: boolean;\n /** Load a PDF source */\n loadPDF: (source: PDFSource) => Promise<PageData[]>;\n /** Clean up renderer */\n cleanup: () => void;\n}\n\n/**\n * Hook for rendering PDF into a container div.\n * Returns pages data that can be passed to useSearchController.\n *\n * ```tsx\n * const { containerRef, pages, loadPDF } = usePDFRenderer(pdfjsLib);\n *\n * return <div ref={containerRef} style={{ height: '80vh', overflow: 'auto' }} />;\n * ```\n */\nexport function usePDFRenderer(\n pdfjsLib: any,\n options: PDFSearchViewerOptions = {}\n): UsePDFRendererReturn {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const rendererRef = useRef<PDFRenderer | null>(null);\n const [pages, setPages] = useState<PageData[]>([]);\n const [pageCount, setPageCount] = useState(0);\n const [loading, setLoading] = useState(false);\n\n // Init renderer when container is available\n useEffect(() => {\n return () => {\n rendererRef.current?.cleanup();\n rendererRef.current = null;\n };\n }, []);\n\n const getRenderer = useCallback(() => {\n if (!containerRef.current) throw new Error('Container ref not attached');\n if (!rendererRef.current) {\n const r = new PDFRenderer(containerRef.current, options);\n r.setPdfjsLib(pdfjsLib);\n rendererRef.current = r;\n }\n return rendererRef.current;\n }, [pdfjsLib, options]);\n\n const loadPDF = useCallback(\n async (source: PDFSource): Promise<PageData[]> => {\n const renderer = getRenderer();\n setLoading(true);\n try {\n const count = await renderer.loadDocument(source);\n const p = await renderer.renderAllPages();\n setPages(p);\n setPageCount(count);\n return p;\n } finally {\n setLoading(false);\n }\n },\n [getRenderer]\n );\n\n const cleanup = useCallback(() => {\n rendererRef.current?.cleanup();\n rendererRef.current = null;\n setPages([]);\n setPageCount(0);\n }, []);\n\n return { containerRef, pages, pageCount, loading, loadPDF, cleanup };\n}\n","import { useRef, useEffect, useCallback, useState } from 'react';\nimport { SearchController } from '../core/SearchController';\nimport type { SearchOptions, ClassNames, PageData } from '../core';\n\nexport interface UseSearchControllerReturn {\n /** Run a search query */\n search: (query: string, options?: SearchOptions) => number;\n /** Go to next match */\n next: () => void;\n /** Go to previous match */\n prev: () => void;\n /** Go to specific match index */\n goTo: (index: number) => void;\n /** Clear all highlights */\n clear: () => void;\n /** Current active match index (0-based), -1 if none */\n current: number;\n /** Total number of matches */\n total: number;\n}\n\nexport interface UseSearchControllerOptions {\n classNames?: Pick<ClassNames, 'highlight' | 'activeHighlight'>;\n}\n\n/**\n * Hook for search + highlight logic (headless).\n * Pass pages from usePDFRenderer to connect them.\n *\n * ```tsx\n * const { containerRef, pages, loadPDF } = usePDFRenderer(pdfjsLib);\n * const { search, next, prev, current, total } = useSearchController(pages);\n *\n * return (\n * <>\n * <input onChange={e => search(e.target.value)} />\n * <span>{current + 1}/{total}</span>\n * <button onClick={prev}>Prev</button>\n * <button onClick={next}>Next</button>\n * <div ref={containerRef} />\n * </>\n * );\n * ```\n */\nexport function useSearchController(\n pages: PageData[],\n options: UseSearchControllerOptions = {}\n): UseSearchControllerReturn {\n const controllerRef = useRef<SearchController | null>(null);\n const [current, setCurrent] = useState(-1);\n const [total, setTotal] = useState(0);\n\n // Create controller once\n if (!controllerRef.current) {\n controllerRef.current = new SearchController(options);\n }\n\n // Sync onChange callback\n useEffect(() => {\n const ctrl = controllerRef.current!;\n ctrl.onChange = ({ current: c, total: t }) => {\n setCurrent(c);\n setTotal(t);\n };\n return () => {\n ctrl.onChange = null;\n };\n }, []);\n\n // Update pages when they change\n useEffect(() => {\n controllerRef.current!.setPages(pages);\n }, [pages]);\n\n const search = useCallback((query: string, opts?: SearchOptions) => {\n return controllerRef.current!.search(query, opts);\n }, []);\n\n const next = useCallback(() => {\n controllerRef.current!.next();\n }, []);\n\n const prev = useCallback(() => {\n controllerRef.current!.prev();\n }, []);\n\n const goTo = useCallback((index: number) => {\n controllerRef.current!.goTo(index);\n }, []);\n\n const clear = useCallback(() => {\n controllerRef.current!.clear();\n }, []);\n\n return { search, next, prev, goTo, clear, current, total };\n}\n","import {\n useRef,\n useEffect,\n useImperativeHandle,\n forwardRef,\n type Ref,\n type CSSProperties,\n} from 'react';\nimport {\n PDFSearchViewer as CorePDFSearchViewer,\n type PDFSearchViewerOptions,\n type SearchOptions,\n type PDFSource,\n} from '../core';\n\nexport interface PDFSearchViewerProps {\n /** pdfjs-dist library instance. Must be passed by consumer. */\n pdfjsLib: any;\n\n /** PDF source: URL string, File, ArrayBuffer, or Uint8Array. */\n source?: PDFSource | null;\n\n /** Search query string. Empty/undefined clears the search. */\n searchQuery?: string;\n\n /** Search options. */\n searchOptions?: SearchOptions;\n\n /** Viewer options (applied once on mount). */\n viewerOptions?: PDFSearchViewerOptions;\n\n /** Called when PDF finishes loading. */\n onLoad?: (data: { pageCount: number }) => void;\n\n /** Called when search completes. */\n onSearch?: (data: { query: string; total: number }) => void;\n\n /** Called when active match changes. */\n onMatchChange?: (data: { current: number; total: number }) => void;\n\n /** Called on error. */\n onError?: (data: { error: Error; context: string }) => void;\n\n /** Additional className for the container div. */\n className?: string;\n\n /** Inline style for the container div. */\n style?: CSSProperties;\n}\n\nexport interface PDFSearchViewerHandle {\n /** Navigate to next match. Returns new index. */\n nextMatch: () => number;\n /** Navigate to previous match. Returns new index. */\n prevMatch: () => number;\n /** Clear all search highlights. */\n clearSearch: () => void;\n /** Get total match count. */\n getMatchCount: () => number;\n /** Get current match index. */\n getCurrentMatchIndex: () => number;\n /** Get underlying core instance. */\n getCore: () => CorePDFSearchViewer | null;\n}\n\nexport const PDFSearchViewer = forwardRef(function PDFSearchViewer(\n props: PDFSearchViewerProps,\n ref: Ref<PDFSearchViewerHandle>\n) {\n const {\n pdfjsLib,\n source,\n searchQuery,\n searchOptions,\n viewerOptions,\n onLoad,\n onSearch,\n onMatchChange,\n onError,\n className,\n style,\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const coreRef = useRef<CorePDFSearchViewer | null>(null);\n\n // Store latest callbacks in refs to avoid re-subscribing\n const callbackRefs = useRef({ onLoad, onSearch, onMatchChange, onError });\n callbackRefs.current = { onLoad, onSearch, onMatchChange, onError };\n\n // Initialize core on mount\n useEffect(() => {\n if (!containerRef.current || !pdfjsLib) return;\n\n const core = new CorePDFSearchViewer(\n containerRef.current,\n pdfjsLib,\n viewerOptions ?? {}\n );\n\n core.on('load', (data) => callbackRefs.current.onLoad?.(data));\n core.on('search', (data) => callbackRefs.current.onSearch?.(data));\n core.on('matchchange', (data) => callbackRefs.current.onMatchChange?.(data));\n core.on('error', (data) => callbackRefs.current.onError?.(data));\n\n coreRef.current = core;\n\n return () => {\n core.destroy();\n coreRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pdfjsLib]);\n\n // Load PDF when source changes\n useEffect(() => {\n if (!coreRef.current || !source) return;\n coreRef.current.loadPDF(source).catch(() => {\n // Error already emitted via 'error' event\n });\n }, [source]);\n\n // Run search when query or options change\n useEffect(() => {\n if (!coreRef.current) return;\n\n if (searchQuery && searchQuery.trim().length > 0) {\n coreRef.current.search(searchQuery, searchOptions);\n } else {\n coreRef.current.clearSearch();\n }\n }, [searchQuery, searchOptions]);\n\n // Expose imperative methods via ref\n useImperativeHandle(ref, () => ({\n nextMatch: () => coreRef.current?.nextMatch() ?? -1,\n prevMatch: () => coreRef.current?.prevMatch() ?? -1,\n clearSearch: () => coreRef.current?.clearSearch(),\n getMatchCount: () => coreRef.current?.getMatchCount() ?? 0,\n getCurrentMatchIndex: () => coreRef.current?.getCurrentMatchIndex() ?? -1,\n getCore: () => coreRef.current,\n }));\n\n return <div ref={containerRef} className={className} style={style} />;\n});\n"],"mappings":"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","names":["useRef","useEffect","useCallback","useState","useRef","useState","useEffect","useCallback","useRef","useEffect","PDFSearchViewer","useRef","useEffect"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pdf-search-highlight",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Drop-in PDF viewer with text search and highlight. Vanilla JS core + React wrapper.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/core/index.cjs",
|
|
7
|
+
"module": "./dist/core/index.js",
|
|
8
|
+
"types": "./dist/core/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/core/index.d.ts",
|
|
13
|
+
"default": "./dist/core/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/core/index.d.cts",
|
|
17
|
+
"default": "./dist/core/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"./react": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/react/index.d.ts",
|
|
23
|
+
"default": "./dist/react/index.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/react/index.d.cts",
|
|
27
|
+
"default": "./dist/react/index.cjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"./styles.css": "./dist/pdf-search-highlight.css"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup && cp src/styles/pdf-search-highlight.css dist/pdf-search-highlight.css",
|
|
39
|
+
"dev": "tsup --watch",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"pdfjs-dist": ">=3.0.0",
|
|
45
|
+
"react": ">=18.0.0",
|
|
46
|
+
"react-dom": ">=18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"react": {
|
|
50
|
+
"optional": true
|
|
51
|
+
},
|
|
52
|
+
"react-dom": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"tsup": "^8.0.0",
|
|
58
|
+
"typescript": "^5.4.0",
|
|
59
|
+
"pdfjs-dist": "^3.11.174",
|
|
60
|
+
"react": "^18.0.0",
|
|
61
|
+
"react-dom": "^18.0.0",
|
|
62
|
+
"@types/react": "^18.0.0",
|
|
63
|
+
"@types/react-dom": "^18.0.0"
|
|
64
|
+
},
|
|
65
|
+
"keywords": [
|
|
66
|
+
"pdf",
|
|
67
|
+
"search",
|
|
68
|
+
"highlight",
|
|
69
|
+
"viewer",
|
|
70
|
+
"pdfjs",
|
|
71
|
+
"react",
|
|
72
|
+
"text-search",
|
|
73
|
+
"pdf-viewer"
|
|
74
|
+
],
|
|
75
|
+
"license": "MIT",
|
|
76
|
+
"repository": {
|
|
77
|
+
"type": "git",
|
|
78
|
+
"url": ""
|
|
79
|
+
}
|
|
80
|
+
}
|