@file-viewer/core 3.0.2 → 3.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.
@@ -19,7 +19,8 @@ import { createFileViewerCoreRendererRegistry } from '../renderers/index.js';
19
19
  import { createFileViewerRequestScope } from '../source/loading.js';
20
20
  import { normalizeSource } from '../source/index.js';
21
21
  import { buildFileViewerWatermarkInlineStyle } from '../features/watermark.js';
22
- import { createFileViewerUnsupportedState } from './state.js';
22
+ import { createFileViewerSnapshotDownload } from '../output/snapshotDownload.js';
23
+ import { createFileViewerErrorState, createFileViewerUnsupportedState } from './state.js';
23
24
  const emitLifecycle = async (options, onEvent, phase, source, version, startedAt, reason) => {
24
25
  const now = Date.now();
25
26
  const context = buildFileViewerLifecycleContextFromNormalizedSource({
@@ -77,6 +78,9 @@ const createFileViewerLoadSignal = (controller, signals) => {
77
78
  }
78
79
  return controller.signal;
79
80
  };
81
+ const isAbortError = (error) => {
82
+ return Boolean(error && typeof error === 'object' && error.name === 'AbortError');
83
+ };
80
84
  const renderMissingRendererState = (container, type, options) => {
81
85
  const documentRef = container.ownerDocument;
82
86
  const state = createFileViewerUnsupportedState(type, undefined, options);
@@ -105,6 +109,43 @@ const renderMissingRendererState = (container, type, options) => {
105
109
  wrapper.append(content);
106
110
  container.replaceChildren(wrapper);
107
111
  };
112
+ const renderFileViewerErrorState = (container, type, error, options) => {
113
+ const documentRef = container.ownerDocument;
114
+ const state = createFileViewerErrorState(type, error, undefined, options);
115
+ const wrapper = documentRef.createElement('div');
116
+ wrapper.className = 'file-viewer-render-error';
117
+ wrapper.setAttribute('role', 'alert');
118
+ wrapper.setAttribute('aria-live', 'assertive');
119
+ wrapper.style.cssText = [
120
+ 'display:flex',
121
+ 'min-height:260px',
122
+ 'height:100%',
123
+ 'align-items:center',
124
+ 'justify-content:center',
125
+ 'padding:32px',
126
+ 'box-sizing:border-box',
127
+ 'color:var(--file-viewer-text,#172033)',
128
+ 'font:14px/1.6 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif',
129
+ ].join(';');
130
+ const content = documentRef.createElement('div');
131
+ content.style.cssText = [
132
+ 'width:min(680px,100%)',
133
+ 'padding:24px',
134
+ 'border:1px solid var(--file-viewer-border,rgba(20,35,53,.12))',
135
+ 'border-radius:14px',
136
+ 'background:var(--file-viewer-render-surface-background,#fff)',
137
+ 'box-shadow:0 16px 42px rgba(25,42,54,.08)',
138
+ ].join(';');
139
+ const title = documentRef.createElement('strong');
140
+ title.textContent = state.title;
141
+ title.style.cssText = 'display:block;margin-bottom:10px;font-size:18px;';
142
+ const message = documentRef.createElement('p');
143
+ message.textContent = state.message;
144
+ message.style.cssText = 'margin:0;white-space:pre-wrap;overflow-wrap:anywhere;';
145
+ content.append(title, message);
146
+ wrapper.append(content);
147
+ container.replaceChildren(wrapper);
148
+ };
108
149
  export const createViewer = (container, createOptions = {}) => {
109
150
  let options = createOptions.options || {};
110
151
  let registry = createBaseRendererRegistry(createOptions, options);
@@ -556,15 +597,27 @@ export const createViewer = (container, createOptions = {}) => {
556
597
  renderContext: {
557
598
  renderPurpose: createOptions.renderPurpose || 'preview',
558
599
  renderNestedBuffer,
600
+ requestSnapshotDownload: createFileViewerSnapshotDownload({
601
+ getOptions: () => options,
602
+ isCurrent: () => requestScope.isCurrentRequest(version),
603
+ beforeDownload: () => runBeforeViewerOperation('download'),
604
+ }),
559
605
  },
560
606
  });
561
607
  }
562
608
  catch (error) {
563
- if (!requestScope.isCurrentRequest(version)) {
609
+ if (!requestScope.isCurrentRequest(version) || (loadSignal === null || loadSignal === void 0 ? void 0 : loadSignal.aborted) || isAbortError(error)) {
564
610
  removeRenderTarget(targetHost);
565
611
  return null;
566
612
  }
567
- removeRenderTarget(targetHost);
613
+ applyFileViewerRenderSurfaceState(renderSurfaceState, {
614
+ session: null,
615
+ exportAdapter: null,
616
+ thumbnailAdapter: null,
617
+ });
618
+ removeWatermarkOverlay();
619
+ renderFileViewerErrorState(target, normalized.extension, error, options);
620
+ emitZoomAndOperationAvailabilityChange();
568
621
  throw error;
569
622
  }
570
623
  if (!requestScope.isCurrentRequest(version)) {
@@ -1,6 +1,15 @@
1
1
  import { type FileViewerI18nInput } from '../i18n/messages';
2
2
  import type { FileViewerRendererCategory, FileViewerStateDescriptor, FileViewerStateTheme } from '../contracts/types';
3
3
  export type FileViewerErrorMessageFormatter = (prefix: string, error: unknown, i18n?: FileViewerI18nInput) => string;
4
+ /**
5
+ * Renderers can attach a localized, user-safe explanation without replacing
6
+ * the original error object that hosts use for diagnostics and recovery.
7
+ */
8
+ export declare const FILE_VIEWER_ERROR_MESSAGE: "fileViewerErrorMessage";
9
+ export interface FileViewerErrorMessageCarrier {
10
+ [FILE_VIEWER_ERROR_MESSAGE]?: string;
11
+ }
12
+ export declare const attachFileViewerErrorMessage: (error: unknown, message: string) => unknown;
4
13
  export declare const FILE_VIEWER_PREVIEW_MESSAGES: Readonly<{
5
14
  downloading: "正在下载文件资源...";
6
15
  streamingPdf: "正在建立 PDF 流式预览...";
@@ -1,6 +1,39 @@
1
1
  import { normalizeFileExtension } from '../source/index.js';
2
2
  import { DEFAULT_RENDERER_DEFINITIONS } from '../registry/formats.js';
3
3
  import { resolveFileViewerLocale, translateFileViewerMessage, } from '../i18n/messages.js';
4
+ /**
5
+ * Renderers can attach a localized, user-safe explanation without replacing
6
+ * the original error object that hosts use for diagnostics and recovery.
7
+ */
8
+ export const FILE_VIEWER_ERROR_MESSAGE = 'fileViewerErrorMessage';
9
+ export const attachFileViewerErrorMessage = (error, message) => {
10
+ const normalizedMessage = message.trim();
11
+ if (!normalizedMessage) {
12
+ return error;
13
+ }
14
+ if (error && typeof error === 'object') {
15
+ try {
16
+ Object.defineProperty(error, FILE_VIEWER_ERROR_MESSAGE, {
17
+ configurable: true,
18
+ value: normalizedMessage,
19
+ });
20
+ return error;
21
+ }
22
+ catch {
23
+ // Frozen cross-realm errors retain their original value as the cause below.
24
+ }
25
+ }
26
+ const presented = new Error(normalizedMessage);
27
+ Object.defineProperty(presented, FILE_VIEWER_ERROR_MESSAGE, {
28
+ configurable: true,
29
+ value: normalizedMessage,
30
+ });
31
+ Object.defineProperty(presented, 'cause', {
32
+ configurable: true,
33
+ value: error,
34
+ });
35
+ return presented;
36
+ };
4
37
  export const FILE_VIEWER_PREVIEW_MESSAGES = Object.freeze({
5
38
  downloading: '正在下载文件资源...',
6
39
  streamingPdf: '正在建立 PDF 流式预览...',
@@ -247,6 +280,12 @@ const readFileViewerErrorProperty = (error, key) => {
247
280
  }
248
281
  };
249
282
  export const normalizeFileViewerErrorMessage = (error, i18n) => {
283
+ if (error && typeof error === 'object') {
284
+ const presentedMessage = readFileViewerErrorProperty(error, FILE_VIEWER_ERROR_MESSAGE);
285
+ if (typeof presentedMessage === 'string' && presentedMessage.trim()) {
286
+ return resolveKnownFileViewerErrorMessage(presentedMessage, i18n);
287
+ }
288
+ }
250
289
  if (error instanceof Error) {
251
290
  return resolveKnownFileViewerErrorMessage(error.message, i18n);
252
291
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/core",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Framework-neutral modular TypeScript core for File Viewer renderer orchestration, lifecycle APIs, search, zoom, print, and export",
@@ -90,7 +90,7 @@
90
90
  "LICENSE"
91
91
  ],
92
92
  "dependencies": {
93
- "dompurify": "3.4.14"
93
+ "dompurify": "3.4.15"
94
94
  },
95
95
  "devDependencies": {
96
96
  "typescript": "^6.0.3"