@file-viewer/core 3.3.0 → 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 +14 -14
  2. package/README.md +14 -14
  3. package/README.zh-CN.md +4 -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 +109 -26
  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 +1 -0
  11. package/dist/browser/toolbar-focus.js +3 -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 +470 -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 +2 -0
  30. package/dist/features/document/imageHandTool.js +5 -1
  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 -77
  55. package/dist/index.js +39 -39
  56. package/dist/lifecycle/facade.d.ts +4 -4
  57. package/dist/lifecycle/facade.js +2 -2
  58. package/dist/lifecycle/operations.d.ts +5 -4
  59. package/dist/lifecycle/operations.js +10 -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 +8 -1
  69. package/dist/platform/assets.js +14 -0
  70. package/dist/platform/copiedAssets.d.ts +1 -0
  71. package/dist/platform/copiedAssets.js +2 -1
  72. package/dist/presentation/colorScheme.d.ts +2 -1
  73. package/dist/presentation/colorScheme.js +2 -1
  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 +7 -5
  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 +4 -4
  99. package/dist/source/loading.js +7 -4
  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 +28 -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 +5 -3
package/README.en.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  [English](https://github.com/flyfish-dev/file-viewer/blob/main/packages/core/README.md) · [简体中文](https://github.com/flyfish-dev/file-viewer/blob/main/packages/core/README.zh-CN.md)
4
4
 
5
+ Framework-neutral TypeScript APIs for browser-side file preview. Build a viewer in Vanilla JavaScript, or use the Vue, React, Svelte and jQuery components with the same loading, search, zoom, page navigation, print and export controls. Files are processed locally, without server-side conversion.
6
+
5
7
  <!-- FILE_VIEWER_PACKAGE_HEADER -->
6
8
  <p>
7
9
  <a href="https://www.npmjs.com/package/@file-viewer/core"><img src="https://img.shields.io/npm/v/@file-viewer%2Fcore?logo=npm" alt="npm version"></a>
@@ -20,20 +22,22 @@
20
22
 
21
23
  > ⭐ If File Viewer helps you, please [star the repo on GitHub](https://github.com/flyfish-dev/file-viewer).
22
24
 
23
- Framework-neutral TypeScript foundation for Flyfish File Viewer.
24
-
25
- This package is the migration base for the next architecture: one pure TypeScript core package with internal `headless`, `browser`, and `renderers` layers, plus multiple native standard component packages for Vue, React, pure JavaScript, jQuery, and Svelte. Core owns the shared format matrix, source detection, renderer protocol, capability calculation, lifecycle context, operation guards, operation/event/search/location/zoom/print/export contracts, heavy renderer asset manifests, option normalization, pure state models, and framework-neutral browser rendering engine.
25
+ File Viewer has a catalog of 275 file extensions through optional renderers. **This package is the lightweight foundation, not an all-format viewer.** Add only the engines your application needs; stable and experimental support are listed separately in the [format matrix](https://doc.file-viewer.app/guide/formats).
26
26
 
27
- Browser execution belongs inside core only when it remains framework-neutral TypeScript. DOM mounting, the standard browser shell and toolbar, `HTMLElement` traversal, search highlights, zoom provider discovery, print windows, download triggers, Canvas, Worker, and WASM renderers must not depend on Vue, React, Svelte, or any component package. Framework component packages keep their own component lifecycle, public type surface, compatibility defaults, and ecosystem interaction layer while depending on `@file-viewer/core` as the only shared base.
27
+ ## Start here
28
28
 
29
- `@file-viewer/core` is the only shared foundation package. It owns the headless contracts plus browser/renderers layers for the direct `createViewer(container, options)` engine, renderer registry, DOM provider registry, print layout helpers, browser asset resolution, and framework-neutral DOM/Canvas/Worker/WASM rendering contracts. Core source is public; the private Gitea aggregate remains available for the complete workspace, unified release automation, sponsorship, and priority support.
30
-
31
- Current framework-neutral browser renderers still inside core are limited to the low-cost image path. Spreadsheet rendering has moved to `@file-viewer/renderer-spreadsheet`; CAD rendering has moved to `@file-viewer/renderer-cad`; Typst rendering has moved to `@file-viewer/renderer-typst`; OFD rendering has moved to `@file-viewer/renderer-ofd`; PDF rendering has moved to `@file-viewer/renderer-pdf`; archive rendering has moved to `@file-viewer/renderer-archive`; email rendering has moved to `@file-viewer/renderer-email`; EPUB and UMD ebook rendering has moved to `@file-viewer/renderer-epub`; 3D model rendering has moved to `@file-viewer/renderer-3d`; Draw.io, Excalidraw, Mermaid, and PlantUML rendering has moved to `@file-viewer/renderer-drawing`; audio, video, HLS, and MIDI rendering has moved to `@file-viewer/renderer-media`; code, text, Markdown, patch, and git bundle rendering has moved to `@file-viewer/renderer-text`; Word DOCX/DOC/RTF/ODT rendering has moved to `@file-viewer/renderer-word`; PowerPoint rendering has moved to `@file-viewer/renderer-presentation`; XMind rendering has moved to `@file-viewer/renderer-mindmap`; geospatial rendering has moved to `@file-viewer/renderer-geo`; data asset rendering has moved to `@file-viewer/renderer-data`; EDA rendering and parsing has moved to `@file-viewer/renderer-eda`. Core-only installs now avoid direct OFD, PDF, Word, Spreadsheet, PPTX, CAD, Typst, XMind, Geo, media, 3D, drawing, archive, email, EPUB, UMD, data asset, EDA, `ofd-xml-parser`, `jszip`, `pdfjs-dist`, `styled-exceljs`, `e-virt-table`, `tinycolor2`, `@flyfish-dev/cad-viewer`, `@myriaddreamin/*`, `highlight.js`, `marked`, `diff2html`, `pako`, `mermaid`, `plantuml-encoder`, `@panzoom/panzoom`, `hls.js`, `@tonejs/midi`, `three`, `@excalidraw/excalidraw`, `roughjs`, `libarchive.js`, `postal-mime`, `@kenjiuno/msgreader`, `epubjs`, `ag-psd`, `sql.js`, `hyparquet`, `avsc`, and `cfb` engine dependencies.
29
+ - [Create a configured application](https://doc.file-viewer.app/guide/cli): `npm create file-viewer@latest my-viewer`.
30
+ - [Use a Web Component](https://doc.file-viewer.app/guide/quickstart-web), or choose a [framework integration](https://github.com/flyfish-dev/file-viewer#readme).
31
+ - For a custom integration, use `core/headless` for metadata and contracts, or `core/browser` for DOM rendering. PDF and Office require an explicit renderer or preset and local runtime assets.
32
32
 
33
33
  ```bash
34
34
  npm install @file-viewer/core
35
35
  ```
36
36
 
37
+ ## Browser source feedback (unreleased)
38
+
39
+ The browser `mountViewer` shell shows localized download feedback while buffering a URL, then reading feedback until the renderer takes over. File and buffer inputs use reading feedback. The status follows the configured locale/theme, reports acquisition failures, and is cleared when a source is replaced or the viewer is destroyed.
40
+
37
41
  ## Preflight Before Rendering
38
42
 
39
43
  `precheckFileViewerSource()` does not create a viewer or load a heavy renderer. It checks whether the extension is available and validates known PDF, OpenXML / OpenDocument, legacy OLE Office, and RTF signatures or package parts. `valid: null` explicitly means capability-only detection, not a full parse; the real renderer remains the final authority for malformed content.
@@ -71,10 +75,7 @@ const pdfAssets = resolveFileViewerRendererAssets('pdf', {
71
75
  Use `@file-viewer/core/browser` when a native component package or pure browser integration needs the shared browser shell and toolbar, direct DOM engine, renderer registry, search/zoom providers, print/export adapters, and framework-neutral Canvas/Worker/WASM renderers.
72
76
 
73
77
  ```ts
74
- import {
75
- createViewer,
76
- coreBrowserRendererHandlers
77
- } from '@file-viewer/core/browser'
78
+ import { createViewer } from '@file-viewer/core/browser'
78
79
 
79
80
  const viewer = createViewer(document.querySelector('#viewer')!, {
80
81
  options: {
@@ -83,7 +84,8 @@ const viewer = createViewer(document.querySelector('#viewer')!, {
83
84
  }
84
85
  })
85
86
 
86
- await viewer.load({ url: '/files/report.pdf' })
87
+ // Images use the built-in renderer; document formats need an optional preset.
88
+ await viewer.load({ url: '/files/photo.png' })
87
89
  ```
88
90
 
89
91
  Zoom buttons keep their positions and become disabled at their limits. Configure the existing `toolbar.items` map to hide reset controls while retaining the `resetZoom()` API. Set `pdf.zoomToolbar` to `false` to hide the PDF renderer's zoom group while keeping its page navigation and rotation controls:
@@ -149,8 +151,6 @@ Public source repositories:
149
151
  - GitHub: https://github.com/flyfish-dev/file-viewer-core
150
152
  - Gitee: https://gitee.com/flyfish-dev/file-viewer-core
151
153
 
152
- The GitHub/Gitee repositories are distributed public source packages. The private Gitea aggregate remains useful for the complete workspace, release automation, integration history, sponsorship, and priority support.
153
-
154
154
  License: Apache-2.0. For second development or commercial use, keep clear Flyfish Viewer attribution and contribute shared compatibility improvements where possible.
155
155
 
156
156
  ## Formats and related packages
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  [English](https://github.com/flyfish-dev/file-viewer/blob/main/packages/core/README.md) · [简体中文](https://github.com/flyfish-dev/file-viewer/blob/main/packages/core/README.zh-CN.md)
4
4
 
5
+ Framework-neutral TypeScript APIs for browser-side file preview. Build a viewer in Vanilla JavaScript, or use the Vue, React, Svelte and jQuery components with the same loading, search, zoom, page navigation, print and export controls. Files are processed locally, without server-side conversion.
6
+
5
7
  <!-- FILE_VIEWER_PACKAGE_HEADER -->
6
8
  <p>
7
9
  <a href="https://www.npmjs.com/package/@file-viewer/core"><img src="https://img.shields.io/npm/v/@file-viewer%2Fcore?logo=npm" alt="npm version"></a>
@@ -20,20 +22,22 @@
20
22
 
21
23
  > ⭐ If File Viewer helps you, please [star the repo on GitHub](https://github.com/flyfish-dev/file-viewer).
22
24
 
23
- Framework-neutral TypeScript foundation for Flyfish File Viewer.
24
-
25
- This package is the migration base for the next architecture: one pure TypeScript core package with internal `headless`, `browser`, and `renderers` layers, plus multiple native standard component packages for Vue, React, pure JavaScript, jQuery, and Svelte. Core owns the shared format matrix, source detection, renderer protocol, capability calculation, lifecycle context, operation guards, operation/event/search/location/zoom/print/export contracts, heavy renderer asset manifests, option normalization, pure state models, and framework-neutral browser rendering engine.
25
+ File Viewer has a catalog of 275 file extensions through optional renderers. **This package is the lightweight foundation, not an all-format viewer.** Add only the engines your application needs; stable and experimental support are listed separately in the [format matrix](https://doc.file-viewer.app/guide/formats).
26
26
 
27
- Browser execution belongs inside core only when it remains framework-neutral TypeScript. DOM mounting, the standard browser shell and toolbar, `HTMLElement` traversal, search highlights, zoom provider discovery, print windows, download triggers, Canvas, Worker, and WASM renderers must not depend on Vue, React, Svelte, or any component package. Framework component packages keep their own component lifecycle, public type surface, compatibility defaults, and ecosystem interaction layer while depending on `@file-viewer/core` as the only shared base.
27
+ ## Start here
28
28
 
29
- `@file-viewer/core` is the only shared foundation package. It owns the headless contracts plus browser/renderers layers for the direct `createViewer(container, options)` engine, renderer registry, DOM provider registry, print layout helpers, browser asset resolution, and framework-neutral DOM/Canvas/Worker/WASM rendering contracts. Core source is public; the private Gitea aggregate remains available for the complete workspace, unified release automation, sponsorship, and priority support.
30
-
31
- Current framework-neutral browser renderers still inside core are limited to the low-cost image path. Spreadsheet rendering has moved to `@file-viewer/renderer-spreadsheet`; CAD rendering has moved to `@file-viewer/renderer-cad`; Typst rendering has moved to `@file-viewer/renderer-typst`; OFD rendering has moved to `@file-viewer/renderer-ofd`; PDF rendering has moved to `@file-viewer/renderer-pdf`; archive rendering has moved to `@file-viewer/renderer-archive`; email rendering has moved to `@file-viewer/renderer-email`; EPUB and UMD ebook rendering has moved to `@file-viewer/renderer-epub`; 3D model rendering has moved to `@file-viewer/renderer-3d`; Draw.io, Excalidraw, Mermaid, and PlantUML rendering has moved to `@file-viewer/renderer-drawing`; audio, video, HLS, and MIDI rendering has moved to `@file-viewer/renderer-media`; code, text, Markdown, patch, and git bundle rendering has moved to `@file-viewer/renderer-text`; Word DOCX/DOC/RTF/ODT rendering has moved to `@file-viewer/renderer-word`; PowerPoint rendering has moved to `@file-viewer/renderer-presentation`; XMind rendering has moved to `@file-viewer/renderer-mindmap`; geospatial rendering has moved to `@file-viewer/renderer-geo`; data asset rendering has moved to `@file-viewer/renderer-data`; EDA rendering and parsing has moved to `@file-viewer/renderer-eda`. Core-only installs now avoid direct OFD, PDF, Word, Spreadsheet, PPTX, CAD, Typst, XMind, Geo, media, 3D, drawing, archive, email, EPUB, UMD, data asset, EDA, `ofd-xml-parser`, `jszip`, `pdfjs-dist`, `styled-exceljs`, `e-virt-table`, `tinycolor2`, `@flyfish-dev/cad-viewer`, `@myriaddreamin/*`, `highlight.js`, `marked`, `diff2html`, `pako`, `mermaid`, `plantuml-encoder`, `@panzoom/panzoom`, `hls.js`, `@tonejs/midi`, `three`, `@excalidraw/excalidraw`, `roughjs`, `libarchive.js`, `postal-mime`, `@kenjiuno/msgreader`, `epubjs`, `ag-psd`, `sql.js`, `hyparquet`, `avsc`, and `cfb` engine dependencies.
29
+ - [Create a configured application](https://doc.file-viewer.app/guide/cli): `npm create file-viewer@latest my-viewer`.
30
+ - [Use a Web Component](https://doc.file-viewer.app/guide/quickstart-web), or choose a [framework integration](https://github.com/flyfish-dev/file-viewer#readme).
31
+ - For a custom integration, use `core/headless` for metadata and contracts, or `core/browser` for DOM rendering. PDF and Office require an explicit renderer or preset and local runtime assets.
32
32
 
33
33
  ```bash
34
34
  npm install @file-viewer/core
35
35
  ```
36
36
 
37
+ ## Browser source feedback (unreleased)
38
+
39
+ The browser `mountViewer` shell shows localized download feedback while buffering a URL, then reading feedback until the renderer takes over. File and buffer inputs use reading feedback. The status follows the configured locale/theme, reports acquisition failures, and is cleared when a source is replaced or the viewer is destroyed.
40
+
37
41
  ## Preflight Before Rendering
38
42
 
39
43
  `precheckFileViewerSource()` does not create a viewer or load a heavy renderer. It checks whether the extension is available and validates known PDF, OpenXML / OpenDocument, legacy OLE Office, and RTF signatures or package parts. `valid: null` explicitly means capability-only detection, not a full parse; the real renderer remains the final authority for malformed content.
@@ -71,10 +75,7 @@ const pdfAssets = resolveFileViewerRendererAssets('pdf', {
71
75
  Use `@file-viewer/core/browser` when a native component package or pure browser integration needs the shared browser shell and toolbar, direct DOM engine, renderer registry, search/zoom providers, print/export adapters, and framework-neutral Canvas/Worker/WASM renderers.
72
76
 
73
77
  ```ts
74
- import {
75
- createViewer,
76
- coreBrowserRendererHandlers
77
- } from '@file-viewer/core/browser'
78
+ import { createViewer } from '@file-viewer/core/browser'
78
79
 
79
80
  const viewer = createViewer(document.querySelector('#viewer')!, {
80
81
  options: {
@@ -83,7 +84,8 @@ const viewer = createViewer(document.querySelector('#viewer')!, {
83
84
  }
84
85
  })
85
86
 
86
- await viewer.load({ url: '/files/report.pdf' })
87
+ // Images use the built-in renderer; document formats need an optional preset.
88
+ await viewer.load({ url: '/files/photo.png' })
87
89
  ```
88
90
 
89
91
  Zoom buttons keep their positions and become disabled at their limits. Configure the existing `toolbar.items` map to hide reset controls while retaining the `resetZoom()` API. Set `pdf.zoomToolbar` to `false` to hide the PDF renderer's zoom group while keeping its page navigation and rotation controls:
@@ -149,8 +151,6 @@ Public source repositories:
149
151
  - GitHub: https://github.com/flyfish-dev/file-viewer-core
150
152
  - Gitee: https://gitee.com/flyfish-dev/file-viewer-core
151
153
 
152
- The GitHub/Gitee repositories are distributed public source packages. The private Gitea aggregate remains useful for the complete workspace, release automation, integration history, sponsorship, and priority support.
153
-
154
154
  License: Apache-2.0. For second development or commercial use, keep clear Flyfish Viewer attribution and contribute shared compatibility improvements where possible.
155
155
 
156
156
  ## Formats and related packages
package/README.zh-CN.md CHANGED
@@ -34,6 +34,10 @@ Current framework-neutral browser renderers still inside core are limited to the
34
34
  npm install @file-viewer/core
35
35
  ```
36
36
 
37
+ ## 浏览器源加载反馈(未发布)
38
+
39
+ 浏览器 `mountViewer` 在缓冲 URL 时显示本地化的下载提示,随后显示读取提示,直至渲染器接管。File 和 buffer 输入显示读取提示。状态遵循配置的语言和主题,展示获取失败,并在切源或销毁时清理。
40
+
37
41
  ## 渲染前预检
38
42
 
39
43
  `precheckFileViewerSource()` 不会创建 viewer,也不会加载重型 renderer。它先检查当前扩展名是否在能力范围内,并对 PDF、OpenXML / OpenDocument、旧版 OLE Office、RTF 等格式执行签名或包结构检查。`valid: null` 明确表示该格式只有能力检查、没有做完整解析;真实 renderer 仍是畸形文件的最终判断者。
package/dist/assets.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from "./platform/assets.js";
2
- export type * from "./platform/assets.js";
1
+ export * from './platform/assets.js';
2
+ export type * from './platform/assets.js';
package/dist/assets.js CHANGED
@@ -1 +1,5 @@
1
- export * from "./platform/assets.js";
1
+ // Public subpath facade for `@file-viewer/core/assets`.
2
+ //
3
+ // Asset resolution belongs to the platform layer, but this root-level file is
4
+ // intentionally kept as the stable package export declared in package.json.
5
+ export * from './platform/assets.js';
@@ -1,4 +1,4 @@
1
- import { type FileViewerAiOptions, type FileViewerApplyViewStateOptions, type FileViewerArchiveOptions, type FileViewerCadOptions, type FileViewerDocxOptions, type FileViewerDocumentAnchor, type FileViewerDocumentChunk, type FileViewerEvent, type FileViewerEventHandler, type FileViewerEventType, type FileViewerFileRef, type FileViewerFitMode, type FileViewerFitOptions, type FileViewerFitResult, type FileViewerInstance, type FileViewerLifecycleContext, type FileViewerOperationAvailability, type FileViewerOptions, type FileViewerPdfOptions, type FileViewerPresentationOptions, type FileViewerSpreadsheetOptions, type FileViewerPublicApi, type FileViewerSearchOptions, type FileViewerSearchState, type FileViewerThemeMode, type FileViewerToolbarOptions, type FileViewerToolbarPosition, type FileViewerTypstOptions, type FileViewerUiDensity, type FileViewerUiOptions, type FileViewerViewState, type FileViewerWatermarkOptions, type FileViewerPrintOptions, type FileViewerZoomState, type RendererRegistry } from "../index.js";
1
+ import { type FileViewerAiOptions, type FileViewerApplyViewStateOptions, type FileViewerArchiveOptions, type FileViewerCadOptions, type FileViewerDocxOptions, type FileViewerDocumentAnchor, type FileViewerDocumentChunk, type FileViewerEvent, type FileViewerEventHandler, type FileViewerEventType, type FileViewerFileRef, type FileViewerFitMode, type FileViewerFitOptions, type FileViewerFitResult, type FileViewerInstance, type FileViewerLifecycleContext, type FileViewerOperationAvailability, type FileViewerOptions, type FileViewerPdfOptions, type FileViewerPresentationOptions, type FileViewerSpreadsheetOptions, type FileViewerPublicApi, type FileViewerSearchOptions, type FileViewerSearchState, type FileViewerThemeMode, type FileViewerToolbarOptions, type FileViewerToolbarPosition, type FileViewerTypstOptions, type FileViewerUiDensity, type FileViewerUiOptions, type FileViewerViewState, type FileViewerWatermarkOptions, type FileViewerPrintOptions, type FileViewerZoomState, type RendererRegistry } from '../index.js';
2
2
  export type FileRef = FileViewerFileRef;
3
3
  export type ViewerWatermarkOptions = FileViewerWatermarkOptions;
4
4
  export type ViewerToolbarPosition = FileViewerToolbarPosition;
@@ -34,6 +34,7 @@ export interface ViewerState {
34
34
  search: FileViewerSearchState | null;
35
35
  zoom: FileViewerZoomState | null;
36
36
  location: FileViewerDocumentAnchor | null;
37
+ /** Added in 2.2; optional so existing state fixtures remain source-compatible. */
37
38
  viewState?: FileViewerViewState | null;
38
39
  }
39
40
  export type ViewerStateListener = (state: ViewerState, event?: ViewerEvent) => void;
@@ -1,7 +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, resolveFileViewerRemoteSourcePlan, resolveFileViewerColorScheme, resolveFileViewerToolbarOrder, resolveFileViewerToolbarPosition, resolveVisibleFileViewerToolbar, toggleFileViewerColorScheme, wrapFileViewerFileRef, } from "../index.js";
3
- import { resolveFileViewerToolbarZoomButtons } from "../lifecycle/operations.js";
4
- import { createToolbarFocusRestorer } from "./toolbar-focus.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';
5
6
  const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';
6
7
  const hasSource = (options = {}) => {
7
8
  return !!(options.url || options.file || options.buffer);
@@ -15,6 +16,8 @@ const toViewerSourceInput = (options = {}) => ({
15
16
  type: options.type,
16
17
  size: options.size,
17
18
  });
19
+ // These options feed renderer selection or rendering itself. Viewer chrome,
20
+ // fitting, watermark, and operation callbacks update in place instead.
18
21
  const RENDER_AFFECTING_VIEWER_OPTION_KEYS = [
19
22
  'theme',
20
23
  'rendererMode',
@@ -116,26 +119,28 @@ const resolveViewerSourceFilename = (source) => {
116
119
  }), source.type ? `preview.${source.type}` : DEFAULT_FILE_VIEWER_SOURCE_FILENAME);
117
120
  };
118
121
  const resolveViewerLoadSource = async (source, options = {}) => {
119
- var _a, _b, _c;
122
+ var _a, _b, _c, _d, _e, _f, _g, _h;
120
123
  const filename = resolveViewerSourceFilename(source);
121
124
  const type = source.type || getExtension(filename);
122
125
  if (source.buffer) {
126
+ (_a = options.onPhase) === null || _a === void 0 ? void 0 : _a.call(options, 'reading');
123
127
  return {
124
128
  buffer: source.buffer,
125
129
  filename,
126
130
  type,
127
- 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,
128
132
  url: source.url,
129
133
  };
130
134
  }
131
135
  if (source.file) {
136
+ (_c = options.onPhase) === null || _c === void 0 ? void 0 : _c.call(options, 'reading');
132
137
  const file = wrapFileViewerFileRef(source.file, filename);
133
138
  return {
134
139
  file,
135
140
  buffer: await readFileViewerBuffer(file),
136
141
  filename: file.name || filename,
137
142
  type: type || getExtension(file.name),
138
- size: (_b = source.size) !== null && _b !== void 0 ? _b : file.size,
143
+ size: (_d = source.size) !== null && _d !== void 0 ? _d : file.size,
139
144
  url: source.url,
140
145
  };
141
146
  }
@@ -144,9 +149,13 @@ const resolveViewerLoadSource = async (source, options = {}) => {
144
149
  url: source.url, filename, type,
145
150
  pageHref: options.pageHref, streaming: options.streaming,
146
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.
147
154
  if (!options.fetchFile && options.acceptsPdfUrl && plan.streamPdf) {
155
+ (_e = options.onPhase) === null || _e === void 0 ? void 0 : _e.call(options, 'streamingPdf');
148
156
  return { url: plan.url, filename: plan.filename, type: plan.extension, size: source.size };
149
157
  }
158
+ (_f = options.onPhase) === null || _f === void 0 ? void 0 : _f.call(options, 'downloading');
150
159
  const fileRef = await (options.fetchFile || defaultFetchFile)({
151
160
  url: source.url,
152
161
  signal: options.signal,
@@ -156,12 +165,13 @@ const resolveViewerLoadSource = async (source, options = {}) => {
156
165
  throw new Error('Downloaded file is empty.');
157
166
  }
158
167
  const file = wrapFileViewerFileRef(fileRef, filename);
168
+ (_g = options.onPhase) === null || _g === void 0 ? void 0 : _g.call(options, 'reading');
159
169
  return {
160
170
  file,
161
171
  buffer: await readFileViewerBuffer(file),
162
172
  filename: file.name || filename,
163
173
  type: type || getExtension(file.name),
164
- size: (_c = source.size) !== null && _c !== void 0 ? _c : file.size,
174
+ size: (_h = source.size) !== null && _h !== void 0 ? _h : file.size,
165
175
  url: source.url,
166
176
  };
167
177
  }
@@ -306,6 +316,9 @@ const DEFAULT_TOOLBAR_ZOOM_STATE = {
306
316
  canZoomOut: false,
307
317
  canReset: false,
308
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.
309
322
  const ownedViewerShadowRoots = new WeakMap();
310
323
  const WEB_VIEWER_STYLE = `
311
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}
@@ -314,6 +327,9 @@ const WEB_VIEWER_STYLE = `
314
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}
315
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}
316
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}
317
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)}
318
334
  .file-viewer-web-toolbar[hidden]{display:none!important}
319
335
  .file-viewer-web-toolbar[data-toolbar-position="top-center"]{justify-content:center}
@@ -406,14 +422,26 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
406
422
  }
407
423
  else if (shouldUseShadowRoot && canAttachShadowRoot) {
408
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.
409
431
  renderRoot = container.attachShadow({ mode: 'open' });
410
432
  ownedViewerShadowRoots.set(container, renderRoot);
411
433
  }
412
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.
413
438
  renderRoot = container;
414
439
  }
415
440
  }
416
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.
417
445
  mountBoundary = documentRef.createElement('div');
418
446
  mountBoundary.dataset.fileViewerMountBoundary = 'true';
419
447
  existingHostShadowRoot.appendChild(mountBoundary);
@@ -430,6 +458,9 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
430
458
  }
431
459
  }
432
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.
433
464
  const slot = documentRef.createElement('slot');
434
465
  slot.dataset.fileViewerLightDomSlot = 'true';
435
466
  existingHostShadowRoot.replaceChildren(slot);
@@ -437,6 +468,9 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
437
468
  }
438
469
  renderRoot.replaceChildren();
439
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.
440
474
  adoptedStyleSheet: isFileViewerShadowRoot(renderRoot),
441
475
  });
442
476
  const shell = documentRef.createElement('div');
@@ -462,7 +496,10 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
462
496
  let currentSource = hasSource(currentOptions)
463
497
  ? toViewerSourceInput(currentOptions)
464
498
  : null;
465
- let abortController = null;
499
+ let sourceRequest = null;
500
+ let renderingRequest = null;
501
+ let lifecycleVersion = -1;
502
+ const updateSourceFeedback = createViewerSourceFeedback(shell, contentEl);
466
503
  const listeners = new Set();
467
504
  const state = {
468
505
  loading: false,
@@ -647,6 +684,12 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
647
684
  group.appendChild(createReadonlyMeter(documentRef, zoomState.label, 'file-viewer-web-zoom-meter'));
648
685
  }
649
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).
650
693
  const button = createButton(documentRef, '1:1', 'file-viewer-web-icon-button', () => controller === null || controller === void 0 ? void 0 : controller.resetZoom());
651
694
  button.title = t('toolbar.zoomReset');
652
695
  button.dataset.viewerToolbarKey = 'zoom-reset';
@@ -804,11 +847,20 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
804
847
  var _a;
805
848
  const snapshot = snapshotState();
806
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')));
807
851
  (_a = currentOptions.onStateChange) === null || _a === void 0 ? void 0 : _a.call(currentOptions, snapshot, event);
808
852
  listeners.forEach(listener => listener(snapshot, event));
809
853
  };
810
854
  const applyViewerEvent = (event) => {
811
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
+ }
812
864
  state.lastEvent = event;
813
865
  if (event.type === 'load-start') {
814
866
  state.loading = true;
@@ -819,6 +871,8 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
819
871
  searchDraft = '';
820
872
  }
821
873
  else if (event.type === 'load-complete') {
874
+ if (sourceRequest)
875
+ sourceRequest.pending = false;
822
876
  state.loading = false;
823
877
  state.ready = true;
824
878
  state.lifecycle = event.payload;
@@ -829,7 +883,7 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
829
883
  state.lifecycle = event.payload;
830
884
  }
831
885
  else if (event.type === 'unload-complete') {
832
- state.loading = false;
886
+ state.loading = Boolean(sourceRequest === null || sourceRequest === void 0 ? void 0 : sourceRequest.pending);
833
887
  state.ready = false;
834
888
  state.lifecycle = event.payload;
835
889
  }
@@ -860,21 +914,27 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
860
914
  (_e = viewerColorSchemeQuery === null || viewerColorSchemeQuery === void 0 ? void 0 : viewerColorSchemeQuery.addEventListener) === null || _e === void 0 ? void 0 : _e.call(viewerColorSchemeQuery, 'change', handleViewerColorSchemeChange);
861
915
  renderToolbar();
862
916
  const cancel = () => {
863
- abortController === null || abortController === void 0 ? void 0 : abortController.abort();
864
- 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();
865
921
  };
866
922
  const loadSource = async (nextSource) => {
867
- var _a, _b, _c, _d, _e, _f;
923
+ var _a, _b, _c, _d, _e, _f, _g, _h;
868
924
  cancel();
869
925
  currentSource = nextSource;
870
- abortController = typeof AbortController !== 'undefined' ? new AbortController() : null;
871
- 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); };
872
932
  try {
873
933
  state.loading = true;
874
934
  state.error = null;
875
935
  notifyState();
876
936
  await ((_a = instance.prepare) === null || _a === void 0 ? void 0 : _a.call(instance));
877
- if (disposed || (controller === null || controller === void 0 ? void 0 : controller.signal.aborted) || abortController !== controller) {
937
+ if (!isCurrent()) {
878
938
  return null;
879
939
  }
880
940
  const extension = nextSource.type || getExtension(resolveViewerSourceFilename(nextSource));
@@ -882,29 +942,43 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
882
942
  const resolvedSource = await resolveViewerLoadSource(nextSource, {
883
943
  acceptsPdfUrl: (renderer === null || renderer === void 0 ? void 0 : renderer.sourceAccess) === 'stream-preferred' && Boolean(renderer.load),
884
944
  fetchFile: coreOptions.fetchFile,
885
- signal: controller === null || controller === void 0 ? void 0 : controller.signal,
886
- streaming: (_c = (_b = currentOptions.options) === null || _b === void 0 ? void 0 : _b.pdf) === null || _c === void 0 ? void 0 : _c.streaming,
887
- pageHref: (_e = (_d = documentRef.defaultView) === null || _d === void 0 ? void 0 : _d.location) === null || _e === void 0 ? void 0 : _e.href,
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
+ },
888
954
  });
889
- if (disposed || (controller === null || controller === void 0 ? void 0 : controller.signal.aborted) || abortController !== controller) {
955
+ if (!isCurrent()) {
890
956
  return null;
891
957
  }
892
- 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 });
893
964
  }
894
965
  catch (error) {
895
- if (disposed || (controller === null || controller === void 0 ? void 0 : controller.signal.aborted) || abortController !== controller) {
966
+ if (!isCurrent()) {
896
967
  return null;
897
968
  }
898
969
  state.loading = false;
899
970
  state.ready = false;
900
- state.error = error;
971
+ state.error = error !== null && error !== void 0 ? error : new Error(createFileViewerTranslator(currentOptions.options)('error.unknown'));
972
+ request.pending = false;
901
973
  notifyState();
902
- (_f = coreOptions.onError) === null || _f === void 0 ? void 0 : _f.call(coreOptions, error, nextSource);
974
+ (_h = coreOptions.onError) === null || _h === void 0 ? void 0 : _h.call(coreOptions, error, nextSource);
903
975
  throw error;
904
976
  }
905
977
  finally {
906
- if (abortController === controller) {
907
- abortController = null;
978
+ if (isCurrent() && request.pending) {
979
+ request.pending = false;
980
+ state.loading = false;
981
+ notifyState();
908
982
  }
909
983
  }
910
984
  };
@@ -922,6 +996,9 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
922
996
  }
923
997
  };
924
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.
925
1002
  void loadSource(currentSource).catch(() => undefined);
926
1003
  }
927
1004
  controller = {
@@ -969,7 +1046,13 @@ export const mountViewer = (container, initialOptions = {}, coreOptions = {}) =>
969
1046
  }
970
1047
  }
971
1048
  else {
1049
+ cancel();
1050
+ renderingRequest = null;
972
1051
  currentSource = null;
1052
+ state.loading = false;
1053
+ state.ready = false;
1054
+ state.error = null;
1055
+ notifyState();
973
1056
  await instance.load({ filename: DEFAULT_FILE_VIEWER_SOURCE_FILENAME });
974
1057
  }
975
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
+ };
@@ -1 +1,2 @@
1
+ /** Preserve the active logical action across state-driven toolbar rebuilds. */
1
2
  export declare function createToolbarFocusRestorer(toolbar: HTMLElement, content: HTMLElement): () => (temporarilyUnavailable?: boolean) => void;
@@ -1,3 +1,4 @@
1
+ /** Preserve the active logical action across state-driven toolbar rebuilds. */
1
2
  export function createToolbarFocusRestorer(toolbar, content) {
2
3
  let pendingKey = null;
3
4
  const controls = () => Array.from(toolbar.querySelectorAll('[data-viewer-toolbar-key]'));
@@ -34,6 +35,8 @@ export function createToolbarFocusRestorer(toolbar, content) {
34
35
  target.setSelectionRange(selection[0], selection[1], selection[2] || undefined);
35
36
  }
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.
37
40
  pendingKey = key;
38
41
  toolbar.tabIndex = -1;
39
42
  toolbar.focus({ preventScroll: true });
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;