@eternalheart/angular-file-preview 1.6.5

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.
Files changed (119) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +879 -0
  3. package/README.zh-CN.md +876 -0
  4. package/lib/LICENSE +22 -0
  5. package/lib/README.md +879 -0
  6. package/lib/components/preview/file-preview-toolbar.d.ts +19 -0
  7. package/lib/components/preview/nav-arrows.d.ts +25 -0
  8. package/lib/components/preview/renderer-outlet.d.ts +23 -0
  9. package/lib/components/preview/toolbar-button.d.ts +12 -0
  10. package/lib/components/resizable-split.d.ts +32 -0
  11. package/lib/di/locale.service.d.ts +12 -0
  12. package/lib/di/request.service.d.ts +20 -0
  13. package/lib/di/theme-mode.d.ts +6 -0
  14. package/lib/di/theme.service.d.ts +9 -0
  15. package/lib/fesm2022/eternalheart-angular-file-preview-avifLoader-BP3fgcMm-CZsJ9svm.mjs +55 -0
  16. package/lib/fesm2022/eternalheart-angular-file-preview-avifLoader-BP3fgcMm-CZsJ9svm.mjs.map +1 -0
  17. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs +58 -0
  18. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs.map +1 -0
  19. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs +5835 -0
  20. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs.map +1 -0
  21. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs +17 -0
  22. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs.map +1 -0
  23. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index--7d8Hgi0-D-sg24Qs.mjs +284 -0
  24. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index--7d8Hgi0-D-sg24Qs.mjs.map +1 -0
  25. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-6dZS_Cfs-B1itLV5R.mjs +703 -0
  26. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-6dZS_Cfs-B1itLV5R.mjs.map +1 -0
  27. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-B1ANUCTy-BRs7TMLn.mjs +231 -0
  28. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-B1ANUCTy-BRs7TMLn.mjs.map +1 -0
  29. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BEaqpRV8-BidVIVlO.mjs +552 -0
  30. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BEaqpRV8-BidVIVlO.mjs.map +1 -0
  31. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BGqTRZbm-CAOLRw20.mjs +688 -0
  32. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BGqTRZbm-CAOLRw20.mjs.map +1 -0
  33. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Bv3JRXKn-B4Wewc-4.mjs +273 -0
  34. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Bv3JRXKn-B4Wewc-4.mjs.map +1 -0
  35. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C0McUoqT-DJ35bhIz.mjs +288 -0
  36. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C0McUoqT-DJ35bhIz.mjs.map +1 -0
  37. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C518mdUd-B5SCzrzU.mjs +550 -0
  38. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C518mdUd-B5SCzrzU.mjs.map +1 -0
  39. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CDjKPqyW-CmX5sdkL.mjs +102 -0
  40. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CDjKPqyW-CmX5sdkL.mjs.map +1 -0
  41. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CHi9tOe0-NOeQYAnM.mjs +516 -0
  42. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CHi9tOe0-NOeQYAnM.mjs.map +1 -0
  43. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CQwhffym-DAGw4Xy_.mjs +823 -0
  44. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CQwhffym-DAGw4Xy_.mjs.map +1 -0
  45. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CSCTj7ft-BfqruUyY.mjs +164 -0
  46. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CSCTj7ft-BfqruUyY.mjs.map +1 -0
  47. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cb1RLXeO-Bj9gjS1Z.mjs +655 -0
  48. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cb1RLXeO-Bj9gjS1Z.mjs.map +1 -0
  49. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cw90Kdyg-BzrW_cyx.mjs +207 -0
  50. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cw90Kdyg-BzrW_cyx.mjs.map +1 -0
  51. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D35x1nwM-CugEEXjg.mjs +199 -0
  52. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D35x1nwM-CugEEXjg.mjs.map +1 -0
  53. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D9jJNzeM-mWX59Aj2.mjs +194 -0
  54. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D9jJNzeM-mWX59Aj2.mjs.map +1 -0
  55. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DbmuvdvN-C29uLyt2.mjs +94 -0
  56. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DbmuvdvN-C29uLyt2.mjs.map +1 -0
  57. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DsNNZ59Q-C8MaSME6.mjs +468 -0
  58. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DsNNZ59Q-C8MaSME6.mjs.map +1 -0
  59. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DufW2Fuc-CTosCut6.mjs +193 -0
  60. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DufW2Fuc-CTosCut6.mjs.map +1 -0
  61. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-TSTa_Q7E-DBhkL6pV.mjs +184 -0
  62. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-TSTa_Q7E-DBhkL6pV.mjs.map +1 -0
  63. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-pG0Ko5mI-DthQJg-X.mjs +492 -0
  64. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-pG0Ko5mI-DthQJg-X.mjs.map +1 -0
  65. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-shiki-highlight-CL9cG481-BKfwdOpN.mjs +68 -0
  66. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-shiki-highlight-CL9cG481-BKfwdOpN.mjs.map +1 -0
  67. package/lib/fesm2022/eternalheart-angular-file-preview-heicLoader-CH_raQNn-BSXHd_oi.mjs +36 -0
  68. package/lib/fesm2022/eternalheart-angular-file-preview-heicLoader-CH_raQNn-BSXHd_oi.mjs.map +1 -0
  69. package/lib/fesm2022/eternalheart-angular-file-preview-jp2Loader-ldvCSsvm-DJEhfimG.mjs +121 -0
  70. package/lib/fesm2022/eternalheart-angular-file-preview-jp2Loader-ldvCSsvm-DJEhfimG.mjs.map +1 -0
  71. package/lib/fesm2022/eternalheart-angular-file-preview-psdLoader-D5CkupyY-D5Gegcdg.mjs +78 -0
  72. package/lib/fesm2022/eternalheart-angular-file-preview-psdLoader-D5CkupyY-D5Gegcdg.mjs.map +1 -0
  73. package/lib/fesm2022/eternalheart-angular-file-preview-rawLoader-0mLvxCNp-CCXn9Rdp.mjs +81 -0
  74. package/lib/fesm2022/eternalheart-angular-file-preview-rawLoader-0mLvxCNp-CCXn9Rdp.mjs.map +1 -0
  75. package/lib/fesm2022/eternalheart-angular-file-preview-tiffLoader-CmUAD1vO-0JqIws9t.mjs +59 -0
  76. package/lib/fesm2022/eternalheart-angular-file-preview-tiffLoader-CmUAD1vO-0JqIws9t.mjs.map +1 -0
  77. package/lib/fesm2022/eternalheart-angular-file-preview.mjs +2 -0
  78. package/lib/fesm2022/eternalheart-angular-file-preview.mjs.map +1 -0
  79. package/lib/file-preview-content.d.ts +65 -0
  80. package/lib/file-preview-embed.d.ts +30 -0
  81. package/lib/file-preview-modal.d.ts +34 -0
  82. package/lib/fp-core.d.ts +1 -0
  83. package/lib/index.css +2119 -0
  84. package/lib/index.d.ts +11 -0
  85. package/lib/renderers/Audio/index.d.ts +74 -0
  86. package/lib/renderers/Cad/index.d.ts +45 -0
  87. package/lib/renderers/Cad/toolbar.d.ts +15 -0
  88. package/lib/renderers/Csv/index.d.ts +26 -0
  89. package/lib/renderers/Doc/index.d.ts +19 -0
  90. package/lib/renderers/Docx/index.d.ts +42 -0
  91. package/lib/renderers/Epub/index.d.ts +57 -0
  92. package/lib/renderers/Font/font-preview-line.d.ts +26 -0
  93. package/lib/renderers/Font/index.d.ts +52 -0
  94. package/lib/renderers/Image/index.d.ts +79 -0
  95. package/lib/renderers/Json/index.d.ts +61 -0
  96. package/lib/renderers/Markdown/index.d.ts +35 -0
  97. package/lib/renderers/Mobi/index.d.ts +51 -0
  98. package/lib/renderers/Msg/index.d.ts +47 -0
  99. package/lib/renderers/Pdf/index.d.ts +55 -0
  100. package/lib/renderers/Ppt/index.d.ts +19 -0
  101. package/lib/renderers/Pptx/index.d.ts +29 -0
  102. package/lib/renderers/RendererError.d.ts +9 -0
  103. package/lib/renderers/RendererLoading.d.ts +7 -0
  104. package/lib/renderers/Subtitle/index.d.ts +25 -0
  105. package/lib/renderers/Text/index.d.ts +29 -0
  106. package/lib/renderers/Unsupported/index.d.ts +15 -0
  107. package/lib/renderers/Video/index.d.ts +28 -0
  108. package/lib/renderers/Xlsx/index.d.ts +25 -0
  109. package/lib/renderers/Xml/index.d.ts +26 -0
  110. package/lib/renderers/Zip/index.d.ts +58 -0
  111. package/lib/renderers/Zip/tree-item.d.ts +29 -0
  112. package/lib/renderers/base.types.d.ts +10 -0
  113. package/lib/renderers/lazy.d.ts +23 -0
  114. package/lib/renderers/registry.d.ts +13 -0
  115. package/lib/renderers/toolbar.types.d.ts +19 -0
  116. package/lib/types.d.ts +26 -0
  117. package/lib/utils/audio-player.d.ts +20 -0
  118. package/lib/utils/shiki-highlight.d.ts +7 -0
  119. package/package.json +119 -0
package/lib/README.md ADDED
@@ -0,0 +1,879 @@
1
+ # Angular File Preview [![npm version](https://img.shields.io/npm/v/@eternalheart/angular-file-preview.svg)](https://www.npmjs.com/package/@eternalheart/angular-file-preview)[![license](https://img.shields.io/npm/l/@eternalheart/angular-file-preview.svg)](https://github.com/wh131462/file-preview/blob/master/LICENSE)[![downloads](https://img.shields.io/npm/dm/@eternalheart/angular-file-preview.svg)](https://www.npmjs.com/package/@eternalheart/angular-file-preview)
2
+
3
+ English | [简体中文](./README.zh-CN.md)
4
+
5
+ A modern, feature-rich file preview component for Angular with support for images, videos, audio, PDFs, Office documents (Word, Excel, PowerPoint), Markdown, and code files.
6
+
7
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2728.svg" width="20" height="20" alt="✨" /> Features
8
+
9
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3a8.svg" width="16" height="16" alt="🎨" style="vertical-align: middle;" /> **Modern UI** - Clean and modern interface with smooth animations
10
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c1.svg" width="16" height="16" alt="📁" style="vertical-align: middle;" /> **Multi-format Support** - Supports 20+ file formats
11
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1fa9f.svg" width="16" height="16" alt="🪟" style="vertical-align: middle;" /> **Two Display Modes** - Full-screen modal **or** inline embedded preview
12
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f5bc.svg" width="16" height="16" alt="🖼️" style="vertical-align: middle;" /> **Powerful Image Viewer** - Zoom, rotate, drag, mouse wheel zoom
13
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3ac.svg" width="16" height="16" alt="🎬" style="vertical-align: middle;" /> **Custom Video Player** - Built on Video.js, supports multiple video formats
14
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3b5.svg" width="16" height="16" alt="🎵" style="vertical-align: middle;" /> **Custom Audio Player** - Beautiful audio control interface
15
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c4.svg" width="16" height="16" alt="📄" style="vertical-align: middle;" /> **PDF Viewer** - Pagination support
16
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4ca.svg" width="16" height="16" alt="📊" style="vertical-align: middle;" /> **Office Documents Support** - Word, Excel, PowerPoint file preview
17
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4dd.svg" width="16" height="16" alt="📝" style="vertical-align: middle;" /> **Markdown Rendering** - GitHub Flavored Markdown support
18
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4bb.svg" width="16" height="16" alt="💻" style="vertical-align: middle;" /> **Code Highlighting** - Supports 40+ programming languages
19
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4f1.svg" width="16" height="16" alt="📱" style="vertical-align: middle;" /> **Responsive Design** - Adapts to all screen sizes
20
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2328.svg" width="16" height="16" alt="⌨️" style="vertical-align: middle;" /> **Keyboard Navigation** - Arrow keys and ESC support
21
+
22
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4e6.svg" width="20" height="20" alt="📦" /> Installation
23
+
24
+ ```bash
25
+ # Using npm
26
+ npm install @eternalheart/angular-file-preview
27
+
28
+ # Using yarn
29
+ yarn add @eternalheart/angular-file-preview
30
+
31
+ # Using pnpm
32
+ pnpm add @eternalheart/angular-file-preview
33
+ ```
34
+
35
+ **Important:** You also need to import the CSS file:
36
+
37
+ ```ts
38
+ import '@eternalheart/angular-file-preview/style.css';
39
+ ```
40
+
41
+ > **Note:** The `pdfjs-dist` dependency will be automatically installed for PDF preview support. No additional installation is required.
42
+
43
+ ### PDF.js Configuration (Optional)
44
+
45
+ If you need to preview PDF files, it's recommended to configure PDF.js to use local static files for better performance and stability:
46
+
47
+ #### Method 1: Use CDN (Default)
48
+
49
+ By default, the component automatically uses unpkg CDN to load PDF.js, no additional configuration needed.
50
+
51
+ #### Method 2: Use Local Static Files (Recommended for Production)
52
+
53
+ 1. Copy PDF.js files to your public directory:
54
+
55
+ ```bash
56
+ cp -r node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdfjs/
57
+ cp -r node_modules/pdfjs-dist/cmaps public/pdfjs/
58
+ ```
59
+
60
+ 2. Configure PDF.js in your app entry:
61
+
62
+ ```ts
63
+ import * as pdfjsLib from 'pdfjs-dist/build/pdf.mjs';
64
+ import { configurePdfWorker } from '@eternalheart/angular-file-preview';
65
+
66
+ configurePdfWorker(pdfjsLib, {
67
+ workerSrc: '/pdfjs/pdf.worker.min.mjs',
68
+ cMapUrl: '/pdfjs/cmaps/',
69
+ cMapPacked: true,
70
+ });
71
+ ```
72
+
73
+ #### Auto-copy with Vite (Recommended)
74
+
75
+ Configure auto-copy in `vite.config.ts`:
76
+
77
+ ```ts
78
+ import { defineConfig } from 'vite';
79
+ import { viteStaticCopy } from 'vite-plugin-static-copy';
80
+
81
+ export default defineConfig({
82
+ plugins: [
83
+ viteStaticCopy({
84
+ targets: [
85
+ {
86
+ src: 'node_modules/pdfjs-dist/build/pdf.worker.min.mjs',
87
+ dest: 'pdfjs'
88
+ },
89
+ {
90
+ src: 'node_modules/pdfjs-dist/cmaps',
91
+ dest: 'pdfjs'
92
+ }
93
+ ]
94
+ })
95
+ ]
96
+ });
97
+ ```
98
+
99
+ ### Vite Bundler Note (AVIF Decoder)
100
+
101
+ If your project bundler is Vite and you happen to have `@jsquash/avif` installed (transitively or directly), the production build may fail with:
102
+
103
+ ```
104
+ [commonjs--resolver] Invalid value "iife" for option "worker.format"
105
+ - UMD and IIFE output formats are not supported for code-splitting builds.
106
+ file: .../@jsquash/avif/codec/enc/avif_enc_mt.js
107
+ ```
108
+
109
+ 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.
110
+
111
+ **Fix** — add this to your `vite.config.ts`:
112
+
113
+ ```ts
114
+ export default defineConfig({
115
+ // ... your existing config
116
+ worker: {
117
+ format: 'es',
118
+ },
119
+ });
120
+ ```
121
+
122
+ `'es'` produces module workers (`type: 'module'`), supported by all modern browsers and compatible with code-splitting.
123
+
124
+ > 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.
125
+
126
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f680.svg" width="20" height="20" alt="🚀" /> Quick Start
127
+
128
+ ### Basic Usage
129
+
130
+ ```ts
131
+ import { Component, signal } from '@angular/core';
132
+ import { FilePreviewModal } from '@eternalheart/angular-file-preview';
133
+ import '@eternalheart/angular-file-preview/style.css';
134
+
135
+ @Component({
136
+ selector: 'app-root',
137
+ standalone: true,
138
+ imports: [FilePreviewModal],
139
+ template: `
140
+ <input type="file" (change)="handleFileSelect($event)" />
141
+ <FilePreviewModal
142
+ [files]="files()"
143
+ [currentIndex]="currentIndex()"
144
+ [isOpen]="isOpen()"
145
+ (close)="isOpen.set(false)"
146
+ (navigate)="currentIndex.set($event)"
147
+ />
148
+ `,
149
+ })
150
+ export class AppComponent {
151
+ files = signal<File[]>([]);
152
+ currentIndex = signal(0);
153
+ isOpen = signal(false);
154
+
155
+ handleFileSelect(e: Event) {
156
+ const file = (e.target as HTMLInputElement).files?.[0];
157
+ if (file) {
158
+ this.files.set([file]);
159
+ this.currentIndex.set(0);
160
+ this.isOpen.set(true);
161
+ }
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### Multiple Input Types
167
+
168
+ The component supports three types of file inputs:
169
+
170
+ ```vue
171
+ <script setup lang="ts">
172
+ import { ref } from 'vue';
173
+ import { FilePreviewModal, type PreviewFileInput } from '@eternalheart/angular-file-preview';
174
+ import '@eternalheart/angular-file-preview/style.css';
175
+
176
+ // Assume file1 comes from a File API source: <input type="file">,
177
+ // drag & drop, clipboard paste, or fetch().then(r => r.blob())
178
+ const file1 = new File(['content'], 'example.txt', { type: 'text/plain' });
179
+
180
+ const files: PreviewFileInput[] = [
181
+ // 1. Native File object (auto revoked when unmounted)
182
+ file1,
183
+
184
+ // 2. HTTP URL string (loaded on demand)
185
+ 'https://example.com/image.jpg',
186
+
187
+ // 3. File object with metadata (recommended for remote resources)
188
+ {
189
+ name: 'document.pdf',
190
+ type: 'application/pdf',
191
+ url: '/path/to/document.pdf',
192
+ size: 1024,
193
+ },
194
+ ];
195
+
196
+ // Memory note: If you generate URLs via URL.createObjectURL(),
197
+ // call URL.revokeObjectURL() when files are removed to avoid memory leaks.
198
+
199
+ const isOpen = ref(true);
200
+ </script>
201
+
202
+ <template>
203
+ <FilePreviewModal
204
+ :files="files"
205
+ :current-index="0"
206
+ :is-open="isOpen"
207
+ @close="isOpen = false"
208
+ />
209
+ </template>
210
+ ```
211
+
212
+ ### Embedded Mode (`FilePreviewEmbed`)
213
+
214
+ 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.
215
+
216
+ ```vue
217
+ <script setup lang="ts">
218
+ import { ref } from 'vue';
219
+ import { FilePreviewEmbed } from '@eternalheart/angular-file-preview';
220
+ import '@eternalheart/angular-file-preview/style.css';
221
+
222
+ const index = ref(0);
223
+
224
+ const files = [
225
+ 'https://example.com/image.jpg',
226
+ { name: 'document.pdf', type: 'application/pdf', url: '/doc.pdf' },
227
+ ];
228
+ </script>
229
+
230
+ <template>
231
+ <div style="width: 100%; height: 520px">
232
+ <FilePreviewEmbed
233
+ :files="files"
234
+ :current-index="index"
235
+ @navigate="index = $event"
236
+ />
237
+ </div>
238
+ </template>
239
+ ```
240
+
241
+ Differences from `FilePreviewModal`:
242
+
243
+ - No teleport, no full-screen overlay, no `isOpen` / `@close`
244
+ - Does **not** show the close button in the toolbar
245
+ - Keyboard navigation (←/→) is scoped to the embed container (focus-based)
246
+ - Size defaults to `width: 100%; height: 100%`; override via `width` / `height` props
247
+
248
+ ```vue
249
+ <!-- Explicit size -->
250
+ <FilePreviewEmbed :files="files" :width="800" :height="500" />
251
+ ```
252
+
253
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4d6.svg" width="20" height="20" alt="📖" /> Supported File Formats
254
+
255
+ ### Images
256
+ - **Formats**: JPG, PNG, GIF, WebP, SVG, BMP, ICO, HEIC/HEIF, AVIF, TIFF, RAW, PSD, JPEG 2000
257
+ - **Features**: Zoom (0.01x - 10x), rotate, drag, mouse wheel zoom, double-click reset, multi-page TIFF
258
+ - **Decoding**: HEIC/RAW/PSD use a Worker-first path with a main-thread fallback; other advanced formats are loaded on demand
259
+
260
+ ### Videos
261
+ - **Formats**: MP4, WebM, OGG, MOV, AVI, MKV, M4V, 3GP, FLV
262
+ - **Features**: Custom player, progress control, volume adjustment, fullscreen
263
+
264
+ ### Audio
265
+ - **Formats**: MP3, WAV, OGG, M4A, AAC, FLAC
266
+ - **Features**: Custom player, progress bar, volume control, skip forward/backward
267
+
268
+ ### Documents
269
+ - **PDF**: Pagination, zoom
270
+ - **Word**: DOCX and legacy DOC (97–2003) support
271
+ - **Excel**: XLSX and legacy XLS (97–2003) support
272
+ - **PowerPoint**: PPTX and legacy PPT (97–2003) slide preview
273
+
274
+ ### Fonts
275
+ - **Formats**: TTF, OTF, WOFF, WOFF2
276
+ - **Features**: Font metadata (family, designer, version), character set preview, custom text input, multi-size display
277
+
278
+ ### CAD / 3D Models
279
+ - **Formats**: DXF, STL, OBJ, GLTF, GLB
280
+ - **Features**: Interactive 3D viewer with orbit/zoom/pan controls, wireframe/solid toggle, grid & axes display, auto-centering
281
+
282
+ ### Code & Text
283
+ - **Markdown**: GitHub Flavored Markdown, code highlighting
284
+ - **Code Files**: Vue, Svelte, Astro, JS/TS, Dart, GraphQL, Protobuf, Prisma, Terraform, PowerShell, Scala, and 40+ languages
285
+ - **Config/Logs**: YAML, TOML, INI, ENV, LOG, DIFF, PATCH, etc.
286
+
287
+ ### Structured Data
288
+ - **JSON**: Auto formatting + syntax highlighting
289
+ - **CSV/TSV**: Zero-dependency parser, table view with headers and row/column stats
290
+ - **XML**: `DOMParser` validation + pretty print + syntax highlighting
291
+
292
+ ### Subtitles & Lyrics
293
+ - **SRT / WebVTT**: Zero-dependency parser, structured cue list (index, time range, text)
294
+ - **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
295
+ - **ASS / SSA**: Advanced SubStation Alpha — extracts Dialogue events, strips `\N` `\h` and `{...}` override codes, surfaces Style names
296
+ - **TTML / DFXP**: W3C / Apple Music XML captions, supports `begin` / `end` / `dur` and `<br/>`
297
+
298
+ ### Archives
299
+ - **ZIP**: Tree view + inline preview for text/code/image entries, download fallback for other types
300
+
301
+ ### Outlook Email
302
+ - **MSG**: Headers, body rendering, attachment list
303
+
304
+ ### E-books
305
+ - **EPUB**: Chapter navigation, pagination
306
+
307
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/26a0.svg" width="20" height="20" alt="⚠️" /> Limitations &amp; Performance
308
+
309
+ ### Support Levels
310
+
311
+ **✅ Full Support (Production Ready)**
312
+ - Images (JPG, PNG, GIF, WebP, SVG, BMP, ICO)
313
+ - Videos (MP4, WebM, OGG)
314
+ - Audio (MP3, WAV, OGG)
315
+ - PDF
316
+ - Markdown
317
+ - Code files (40+ languages via Shiki, lazy-loaded)
318
+ - JSON, CSV, XML
319
+
320
+ **⚠️ Partial Support (Preview Only)**
321
+ - **Office (DOCX, XLSX, PPTX)**: Basic layout and text rendering. Complex formatting (charts, macros, embedded objects) may not render accurately.
322
+ - **ZIP**: Directory tree browsing + inline preview for text/code/images. Large archives (&gt;100MB) may cause performance issues.
323
+ - **Fonts (TTF, OTF, WOFF)**: Metadata + character preview. Full font feature testing not supported.
324
+
325
+ **🧪 Experimental**
326
+ - **MSG (Outlook Email)**: Headers and plain text body. Complex HTML body may not render correctly.
327
+ - **EPUB**: Basic chapter navigation. CSS styling may differ from native readers. DRM-protected files not supported.
328
+ - **Subtitle formats (SRT, ASS, TTML, LRC)**: Text display only. No video sync or advanced styling.
329
+
330
+ ### Performance Boundaries
331
+
332
+ | File Size | Status | Notes |
333
+ |-----------|--------|-------|
334
+ | &lt; 50MB | ✅ Recommended | Smooth preview experience |
335
+ | 50-100MB | ⚠️ May lag | UI may become unresponsive during load |
336
+ | &gt; 100MB | ❌ Not recommended | Browser memory limits may be exceeded |
337
+
338
+ **Special Cases:**
339
+ - **ZIP archives**: Performance depends on file count, not just size
340
+ - **Office documents**: Complex files (&gt;200 pages, heavy images) may timeout
341
+ - **Code highlighting**: Files &gt;5MB may take 3-5s to highlight
342
+
343
+ ### Browser Compatibility
344
+
345
+ **Minimum Requirements:**
346
+ - Chrome 90+ / Edge 90+
347
+ - Firefox 88+
348
+ - Safari 14+
349
+
350
+ **Known Limitations:**
351
+ - **Safari iOS**: Video autoplay requires user interaction
352
+ - **Firefox**: AVIF support requires Firefox 93+ (fallback decoder included)
353
+ - **Office formats**: Rendering quality varies across browsers
354
+ - **EPUB**: Some CSS features unsupported in older browsers
355
+
356
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3ae.svg" width="20" height="20" alt="🎮" /> API Reference
357
+
358
+ ### FilePreviewModal Props
359
+
360
+ | Prop | Type | Required | Description |
361
+ |------|------|----------|-------------|
362
+ | `files` | `PreviewFileInput[]` | ✅ | Array of files (supports File objects, file objects, or URL strings) |
363
+ | `currentIndex` | `number` | ✅ | Current file index |
364
+ | `isOpen` | `boolean` | ✅ | Whether the modal is open |
365
+ | `customRenderers` | `CustomRenderer[]` | ❌ | Custom renderers for specific file types |
366
+ | `locale` | `Locale` | ❌ | UI language (`'zh-CN'` default, `'en-US'` built-in) |
367
+ | `messages` | `Partial<Record<Locale, Partial<Messages>>>` | ❌ | Custom translation overrides |
368
+ | `headless` | `boolean` | ❌ | Headless mode — hides toolbar and navigation arrows |
369
+ | `theme` | `Theme` | ❌ | Theme mode: `'auto' \| 'dark' \| 'light'` (default `'dark'`) |
370
+ | `showDownload` | `boolean` | ❌ | Whether to show the download button (default `true`) |
371
+ | `showClose` | `boolean` | ❌ | Whether to show the close button (default `true` for modal) |
372
+
373
+ ### FilePreviewModal Events
374
+
375
+ | Event | Payload | Description |
376
+ |-------|---------|-------------|
377
+ | `close` | - | Emitted when the modal should close |
378
+ | `navigate` | `number` | Emitted when navigating to a different file index |
379
+
380
+ ### FilePreviewEmbed Props
381
+
382
+ | Prop | Type | Required | Default | Description |
383
+ |------|------|----------|---------|-------------|
384
+ | `files` | `PreviewFileInput[]` | ✅ | - | Array of files |
385
+ | `currentIndex` | `number` | ❌ | `0` | Current file index |
386
+ | `customRenderers` | `CustomRenderer[]` | ❌ | - | Custom renderers |
387
+ | `width` | `number \| string` | ❌ | `'100%'` | Container width |
388
+ | `height` | `number \| string` | ❌ | `'100%'` | Container height |
389
+ | `locale` | `Locale` | ❌ | `'zh-CN'` | UI language (`'zh-CN'` or `'en-US'`) |
390
+ | `messages` | `Partial<Record<Locale, Partial<Messages>>>` | ❌ | - | Custom translation overrides |
391
+ | `headless` | `boolean` | ❌ | `false` | Headless mode — hides toolbar and navigation arrows |
392
+ | `theme` | `Theme` | ❌ | `'dark'` | Theme mode: `'auto' \| 'dark' \| 'light'` |
393
+ | `showDownload` | `boolean` | ❌ | `true` | Whether to show the download button |
394
+ | `showClose` | `boolean` | ❌ | `false` | Whether to show the close button (default `false` for embed) |
395
+
396
+ ### FilePreviewEmbed Events
397
+
398
+ | Event | Payload | Description |
399
+ |-------|---------|-------------|
400
+ | `navigate` | `number` | Emitted when navigating to a different file index |
401
+
402
+ ### FilePreviewContent (advanced)
403
+
404
+ Both `FilePreviewModal` and `FilePreviewEmbed` are thin wrappers around the exported lower-level `FilePreviewContent` component. Use it directly when building a fully custom wrapper:
405
+
406
+ ```vue
407
+ <FilePreviewContent
408
+ mode="embed"
409
+ :files="files"
410
+ :current-index="index"
411
+ @navigate="index = $event"
412
+ />
413
+ ```
414
+
415
+ ### File Type Definitions
416
+
417
+ ```typescript
418
+ // Supports three types of file input
419
+ type PreviewFileInput = File | PreviewFileLink | string;
420
+
421
+ // 1. Native File object (Browser File API)
422
+ const file: File = ...;
423
+
424
+ // 2. File object
425
+ interface PreviewFileLink {
426
+ id?: string; // Optional unique identifier
427
+ name: string; // File name
428
+ type: string; // MIME type
429
+ url: string; // File URL (supports blob URLs and HTTP URLs)
430
+ size?: number; // File size in bytes
431
+ }
432
+
433
+ // 3. HTTP URL string
434
+ const url: string = 'https://example.com/file.pdf';
435
+ ```
436
+
437
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f9e9.svg" width="20" height="20" alt="🧩" /> Custom Renderers
438
+
439
+ 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.
440
+
441
+ ### Event-Driven Toolbar Updates
442
+
443
+ Custom renderers can implement real-time toolbar updates using Angular's reactivity system:
444
+
445
+ **Benefits:**
446
+ - **Real-time updates**: Toolbar reflects state changes immediately via Vue reactivity
447
+ - **Better performance**: No polling overhead, leverages Vue's efficient change detection
448
+ - **Type-safe**: Full TypeScript support with proper interfaces
449
+
450
+ **Implementation:**
451
+
452
+ ```vue
453
+ <script setup lang="ts">
454
+ import { ref, computed, watch } from 'vue';
455
+ import { ChevronLeft, ChevronRight } from 'lucide-vue-next';
456
+ import type { ToolbarGroup } from '@eternalheart/angular-file-preview';
457
+
458
+ interface Props {
459
+ url: string;
460
+ }
461
+
462
+ const props = defineProps<Props>();
463
+ const emit = defineEmits<{
464
+ pageChange: [current: number, total: number];
465
+ }>();
466
+
467
+ const currentPage = ref(1);
468
+ const totalPages = ref(10);
469
+
470
+ // Emit page changes
471
+ watch([currentPage, totalPages], () => {
472
+ emit('pageChange', currentPage.value, totalPages.value);
473
+ });
474
+
475
+ const getToolbarGroups = (): ToolbarGroup[] => [
476
+ {
477
+ items: [
478
+ {
479
+ type: 'button',
480
+ icon: ChevronLeft,
481
+ tooltip: 'Previous Page',
482
+ action: () => currentPage.value = Math.max(1, currentPage.value - 1),
483
+ disabled: currentPage.value <= 1
484
+ },
485
+ {
486
+ type: 'text',
487
+ content: `${currentPage.value} / ${totalPages.value}`,
488
+ minWidth: '4rem'
489
+ },
490
+ {
491
+ type: 'button',
492
+ icon: ChevronRight,
493
+ tooltip: 'Next Page',
494
+ action: () => currentPage.value = Math.min(totalPages.value, currentPage.value + 1),
495
+ disabled: currentPage.value >= totalPages.value
496
+ }
497
+ ]
498
+ }
499
+ ];
500
+
501
+ // Expose for parent component
502
+ defineExpose({
503
+ getToolbarGroups
504
+ });
505
+ </script>
506
+
507
+ <template>
508
+ <div>Your custom renderer UI</div>
509
+ </template>
510
+ ```
511
+
512
+ **Main component usage:**
513
+
514
+ ```vue
515
+ <script setup>
516
+ import { CustomRenderer } from './CustomRenderer.vue';
517
+
518
+ const files = [
519
+ { name: 'custom.xyz', type: 'application/custom', url: '/path/to/file' }
520
+ ];
521
+ </script>
522
+
523
+ <template>
524
+ <FilePreviewModal
525
+ :files="files"
526
+ :custom-renderers="[
527
+ {
528
+ test: (file) => file.type === 'application/custom',
529
+ render: () => CustomRenderer
530
+ }
531
+ ]"
532
+ />
533
+ </template>
534
+ ```
535
+
536
+ The main component automatically tracks reactive changes in `getToolbarGroups()` via Vue's reactivity system. No manual subscription needed.
537
+
538
+ ### Renderer Lazy Loading
539
+
540
+ All built-in renderers use code-splitting via `defineAsyncComponent` to minimize the main bundle size and improve initial load performance.
541
+
542
+ **Architecture:**
543
+
544
+ - **Registration**: Renderers register in `src/renderers/lazy.ts` using `defineAsyncComponent` wrappers
545
+ - **Loading**: Each renderer is a separate chunk, loaded on-demand when needed
546
+ - **Fallback**: `RendererLoading` component handles the loading state
547
+
548
+ Each renderer is emitted as a separate async chunk and loaded only when needed.
549
+
550
+ **Implementation Example:**
551
+
552
+ ```ts
553
+ // src/renderers/lazy.ts
554
+ import { defineAsyncComponent } from 'vue';
555
+
556
+ const wrap = (loader: () => Promise<any>) =>
557
+ defineAsyncComponent({
558
+ loader,
559
+ loadingComponent: RendererLoading,
560
+ delay: 0
561
+ });
562
+
563
+ export const CustomRenderer = wrap(() => import('./Custom/index.vue'));
564
+ ```
565
+
566
+ ```vue
567
+ <!-- src/FilePreviewContent.vue -->
568
+ <script setup>
569
+ import { CustomRenderer } from './renderers/lazy'; // ✅ Lazy import
570
+ // NOT: import CustomRenderer from './renderers/Custom/index.vue'; // ❌ Direct import breaks code-splitting
571
+ </script>
572
+
573
+ <template>
574
+ <CustomRenderer
575
+ v-if="fileType === 'custom'"
576
+ ref="rendererRef"
577
+ :url="currentFile.url"
578
+ />
579
+ </template>
580
+ ```
581
+
582
+ **For Custom Renderers:**
583
+
584
+ If you want your custom renderer to benefit from code-splitting, use the same pattern:
585
+
586
+ ```vue
587
+ <script setup>
588
+ import { defineAsyncComponent } from 'vue';
589
+
590
+ const MyCustomRenderer = defineAsyncComponent(() => import('./MyCustomRenderer.vue'));
591
+
592
+ const files = [...];
593
+ </script>
594
+
595
+ <template>
596
+ <FilePreviewModal
597
+ :files="files"
598
+ :custom-renderers="[
599
+ {
600
+ test: (file) => file.type === 'application/custom',
601
+ render: () => MyCustomRenderer
602
+ }
603
+ ]"
604
+ />
605
+ </template>
606
+ ```
607
+
608
+ ### i18n Integration
609
+
610
+ Custom renderers can access the library's i18n system via the `useTranslator()` composable for consistent multilingual support.
611
+
612
+ **Architecture:**
613
+
614
+ - **Dictionary Source**: `file-preview-core/src/i18n/messages/` (zh-CN.ts, en-US.ts)
615
+ - **No Hardcoding**: All user-visible text must use translation keys
616
+ - **Automatic Locale Switching**: Follows the `locale` prop passed to `FilePreviewModal`
617
+
618
+ **Usage in Custom Renderers:**
619
+
620
+ ```vue
621
+ <script setup lang="ts">
622
+ import { ref } from 'vue';
623
+ import { useTranslator } from '@eternalheart/angular-file-preview';
624
+
625
+ interface Props {
626
+ url: string;
627
+ }
628
+
629
+ const props = defineProps<Props>();
630
+ const { t } = useTranslator();
631
+ const error = ref<string | null>(null);
632
+ </script>
633
+
634
+ <template>
635
+ <div v-if="error" class="vfp-text-fg-primary">
636
+ {{ t('custom.load_failed') }}: {{ error }}
637
+ </div>
638
+ <div v-else>
639
+ <button>{{ t('common.download') }}</button>
640
+ <span>{{ t('custom.loading') }}</span>
641
+ </div>
642
+ </template>
643
+ ```
644
+
645
+ **Note on Usage:**
646
+
647
+ - In `<template>`: Use `t('key')` directly (automatically unwrapped)
648
+ - In `<script>`: Use `t.value('key')` for imperative calls
649
+
650
+ **Adding Custom Translation Keys:**
651
+
652
+ For custom renderers, extend translations via the `messages` prop (do NOT modify source files in `node_modules`):
653
+
654
+ ```vue
655
+ <template>
656
+ <FilePreviewModal
657
+ :files="files"
658
+ locale="en-US"
659
+ :messages="{
660
+ 'en-US': {
661
+ 'custom.load_failed': 'Failed to load custom file',
662
+ 'custom.file_size': 'File size: {size} KB'
663
+ },
664
+ 'zh-CN': {
665
+ 'custom.load_failed': '自定义文件加载失败',
666
+ 'custom.file_size': '文件大小: {size} KB'
667
+ }
668
+ }"
669
+ :custom-renderers="[...]"
670
+ />
671
+ </template>
672
+ ```
673
+
674
+ **Guidelines:**
675
+ - Use `<scope>.<snake_name>` format (e.g., `custom.load_failed`, `custom.parse_error`)
676
+ - Provide translations for all enabled locales (`zh-CN` and `en-US`)
677
+ - Common keys already available: `common.loading`, `common.download`, `common.close`, `toolbar.*`
678
+
679
+ **Parameterized Translations:**
680
+
681
+ ```vue
682
+ <template>
683
+ <!-- Dictionary: 'custom.file_size': 'File size: {size} KB' -->
684
+ <span>{{ t('custom.file_size', { size: 1024 }) }}</span>
685
+ <!-- → "File size: 1024 KB" -->
686
+ </template>
687
+ ```
688
+
689
+ **Toolbar Integration:**
690
+
691
+ Toolbar items should also use translated strings:
692
+
693
+ ```ts
694
+ const getToolbarGroups = (): ToolbarGroup[] => [
695
+ {
696
+ items: [
697
+ {
698
+ type: 'button',
699
+ icon: Download,
700
+ tooltip: t.value('common.download'), // ✅ Translated (use .value in script)
701
+ action: handleDownload
702
+ }
703
+ ]
704
+ }
705
+ ];
706
+ ```
707
+
708
+ ### Theme Adaptation
709
+
710
+ Custom renderers must use semantic color tokens to support the library's `'auto' | 'dark' | 'light'` theme system.
711
+
712
+ **Semantic Token System:**
713
+
714
+ All colors are defined as CSS variables (`--fp-*`) and exposed via Tailwind classes with the `vfp-` prefix:
715
+
716
+ | Usage | Class | Description |
717
+ |-------|-------|-------------|
718
+ | **Text (fg)** | | |
719
+ | Primary text | `vfp-text-fg-primary` | Highest contrast |
720
+ | Body text | `vfp-text-fg-secondary` | Default text |
721
+ | Secondary text | `vfp-text-fg-tertiary` | Captions, counters |
722
+ | Muted text | `vfp-text-fg-muted` | Placeholders |
723
+ | Disabled text | `vfp-text-fg-disabled` | Disabled buttons |
724
+ | **Background (surface)** | | |
725
+ | Surface 1 | `vfp-bg-surface-1` | Cards, weakest |
726
+ | Surface 2 | `vfp-bg-surface-2` | Hover states |
727
+ | Surface 3 | `vfp-bg-surface-3` | Emphasis |
728
+ | Toolbar | `vfp-bg-surface-toolbar` | Top toolbar |
729
+ | **Borders** | | |
730
+ | Weak border | `vfp-border-line-weak` | Subtle lines |
731
+ | Standard border | `vfp-border-line` | Default borders |
732
+ | Strong border | `vfp-border-line-strong` | Emphasis |
733
+ | **Code** | | |
734
+ | Code background | `vfp-bg-code-bg` | Dark: #1e1e1e / Light: #f6f8fa |
735
+ | Code text | `vfp-text-code-fg` | Follows theme |
736
+ | **Accent** | | |
737
+ | Accent background | `vfp-bg-accent` | Primary buttons |
738
+ | Accent hover | `vfp-bg-accent-hover` | Hover state |
739
+
740
+ **✅ Correct Usage:**
741
+
742
+ ```vue
743
+ <template>
744
+ <div class="vfp-bg-surface-1 vfp-border vfp-border-line-weak vfp-rounded">
745
+ <h2 class="vfp-text-fg-primary vfp-text-lg">Title</h2>
746
+ <p class="vfp-text-fg-secondary">Body text</p>
747
+ <button class="vfp-bg-surface-2 hover:vfp-bg-surface-3 vfp-text-fg-primary">
748
+ Click me
749
+ </button>
750
+ <pre class="vfp-bg-code-bg vfp-text-code-fg">{{ code }}</pre>
751
+ </div>
752
+ </template>
753
+ ```
754
+
755
+ **For `<style scoped>` blocks**, use CSS variables:
756
+
757
+ ```vue
758
+ <style scoped>
759
+ .my-block {
760
+ color: var(--fp-fg-primary);
761
+ background: var(--fp-surface-2);
762
+ border: 1px solid var(--fp-line);
763
+ }
764
+
765
+ .my-code {
766
+ background: var(--fp-code-bg);
767
+ color: var(--fp-code-fg);
768
+ }
769
+ </style>
770
+ ```
771
+
772
+ **❌ Incorrect Usage (DO NOT USE):**
773
+
774
+ ```vue
775
+ <!-- ❌ Literal color classes — breaks theme switching -->
776
+ <div class="vfp-text-white/90 vfp-bg-white/10 vfp-border-white/15">
777
+ <div class="vfp-text-gray-700 vfp-bg-gray-100">
778
+
779
+ <!-- ❌ Inline literal colors -->
780
+ <div :style="{ color: '#ffffff', background: '#1f2937' }">
781
+
782
+ <!-- ❌ Hardcoded dark-only colors in scoped style -->
783
+ <style scoped>
784
+ .foo { color: rgba(255, 255, 255, 0.75); } /* Use var(--fp-fg-secondary) */
785
+ .foo { background: #1e1e1e; } /* Use var(--fp-code-bg) */
786
+ </style>
787
+ ```
788
+
789
+ **Theme-Aware Third-Party Libraries:**
790
+
791
+ For libraries with theme props (e.g., `shiki`), use `useResolvedTheme()`:
792
+
793
+ ```vue
794
+ <script setup lang="ts">
795
+ import { ref, watch } from 'vue';
796
+ import { codeToHtml } from 'shiki';
797
+ import { useResolvedTheme } from '@eternalheart/angular-file-preview';
798
+
799
+ const props = defineProps<{ code: string; lang: string }>();
800
+ const resolvedTheme = useResolvedTheme(); // Ref<'dark' | 'light'>
801
+ const highlighted = ref('');
802
+
803
+ const highlightCode = async () => {
804
+ highlighted.value = await codeToHtml(props.code, {
805
+ lang: props.lang,
806
+ theme: resolvedTheme.value === 'light' ? 'github-light' : 'dark-plus'
807
+ });
808
+ };
809
+
810
+ // Re-highlight when theme changes
811
+ watch(resolvedTheme, highlightCode, { immediate: true });
812
+ </script>
813
+
814
+ <template>
815
+ <div v-html="highlighted"></div>
816
+ </template>
817
+ ```
818
+
819
+ **Testing:**
820
+
821
+ Always test your custom renderer in both Light and Dark themes:
822
+
823
+ ```vue
824
+ <FilePreviewModal
825
+ :files="files"
826
+ theme="light" // Switch between 'light', 'dark', 'auto'
827
+ :custom-renderers="[...]"
828
+ />
829
+ ```
830
+
831
+ Verify:
832
+ - Text is readable in both themes (no white-on-white or black-on-black)
833
+ - Borders and dividers are visible
834
+ - Hover states have sufficient contrast
835
+ - Code blocks follow theme (not always dark)
836
+
837
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2328.svg" width="20" height="20" alt="⌨️" /> Keyboard Shortcuts
838
+
839
+ - `ESC` - Close preview
840
+ - `←` - Previous file
841
+ - `→` - Next file
842
+ - `Mouse Wheel` - Zoom image (image preview only)
843
+
844
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4da.svg" width="20" height="20" alt="📚" /> Documentation
845
+
846
+ - [Full Documentation](https://wh131462.github.io/file-preview/docs/)
847
+ - [Vue Demo](https://wh131462.github.io/file-preview/vue/)
848
+ - [React Demo](https://wh131462.github.io/file-preview/)
849
+
850
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f6e0.svg" width="20" height="20" alt="🛠️" /> Development
851
+
852
+ ```bash
853
+ # Clone repository
854
+ git clone https://github.com/wh131462/file-preview.git
855
+
856
+ # Install dependencies
857
+ pnpm install
858
+
859
+ # Start dev server (Vue demo app)
860
+ pnpm dev:vue-example
861
+
862
+ # Build library
863
+ pnpm build:vue
864
+ ```
865
+
866
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c4.svg" width="20" height="20" alt="📄" /> License
867
+
868
+ [MIT](./LICENSE) © [EternalHeart](https://github.com/wh131462)
869
+
870
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f91d.svg" width="20" height="20" alt="🤝" /> Contributing
871
+
872
+ Issues and Pull Requests are welcome!
873
+
874
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f517.svg" width="20" height="20" alt="🔗" /> Links
875
+
876
+ - [GitHub](https://github.com/wh131462/file-preview)
877
+ - [npm](https://www.npmjs.com/package/@eternalheart/angular-file-preview)
878
+ - [Vue Demo](https://wh131462.github.io/file-preview/vue/)
879
+ - [Issue Tracker](https://github.com/wh131462/file-preview/issues)