Code
Custom Search
Add powerful search capabilities to your PDF viewer with real-time highlighting
Exact Search Term Highlighting
This viewer highlights only the exact search term you type
Loading...
Full Context Highlighting
This viewer highlights the entire text chunk containing your search term
Loading...
Implementation
This example demonstrates two different highlighting behaviors when searching in a PDF document.
Exact Term Highlighting
// Highlight only the exact search term
const ResultItem = ({ result, originalSearchText }) => {
const onClick = async () => {
const rects = await calculateHighlightRects(pageProxy, {
pageNumber: result.pageNumber,
text: result.text,
matchIndex: result.matchIndex,
searchText: originalSearchText, // Pass searchText to highlight only the exact term
});
jumpToHighlightRects(rects, "pixels");
};
};
Full Context Highlighting
// Highlight the entire text chunk containing the search term
const ResultItemFullHighlight = ({ result }) => {
const onClick = async () => {
const rects = await calculateHighlightRects(pageProxy, {
pageNumber: result.pageNumber,
text: result.text,
matchIndex: result.matchIndex,
// No searchText parameter = highlight full context
});
jumpToHighlightRects(rects, "pixels");
};
};
Complete Example
"use client";
import {
Root,
Pages,
Page,
CanvasLayer,
TextLayer,
HighlightLayer,
Search,
} from "@anaralabs/lector";
import { useDebounce } from "use-debounce";
import {
calculateHighlightRects,
SearchResult,
usePdf,
usePdfJump,
useSearch,
} from "@anaralabs/lector";
import { useEffect, useState } from "react";
// Define the TextPosition interface
interface TextPosition {
pageNumber: number;
text: string;
matchIndex: number;
searchText?: string;
}
interface ResultItemProps {
result: SearchResult;
originalSearchText: string;
}
// Exact term highlighting component
const ResultItem = ({ result, originalSearchText }: ResultItemProps) => {
const { jumpToHighlightRects } = usePdfJump();
const getPdfPageProxy = usePdf((state) => state.getPdfPageProxy);
const onClick = async () => {
const pageProxy = getPdfPageProxy(result.pageNumber);
const rects = await calculateHighlightRects(pageProxy, {
pageNumber: result.pageNumber,
text: result.text,
matchIndex: result.matchIndex,
searchText: originalSearchText, // Pass searchText for exact term highlighting
});
jumpToHighlightRects(rects, "pixels");
};
return (
<div
className="flex py-2 hover:bg-gray-50 flex-col cursor-pointer"
onClick={onClick}
>
<div className="flex-1 min-w-0">
<p className="text-sm text-gray-900">{result.text}</p>
</div>
<div className="flex items-center gap-4 text-sm text-gray-500">
<span className="ml-auto">Page {result.pageNumber}</span>
</div>
</div>
);
};
// Search UI component
function SearchUI() {
const [searchText, setSearchText] = useState("");
const [debouncedSearchText] = useDebounce(searchText, 500);
const [limit, setLimit] = useState(5);
const { searchResults: results, search } = useSearch();
useEffect(() => {
setLimit(5);
search(debouncedSearchText, { limit: 5 });
}, [debouncedSearchText]);
const handleLoadMore = async () => {
const newLimit = limit + 5;
await search(debouncedSearchText, { limit: newLimit });
setLimit(newLimit);
};
return (
<div className="flex flex-col w-80 h-full">
<div className="px-4 py-4 border-b border-gray-200 bg-white">
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="Search in document..."
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="flex-1 overflow-y-auto px-4">
<div className="py-4">
{!searchText ? null : !results.exactMatches.length &&
!results.fuzzyMatches.length ? (
<div className="text-center py-4 text-gray-500">
No results found
</div>
) : (
<div className="flex flex-col gap-4">
{results.exactMatches.length > 0 && (
<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-700">
Exact Matches
</h3>
<div className="divide-y divide-gray-100">
{results.exactMatches.map((result) => (
<ResultItem
key={`${result.pageNumber}-${result.matchIndex}`}
result={result}
originalSearchText={searchText}
/>
))}
</div>
</div>
)}
{results.hasMoreResults && (
<button
onClick={handleLoadMore}
className="w-full py-2 px-4 text-sm text-white bg-blue-500 rounded-lg hover:bg-blue-600 transition-colors"
>
Load More Results
</button>
)}
</div>
)}
</div>
</div>
</div>
);
}
// Main component
export default function CustomSearchExample() {
return (
<div className="flex flex-col gap-8">
<div className="flex flex-col">
<h3 className="text-lg font-semibold mb-2">
Exact Search Term Highlighting
</h3>
<Root
source="/pdf/pathways.pdf"
className="flex bg-gray-50 h-[500px]"
loader={<div className="p-4">Loading...</div>}
>
<Search>
<SearchUI />
</Search>
<Pages className="p-4 w-full">
<Page>
<CanvasLayer />
<TextLayer />
<HighlightLayer className="bg-yellow-200/70" />
</Page>
</Pages>
</Root>
</div>
</div>
);
}
Features
- Real-time search with debouncing
- Configurable highlighting (exact term or full context)
- Result highlighting
- Page jumping to search results
- Load more functionality
Best Practices
- Use debouncing to prevent excessive searches
- Choose appropriate highlighting mode for your use case
- Include proper page navigation
- Handle empty states gracefully
- Optimize search performance