@file-viewer/core 2.1.16 → 2.1.18

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.
@@ -13,6 +13,48 @@ import { findFileViewerViewStateProvider } from '../features/document/dom/index.
13
13
  import { registerFileViewerGenericViewStateProvider } from '../features/document/viewState.js';
14
14
  export const FILE_VIEWER_RENDER_SESSION_DISPOSE_ERROR_MESSAGE = '预览内容卸载失败';
15
15
  export const DEFAULT_FILE_VIEWER_RENDER_TARGET_CLASS = 'file-render';
16
+ const FILE_VIEWER_RENDER_HOST_CLASS = 'file-render-host';
17
+ const FILE_VIEWER_SHADOW_RENDER_TARGET_CLASS = 'file-render-shadow-target';
18
+ export const isFileViewerShadowRoot = (value) => {
19
+ if (!value || typeof value !== 'object') {
20
+ return false;
21
+ }
22
+ if (typeof ShadowRoot !== 'undefined' && value instanceof ShadowRoot) {
23
+ return true;
24
+ }
25
+ return value.nodeType === 11 &&
26
+ !!value.host;
27
+ };
28
+ const getElementRootNode = (element) => {
29
+ var _a;
30
+ return ((_a = element === null || element === void 0 ? void 0 : element.getRootNode) === null || _a === void 0 ? void 0 : _a.call(element)) || null;
31
+ };
32
+ export const getFileViewerShadowRootForNode = (node) => {
33
+ var _a;
34
+ if (!node) {
35
+ return null;
36
+ }
37
+ if (isFileViewerShadowRoot(node)) {
38
+ return node;
39
+ }
40
+ const root = (_a = node.getRootNode) === null || _a === void 0 ? void 0 : _a.call(node);
41
+ return isFileViewerShadowRoot(root) ? root : null;
42
+ };
43
+ export const normalizeFileViewerStyleIsolation = (isolation) => {
44
+ return isolation === 'shadow' || isolation === 'scoped' || isolation === 'none'
45
+ ? isolation
46
+ : 'auto';
47
+ };
48
+ export const resolveFileViewerStyleIsolation = (options, container) => {
49
+ const isolation = normalizeFileViewerStyleIsolation(options === null || options === void 0 ? void 0 : options.styleIsolation);
50
+ if (isolation === 'auto') {
51
+ return isFileViewerShadowRoot(getElementRootNode(container)) ? 'scoped' : 'none';
52
+ }
53
+ if (isolation === 'shadow' && isFileViewerShadowRoot(getElementRootNode(container))) {
54
+ return 'scoped';
55
+ }
56
+ return isolation;
57
+ };
16
58
  const isPromiseLike = (value) => {
17
59
  return !!value &&
18
60
  (typeof value === 'object' || typeof value === 'function') &&
@@ -29,9 +71,82 @@ export const clearFileViewerRenderSurface = (container) => {
29
71
  export const createFileViewerRenderTarget = (container, options = {}) => {
30
72
  const target = container.ownerDocument.createElement('div');
31
73
  target.className = options.className || DEFAULT_FILE_VIEWER_RENDER_TARGET_CLASS;
74
+ target.style.width = '100%';
75
+ target.style.minWidth = '0';
76
+ target.style.minHeight = '0';
32
77
  container.appendChild(target);
33
78
  return target;
34
79
  };
80
+ export const createFileViewerRenderSurface = (container, options = {}) => {
81
+ const styleIsolation = resolveFileViewerStyleIsolation({
82
+ styleIsolation: options.styleIsolation,
83
+ }, container);
84
+ const className = options.className || DEFAULT_FILE_VIEWER_RENDER_TARGET_CLASS;
85
+ if (styleIsolation === 'shadow' && typeof container.attachShadow === 'function') {
86
+ const host = container.ownerDocument.createElement('div');
87
+ host.className = `${FILE_VIEWER_RENDER_HOST_CLASS} ${className}`;
88
+ host.dataset.fileViewerStyleIsolation = 'shadow';
89
+ host.style.width = '100%';
90
+ host.style.minWidth = '0';
91
+ host.style.minHeight = '0';
92
+ const shadowRoot = host.attachShadow({ mode: 'open' });
93
+ const target = container.ownerDocument.createElement('div');
94
+ target.className = `${FILE_VIEWER_SHADOW_RENDER_TARGET_CLASS} ${className}`;
95
+ target.dataset.fileViewerStyleIsolation = 'shadow';
96
+ target.style.width = '100%';
97
+ target.style.minWidth = '0';
98
+ target.style.minHeight = '0';
99
+ shadowRoot.appendChild(target);
100
+ container.appendChild(host);
101
+ return {
102
+ host,
103
+ container: target,
104
+ shadowRoot,
105
+ styleIsolation,
106
+ };
107
+ }
108
+ const target = createFileViewerRenderTarget(container, { className });
109
+ target.dataset.fileViewerStyleIsolation = styleIsolation;
110
+ return {
111
+ host: target,
112
+ container: target,
113
+ styleIsolation,
114
+ };
115
+ };
116
+ const appendStyleElement = (target, css) => {
117
+ const documentRef = target.ownerDocument;
118
+ const style = documentRef.createElement('style');
119
+ style.textContent = css;
120
+ target.appendChild(style);
121
+ return style;
122
+ };
123
+ const canUseAdoptedStyleSheet = (root) => {
124
+ return 'adoptedStyleSheets' in root &&
125
+ typeof CSSStyleSheet !== 'undefined' &&
126
+ typeof CSSStyleSheet.prototype.replaceSync === 'function';
127
+ };
128
+ export const appendFileViewerStyle = (target, css, options = {}) => {
129
+ const shadowRoot = getFileViewerShadowRootForNode(target);
130
+ if (options.adoptedStyleSheet && shadowRoot && canUseAdoptedStyleSheet(shadowRoot)) {
131
+ const sheet = new CSSStyleSheet();
132
+ sheet.replaceSync(css);
133
+ shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets, sheet];
134
+ return {
135
+ sheet,
136
+ remove() {
137
+ shadowRoot.adoptedStyleSheets = shadowRoot.adoptedStyleSheets.filter(item => item !== sheet);
138
+ },
139
+ };
140
+ }
141
+ const styleTarget = isFileViewerShadowRoot(target) ? target : target;
142
+ const node = appendStyleElement(styleTarget, css);
143
+ return {
144
+ node,
145
+ remove() {
146
+ node.remove();
147
+ },
148
+ };
149
+ };
35
150
  export const removeFileViewerRenderTarget = (container, target) => {
36
151
  if (target.parentNode !== container) {
37
152
  return false;
@@ -158,7 +273,7 @@ export const resetFileViewerRenderSurface = ({ surfaceState, readinessState, con
158
273
  clearFileViewerRenderSurface(container);
159
274
  return session;
160
275
  };
161
- export const runFileViewerRenderSurfaceClear = ({ reason = 'replace', surfaceState, readinessState, container, disposeOptions, onUnloadStart, onUnloadComplete, onClearActiveDocumentContext, onClearDocumentState, onStopZoomObserver, onClearZoomProvider, onStopViewStateObserver, onClearViewStateProvider, }) => {
276
+ export const runFileViewerRenderSurfaceClear = ({ reason = 'replace', surfaceState, readinessState, container, disposeOptions, onUnloadStart, onUnloadComplete, onClearActiveDocumentContext, onClearDocumentState, onStopZoomObserver, onClearZoomProvider, onStopFitObserver, onStopViewStateObserver, onClearViewStateProvider, }) => {
162
277
  const unloadContext = onUnloadStart === null || onUnloadStart === void 0 ? void 0 : onUnloadStart(reason);
163
278
  let session;
164
279
  try {
@@ -174,6 +289,7 @@ export const runFileViewerRenderSurfaceClear = ({ reason = 'replace', surfaceSta
174
289
  onClearDocumentState === null || onClearDocumentState === void 0 ? void 0 : onClearDocumentState();
175
290
  onStopZoomObserver === null || onStopZoomObserver === void 0 ? void 0 : onStopZoomObserver();
176
291
  onClearZoomProvider === null || onClearZoomProvider === void 0 ? void 0 : onClearZoomProvider();
292
+ onStopFitObserver === null || onStopFitObserver === void 0 ? void 0 : onStopFitObserver();
177
293
  onStopViewStateObserver === null || onStopViewStateObserver === void 0 ? void 0 : onStopViewStateObserver();
178
294
  onClearViewStateProvider === null || onClearViewStateProvider === void 0 ? void 0 : onClearViewStateProvider();
179
295
  }
@@ -184,7 +300,7 @@ export const runFileViewerRenderSurfaceClear = ({ reason = 'replace', surfaceSta
184
300
  session,
185
301
  };
186
302
  };
187
- export const runFileViewerRenderSurfaceMount = async ({ buffer, file, version, sourceUrl, streamUrl, getContainer, surfaceState, readinessState, isCurrent, clearRenderedContent, render, waitForContainer, waitForPaint = waitForFileViewerNextPaint, disposeSession = disposeFileViewerRendererSession, onStartZoomObserver, onStartViewStateObserver, onRefreshDocumentIndex, onRefreshZoomProvider, onRefreshViewStateProvider, }) => {
303
+ export const runFileViewerRenderSurfaceMount = async ({ buffer, file, version, sourceUrl, streamUrl, getContainer, surfaceState, readinessState, isCurrent, clearRenderedContent, render, waitForContainer, waitForPaint = waitForFileViewerNextPaint, disposeSession = disposeFileViewerRendererSession, onStartZoomObserver, onStartFitObserver, onStartViewStateObserver, onApplyInitialFit, onRefreshDocumentIndex, onRefreshZoomProvider, onRefreshViewStateProvider, }) => {
188
304
  if (!getContainer()) {
189
305
  await (waitForContainer === null || waitForContainer === void 0 ? void 0 : waitForContainer());
190
306
  }
@@ -195,6 +311,7 @@ export const runFileViewerRenderSurfaceMount = async ({ buffer, file, version, s
195
311
  clearRenderedContent('replace');
196
312
  const target = createFileViewerRenderTarget(container);
197
313
  onStartZoomObserver === null || onStartZoomObserver === void 0 ? void 0 : onStartZoomObserver();
314
+ onStartFitObserver === null || onStartFitObserver === void 0 ? void 0 : onStartFitObserver();
198
315
  onStartViewStateObserver === null || onStartViewStateObserver === void 0 ? void 0 : onStartViewStateObserver();
199
316
  await (waitForContainer === null || waitForContainer === void 0 ? void 0 : waitForContainer());
200
317
  await waitForPaint();
@@ -233,6 +350,11 @@ export const runFileViewerRenderSurfaceMount = async ({ buffer, file, version, s
233
350
  void (onRefreshDocumentIndex === null || onRefreshDocumentIndex === void 0 ? void 0 : onRefreshDocumentIndex());
234
351
  onRefreshZoomProvider === null || onRefreshZoomProvider === void 0 ? void 0 : onRefreshZoomProvider();
235
352
  onRefreshViewStateProvider === null || onRefreshViewStateProvider === void 0 ? void 0 : onRefreshViewStateProvider();
353
+ if (onApplyInitialFit) {
354
+ await onApplyInitialFit();
355
+ onRefreshZoomProvider === null || onRefreshZoomProvider === void 0 ? void 0 : onRefreshZoomProvider();
356
+ onRefreshViewStateProvider === null || onRefreshViewStateProvider === void 0 ? void 0 : onRefreshViewStateProvider();
357
+ }
236
358
  return session;
237
359
  }
238
360
  catch (error) {
@@ -240,7 +362,7 @@ export const runFileViewerRenderSurfaceMount = async ({ buffer, file, version, s
240
362
  throw error;
241
363
  }
242
364
  };
243
- export const createFileViewerRenderSurfaceActionHandlers = ({ getContainer, surfaceState, readinessState, isCurrent, render, waitForContainer, waitForPaint, disposeOptions, onUnloadStart, onUnloadComplete, onClearActiveDocumentContext, onClearDocumentState, onStartZoomObserver, onStopZoomObserver, onClearZoomProvider, onStartViewStateObserver, onStopViewStateObserver, onClearViewStateProvider, onRefreshDocumentIndex, onRefreshZoomProvider, onRefreshViewStateProvider, }) => {
365
+ export const createFileViewerRenderSurfaceActionHandlers = ({ getContainer, surfaceState, readinessState, isCurrent, render, waitForContainer, waitForPaint, disposeOptions, onUnloadStart, onUnloadComplete, onClearActiveDocumentContext, onClearDocumentState, onStartZoomObserver, onStopZoomObserver, onClearZoomProvider, onStartFitObserver, onStopFitObserver, onStartViewStateObserver, onStopViewStateObserver, onClearViewStateProvider, onApplyInitialFit, onRefreshDocumentIndex, onRefreshZoomProvider, onRefreshViewStateProvider, }) => {
244
366
  const handleDisposeError = (error) => {
245
367
  if (disposeOptions === null || disposeOptions === void 0 ? void 0 : disposeOptions.onError) {
246
368
  disposeOptions.onError(error);
@@ -271,6 +393,7 @@ export const createFileViewerRenderSurfaceActionHandlers = ({ getContainer, surf
271
393
  onClearDocumentState,
272
394
  onStopZoomObserver,
273
395
  onClearZoomProvider,
396
+ onStopFitObserver,
274
397
  onStopViewStateObserver,
275
398
  onClearViewStateProvider,
276
399
  });
@@ -292,7 +415,9 @@ export const createFileViewerRenderSurfaceActionHandlers = ({ getContainer, surf
292
415
  waitForPaint,
293
416
  disposeSession: destroyRenderSession,
294
417
  onStartZoomObserver,
418
+ onStartFitObserver,
295
419
  onStartViewStateObserver,
420
+ onApplyInitialFit,
296
421
  onRefreshDocumentIndex,
297
422
  onRefreshZoomProvider,
298
423
  onRefreshViewStateProvider,
@@ -305,11 +430,12 @@ export const createFileViewerRenderSurfaceActionHandlers = ({ getContainer, surf
305
430
  mountRenderedContent,
306
431
  };
307
432
  };
308
- export const buildFileRenderContextFromLoadContext = ({ source, options, registerExportAdapter, renderContext, }) => ({
433
+ export const buildFileRenderContextFromLoadContext = ({ source, surface, options, registerExportAdapter, renderContext, }) => ({
309
434
  filename: source.filename,
310
435
  url: source.url,
311
436
  streamUrl: source.url,
312
437
  options,
438
+ surface,
313
439
  registerExportAdapter,
314
440
  ...renderContext,
315
441
  });
@@ -430,8 +430,10 @@ export interface RunFileViewerPreviewSourceChangeInput {
430
430
  export interface RunFileViewerPreviewComponentUnmountInput {
431
431
  reason?: FileViewerLifecycleContext['reason'];
432
432
  onCancelPreview?: (reason: FileViewerLifecycleContext['reason']) => void;
433
+ onClearRenderedContent?: (reason: FileViewerLifecycleContext['reason']) => void;
433
434
  onResetLoading?: () => void;
434
435
  onStopZoomObserver?: () => void;
436
+ onStopFitObserver?: () => void;
435
437
  onStopViewStateObserver?: () => void;
436
438
  }
437
439
  export interface FileViewerPreviewComponentUnmountState {
@@ -502,7 +504,7 @@ export declare const applyFileViewerPreviewRequestResetState: <Target extends Mu
502
504
  export declare const commitFileViewerPreviewRequestStartState: ({ reason, requestController, previewTarget, onClearRenderedContent, onClearError, }: CommitFileViewerPreviewRequestStartStateInput) => number;
503
505
  export declare const cancelFileViewerPreviewRequest: ({ reason, requestController, previewTarget, onClearRenderedContent, onClearError, }: CancelFileViewerPreviewRequestInput) => number;
504
506
  export declare const runFileViewerPreviewSourceChange: ({ onRefreshPreview, }?: RunFileViewerPreviewSourceChangeInput) => unknown;
505
- export declare const runFileViewerPreviewComponentUnmount: ({ reason, onCancelPreview, onResetLoading, onStopZoomObserver, onStopViewStateObserver, }?: RunFileViewerPreviewComponentUnmountInput) => FileViewerPreviewComponentUnmountState;
507
+ export declare const runFileViewerPreviewComponentUnmount: ({ reason, onCancelPreview, onClearRenderedContent, onResetLoading, onStopZoomObserver, onStopFitObserver, onStopViewStateObserver, }?: RunFileViewerPreviewComponentUnmountInput) => FileViewerPreviewComponentUnmountState;
506
508
  export declare const applyFileViewerEmptyPreviewState: <Target extends MutableFileViewerPreviewState>(target: Target, state?: FileViewerEmptyPreviewState) => Target;
507
509
  export declare const commitFileViewerEmptyPreviewResetState: ({ previewTarget, state, reason, onClearRenderedContent, onResetLoading, }: CommitFileViewerEmptyPreviewResetStateInput) => MutableFileViewerPreviewState;
508
510
  export declare const runFileViewerPreviewRequest: <LocalResult = unknown, RemoteResult = unknown>({ file, url, reason, requestController, previewTarget, onPreviewLocalFile, onPreviewRemoteFile, onClearRenderedContent, onClearError, onResetLoading, }: RunFileViewerPreviewRequestInput<LocalResult, RemoteResult>) => Promise<FileViewerPreviewRequestRunState<LocalResult, RemoteResult>>;
@@ -17,7 +17,7 @@ export const resolveFileViewerPreviewLoadErrorMessage = ({ kind, error, formatEr
17
17
  : kind === 'stream'
18
18
  ? translateFileViewerMessage(i18n, 'error.stream')
19
19
  : translateFileViewerMessage(i18n, 'error.load');
20
- return formatErrorMessage((_a = prefixes === null || prefixes === void 0 ? void 0 : prefixes[kind]) !== null && _a !== void 0 ? _a : fallbackPrefix, error);
20
+ return formatErrorMessage((_a = prefixes === null || prefixes === void 0 ? void 0 : prefixes[kind]) !== null && _a !== void 0 ? _a : fallbackPrefix, error, i18n);
21
21
  };
22
22
  export const resolveFileViewerMissingRemoteDataErrorMessage = ({ message, i18n, } = {}) => message || translateFileViewerMessage(i18n, 'error.remoteDownload');
23
23
  export const DEFAULT_FILE_VIEWER_PREVIEW_LOAD_ERROR_LOGGER = error => {
@@ -182,10 +182,14 @@ export const cancelFileViewerPreviewRequest = ({ reason = 'component-unmount', r
182
182
  export const runFileViewerPreviewSourceChange = ({ onRefreshPreview, } = {}) => {
183
183
  return onRefreshPreview === null || onRefreshPreview === void 0 ? void 0 : onRefreshPreview();
184
184
  };
185
- export const runFileViewerPreviewComponentUnmount = ({ reason = 'component-unmount', onCancelPreview, onResetLoading, onStopZoomObserver, onStopViewStateObserver, } = {}) => {
185
+ export const runFileViewerPreviewComponentUnmount = ({ reason = 'component-unmount', onCancelPreview, onClearRenderedContent, onResetLoading, onStopZoomObserver, onStopFitObserver, onStopViewStateObserver, } = {}) => {
186
186
  onCancelPreview === null || onCancelPreview === void 0 ? void 0 : onCancelPreview(reason);
187
+ if (!onCancelPreview) {
188
+ onClearRenderedContent === null || onClearRenderedContent === void 0 ? void 0 : onClearRenderedContent(reason);
189
+ }
187
190
  onResetLoading === null || onResetLoading === void 0 ? void 0 : onResetLoading();
188
191
  onStopZoomObserver === null || onStopZoomObserver === void 0 ? void 0 : onStopZoomObserver();
192
+ onStopFitObserver === null || onStopFitObserver === void 0 ? void 0 : onStopFitObserver();
189
193
  onStopViewStateObserver === null || onStopViewStateObserver === void 0 ? void 0 : onStopViewStateObserver();
190
194
  return {
191
195
  reason,
@@ -8,11 +8,12 @@ import { createEmptyFileViewerSearchState, } from '../features/document/model.js
8
8
  import { createFileViewerDocumentFeatureControllerActionHandlers, } from '../features/document/events.js';
9
9
  import { createFileViewerZoomController } from '../features/document/zoom.js';
10
10
  import { createFileViewerViewStateController } from '../features/document/viewState.js';
11
+ import { createFileViewerFitController, hasFileViewerExplicitInitialViewState, } from '../features/document/fit.js';
11
12
  import { DEFAULT_FILE_VIEWER_DOWNLOAD_FILENAME, DEFAULT_FILE_VIEWER_EXPORT_FILENAME, DEFAULT_FILE_VIEWER_PREVIEW_TITLE, createFileViewerOriginalSourceStateFromNormalizedSource, executeFileViewerDownloadOperation, executeFileViewerExportHtmlOperation, executeFileViewerPrintOperation, resolveFileViewerDisplayFilename, resolveFileViewerOperationFilename, } from './operations.js';
12
13
  import { applyFileViewerZoomAvailability, createUnsupportedAvailability, getRendererAvailability, } from '../registry/capabilities.js';
13
14
  import { buildFileViewerLifecycleContextFromNormalizedSource, buildFileViewerOperationContext, runFileViewerBeforeOperation, runFileViewerLifecycleHook, } from '../lifecycle/operations.js';
14
15
  import { collectFileViewerRendererPlugins, createRendererRegistry, getFileViewerAutoRendererPresetVersion, hasFileViewerRendererPresetName, installFileViewerRendererPlugins, listFileViewerAutoRendererPresets, resolveFileViewerRendererPresetInputs, } from '../registry/registry.js';
15
- import { createFileRenderHandlerLoader, applyFileViewerRenderSurfaceState, createFileViewerRenderSurfaceState, } from '../rendering/handler.js';
16
+ import { createFileRenderHandlerLoader, applyFileViewerRenderSurfaceState, createFileViewerRenderSurfaceState, createFileViewerRenderSurface, removeFileViewerRenderTarget, } from '../rendering/handler.js';
16
17
  import { createFileViewerCoreRendererRegistry } from '../renderers/index.js';
17
18
  import { createFileViewerRequestScope } from '../source/loading.js';
18
19
  import { normalizeSource } from '../source/index.js';
@@ -93,6 +94,8 @@ export const createViewer = (container, createOptions = {}) => {
93
94
  let installedAutoRenderersEnabled = resolveAutoRenderersEnabled(options);
94
95
  let installedAutoRendererVersion = -1;
95
96
  let currentSource = null;
97
+ let currentRenderTarget = null;
98
+ let currentDocumentRoot = null;
96
99
  const renderSurfaceState = createFileViewerRenderSurfaceState();
97
100
  const requestScope = createFileViewerRequestScope();
98
101
  const documentTarget = {
@@ -179,6 +182,36 @@ export const createViewer = (container, createOptions = {}) => {
179
182
  forcedWatermarkContainerPosition = false;
180
183
  }
181
184
  };
185
+ const removeRenderTarget = (target = currentRenderTarget) => {
186
+ if (!target) {
187
+ return;
188
+ }
189
+ removeFileViewerRenderTarget(container, target);
190
+ if (currentRenderTarget === target) {
191
+ currentRenderTarget = null;
192
+ }
193
+ };
194
+ const createRenderTarget = (fillHeight = true) => {
195
+ const surface = createFileViewerRenderSurface(container, {
196
+ styleIsolation: options.styleIsolation,
197
+ });
198
+ const target = surface.host || surface.container;
199
+ if (fillHeight) {
200
+ target.style.height = '100%';
201
+ surface.container.style.height = '100%';
202
+ }
203
+ currentRenderTarget = target;
204
+ return surface;
205
+ };
206
+ const disposeStaleSession = async (session, target) => {
207
+ var _a;
208
+ try {
209
+ await ((_a = session === null || session === void 0 ? void 0 : session.destroy) === null || _a === void 0 ? void 0 : _a.call(session));
210
+ }
211
+ finally {
212
+ removeRenderTarget(target);
213
+ }
214
+ };
182
215
  const syncWatermarkOverlay = () => {
183
216
  const inlineStyle = buildFileViewerWatermarkInlineStyle(options.watermark);
184
217
  if (!inlineStyle) {
@@ -323,20 +356,41 @@ export const createViewer = (container, createOptions = {}) => {
323
356
  emitZoomChange(state);
324
357
  emitOperationAvailabilityChange();
325
358
  };
359
+ const emitFitChange = (result) => {
360
+ var _a;
361
+ (_a = createOptions.onEvent) === null || _a === void 0 ? void 0 : _a.call(createOptions, {
362
+ type: 'fit-change',
363
+ payload: result,
364
+ });
365
+ };
326
366
  const zoomController = createFileViewerZoomController({
327
- root: () => container,
367
+ root: () => currentDocumentRoot || container,
328
368
  beforeZoom: runBeforeViewerOperation,
329
369
  onChange: state => emitZoomAndOperationAvailabilityChange(state),
330
370
  });
371
+ const fitController = createFileViewerFitController({
372
+ root: () => currentDocumentRoot || container,
373
+ getFit: () => options.fit,
374
+ onFit: result => {
375
+ zoomController.refreshProvider();
376
+ viewStateController.refreshProvider();
377
+ emitZoomAndOperationAvailabilityChange();
378
+ emitFitChange(result);
379
+ },
380
+ });
331
381
  const viewStateController = createFileViewerViewStateController({
332
- root: () => container,
382
+ root: () => currentDocumentRoot || container,
333
383
  onChange: change => {
334
384
  var _a;
385
+ if ((change.source === 'user' || change.source === 'api') &&
386
+ change.action !== 'fit') {
387
+ fitController.markUserInteraction();
388
+ }
335
389
  (_a = createOptions.onEvent) === null || _a === void 0 ? void 0 : _a.call(createOptions, { type: 'view-state-change', payload: change });
336
390
  },
337
391
  });
338
392
  const documentActions = createFileViewerDocumentFeatureControllerActionHandlers({
339
- root: () => container,
393
+ root: () => currentDocumentRoot || container,
340
394
  searchTarget: documentTarget,
341
395
  searchOptions: () => options.search,
342
396
  getAiOptions: () => options.ai,
@@ -351,17 +405,24 @@ export const createViewer = (container, createOptions = {}) => {
351
405
  });
352
406
  zoomController.observe();
353
407
  viewStateController.observe();
408
+ fitController.observe();
354
409
  const destroyCurrent = async (reason = 'replace') => {
355
- var _a, _b;
356
- if (!currentSource) {
410
+ var _a;
411
+ const session = renderSurfaceState.session;
412
+ const target = currentRenderTarget;
413
+ if (!currentSource && !session && !target) {
357
414
  return;
358
415
  }
359
416
  const source = currentSource;
360
417
  const startedAt = Date.now();
361
418
  const version = requestScope.getCurrentVersion();
362
- await emitLifecycle(options, createOptions.onEvent, 'unload-start', source, version, startedAt, reason);
363
- await ((_b = (_a = renderSurfaceState.session) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a));
419
+ if (source) {
420
+ await emitLifecycle(options, createOptions.onEvent, 'unload-start', source, version, startedAt, reason);
421
+ }
422
+ await ((_a = session === null || session === void 0 ? void 0 : session.destroy) === null || _a === void 0 ? void 0 : _a.call(session));
423
+ removeRenderTarget(target || undefined);
364
424
  currentSource = null;
425
+ currentDocumentRoot = null;
365
426
  applyFileViewerRenderSurfaceState(renderSurfaceState, {
366
427
  session: null,
367
428
  exportAdapter: null,
@@ -370,59 +431,98 @@ export const createViewer = (container, createOptions = {}) => {
370
431
  await documentActions.clearDocumentState();
371
432
  zoomController.clearProvider();
372
433
  viewStateController.clearProvider();
434
+ fitController.resetAutoFit();
373
435
  emitZoomAndOperationAvailabilityChange();
374
- await emitLifecycle(options, createOptions.onEvent, 'unload-complete', source, version, startedAt, reason);
436
+ if (source) {
437
+ await emitLifecycle(options, createOptions.onEvent, 'unload-complete', source, version, startedAt, reason);
438
+ }
375
439
  };
376
440
  return {
377
441
  container,
378
442
  async load(source) {
443
+ const version = requestScope.requestController.createVersion();
379
444
  await destroyCurrent('replace');
380
445
  await ensureRendererPluginsInstalled();
446
+ if (!requestScope.isCurrentRequest(version)) {
447
+ return null;
448
+ }
381
449
  const normalized = normalizeSource(source);
382
450
  currentSource = normalized;
383
- const version = requestScope.requestController.createVersion();
384
451
  const renderer = registry.getByExtension(normalized.extension);
385
452
  const startedAt = Date.now();
386
453
  await emitLifecycle(options, createOptions.onEvent, 'load-start', normalized, version, startedAt);
454
+ const surface = createRenderTarget((renderer === null || renderer === void 0 ? void 0 : renderer.id) !== 'office-presentation');
455
+ const target = surface.container;
456
+ const targetHost = surface.host || target;
457
+ currentDocumentRoot = target;
387
458
  if (!(renderer === null || renderer === void 0 ? void 0 : renderer.load)) {
388
- renderMissingRendererState(container, normalized.extension, options);
459
+ renderMissingRendererState(target, normalized.extension, options);
389
460
  applyFileViewerRenderSurfaceState(renderSurfaceState, { session: null });
390
461
  syncWatermarkOverlay();
391
462
  emitZoomAndOperationAvailabilityChange();
392
463
  await emitLifecycle(options, createOptions.onEvent, 'load-complete', normalized, version, startedAt);
393
464
  return null;
394
465
  }
395
- const session = await renderer.load({
396
- source: normalized,
397
- surface: { container },
398
- options,
399
- signal: createOptions.signal,
400
- registerExportAdapter: adapter => {
401
- applyFileViewerRenderSurfaceState(renderSurfaceState, { exportAdapter: adapter });
402
- },
403
- });
466
+ let session;
467
+ try {
468
+ session = await renderer.load({
469
+ source: normalized,
470
+ surface,
471
+ options,
472
+ signal: createOptions.signal,
473
+ registerExportAdapter: adapter => {
474
+ if (requestScope.isCurrentRequest(version)) {
475
+ applyFileViewerRenderSurfaceState(renderSurfaceState, { exportAdapter: adapter });
476
+ }
477
+ },
478
+ });
479
+ }
480
+ catch (error) {
481
+ if (!requestScope.isCurrentRequest(version)) {
482
+ removeRenderTarget(targetHost);
483
+ return null;
484
+ }
485
+ removeRenderTarget(targetHost);
486
+ throw error;
487
+ }
488
+ if (!requestScope.isCurrentRequest(version)) {
489
+ await disposeStaleSession(session, targetHost);
490
+ return null;
491
+ }
404
492
  applyFileViewerRenderSurfaceState(renderSurfaceState, { session });
405
493
  syncWatermarkOverlay();
406
494
  zoomController.refreshProvider();
407
495
  viewStateController.refreshProvider();
408
496
  await documentActions.refreshDocumentIndex({ notify: false });
497
+ await fitController.applyInitialFit({
498
+ skip: hasFileViewerExplicitInitialViewState(options.initialViewState),
499
+ });
500
+ zoomController.refreshProvider();
501
+ viewStateController.refreshProvider();
409
502
  emitZoomAndOperationAvailabilityChange();
410
503
  await emitLifecycle(options, createOptions.onEvent, 'load-complete', normalized, version, startedAt);
411
504
  return session;
412
505
  },
413
506
  async destroy(reason = 'component-unmount') {
507
+ requestScope.requestController.createVersion();
414
508
  await destroyCurrent(reason);
415
509
  documentActions.destroyDocumentFeatures();
416
510
  zoomController.destroy();
417
511
  viewStateController.destroy();
512
+ fitController.destroy();
418
513
  removeWatermarkOverlay();
419
514
  },
420
515
  updateOptions(nextOptions) {
516
+ const previousFit = options.fit;
421
517
  options = {
422
518
  ...options,
423
519
  ...nextOptions,
424
520
  };
425
521
  syncWatermarkOverlay();
522
+ if ('fit' in nextOptions && nextOptions.fit !== previousFit) {
523
+ fitController.resetAutoFit();
524
+ fitController.scheduleFit('resize');
525
+ }
426
526
  },
427
527
  getCapabilities(extension) {
428
528
  return getCapabilitiesForExtension(extension);
@@ -484,20 +584,30 @@ export const createViewer = (container, createOptions = {}) => {
484
584
  });
485
585
  },
486
586
  async zoomIn() {
587
+ fitController.markUserInteraction();
487
588
  const state = await zoomController.zoomIn();
488
589
  emitZoomAndOperationAvailabilityChange(state);
489
590
  return state;
490
591
  },
491
592
  async zoomOut() {
593
+ fitController.markUserInteraction();
492
594
  const state = await zoomController.zoomOut();
493
595
  emitZoomAndOperationAvailabilityChange(state);
494
596
  return state;
495
597
  },
496
598
  async resetZoom() {
599
+ fitController.markUserInteraction();
497
600
  const state = await zoomController.resetZoom();
498
601
  emitZoomAndOperationAvailabilityChange(state);
499
602
  return state;
500
603
  },
604
+ async fitToView(fit) {
605
+ const result = await fitController.fit(fit, { source: 'api', reason: 'api' });
606
+ zoomController.refreshProvider();
607
+ viewStateController.refreshProvider();
608
+ emitZoomAndOperationAvailabilityChange();
609
+ return result;
610
+ },
501
611
  getZoomState() {
502
612
  return zoomController.getState();
503
613
  },
@@ -505,6 +615,7 @@ export const createViewer = (container, createOptions = {}) => {
505
615
  return viewStateController.getState();
506
616
  },
507
617
  applyViewState(state, applyOptions) {
618
+ fitController.markUserInteraction();
508
619
  return viewStateController.applyState(state, applyOptions);
509
620
  },
510
621
  search(query) {
@@ -50,7 +50,7 @@ export const resolveFileViewerOperationFilename = ({ filename, source, fallback
50
50
  };
51
51
  export const resolveFileViewerOperationActionErrorMessage = ({ context, formatErrorMessage, prefixes, i18n, }) => {
52
52
  var _a;
53
- return formatErrorMessage((_a = prefixes === null || prefixes === void 0 ? void 0 : prefixes[context.operation]) !== null && _a !== void 0 ? _a : translateFileViewerMessage(i18n, FILE_VIEWER_OPERATION_ACTION_ERROR_MESSAGE_KEYS[context.operation]), context.error);
53
+ return formatErrorMessage((_a = prefixes === null || prefixes === void 0 ? void 0 : prefixes[context.operation]) !== null && _a !== void 0 ? _a : translateFileViewerMessage(i18n, FILE_VIEWER_OPERATION_ACTION_ERROR_MESSAGE_KEYS[context.operation]), context.error, i18n);
54
54
  };
55
55
  export const hasFileViewerOriginalSource = (source) => {
56
56
  return !!source.buffer || !!source.file || !!source.url;
@@ -1,6 +1,6 @@
1
1
  import { type FileViewerI18nInput } from '../i18n/messages';
2
2
  import type { FileViewerRendererCategory, FileViewerStateDescriptor, FileViewerStateTheme } from '../contracts/types';
3
- export type FileViewerErrorMessageFormatter = (prefix: string, error: unknown) => string;
3
+ export type FileViewerErrorMessageFormatter = (prefix: string, error: unknown, i18n?: FileViewerI18nInput) => string;
4
4
  export declare const FILE_VIEWER_PREVIEW_MESSAGES: Readonly<{
5
5
  downloading: "正在下载文件资源...";
6
6
  streamingPdf: "正在建立 PDF 流式预览...";
@@ -28,6 +28,6 @@ export declare const createFileViewerPreviewLoadingState: (extension?: string, m
28
28
  export declare const createFileViewerReadyState: (extension?: string, theme?: FileViewerStateTheme, i18n?: FileViewerI18nInput) => FileViewerStateDescriptor;
29
29
  export declare const createFileViewerEmptyState: (extension?: string, theme?: FileViewerStateTheme, i18n?: FileViewerI18nInput) => FileViewerStateDescriptor;
30
30
  export declare const createFileViewerUnsupportedState: (extension?: string, theme?: FileViewerStateTheme, i18n?: FileViewerI18nInput) => FileViewerStateDescriptor;
31
- export declare const normalizeFileViewerErrorMessage: (error: unknown) => string;
31
+ export declare const normalizeFileViewerErrorMessage: (error: unknown, i18n?: FileViewerI18nInput) => string;
32
32
  export declare const formatFileViewerErrorMessage: FileViewerErrorMessageFormatter;
33
33
  export declare const createFileViewerErrorState: (extension?: string, error?: unknown, theme?: FileViewerStateTheme, i18n?: FileViewerI18nInput) => FileViewerStateDescriptor;
@@ -197,20 +197,53 @@ export const createFileViewerUnsupportedState = (extension = '', theme = DEFAULT
197
197
  recoverable: true,
198
198
  });
199
199
  };
200
- export const normalizeFileViewerErrorMessage = (error) => {
200
+ const FILE_VIEWER_KNOWN_ERROR_MESSAGE_KEYS = {
201
+ 'Blob is not available in the current execution environment.': 'error.blobUnavailable',
202
+ 'Unsupported file source input.': 'error.sourceUnsupported',
203
+ 'Failed to read file as ArrayBuffer.': 'error.fileReadArrayBuffer',
204
+ 'Failed to read file as data URL.': 'error.fileReadDataUrl',
205
+ 'Failed to read file as text.': 'error.fileReadText',
206
+ 'Unable to read image data URL.': 'error.imageDataUrlRead',
207
+ '文件不是有效的 DOCX/OOXML 压缩包,可能下载不完整或被服务端错误内容替换,请重新上传或检查文件源。': 'word.error.invalidDocx',
208
+ 'ODF 文件缺少 content.xml': 'word.error.missingOdfContent',
209
+ 'ODF 文件缺少 content.xml。': 'word.error.missingOdfContent',
210
+ 'ODF XML 解析失败': 'word.error.odfXmlParseFailed',
211
+ 'ODF XML 解析失败。': 'word.error.odfXmlParseFailed',
212
+ 'OFD 文件中没有可渲染的文档': 'ofd.error.empty',
213
+ 'OFD 文件解析失败': 'ofd.error.parseFailed',
214
+ 'IndexedDB 不可用': 'archive.error.indexedDbUnavailable',
215
+ 'Web Worker is not supported by this browser.': 'archive.error.workerUnsupported',
216
+ };
217
+ const resolveKnownFileViewerErrorMessage = (message, i18n) => {
218
+ const key = FILE_VIEWER_KNOWN_ERROR_MESSAGE_KEYS[message.trim()];
219
+ return key ? translateFileViewerMessage(i18n, key) : message;
220
+ };
221
+ export const normalizeFileViewerErrorMessage = (error, i18n) => {
201
222
  if (error instanceof Error) {
202
- return error.message;
223
+ return resolveKnownFileViewerErrorMessage(error.message, i18n);
224
+ }
225
+ if (typeof error === 'string') {
226
+ return resolveKnownFileViewerErrorMessage(error, i18n);
227
+ }
228
+ if (error === undefined || error === null) {
229
+ return translateFileViewerMessage(i18n, 'error.unknown');
203
230
  }
204
231
  return String(error);
205
232
  };
206
- export const formatFileViewerErrorMessage = (prefix, error) => {
207
- return `${prefix}:${normalizeFileViewerErrorMessage(error)}`;
233
+ export const formatFileViewerErrorMessage = (prefix, error, i18n) => {
234
+ const detail = normalizeFileViewerErrorMessage(error, i18n);
235
+ if (!detail) {
236
+ return prefix;
237
+ }
238
+ return resolveFileViewerLocale(i18n) === 'zh-CN'
239
+ ? `${prefix}:${detail}`
240
+ : `${prefix}: ${detail}`;
208
241
  };
209
- export const createFileViewerErrorState = (extension = '', error = '未知错误', theme = DEFAULT_FILE_VIEWER_STATE_THEME, i18n) => createFileViewerStateDescriptor({
242
+ export const createFileViewerErrorState = (extension = '', error = undefined, theme = DEFAULT_FILE_VIEWER_STATE_THEME, i18n) => createFileViewerStateDescriptor({
210
243
  state: 'error',
211
244
  extension,
212
245
  title: translateFileViewerMessage(i18n, 'state.error.title'),
213
- message: normalizeFileViewerErrorMessage(error),
246
+ message: normalizeFileViewerErrorMessage(error, i18n),
214
247
  theme,
215
248
  recoverable: true,
216
249
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/core",
3
- "version": "2.1.16",
3
+ "version": "2.1.18",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Framework-neutral modular TypeScript core for Flyfish File Viewer renderer orchestration, lifecycle APIs, search, zoom, print, and export",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "funding": {
37
37
  "type": "individual",
38
- "url": "https://dev.flyfish.group/shop"
38
+ "url": "https://dev.flyfish.group/donate?source=npm"
39
39
  },
40
40
  "homepage": "https://doc.file-viewer.app/",
41
41
  "main": "./dist/index.js",