Skip to content

Latest commit

 

History

History
896 lines (699 loc) · 30.6 KB

File metadata and controls

896 lines (699 loc) · 30.6 KB

Vue File Preview npm versionlicensedownloads

English | 简体中文

A modern, feature-rich file preview component for Vue 3 with support for images, videos, audio, PDFs, Office documents (Word, Excel, PowerPoint), Markdown, and code files.

✨ Features

  • 🎨 Modern UI - Clean and modern interface with smooth animations
  • 📁 Multi-format Support - Supports 20+ file formats
  • 🪟 Two Display Modes - Full-screen modal or inline embedded preview
  • 🖼️ Powerful Image Viewer - Zoom, rotate, drag, mouse wheel zoom
  • 🎬 Custom Video Player - Built on Video.js, supports multiple video formats
  • 🎵 Custom Audio Player - Beautiful audio control interface
  • 📄 PDF Viewer - Pagination support
  • 📊 Office Documents Support - Word, Excel, PowerPoint file preview
  • 📝 Markdown Rendering - GitHub Flavored Markdown support
  • 💻 Code Highlighting - Supports 40+ programming languages
  • 📱 Responsive Design - Adapts to all screen sizes
  • ⌨️ Keyboard Navigation - Arrow, Home, End, and ESC key support

📦 Installation

# Using npm
npm install @eternalheart/vue-file-preview

# Using yarn
yarn add @eternalheart/vue-file-preview

# Using pnpm
pnpm add @eternalheart/vue-file-preview

Important: You also need to import the CSS file:

import '@eternalheart/vue-file-preview/style.css';

Note: The pdfjs-dist dependency will be automatically installed for PDF preview support. No additional installation is required.

PDF.js Configuration (Optional)

If you need to preview PDF files, it's recommended to configure PDF.js to use local static files for better performance and stability:

Method 1: Use CDN (Default)

By default, the component automatically uses unpkg CDN to load PDF.js, no additional configuration needed.

Method 2: Use Local Static Files (Recommended for Production)

  1. Copy PDF.js files to your public directory:
cp -r node_modules/pdfjs-dist/legacy/build/pdf.worker.min.mjs public/pdfjs/
cp -r node_modules/pdfjs-dist/cmaps public/pdfjs/
cp -r node_modules/pdfjs-dist/standard_fonts public/pdfjs/
cp -r node_modules/pdfjs-dist/wasm public/pdfjs/
  1. Configure PDF.js in your app entry:
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
import { configurePdfWorker } from '@eternalheart/vue-file-preview';

configurePdfWorker(pdfjsLib, {
  workerSrc: '/pdfjs/pdf.worker.min.mjs',
  cMapUrl: '/pdfjs/cmaps/',
  cMapPacked: true,
  standardFontDataUrl: '/pdfjs/standard_fonts/',
  wasmUrl: '/pdfjs/wasm/',
});

Call this during client application initialization, before the first PDF render. The worker must use the legacy build matching the installed pdfjs-dist version.

Auto-copy with Vite (Recommended)

Configure auto-copy in vite.config.ts:

import { defineConfig } from 'vite';
import { viteStaticCopy } from 'vite-plugin-static-copy';

export default defineConfig({
  plugins: [
    viteStaticCopy({
      targets: [
        {
          src: 'node_modules/pdfjs-dist/legacy/build/pdf.worker.min.mjs',
          dest: 'pdfjs'
        },
        {
          src: 'node_modules/pdfjs-dist/cmaps',
          dest: 'pdfjs'
        },
        {
          src: 'node_modules/pdfjs-dist/standard_fonts',
          dest: 'pdfjs'
        },
        {
          src: 'node_modules/pdfjs-dist/wasm',
          dest: 'pdfjs'
        }
      ]
    })
  ]
});

Vite Bundler Note (AVIF Decoder)

If your project bundler is Vite and you happen to have @jsquash/avif installed (transitively or directly), the production build may fail with:

[commonjs--resolver] Invalid value "iife" for option "worker.format"
- UMD and IIFE output formats are not supported for code-splitting builds.
file: .../@jsquash/avif/codec/enc/avif_enc_mt.js

This happens because @jsquash/avif ships a multi-threaded worker that uses code-splitting, while Vite's default worker.format is 'iife', which does not support split chunks.

Fix — add this to your vite.config.ts:

export default defineConfig({
  // ... your existing config
  worker: {
    format: 'es',
  },
});

'es' produces module workers (type: 'module'), supported by all modern browsers and compatible with code-splitting.

Note: @jsquash/avif is only used as a fallback when the browser does not natively support AVIF (Chrome 85+, Firefox 93+, Safari 16+ all support it natively). If your target browsers cover the native list, you can also remove @jsquash/avif from your dependencies entirely.

🚀 Quick Start

Basic Usage

<script setup lang="ts">
import { ref } from 'vue';
import { FilePreviewModal } from '@eternalheart/vue-file-preview';
import '@eternalheart/vue-file-preview/style.css';

const files = ref<File[]>([]);
const currentIndex = ref(0);
const isOpen = ref(false);

const handleFileSelect = (e: Event) => {
  const file = (e.target as HTMLInputElement).files?.[0];
  if (file) {
    files.value = [file];
    currentIndex.value = 0;
    isOpen.value = true;
  }
};
</script>

<template>
  <input type="file" @change="handleFileSelect" />

  <FilePreviewModal
    :files="files"
    :current-index="currentIndex"
    :is-open="isOpen"
    @close="isOpen = false"
    @navigate="currentIndex = $event"
  />
</template>

Multiple Input Types

The component supports three types of file inputs:

<script setup lang="ts">
import { ref } from 'vue';
import { FilePreviewModal, type PreviewFileInput } from '@eternalheart/vue-file-preview';
import '@eternalheart/vue-file-preview/style.css';

// Assume file1 comes from a File API source: <input type="file">,
// drag & drop, clipboard paste, or fetch().then(r => r.blob())
const file1 = new File(['content'], 'example.txt', { type: 'text/plain' });

const files: PreviewFileInput[] = [
  // 1. Native File object (auto revoked when unmounted)
  file1,

  // 2. HTTP URL string (loaded on demand)
  'https://example.com/image.jpg',

  // 3. File object with metadata (recommended for remote resources)
  {
    name: 'document.pdf',
    type: 'application/pdf',
    url: '/path/to/document.pdf',
    size: 1024,
  },
];

// Memory note: If you generate URLs via URL.createObjectURL(),
// call URL.revokeObjectURL() when files are removed to avoid memory leaks.

const isOpen = ref(true);
</script>

<template>
  <FilePreviewModal
    :files="files"
    :current-index="0"
    :is-open="isOpen"
    @close="isOpen = false"
  />
</template>

Embedded Mode (FilePreviewEmbed)

Besides the full-screen modal, the library also ships an embedded variant that renders the preview inline inside any container. Useful for detail panels, side-by-side layouts, dashboards, etc.

<script setup lang="ts">
import { ref } from 'vue';
import { FilePreviewEmbed } from '@eternalheart/vue-file-preview';
import '@eternalheart/vue-file-preview/style.css';

const index = ref(0);

const files = [
  'https://example.com/image.jpg',
  { name: 'document.pdf', type: 'application/pdf', url: '/doc.pdf' },
];
</script>

<template>
  <div style="width: 100%; height: 520px">
    <FilePreviewEmbed
      :files="files"
      :current-index="index"
      @navigate="index = $event"
    />
  </div>
</template>

Differences from FilePreviewModal:

  • No teleport, no full-screen overlay, no isOpen / @close
  • Does not show the close button in the toolbar
  • Keyboard navigation (←/→/Home/End) is scoped to the embed container (focus-based)
  • Size defaults to width: 100%; height: 100%; override via width / height props
<!-- Explicit size -->
<FilePreviewEmbed :files="files" :width="800" :height="500" />

📖 Supported File Formats

Images

  • Formats: JPG, PNG, GIF, WebP, SVG, BMP, ICO, HEIC/HEIF, AVIF, TIFF, RAW, PSD, JPEG 2000
  • Features: Zoom (0.01x - 10x), rotate, drag, mouse wheel zoom, double-click reset, multi-page TIFF
  • Decoding: HEIC/RAW/PSD use a Worker-first path with a main-thread fallback; other advanced formats are loaded on demand

Videos

  • Formats: MP4, WebM, OGG, MOV, AVI, MKV, M4V, 3GP, FLV
  • Features: Custom player, progress control, volume adjustment, fullscreen

Audio

  • Formats: MP3, WAV, OGG, M4A, AAC, FLAC
  • Features: Custom player, progress bar, volume control, skip forward/backward

Documents

  • PDF: Pagination, zoom
  • Word: DOCX and legacy DOC (97–2003) support
  • Excel: XLSX and legacy XLS (97–2003) support
  • PowerPoint: PPTX and legacy PPT (97–2003) slide preview

Fonts

  • Formats: TTF, OTF, WOFF, WOFF2
  • Features: Font metadata (family, designer, version), character set preview, custom text input, multi-size display

CAD / 3D Models

  • Formats: DXF, STL, OBJ, GLTF, GLB
  • Features: Interactive 3D viewer with orbit/zoom/pan controls, wireframe/solid toggle, grid & axes display, auto-centering

Code & Text

  • Markdown: GitHub Flavored Markdown, code highlighting
  • Code Files: Vue, Svelte, Astro, JS/TS, Dart, GraphQL, Protobuf, Prisma, Terraform, PowerShell, Scala, and 40+ languages
  • Config/Logs: YAML, TOML, INI, ENV, PROPERTIES, BAT, CMD, TEX, MAP, LOG, DIFF, PATCH, etc.

Structured Data

  • JSON: Auto formatting + syntax highlighting
  • CSV/TSV: Zero-dependency parser, table view with headers and row/column stats
  • XML: DOMParser validation + pretty print + syntax highlighting

Subtitles & Lyrics

  • SRT / WebVTT: Zero-dependency parser, structured cue list (index, time range, text)
  • LRC / Enhanced LRC: Lyric files with [mm:ss.xx] line stamps (and inline <mm:ss.xx> per-word stamps for ELRC), with [ti:][ar:][al:] metadata header
  • ASS / SSA: Advanced SubStation Alpha — extracts Dialogue events, strips \N \h and {...} override codes, surfaces Style names
  • TTML / DFXP: W3C / Apple Music XML captions, supports begin / end / dur and <br/>

Archives

  • ZIP: Tree view + inline preview for text/code/image entries, download fallback for other types

Outlook Email

  • MSG: Headers, body rendering, attachment list

E-books

  • EPUB: Chapter navigation, pagination

⚠️ Limitations & Performance

Support Levels

✅ Full Support (Production Ready)

  • Images (JPG, PNG, GIF, WebP, SVG, BMP, ICO)
  • Videos (MP4, WebM, OGG)
  • Audio (MP3, WAV, OGG)
  • PDF
  • Markdown
  • Code files (40+ languages via Shiki, lazy-loaded)
  • JSON, CSV, XML

⚠️ Partial Support (Preview Only)

  • Office (DOCX, XLSX, PPTX): Basic layout and text rendering. Complex formatting (charts, macros, embedded objects) may not render accurately.
  • ZIP: Directory tree browsing + inline preview for text/code/images. Large archives (>100MB) may cause performance issues.
  • Fonts (TTF, OTF, WOFF): Metadata + character preview. Full font feature testing not supported.

🧪 Experimental

  • MSG (Outlook Email): Headers and plain text body. Complex HTML body may not render correctly.
  • EPUB: Basic chapter navigation. CSS styling may differ from native readers. DRM-protected files not supported.
  • Subtitle formats (SRT, ASS, TTML, LRC): Text display only. No video sync or advanced styling.

Performance Boundaries

File Size Status Notes
< 50MB ✅ Recommended Smooth preview experience
50-100MB ⚠️ May lag UI may become unresponsive during load
> 100MB ❌ Not recommended Browser memory limits may be exceeded

Special Cases:

  • ZIP archives: Performance depends on file count, not just size
  • Office documents: Complex files (>200 pages, heavy images) may timeout
  • Code highlighting: Files >5MB may take 3-5s to highlight

Browser Compatibility

Minimum Requirements:

  • Chrome 90+ / Edge 90+
  • Firefox 88+
  • Safari 14+

Known Limitations:

  • Safari iOS: Video autoplay requires user interaction
  • Firefox: AVIF support requires Firefox 93+ (fallback decoder included)
  • Office formats: Rendering quality varies across browsers
  • EPUB: Some CSS features unsupported in older browsers

🎮 API Reference

FilePreviewModal Props

Prop Type Required Description
files PreviewFileInput[] Array of files (supports File objects, file objects, or URL strings)
currentIndex number Current file index
isOpen boolean Whether the modal is open
customRenderers CustomRenderer[] Custom renderers for specific file types
locale Locale UI language ('zh-CN' default, 'en-US' built-in)
messages Partial<Record<Locale, Partial<Messages>>> Custom translation overrides
headless boolean Headless mode — hides toolbar and navigation arrows
theme Theme Theme mode: 'auto' | 'dark' | 'light' (default 'dark')
showDownload boolean Whether to show the download button (default true)
showClose boolean Whether to show the close button (default true for modal)
showNavigation boolean Whether to show file navigation arrows (default true)
loopNavigation boolean Whether file navigation wraps around (default false)

FilePreviewModal Events

Event Payload Description
close - Emitted when the modal should close
navigate number Emitted when navigating to a different file index

FilePreviewEmbed Props

Prop Type Required Default Description
files PreviewFileInput[] - Array of files
currentIndex number 0 Current file index
customRenderers CustomRenderer[] - Custom renderers
width number | string '100%' Container width
height number | string '100%' Container height
locale Locale 'zh-CN' UI language ('zh-CN' or 'en-US')
messages Partial<Record<Locale, Partial<Messages>>> - Custom translation overrides
headless boolean false Headless mode — hides toolbar and navigation arrows
theme Theme 'dark' Theme mode: 'auto' | 'dark' | 'light'
showDownload boolean true Whether to show the download button
showClose boolean false Whether to show the close button (default false for embed)
showNavigation boolean true Whether to show file navigation arrows
loopNavigation boolean false Whether file navigation wraps around

FilePreviewEmbed Events

Event Payload Description
navigate number Emitted when navigating to a different file index

FilePreviewContent (advanced)

Both FilePreviewModal and FilePreviewEmbed are thin wrappers around the exported lower-level FilePreviewContent component. Use it directly when building a fully custom wrapper:

<FilePreviewContent
  mode="embed"
  :files="files"
  :current-index="index"
  @navigate="index = $event"
/>

File Type Definitions

// Supports three types of file input
type PreviewFileInput = File | PreviewFileLink | string;

// 1. Native File object (Browser File API)
const file: File = ...;

// 2. File object
interface PreviewFileLink {
  id?: string;       // Optional unique identifier
  name: string;      // File name
  type: string;      // MIME type
  url: string;       // File URL (supports blob URLs and HTTP URLs)
  size?: number;     // File size in bytes
}

// 3. HTTP URL string
const url: string = 'https://example.com/file.pdf';

🧩 Custom Renderers

The library supports custom renderers for handling file types not built-in. Custom renderers can optionally provide toolbar configurations and integrate with the library's architecture.

Event-Driven Toolbar Updates

Custom renderers can implement real-time toolbar updates using Vue 3's reactivity system:

Benefits:

  • Real-time updates: Toolbar reflects state changes immediately via Vue reactivity
  • Better performance: No polling overhead, leverages Vue's efficient change detection
  • Type-safe: Full TypeScript support with proper interfaces

Implementation:

<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { ChevronLeft, ChevronRight } from 'lucide-vue-next';
import type { ToolbarGroup } from '@eternalheart/vue-file-preview';

interface Props {
  url: string;
}

const props = defineProps<Props>();
const emit = defineEmits<{
  pageChange: [current: number, total: number];
}>();

const currentPage = ref(1);
const totalPages = ref(10);

// Emit page changes
watch([currentPage, totalPages], () => {
  emit('pageChange', currentPage.value, totalPages.value);
});

const getToolbarGroups = (): ToolbarGroup[] => [
  {
    items: [
      {
        type: 'button',
        icon: ChevronLeft,
        tooltip: 'Previous Page',
        action: () => currentPage.value = Math.max(1, currentPage.value - 1),
        disabled: currentPage.value <= 1
      },
      {
        type: 'text',
        content: `${currentPage.value} / ${totalPages.value}`,
        minWidth: '4rem'
      },
      {
        type: 'button',
        icon: ChevronRight,
        tooltip: 'Next Page',
        action: () => currentPage.value = Math.min(totalPages.value, currentPage.value + 1),
        disabled: currentPage.value >= totalPages.value
      }
    ]
  }
];

// Expose for parent component
defineExpose({
  getToolbarGroups
});
</script>

<template>
  <div>Your custom renderer UI</div>
</template>

Main component usage:

<script setup>
import { CustomRenderer } from './CustomRenderer.vue';

const files = [
  { name: 'custom.xyz', type: 'application/custom', url: '/path/to/file' }
];
</script>

<template>
  <FilePreviewModal
    :files="files"
    :custom-renderers="[
      {
        test: (file) => file.type === 'application/custom',
        render: () => CustomRenderer
      }
    ]"
  />
</template>

The main component automatically tracks reactive changes in getToolbarGroups() via Vue's reactivity system. No manual subscription needed.

Renderer Lazy Loading

All built-in renderers use code-splitting via defineAsyncComponent to minimize the main bundle size and improve initial load performance.

Architecture:

  • Registration: Renderers register in src/renderers/lazy.ts using defineAsyncComponent wrappers
  • Loading: Each renderer is a separate chunk, loaded on-demand when needed
  • Fallback: RendererLoading component handles the loading state

Each renderer is emitted as a separate async chunk and loaded only when needed.

Implementation Example:

// src/renderers/lazy.ts
import { defineAsyncComponent } from 'vue';

const wrap = (loader: () => Promise<any>) =>
  defineAsyncComponent({
    loader,
    loadingComponent: RendererLoading,
    delay: 0
  });

export const CustomRenderer = wrap(() => import('./Custom/index.vue'));
<!-- src/FilePreviewContent.vue -->
<script setup>
import { CustomRenderer } from './renderers/lazy';  // ✅ Lazy import
// NOT: import CustomRenderer from './renderers/Custom/index.vue';  // ❌ Direct import breaks code-splitting
</script>

<template>
  <CustomRenderer
    v-if="fileType === 'custom'"
    ref="rendererRef"
    :url="currentFile.url"
  />
</template>

For Custom Renderers:

If you want your custom renderer to benefit from code-splitting, use the same pattern:

<script setup>
import { defineAsyncComponent } from 'vue';

const MyCustomRenderer = defineAsyncComponent(() => import('./MyCustomRenderer.vue'));

const files = [...];
</script>

<template>
  <FilePreviewModal
    :files="files"
    :custom-renderers="[
      {
        test: (file) => file.type === 'application/custom',
        render: () => MyCustomRenderer
      }
    ]"
  />
</template>

i18n Integration

Custom renderers can access the library's i18n system via the useTranslator() composable for consistent multilingual support.

Architecture:

  • Dictionary Source: file-preview-core/src/i18n/messages/ (zh-CN.ts, en-US.ts)
  • No Hardcoding: All user-visible text must use translation keys
  • Automatic Locale Switching: Follows the locale prop passed to FilePreviewModal

Usage in Custom Renderers:

<script setup lang="ts">
import { ref } from 'vue';
import { useTranslator } from '@eternalheart/vue-file-preview';

interface Props {
  url: string;
}

const props = defineProps<Props>();
const { t } = useTranslator();
const error = ref<string | null>(null);
</script>

<template>
  <div v-if="error" class="vfp-text-fg-primary">
    {{ t('custom.load_failed') }}: {{ error }}
  </div>
  <div v-else>
    <button>{{ t('common.download') }}</button>
    <span>{{ t('custom.loading') }}</span>
  </div>
</template>

Note on Usage:

  • In <template>: Use t('key') directly (automatically unwrapped)
  • In <script>: Use t.value('key') for imperative calls

Adding Custom Translation Keys:

For custom renderers, extend translations via the messages prop (do NOT modify source files in node_modules):

<template>
  <FilePreviewModal
    :files="files"
    locale="en-US"
    :messages="{
      'en-US': {
        'custom.load_failed': 'Failed to load custom file',
        'custom.file_size': 'File size: {size} KB'
      },
      'zh-CN': {
        'custom.load_failed': '自定义文件加载失败',
        'custom.file_size': '文件大小: {size} KB'
      }
    }"
    :custom-renderers="[...]"
  />
</template>

Guidelines:

  • Use <scope>.<snake_name> format (e.g., custom.load_failed, custom.parse_error)
  • Provide translations for all enabled locales (zh-CN and en-US)
  • Common keys already available: common.loading, common.download, common.close, toolbar.*

Parameterized Translations:

<template>
  <!-- Dictionary: 'custom.file_size': 'File size: {size} KB' -->
  <span>{{ t('custom.file_size', { size: 1024 }) }}</span>
  <!-- → "File size: 1024 KB" -->
</template>

Toolbar Integration:

Toolbar items should also use translated strings:

const getToolbarGroups = (): ToolbarGroup[] => [
  {
    items: [
      {
        type: 'button',
        icon: Download,
        tooltip: t.value('common.download'),  // ✅ Translated (use .value in script)
        action: handleDownload
      }
    ]
  }
];

Theme Adaptation

Custom renderers must use semantic color tokens to support the library's 'auto' | 'dark' | 'light' theme system.

Semantic Token System:

All colors are defined as CSS variables (--fp-*) and exposed via Tailwind classes with the vfp- prefix:

Usage Class Description
Text (fg)
Primary text vfp-text-fg-primary Highest contrast
Body text vfp-text-fg-secondary Default text
Secondary text vfp-text-fg-tertiary Captions, counters
Muted text vfp-text-fg-muted Placeholders
Disabled text vfp-text-fg-disabled Disabled buttons
Background (surface)
Surface 1 vfp-bg-surface-1 Cards, weakest
Surface 2 vfp-bg-surface-2 Hover states
Surface 3 vfp-bg-surface-3 Emphasis
Toolbar vfp-bg-surface-toolbar Top toolbar
Borders
Weak border vfp-border-line-weak Subtle lines
Standard border vfp-border-line Default borders
Strong border vfp-border-line-strong Emphasis
Code
Code background vfp-bg-code-bg Dark: #1e1e1e / Light: #f6f8fa
Code text vfp-text-code-fg Follows theme
Accent
Accent background vfp-bg-accent Primary buttons
Accent hover vfp-bg-accent-hover Hover state

✅ Correct Usage:

<template>
  <div class="vfp-bg-surface-1 vfp-border vfp-border-line-weak vfp-rounded">
    <h2 class="vfp-text-fg-primary vfp-text-lg">Title</h2>
    <p class="vfp-text-fg-secondary">Body text</p>
    <button class="vfp-bg-surface-2 hover:vfp-bg-surface-3 vfp-text-fg-primary">
      Click me
    </button>
    <pre class="vfp-bg-code-bg vfp-text-code-fg">{{ code }}</pre>
  </div>
</template>

For <style scoped> blocks, use CSS variables:

<style scoped>
.my-block {
  color: var(--fp-fg-primary);
  background: var(--fp-surface-2);
  border: 1px solid var(--fp-line);
}

.my-code {
  background: var(--fp-code-bg);
  color: var(--fp-code-fg);
}
</style>

❌ Incorrect Usage (DO NOT USE):

<!-- ❌ Literal color classes — breaks theme switching -->
<div class="vfp-text-white/90 vfp-bg-white/10 vfp-border-white/15">
<div class="vfp-text-gray-700 vfp-bg-gray-100">

<!-- ❌ Inline literal colors -->
<div :style="{ color: '#ffffff', background: '#1f2937' }">

<!-- ❌ Hardcoded dark-only colors in scoped style -->
<style scoped>
.foo { color: rgba(255, 255, 255, 0.75); }   /* Use var(--fp-fg-secondary) */
.foo { background: #1e1e1e; }                 /* Use var(--fp-code-bg) */
</style>

Theme-Aware Third-Party Libraries:

For libraries with theme props (e.g., shiki), use useResolvedTheme():

<script setup lang="ts">
import { ref, watch } from 'vue';
import { codeToHtml } from 'shiki';
import { useResolvedTheme } from '@eternalheart/vue-file-preview';

const props = defineProps<{ code: string; lang: string }>();
const resolvedTheme = useResolvedTheme();  // Ref<'dark' | 'light'>
const highlighted = ref('');

const highlightCode = async () => {
  highlighted.value = await codeToHtml(props.code, {
    lang: props.lang,
    theme: resolvedTheme.value === 'light' ? 'github-light' : 'dark-plus'
  });
};

// Re-highlight when theme changes
watch(resolvedTheme, highlightCode, { immediate: true });
</script>

<template>
  <div v-html="highlighted"></div>
</template>

Testing:

Always test your custom renderer in both Light and Dark themes:

<FilePreviewModal
  :files="files"
  theme="light"  // Switch between 'light', 'dark', 'auto'
  :custom-renderers="[...]"
/>

Verify:

  • Text is readable in both themes (no white-on-white or black-on-black)
  • Borders and dividers are visible
  • Hover states have sufficient contrast
  • Code blocks follow theme (not always dark)

⌨️ Keyboard Shortcuts

  • ESC - Close preview
  • - Previous file
  • - Next file
  • Home - First file
  • End - Last file
  • Mouse Wheel - Zoom image (image preview only)

📚 Documentation

🛠️ Development

# Clone repository
git clone https://github.com/wh131462/file-preview.git

# Install dependencies
pnpm install

# Start dev server (Vue demo app)
pnpm dev:vue-example

# Build library
pnpm build:vue

📄 License

MIT © EternalHeart

🤝 Contributing

Issues and Pull Requests are welcome!

🔗 Links