@file-viewer/core 3.2.2 → 3.3.1

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 (112) hide show
  1. package/README.en.md +26 -15
  2. package/README.md +47 -41
  3. package/README.zh-CN.md +175 -0
  4. package/dist/assets.d.ts +2 -2
  5. package/dist/assets.js +5 -1
  6. package/dist/browser/controller.d.ts +2 -1
  7. package/dist/browser/controller.js +156 -32
  8. package/dist/browser/source-feedback.d.ts +4 -0
  9. package/dist/browser/source-feedback.js +40 -0
  10. package/dist/browser/toolbar-focus.d.ts +2 -0
  11. package/dist/browser/toolbar-focus.js +46 -0
  12. package/dist/browser.d.ts +2 -2
  13. package/dist/browser.js +2 -2
  14. package/dist/config/options.d.ts +1 -1
  15. package/dist/contracts/types.d.ts +479 -1
  16. package/dist/contracts/xml.d.ts +11 -0
  17. package/dist/export.d.ts +10 -3
  18. package/dist/export.js +9 -2
  19. package/dist/exportDocument.d.ts +12 -1
  20. package/dist/exportDocument.js +31 -2
  21. package/dist/features/document/dom/anchors.d.ts +1 -1
  22. package/dist/features/document/dom/index.d.ts +5 -5
  23. package/dist/features/document/dom/index.js +3 -3
  24. package/dist/features/document/dom/providers.d.ts +1 -1
  25. package/dist/features/document/events.d.ts +2 -2
  26. package/dist/features/document/events.js +3 -3
  27. package/dist/features/document/fit.d.ts +1 -1
  28. package/dist/features/document/fit.js +1 -1
  29. package/dist/features/document/imageHandTool.d.ts +3 -0
  30. package/dist/features/document/imageHandTool.js +141 -0
  31. package/dist/features/document/model.d.ts +1 -1
  32. package/dist/features/document/search.d.ts +1 -1
  33. package/dist/features/document/search.js +2 -2
  34. package/dist/features/document/viewState.d.ts +1 -1
  35. package/dist/features/document/viewState.js +2 -2
  36. package/dist/features/document/zoom.d.ts +1 -1
  37. package/dist/features/document/zoom.js +3 -3
  38. package/dist/features/document/zoomGestures.d.ts +2 -2
  39. package/dist/features/document/zoomGestures.js +2 -0
  40. package/dist/features/printMask.d.ts +1 -1
  41. package/dist/features/printMaskDesigner.d.ts +6 -2
  42. package/dist/features/printMaskDesigner.js +11 -2
  43. package/dist/features/printMaskLoader.d.ts +7 -1
  44. package/dist/features/printMaskLoader.js +7 -1
  45. package/dist/features/watermark.d.ts +1 -1
  46. package/dist/headless.d.ts +29 -29
  47. package/dist/headless.js +21 -16
  48. package/dist/i18n/messages.d.ts +1 -1
  49. package/dist/i18n/messages.de.d.ts +5 -1
  50. package/dist/i18n/messages.de.js +4 -0
  51. package/dist/i18n/messages.ja.d.ts +6 -1
  52. package/dist/i18n/messages.ja.js +5 -0
  53. package/dist/i18n/messages.js +2 -2
  54. package/dist/index.d.ts +77 -75
  55. package/dist/index.js +39 -37
  56. package/dist/lifecycle/facade.d.ts +4 -4
  57. package/dist/lifecycle/facade.js +2 -2
  58. package/dist/lifecycle/operations.d.ts +6 -4
  59. package/dist/lifecycle/operations.js +21 -4
  60. package/dist/output/canvasImage.d.ts +2 -1
  61. package/dist/output/canvasImage.js +4 -1
  62. package/dist/output/export.d.ts +3 -1
  63. package/dist/output/export.js +12 -0
  64. package/dist/output/printLayout.d.ts +5 -0
  65. package/dist/output/printLayout.js +4 -0
  66. package/dist/output/snapshotDownload.d.ts +2 -1
  67. package/dist/output/snapshotDownload.js +3 -2
  68. package/dist/platform/assets.d.ts +9 -2
  69. package/dist/platform/assets.js +15 -1
  70. package/dist/platform/copiedAssets.d.ts +1 -0
  71. package/dist/platform/copiedAssets.js +2 -1
  72. package/dist/presentation/colorScheme.d.ts +3 -0
  73. package/dist/presentation/colorScheme.js +40 -0
  74. package/dist/presentation/state.d.ts +1 -1
  75. package/dist/presentation/state.js +3 -3
  76. package/dist/print-mask.d.ts +2 -2
  77. package/dist/print-mask.js +1 -1
  78. package/dist/registry/capabilities.d.ts +14 -1
  79. package/dist/registry/capabilities.js +15 -2
  80. package/dist/registry/formats.d.ts +1 -1
  81. package/dist/registry/formats.generated.d.ts +1 -0
  82. package/dist/registry/formats.generated.js +1 -0
  83. package/dist/registry/formats.js +2 -2
  84. package/dist/registry/registry.d.ts +15 -1
  85. package/dist/registry/registry.js +10 -2
  86. package/dist/renderers/image.d.ts +1 -1
  87. package/dist/renderers/image.js +13 -7
  88. package/dist/renderers/index.d.ts +1 -1
  89. package/dist/renderers/index.js +11 -5
  90. package/dist/rendering/dispatcher.d.ts +6 -1
  91. package/dist/rendering/dispatcher.js +3 -3
  92. package/dist/rendering/handler.d.ts +13 -3
  93. package/dist/rendering/handler.js +21 -9
  94. package/dist/security/svgResources.d.ts +10 -0
  95. package/dist/security/svgResources.js +12 -0
  96. package/dist/source/index.d.ts +3 -3
  97. package/dist/source/index.js +1 -1
  98. package/dist/source/loading.d.ts +12 -7
  99. package/dist/source/loading.js +22 -17
  100. package/dist/source/precheck.d.ts +13 -1
  101. package/dist/source/precheck.js +9 -2
  102. package/dist/source/textEncoding.d.ts +18 -0
  103. package/dist/source/textEncoding.js +15 -0
  104. package/dist/viewer/createViewer.d.ts +1 -1
  105. package/dist/viewer/createViewer.js +30 -17
  106. package/dist/viewer/loading.d.ts +6 -2
  107. package/dist/viewer/loading.js +9 -1
  108. package/dist/viewer/operations.d.ts +3 -3
  109. package/dist/viewer/operations.js +14 -6
  110. package/dist/viewer/state.d.ts +6 -2
  111. package/dist/viewer/state.js +8 -3
  112. package/package.json +14 -5
@@ -1,5 +1,8 @@
1
- import { appendFileViewerStyle, cloneFileViewerViewState, applyFileViewerZoomAvailability, createViewer, isFileViewerShadowRoot, normalizeFileViewerUiDensity, normalizeFileViewerTheme, normalizeFileViewerStyleIsolation, syncFileViewerRenderSurfaceBackground, } from "../index.js";
2
- import { DEFAULT_FILE_VIEWER_SOURCE_FILENAME, createFileViewerTranslator, getExtension, normalizeFileViewerSourceUrl, hasVisibleFileViewerToolbarActions, isFileViewerZoomButtonDisabled, normalizeFileViewerToolbar, normalizeFilename, readFileViewerBuffer, resolveFileViewerSourceFilename, resolveFileViewerColorScheme, resolveFileViewerToolbarOrder, resolveFileViewerToolbarPosition, resolveVisibleFileViewerToolbar, toggleFileViewerColorScheme, wrapFileViewerFileRef, } from "../index.js";
1
+ import { appendFileViewerStyle, cloneFileViewerViewState, applyFileViewerZoomAvailability, createViewer, isFileViewerShadowRoot, normalizeFileViewerUiDensity, normalizeFileViewerTheme, normalizeFileViewerStyleIsolation, syncFileViewerRenderSurfaceBackground, } from '../index.js';
2
+ import { DEFAULT_FILE_VIEWER_SOURCE_FILENAME, createFileViewerTranslator, getExtension, normalizeFileViewerSourceUrl, hasVisibleFileViewerToolbarActions, isFileViewerZoomButtonDisabled, normalizeFileViewerToolbar, normalizeFilename, readFileViewerBuffer, resolveFileViewerSourceFilename, resolveFileViewerRemoteSourcePlan, resolveFileViewerColorScheme, resolveFileViewerToolbarOrder, resolveFileViewerToolbarPosition, resolveVisibleFileViewerToolbar, toggleFileViewerColorScheme, wrapFileViewerFileRef, } from '../index.js';
3
+ import { resolveFileViewerToolbarZoomButtons } from '../lifecycle/operations.js';
4
+ import { createToolbarFocusRestorer } from './toolbar-focus.js';
5
+ import { createViewerSourceFeedback } from './source-feedback.js';
3
6
  const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';
4
7
  const hasSource = (options = {}) => {
5
8
  return !!(options.url || options.file || options.buffer);
@@ -13,6 +16,8 @@ const toViewerSourceInput = (options = {}) => ({
13
16
  type: options.type,
14
17
  size: options.size,
15
18
  });
19
+ // These options feed renderer selection or rendering itself. Viewer chrome,
20
+ // fitting, watermark, and operation callbacks update in place instead.
16
21
  const RENDER_AFFECTING_VIEWER_OPTION_KEYS = [
17
22
  'theme',
18
23
  'rendererMode',
@@ -25,6 +30,7 @@ const RENDER_AFFECTING_VIEWER_OPTION_KEYS = [
25
30
  'archive',
26
31
  'chm',
27
32
  'pdf',
33
+ 'epub',
28
34
  'docx',
29
35
  'presentation',
30
36
  'spreadsheet',
@@ -113,30 +119,43 @@ const resolveViewerSourceFilename = (source) => {
113
119
  }), source.type ? `preview.${source.type}` : DEFAULT_FILE_VIEWER_SOURCE_FILENAME);
114
120
  };
115
121
  const resolveViewerLoadSource = async (source, options = {}) => {
116
- var _a, _b, _c;
122
+ var _a, _b, _c, _d, _e, _f, _g, _h;
117
123
  const filename = resolveViewerSourceFilename(source);
118
124
  const type = source.type || getExtension(filename);
119
125
  if (source.buffer) {
126
+ (_a = options.onPhase) === null || _a === void 0 ? void 0 : _a.call(options, 'reading');
120
127
  return {
121
128
  buffer: source.buffer,
122
129
  filename,
123
130
  type,
124
- size: (_a = source.size) !== null && _a !== void 0 ? _a : source.buffer.byteLength,
131
+ size: (_b = source.size) !== null && _b !== void 0 ? _b : source.buffer.byteLength,
125
132
  url: source.url,
126
133
  };
127
134
  }
128
135
  if (source.file) {
136
+ (_c = options.onPhase) === null || _c === void 0 ? void 0 : _c.call(options, 'reading');
129
137
  const file = wrapFileViewerFileRef(source.file, filename);
130
138
  return {
131
139
  file,
132
140
  buffer: await readFileViewerBuffer(file),
133
141
  filename: file.name || filename,
134
142
  type: type || getExtension(file.name),
135
- size: (_b = source.size) !== null && _b !== void 0 ? _b : file.size,
143
+ size: (_d = source.size) !== null && _d !== void 0 ? _d : file.size,
136
144
  url: source.url,
137
145
  };
138
146
  }
139
147
  if (source.url) {
148
+ const plan = resolveFileViewerRemoteSourcePlan({
149
+ url: source.url, filename, type,
150
+ pageHref: options.pageHref, streaming: options.streaming,
151
+ });
152
+ // A supplied transport owns authentication/acquisition and keeps its normal
153
+ // buffered contract. Only the default PDF URL path defers bytes to PDF.js.
154
+ if (!options.fetchFile && options.acceptsPdfUrl && plan.streamPdf) {
155
+ (_e = options.onPhase) === null || _e === void 0 ? void 0 : _e.call(options, 'streamingPdf');
156
+ return { url: plan.url, filename: plan.filename, type: plan.extension, size: source.size };
157
+ }
158
+ (_f = options.onPhase) === null || _f === void 0 ? void 0 : _f.call(options, 'downloading');
140
159
  const fileRef = await (options.fetchFile || defaultFetchFile)({
141
160
  url: source.url,
142
161
  signal: options.signal,
@@ -146,12 +165,13 @@ const resolveViewerLoadSource = async (source, options = {}) => {
146
165
  throw new Error('Downloaded file is empty.');
147
166
  }
148
167
  const file = wrapFileViewerFileRef(fileRef, filename);
168
+ (_g = options.onPhase) === null || _g === void 0 ? void 0 : _g.call(options, 'reading');
149
169
  return {
150
170
  file,
151
171
  buffer: await readFileViewerBuffer(file),
152
172
  filename: file.name || filename,
153
173
  type: type || getExtension(file.name),
154
- size: (_c = source.size) !== null && _c !== void 0 ? _c : file.size,
174
+ size: (_h = source.size) !== null && _h !== void 0 ? _h : file.size,
155
175
  url: source.url,
156
176
  };
157
177
  }
@@ -280,9 +300,6 @@ const callApi = async (api, action, fallback) => {
280
300
  }
281
301
  return action(api);
282
302
  };
283
- const isAbortError = (error) => {
284
- return Boolean(error && typeof error === 'object' && error.name === 'AbortError');
285
- };
286
303
  const DEFAULT_TOOLBAR_AVAILABILITY = {
287
304
  download: false,
288
305
  print: false,
@@ -299,6 +316,9 @@ const DEFAULT_TOOLBAR_ZOOM_STATE = {
299
316
  canZoomOut: false,
300
317
  canReset: false,
301
318
  };
319
+ // A ShadowRoot cannot be detached. Track the exact roots created on viewer
320
+ // hosts so a later light-DOM remount can expose its children through a slot,
321
+ // without mistaking and clearing a ShadowRoot owned by the host application.
302
322
  const ownedViewerShadowRoots = new WeakMap();
303
323
  const WEB_VIEWER_STYLE = `
304
324
  :host,.file-viewer-web-shell{display:block;width:100%;height:100%;min-width:0;min-height:0;contain:content;--file-viewer-bg:transparent;--file-viewer-content-bg:transparent;--file-viewer-text:#172033;--file-viewer-muted:#607282;--file-viewer-font:14px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;--file-viewer-border:rgba(20,35,53,.08);--file-viewer-toolbar-bg:rgba(255,255,255,.92);--file-viewer-toolbar-border:rgba(20,35,53,.06);--file-viewer-toolbar-shadow:0 18px 44px rgba(15,23,42,.16);--file-viewer-toolbar-radius:999px;--file-viewer-toolbar-gap:6px;--file-viewer-toolbar-min-height:45px;--file-viewer-toolbar-padding:6px 10px;--file-viewer-toolbar-floating-min-height:42px;--file-viewer-toolbar-floating-padding:6px;--file-viewer-toolbar-floating-offset:16px;--file-viewer-group-bg:rgba(20,35,53,.035);--file-viewer-group-border:rgba(20,35,53,.08);--file-viewer-group-gap:2px;--file-viewer-group-padding:2px;--file-viewer-button-color:#40546a;--file-viewer-button-hover-bg:rgba(33,163,102,.1);--file-viewer-button-hover-color:#16774c;--file-viewer-button-disabled-color:#aab5c0;--file-viewer-button-radius:8px;--file-viewer-button-min-width:42px;--file-viewer-button-height:30px;--file-viewer-button-padding:0 10px;--file-viewer-icon-button-size:30px;--file-viewer-zoom-meter-min-width:48px;--file-viewer-zoom-meter-padding:0 8px;--file-viewer-floating-button-min-width:48px;--file-viewer-floating-button-height:32px;--file-viewer-floating-icon-button-size:32px;--file-viewer-floating-zoom-meter-min-width:54px;--file-viewer-search-input-height:30px;--file-viewer-search-input-padding:0 10px;--file-viewer-search-button-min-width:32px;--file-viewer-search-button-padding:0 8px;--file-viewer-search-count-min-width:42px;--file-viewer-floating-search-input-height:32px;--file-viewer-focus-ring:rgba(31,157,103,.22);--file-viewer-z-toolbar:20;--file-viewer-z-floating-toolbar:30}
@@ -307,6 +327,9 @@ const WEB_VIEWER_STYLE = `
307
327
  .file-viewer-web-shell{position:relative;width:100%;height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden;background:var(--file-viewer-render-surface-background,var(--file-viewer-bg));color:var(--file-viewer-text);font:var(--file-viewer-font);letter-spacing:0;box-sizing:border-box;contain:content}
308
328
  .file-viewer-web-shell[data-viewer-density="compact"]{--file-viewer-toolbar-gap:3px;--file-viewer-toolbar-min-height:34px;--file-viewer-toolbar-padding:3px 5px;--file-viewer-toolbar-floating-min-height:32px;--file-viewer-toolbar-floating-padding:3px;--file-viewer-toolbar-floating-offset:10px;--file-viewer-group-gap:2px;--file-viewer-group-padding:2px;--file-viewer-button-radius:6px;--file-viewer-button-min-width:34px;--file-viewer-button-height:26px;--file-viewer-button-padding:0 6px;--file-viewer-icon-button-size:26px;--file-viewer-zoom-meter-min-width:42px;--file-viewer-zoom-meter-padding:0 5px;--file-viewer-floating-button-min-width:38px;--file-viewer-floating-button-height:28px;--file-viewer-floating-icon-button-size:28px;--file-viewer-floating-zoom-meter-min-width:46px;--file-viewer-search-input-height:26px;--file-viewer-search-input-padding:0 8px;--file-viewer-search-button-min-width:28px;--file-viewer-search-button-padding:0 6px;--file-viewer-search-count-min-width:36px;--file-viewer-floating-search-input-height:28px}
309
329
  .file-viewer-web-content{position:relative;flex:1 1 auto;min-height:0;min-width:0;overflow:auto;overscroll-behavior:contain;background:var(--file-viewer-render-surface-background,var(--file-viewer-content-bg))}
330
+ .file-viewer-web-source-status{position:absolute;inset:0;z-index:10;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:24px;background:var(--file-viewer-toolbar-bg);color:var(--file-viewer-text);text-align:center;overflow:auto}
331
+ .file-viewer-web-source-status p{margin:0;max-width:560px;white-space:pre-wrap;overflow-wrap:anywhere}
332
+ .file-viewer-web-source-status strong{font-size:18px}
310
333
  .file-viewer-web-toolbar{flex:0 0 auto;min-height:var(--file-viewer-toolbar-min-height);display:inline-flex;align-items:center;justify-content:flex-end;gap:var(--file-viewer-toolbar-gap);padding:var(--file-viewer-toolbar-padding);border-bottom:1px solid var(--file-viewer-toolbar-border);background:var(--file-viewer-toolbar-bg);box-sizing:border-box;z-index:var(--file-viewer-z-toolbar)}
311
334
  .file-viewer-web-toolbar[hidden]{display:none!important}
312
335
  .file-viewer-web-toolbar[data-toolbar-position="top-center"]{justify-content:center}
@@ -399,14 +422,26 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
399
422
  }
400
423
  else if (shouldUseShadowRoot && canAttachShadowRoot) {
401
424
  try {
425
+ // The viewer owns this root even when its host already lives inside a
426
+ // customer's ShadowRoot. Reusing the ancestor would let that component's
427
+ // resets override the toolbar and would make our :host rules mutate the
428
+ // customer's outer custom element.
429
+ // Delegated focus makes Safari taps on renderer controls focus the first
430
+ // toolbar input instead, triggering keyboard zoom and viewport scrolling.
402
431
  renderRoot = container.attachShadow({ mode: 'open' });
403
432
  ownedViewerShadowRoots.set(container, renderRoot);
404
433
  }
405
434
  catch {
435
+ // Some HTMLElement subclasses cannot host Shadow DOM, and a closed root
436
+ // is intentionally invisible through `shadowRoot`. Keep the legacy
437
+ // scoped path usable instead of surfacing a raw NotSupportedError.
406
438
  renderRoot = container;
407
439
  }
408
440
  }
409
441
  else if (existingHostShadowRoot && !hasOwnedHostShadowRoot) {
442
+ // The application owns this root. Preserve every existing child and mount
443
+ // the viewer in a dedicated boundary. Shadow mode gets a nested root so
444
+ // customer resets in the outer component cannot reach the viewer.
410
445
  mountBoundary = documentRef.createElement('div');
411
446
  mountBoundary.dataset.fileViewerMountBoundary = 'true';
412
447
  existingHostShadowRoot.appendChild(mountBoundary);
@@ -423,6 +458,9 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
423
458
  }
424
459
  }
425
460
  else if (!shouldUseShadowRoot && hasOwnedHostShadowRoot && existingHostShadowRoot) {
461
+ // Light-DOM children of a shadow host are not painted unless the root has
462
+ // a slot. Keep the immutable owned root, but project the real light-DOM
463
+ // viewer so host selectors and legacy deep overrides work as documented.
426
464
  const slot = documentRef.createElement('slot');
427
465
  slot.dataset.fileViewerLightDomSlot = 'true';
428
466
  existingHostShadowRoot.replaceChildren(slot);
@@ -430,6 +468,9 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
430
468
  }
431
469
  renderRoot.replaceChildren();
432
470
  const styleHandle = appendFileViewerStyle(renderRoot, WEB_VIEWER_STYLE, {
471
+ // Adopt only into a root owned by the viewer. Adopting into an ancestor
472
+ // ShadowRoot would leak toolbar selectors and apply :host rules to the
473
+ // customer's custom element.
433
474
  adoptedStyleSheet: isFileViewerShadowRoot(renderRoot),
434
475
  });
435
476
  const shell = documentRef.createElement('div');
@@ -455,7 +496,10 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
455
496
  let currentSource = hasSource(currentOptions)
456
497
  ? toViewerSourceInput(currentOptions)
457
498
  : null;
458
- let abortController = null;
499
+ let sourceRequest = null;
500
+ let renderingRequest = null;
501
+ let lifecycleVersion = -1;
502
+ const updateSourceFeedback = createViewerSourceFeedback(shell, contentEl);
459
503
  const listeners = new Set();
460
504
  const state = {
461
505
  loading: false,
@@ -494,6 +538,7 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
494
538
  shell.dataset.viewerDensity = normalizeFileViewerUiDensity((_c = (_b = currentOptions.options) === null || _b === void 0 ? void 0 : _b.ui) === null || _c === void 0 ? void 0 : _c.density);
495
539
  syncFileViewerRenderSurfaceBackground(shell, currentOptions.options);
496
540
  };
541
+ const captureToolbarFocus = createToolbarFocusRestorer(toolbarEl, contentEl);
497
542
  let controller = null;
498
543
  let searchDraft = '';
499
544
  const isSearchToolbarVisible = (toolbar, options) => {
@@ -511,21 +556,30 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
511
556
  if (disposed) {
512
557
  return;
513
558
  }
559
+ const restoreToolbarFocus = captureToolbarFocus();
514
560
  syncShellTheme();
515
561
  const options = currentOptions.options || {};
516
562
  const toolbar = normalizeFileViewerToolbar(options);
517
563
  const availability = getToolbarAvailability();
518
564
  const showSearchToolbar = isSearchToolbarVisible(toolbar, options);
565
+ const zoomState = getToolbarZoomState();
566
+ const zoomButtons = resolveFileViewerToolbarZoomButtons({
567
+ toolbar,
568
+ zoomState,
569
+ renderedReady: state.ready,
570
+ hasError: !!state.error,
571
+ });
519
572
  const visibleToolbar = resolveVisibleFileViewerToolbar(toolbar, availability, showSearchToolbar);
573
+ visibleToolbar.zoom = zoomButtons.zoomIn || zoomButtons.zoomOut || zoomButtons.zoomReset;
520
574
  const showToolbar = hasVisibleFileViewerToolbarActions(visibleToolbar);
521
575
  const toolbarPosition = resolveFileViewerToolbarPosition(options, getCurrentExtension());
522
576
  const toolbarDisabled = state.loading || !!state.error;
523
- const zoomState = getToolbarZoomState();
524
577
  const t = createFileViewerTranslator(options);
525
578
  toolbarEl.hidden = !showToolbar;
526
579
  toolbarEl.dataset.toolbarPosition = toolbarPosition;
527
580
  toolbarEl.replaceChildren();
528
581
  if (!showToolbar) {
582
+ restoreToolbarFocus();
529
583
  return;
530
584
  }
531
585
  const appendSearchToolbar = () => {
@@ -543,6 +597,7 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
543
597
  group.setAttribute('aria-label', t('toolbar.search'));
544
598
  const input = documentRef.createElement('input');
545
599
  input.type = 'search';
600
+ input.dataset.viewerToolbarKey = 'search-input';
546
601
  addPart(input, 'input');
547
602
  input.value = searchDraft;
548
603
  input.placeholder = t('toolbar.searchPlaceholder');
@@ -554,16 +609,19 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
554
609
  });
555
610
  const searchButton = documentRef.createElement('button');
556
611
  searchButton.type = 'submit';
612
+ searchButton.dataset.viewerToolbarKey = 'search-submit';
557
613
  addPart(searchButton, 'button');
558
614
  searchButton.textContent = t('toolbar.search');
559
615
  searchButton.title = t('toolbar.search');
560
616
  searchButton.setAttribute('aria-label', t('toolbar.search'));
561
617
  searchButton.disabled = toolbarDisabled;
562
618
  const previousButton = createButton(documentRef, '<', 'file-viewer-web-icon-button', () => controller === null || controller === void 0 ? void 0 : controller.previousSearchResult());
619
+ previousButton.dataset.viewerToolbarKey = 'search-previous';
563
620
  previousButton.title = t('toolbar.searchPrevious');
564
621
  previousButton.setAttribute('aria-label', t('toolbar.searchPrevious'));
565
622
  previousButton.disabled = toolbarDisabled || searchTotal < 1;
566
623
  const nextButton = createButton(documentRef, '>', 'file-viewer-web-icon-button', () => controller === null || controller === void 0 ? void 0 : controller.nextSearchResult());
624
+ nextButton.dataset.viewerToolbarKey = 'search-next';
567
625
  nextButton.title = t('toolbar.searchNext');
568
626
  nextButton.setAttribute('aria-label', t('toolbar.searchNext'));
569
627
  nextButton.disabled = toolbarDisabled || searchTotal < 1;
@@ -571,6 +629,7 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
571
629
  searchDraft = '';
572
630
  await (controller === null || controller === void 0 ? void 0 : controller.clearDocumentSearch());
573
631
  });
632
+ clearButton.dataset.viewerToolbarKey = 'search-clear';
574
633
  clearButton.title = t('toolbar.searchClear');
575
634
  clearButton.setAttribute('aria-label', t('toolbar.searchClear'));
576
635
  clearButton.disabled = toolbarDisabled || (!searchDraft && !(searchState === null || searchState === void 0 ? void 0 : searchState.query));
@@ -596,9 +655,10 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
596
655
  group.className = 'file-viewer-web-toolbar-group';
597
656
  addPart(group, 'toolbar-group');
598
657
  group.setAttribute('aria-label', t('toolbar.zoomGroup'));
599
- if (availability.zoomOut) {
658
+ if (zoomButtons.zoomOut) {
600
659
  const button = createButton(documentRef, '-', 'file-viewer-web-icon-button', () => controller === null || controller === void 0 ? void 0 : controller.zoomOut());
601
660
  button.title = t('toolbar.zoomOut');
661
+ button.dataset.viewerToolbarKey = 'zoom-out';
602
662
  button.setAttribute('aria-label', t('toolbar.zoomOut'));
603
663
  button.disabled = isFileViewerZoomButtonDisabled({
604
664
  action: 'canZoomOut',
@@ -608,9 +668,10 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
608
668
  });
609
669
  group.appendChild(button);
610
670
  }
611
- if (availability.zoomReset) {
671
+ if (zoomButtons.zoomReset) {
612
672
  const meter = createButton(documentRef, zoomState.label, 'file-viewer-web-zoom-meter', () => controller === null || controller === void 0 ? void 0 : controller.resetZoom());
613
673
  meter.title = t('toolbar.zoomReset');
674
+ meter.dataset.viewerToolbarKey = 'zoom-meter';
614
675
  meter.disabled = isFileViewerZoomButtonDisabled({
615
676
  action: 'canReset',
616
677
  availability,
@@ -622,9 +683,16 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
622
683
  else {
623
684
  group.appendChild(createReadonlyMeter(documentRef, zoomState.label, 'file-viewer-web-zoom-meter'));
624
685
  }
625
- if (availability.zoomReset) {
686
+ if (zoomButtons.zoomReset) {
687
+ // Placed before the zoom-in button (not after) so that toggling this
688
+ // button's visibility never shifts the zoom-in button's position: the
689
+ // toolbar group is right-aligned, and both icon buttons share the same
690
+ // width, so inserting "1:1" after "+" would make it land exactly on
691
+ // top of the previous "+" position, turning a repeated zoom-in click
692
+ // into an accidental reset (see GitHub issue #88).
626
693
  const button = createButton(documentRef, '1:1', 'file-viewer-web-icon-button', () => controller === null || controller === void 0 ? void 0 : controller.resetZoom());
627
694
  button.title = t('toolbar.zoomReset');
695
+ button.dataset.viewerToolbarKey = 'zoom-reset';
628
696
  button.setAttribute('aria-label', t('toolbar.zoomReset'));
629
697
  button.disabled = isFileViewerZoomButtonDisabled({
630
698
  action: 'canReset',
@@ -634,9 +702,10 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
634
702
  });
635
703
  group.appendChild(button);
636
704
  }
637
- if (availability.zoomIn) {
705
+ if (zoomButtons.zoomIn) {
638
706
  const button = createButton(documentRef, '+', 'file-viewer-web-icon-button', () => controller === null || controller === void 0 ? void 0 : controller.zoomIn());
639
707
  button.title = t('toolbar.zoomIn');
708
+ button.dataset.viewerToolbarKey = 'zoom-in';
640
709
  button.setAttribute('aria-label', t('toolbar.zoomIn'));
641
710
  button.disabled = isFileViewerZoomButtonDisabled({
642
711
  action: 'canZoomIn',
@@ -650,11 +719,12 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
650
719
  toolbarEl.appendChild(group);
651
720
  }
652
721
  };
653
- const appendToolbarButton = (visible, label, title, onClick) => {
722
+ const appendToolbarButton = (visible, key, label, title, onClick) => {
654
723
  if (!visible) {
655
724
  return;
656
725
  }
657
726
  const button = createButton(documentRef, label, '', onClick);
727
+ button.dataset.viewerToolbarKey = key;
658
728
  button.title = title;
659
729
  button.disabled = toolbarDisabled;
660
730
  toolbarEl.appendChild(button);
@@ -691,6 +761,7 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
691
761
  }
692
762
  });
693
763
  addPart(button, 'theme-toggle');
764
+ button.dataset.viewerToolbarKey = 'theme';
694
765
  setThemeButtonIcon(button, currentTheme);
695
766
  button.title = title;
696
767
  button.setAttribute('aria-label', title);
@@ -706,7 +777,7 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
706
777
  appendZoomToolbar();
707
778
  }
708
779
  else if (item === 'download') {
709
- appendToolbarButton(visibleToolbar.download, t('toolbar.download'), t('toolbar.downloadTitle'), () => controller === null || controller === void 0 ? void 0 : controller.downloadOriginalFile());
780
+ appendToolbarButton(visibleToolbar.download, 'download', t('toolbar.download'), t('toolbar.downloadTitle'), () => controller === null || controller === void 0 ? void 0 : controller.downloadOriginalFile());
710
781
  }
711
782
  else if (item === 'print') {
712
783
  if (!visibleToolbar.print) {
@@ -719,6 +790,7 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
719
790
  menu.dataset.open = menu.dataset.open === 'true' ? 'false' : 'true';
720
791
  });
721
792
  trigger.title = t('toolbar.printTitle');
793
+ trigger.dataset.viewerToolbarKey = 'print';
722
794
  trigger.setAttribute('aria-label', t('toolbar.printTitle'));
723
795
  trigger.setAttribute('aria-haspopup', 'menu');
724
796
  trigger.disabled = toolbarDisabled;
@@ -733,6 +805,7 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
733
805
  await (controller === null || controller === void 0 ? void 0 : controller.printRenderedHtml());
734
806
  });
735
807
  directButton.title = t('toolbar.printTitle');
808
+ directButton.dataset.viewerToolbarKey = 'print-direct';
736
809
  directButton.setAttribute('role', 'menuitem');
737
810
  directButton.disabled = toolbarDisabled;
738
811
  const maskButton = createButton(documentRef, t('toolbar.printMask'), '', async () => {
@@ -740,6 +813,7 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
740
813
  await (controller === null || controller === void 0 ? void 0 : controller.printWithMask());
741
814
  });
742
815
  maskButton.title = t('toolbar.printMaskTitle');
816
+ maskButton.dataset.viewerToolbarKey = 'print-mask';
743
817
  maskButton.setAttribute('role', 'menuitem');
744
818
  maskButton.disabled = toolbarDisabled;
745
819
  panel.append(directButton, maskButton);
@@ -753,12 +827,13 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
753
827
  toolbarEl.appendChild(menu);
754
828
  }
755
829
  else if (item === 'exportHtml') {
756
- appendToolbarButton(visibleToolbar.exportHtml, t('toolbar.exportHtml'), t('toolbar.exportHtmlTitle'), () => controller === null || controller === void 0 ? void 0 : controller.exportRenderedHtml());
830
+ appendToolbarButton(visibleToolbar.exportHtml, 'export-html', t('toolbar.exportHtml'), t('toolbar.exportHtmlTitle'), () => controller === null || controller === void 0 ? void 0 : controller.exportRenderedHtml());
757
831
  }
758
832
  else if (item === 'theme') {
759
833
  appendThemeToolbar();
760
834
  }
761
835
  });
836
+ restoreToolbarFocus(state.loading || (!state.ready && !state.error));
762
837
  };
763
838
  let refreshSystemThemeDocument = null;
764
839
  const handleViewerColorSchemeChange = () => {
@@ -772,11 +847,20 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
772
847
  var _a;
773
848
  const snapshot = snapshotState();
774
849
  renderToolbar();
850
+ updateSourceFeedback((sourceRequest === null || sourceRequest === void 0 ? void 0 : sourceRequest.pending) ? sourceRequest.phase : null, state.error, currentSource ? currentSource.type || getExtension(resolveViewerSourceFilename(currentSource)) : '', currentOptions.options, Boolean((sourceRequest === null || sourceRequest === void 0 ? void 0 : sourceRequest.rendering) && contentEl.querySelector('.file-viewer-render-error')));
775
851
  (_a = currentOptions.onStateChange) === null || _a === void 0 ? void 0 : _a.call(currentOptions, snapshot, event);
776
852
  listeners.forEach(listener => listener(snapshot, event));
777
853
  };
778
854
  const applyViewerEvent = (event) => {
779
855
  var _a;
856
+ if (disposed || sourceRequest !== renderingRequest)
857
+ return;
858
+ if (event.type === 'load-start' || event.type === 'load-complete' ||
859
+ event.type === 'unload-start' || event.type === 'unload-complete') {
860
+ if (event.payload.version < lifecycleVersion)
861
+ return;
862
+ lifecycleVersion = event.payload.version;
863
+ }
780
864
  state.lastEvent = event;
781
865
  if (event.type === 'load-start') {
782
866
  state.loading = true;
@@ -787,6 +871,8 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
787
871
  searchDraft = '';
788
872
  }
789
873
  else if (event.type === 'load-complete') {
874
+ if (sourceRequest)
875
+ sourceRequest.pending = false;
790
876
  state.loading = false;
791
877
  state.ready = true;
792
878
  state.lifecycle = event.payload;
@@ -797,7 +883,7 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
797
883
  state.lifecycle = event.payload;
798
884
  }
799
885
  else if (event.type === 'unload-complete') {
800
- state.loading = false;
886
+ state.loading = Boolean(sourceRequest === null || sourceRequest === void 0 ? void 0 : sourceRequest.pending);
801
887
  state.ready = false;
802
888
  state.lifecycle = event.payload;
803
889
  }
@@ -828,42 +914,71 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
828
914
  (_e = viewerColorSchemeQuery === null || viewerColorSchemeQuery === void 0 ? void 0 : viewerColorSchemeQuery.addEventListener) === null || _e === void 0 ? void 0 : _e.call(viewerColorSchemeQuery, 'change', handleViewerColorSchemeChange);
829
915
  renderToolbar();
830
916
  const cancel = () => {
831
- abortController === null || abortController === void 0 ? void 0 : abortController.abort();
832
- abortController = null;
917
+ var _a;
918
+ const previous = sourceRequest;
919
+ sourceRequest = null;
920
+ (_a = previous === null || previous === void 0 ? void 0 : previous.abort) === null || _a === void 0 ? void 0 : _a.abort();
833
921
  };
834
922
  const loadSource = async (nextSource) => {
835
- var _a;
923
+ var _a, _b, _c, _d, _e, _f, _g, _h;
836
924
  cancel();
837
925
  currentSource = nextSource;
838
- abortController = typeof AbortController !== 'undefined' ? new AbortController() : null;
839
- const controller = abortController;
926
+ const request = {
927
+ abort: typeof AbortController !== 'undefined' ? new AbortController() : null,
928
+ phase: 'reading', pending: true, rendering: false,
929
+ };
930
+ sourceRequest = request;
931
+ const isCurrent = () => { var _a; return !disposed && sourceRequest === request && !((_a = request.abort) === null || _a === void 0 ? void 0 : _a.signal.aborted); };
840
932
  try {
841
933
  state.loading = true;
842
934
  state.error = null;
843
935
  notifyState();
936
+ await ((_a = instance.prepare) === null || _a === void 0 ? void 0 : _a.call(instance));
937
+ if (!isCurrent()) {
938
+ return null;
939
+ }
940
+ const extension = nextSource.type || getExtension(resolveViewerSourceFilename(nextSource));
941
+ const renderer = instance.getRenderer(extension);
844
942
  const resolvedSource = await resolveViewerLoadSource(nextSource, {
943
+ acceptsPdfUrl: (renderer === null || renderer === void 0 ? void 0 : renderer.sourceAccess) === 'stream-preferred' && Boolean(renderer.load),
845
944
  fetchFile: coreOptions.fetchFile,
846
- signal: controller === null || controller === void 0 ? void 0 : controller.signal,
945
+ signal: (_b = request.abort) === null || _b === void 0 ? void 0 : _b.signal,
946
+ streaming: (_d = (_c = currentOptions.options) === null || _c === void 0 ? void 0 : _c.pdf) === null || _d === void 0 ? void 0 : _d.streaming,
947
+ pageHref: (_f = (_e = documentRef.defaultView) === null || _e === void 0 ? void 0 : _e.location) === null || _f === void 0 ? void 0 : _f.href,
948
+ onPhase(phase) {
949
+ if (!isCurrent())
950
+ return;
951
+ request.phase = phase;
952
+ updateSourceFeedback(phase, null, extension, currentOptions.options);
953
+ },
847
954
  });
848
- if (disposed || (controller === null || controller === void 0 ? void 0 : controller.signal.aborted) || abortController !== controller) {
955
+ if (!isCurrent()) {
849
956
  return null;
850
957
  }
851
- return await instance.load(resolvedSource);
958
+ if (resolvedSource.buffer || resolvedSource.file)
959
+ request.phase = 'reading';
960
+ request.rendering = true;
961
+ renderingRequest = request;
962
+ notifyState();
963
+ return await instance.load(resolvedSource, { signal: (_g = request.abort) === null || _g === void 0 ? void 0 : _g.signal });
852
964
  }
853
965
  catch (error) {
854
- if (isAbortError(error) && (controller === null || controller === void 0 ? void 0 : controller.signal.aborted)) {
966
+ if (!isCurrent()) {
855
967
  return null;
856
968
  }
857
969
  state.loading = false;
858
970
  state.ready = false;
859
- state.error = error;
971
+ state.error = error !== null && error !== void 0 ? error : new Error(createFileViewerTranslator(currentOptions.options)('error.unknown'));
972
+ request.pending = false;
860
973
  notifyState();
861
- (_a = coreOptions.onError) === null || _a === void 0 ? void 0 : _a.call(coreOptions, error, nextSource);
974
+ (_h = coreOptions.onError) === null || _h === void 0 ? void 0 : _h.call(coreOptions, error, nextSource);
862
975
  throw error;
863
976
  }
864
977
  finally {
865
- if (abortController === controller) {
866
- abortController = null;
978
+ if (isCurrent() && request.pending) {
979
+ request.pending = false;
980
+ state.loading = false;
981
+ notifyState();
867
982
  }
868
983
  }
869
984
  };
@@ -881,6 +996,9 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
881
996
  }
882
997
  };
883
998
  if (currentSource) {
999
+ // The initial declarative source has no caller awaiting it. State changes
1000
+ // and the visible error surface remain authoritative without a global
1001
+ // unhandled-rejection event for an expected rendering failure.
884
1002
  void loadSource(currentSource).catch(() => undefined);
885
1003
  }
886
1004
  controller = {
@@ -928,7 +1046,13 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
928
1046
  }
929
1047
  }
930
1048
  else {
1049
+ cancel();
1050
+ renderingRequest = null;
931
1051
  currentSource = null;
1052
+ state.loading = false;
1053
+ state.ready = false;
1054
+ state.error = null;
1055
+ notifyState();
932
1056
  await instance.load({ filename: DEFAULT_FILE_VIEWER_SOURCE_FILENAME });
933
1057
  }
934
1058
  },
@@ -0,0 +1,4 @@
1
+ import { type FileViewerI18nInput } from '../i18n/messages.js';
2
+ export type ViewerSourcePhase = 'downloading' | 'reading' | 'streamingPdf';
3
+ /** The shell owns acquisition feedback; renderers retain their own error surface. */
4
+ export declare const createViewerSourceFeedback: (shell: HTMLElement, content: HTMLElement) => (phase: ViewerSourcePhase | null, error: unknown, extension: string, options?: FileViewerI18nInput, rendererHasError?: boolean) => void;
@@ -0,0 +1,40 @@
1
+ import { createFileViewerTranslator } from '../i18n/messages.js';
2
+ import { createFileViewerErrorState } from '../viewer/state.js';
3
+ /** The shell owns acquisition feedback; renderers retain their own error surface. */
4
+ export const createViewerSourceFeedback = (shell, content) => {
5
+ let surface = null;
6
+ let title = null;
7
+ let message = null;
8
+ return (phase, error, extension, options, rendererHasError = false) => {
9
+ content.setAttribute('aria-busy', String(Boolean(phase)));
10
+ content.toggleAttribute('inert', Boolean(phase));
11
+ if (!phase && (error == null || rendererHasError)) {
12
+ surface === null || surface === void 0 ? void 0 : surface.remove();
13
+ surface = title = message = null;
14
+ return;
15
+ }
16
+ if (!surface) {
17
+ surface = shell.ownerDocument.createElement('div');
18
+ surface.className = 'file-viewer-web-source-status';
19
+ surface.setAttribute('part', 'source-status');
20
+ title = shell.ownerDocument.createElement('strong');
21
+ message = shell.ownerDocument.createElement('p');
22
+ surface.append(title, message);
23
+ shell.append(surface);
24
+ }
25
+ surface.dataset.fileViewerSourceState = phase ? 'loading' : 'error';
26
+ surface.setAttribute('role', phase ? 'status' : 'alert');
27
+ surface.setAttribute('aria-live', phase ? 'polite' : 'assertive');
28
+ surface.setAttribute('aria-atomic', 'true');
29
+ const failure = phase ? null : createFileViewerErrorState(extension, error, undefined, options);
30
+ const nextTitle = (failure === null || failure === void 0 ? void 0 : failure.title) || '';
31
+ const nextMessage = phase
32
+ ? createFileViewerTranslator(options)(`preview.${phase}`)
33
+ : failure.message;
34
+ title.hidden = Boolean(phase);
35
+ if (title.textContent !== nextTitle)
36
+ title.textContent = nextTitle;
37
+ if (message.textContent !== nextMessage)
38
+ message.textContent = nextMessage;
39
+ };
40
+ };
@@ -0,0 +1,2 @@
1
+ /** Preserve the active logical action across state-driven toolbar rebuilds. */
2
+ export declare function createToolbarFocusRestorer(toolbar: HTMLElement, content: HTMLElement): () => (temporarilyUnavailable?: boolean) => void;
@@ -0,0 +1,46 @@
1
+ /** Preserve the active logical action across state-driven toolbar rebuilds. */
2
+ export function createToolbarFocusRestorer(toolbar, content) {
3
+ let pendingKey = null;
4
+ const controls = () => Array.from(toolbar.querySelectorAll('[data-viewer-toolbar-key]'));
5
+ return () => {
6
+ const root = toolbar.getRootNode();
7
+ const active = root.activeElement;
8
+ const key = active === toolbar ? pendingKey : active && toolbar.contains(active)
9
+ ? active.dataset.viewerToolbarKey : null;
10
+ pendingKey = null;
11
+ if (!key)
12
+ return (_temporarilyUnavailable = false) => undefined;
13
+ const oldIndex = controls().indexOf(active);
14
+ const input = (active === null || active === void 0 ? void 0 : active.tagName) === 'INPUT' ? active : null;
15
+ const selection = input && input.selectionStart !== null
16
+ ? [input.selectionStart, input.selectionEnd, input.selectionDirection] : null;
17
+ return (temporarilyUnavailable = false) => {
18
+ if (!toolbar.isConnected)
19
+ return;
20
+ if (toolbar.hidden) {
21
+ content.tabIndex = -1;
22
+ content.focus({ preventScroll: true });
23
+ return;
24
+ }
25
+ const all = controls();
26
+ const original = all.find(element => element.dataset.viewerToolbarKey === key);
27
+ const available = (element) => !element.disabled && element.getClientRects().length > 0;
28
+ const index = original ? all.indexOf(original) : Math.max(0, oldIndex);
29
+ const target = original && available(original) ? original
30
+ : temporarilyUnavailable ? undefined
31
+ : all.slice(index).find(available) || all.slice(0, index).reverse().find(available);
32
+ if (target) {
33
+ target.focus({ preventScroll: true });
34
+ if (target === original && target.tagName === 'INPUT' && selection)
35
+ target.setSelectionRange(selection[0], selection[1], selection[2] || undefined);
36
+ }
37
+ else {
38
+ // All actions may be temporarily disabled during a load. Restore only
39
+ // while the user remains here; moving elsewhere clears this pending key.
40
+ pendingKey = key;
41
+ toolbar.tabIndex = -1;
42
+ toolbar.focus({ preventScroll: true });
43
+ }
44
+ };
45
+ };
46
+ }
package/dist/browser.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from "./index.js";
2
- export * from "./browser/controller.js";
1
+ export * from './index.js';
2
+ export * from './browser/controller.js';
package/dist/browser.js CHANGED
@@ -1,2 +1,2 @@
1
- export * from "./index.js";
2
- export * from "./browser/controller.js";
1
+ export * from './index.js';
2
+ export * from './browser/controller.js';
@@ -1,4 +1,4 @@
1
- import type { FileViewerArchiveOptions, FileViewerCadOptions, FileViewerOptions, FileViewerResolvedThemeMode, FileViewerThemeMode, FileViewerToolbarOptions, FileViewerUiDensity } from "../contracts/types.js";
1
+ import type { FileViewerArchiveOptions, FileViewerCadOptions, FileViewerOptions, FileViewerResolvedThemeMode, FileViewerThemeMode, FileViewerToolbarOptions, FileViewerUiDensity } from '../contracts/types.js';
2
2
  export type FileViewerSerializableToolbarOptions = Omit<FileViewerToolbarOptions, 'beforeOperation' | 'beforeDownload' | 'beforePrint' | 'beforeExportHtml'>;
3
3
  export type FileViewerSerializableCadOptions = Omit<FileViewerCadOptions, 'workerUrl'> & {
4
4
  workerUrl?: string;