${this.loading ? html`
` : ''}
${this.error ? html`
` : ''}
${!this.loading && !this.error ? html`
${this.pageCount > 1 && this.isHovering ? html`
Page ${this.currentPreviewPage} of ${this.pageCount}
` : ''}
${this.pageCount > 0 && !this.isHovering ? html`
${this.pageCount} page${this.pageCount > 1 ? 's' : ''}
` : ''}
${this.clickable ? html`
View PDF
` : ''}
` : ''}
`;
}
private handleMouseEnter() {
this.isHovering = true;
}
private handleMouseLeave() {
this.isHovering = false;
// Reset to first page when not hovering
if (this.currentPreviewPage !== 1) {
this.currentPreviewPage = 1;
void this.scheduleRenderPages();
}
}
private handleMouseMove(e: MouseEvent) {
if (!this.isHovering || this.pageCount <= 1) return;
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const width = rect.width;
// Calculate which page to show based on horizontal position
const percentage = Math.max(0, Math.min(1, x / width));
const newPage = Math.ceil(percentage * this.pageCount) || 1;
if (newPage !== this.currentPreviewPage) {
this.currentPreviewPage = newPage;
void this.scheduleRenderPages();
}
}
public async connectedCallback() {
await super.connectedCallback();
this.setupIntersectionObserver();
await this.updateComplete;
this.cacheElements();
this.setupResizeObserver();
}
public async disconnectedCallback() {
await super.disconnectedCallback();
this.cleanup();
if (this.observer) {
this.observer.disconnect();
}
this.resizeObserver?.disconnect();
this.resizeObserver = undefined;
}
private setupIntersectionObserver() {
const options = {
root: null,
rootMargin: '200px',
threshold: 0.01,
};
this.observer = new IntersectionObserver(
throttle((entries) => {
for (const entry of entries) {
if (entry.isIntersecting && !this.rendered && this.pdfUrl) {
this.loadAndRenderPreview();
} else if (!entry.isIntersecting && this.rendered) {
// Optional: Clear canvases when out of view for memory optimization
// this.clearCanvases();
}
}
}, 100),
options
);
this.observer.observe(this);
}
private async loadAndRenderPreview() {
if (this.rendered || this.loading) return;
this.loading = true;
this.error = false;
PerformanceMonitor.mark(`preview-load-${this.pdfUrl}`);
try {
this.pdfDocument = await PdfManager.loadDocument(this.pdfUrl);
this.pageCount = this.pdfDocument.numPages;
this.currentPreviewPage = 1;
this.loadedPdfUrl = this.pdfUrl;
// Force an update to ensure the canvas element is in the DOM
this.loading = false;
await this.updateComplete;
this.cacheElements();
// Now render the first page
await this.scheduleRenderPages();
this.rendered = true;
const duration = PerformanceMonitor.measure(`preview-render-${this.pdfUrl}`, `preview-load-${this.pdfUrl}`);
console.log(`PDF preview rendered in ${duration}ms`);
} catch (error) {
console.error('Failed to load PDF preview:', error);
this.error = true;
this.loading = false;
}
}
private scheduleRenderPages(): Promise