@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.
- package/README.en.md +26 -15
- package/README.md +47 -41
- package/README.zh-CN.md +175 -0
- package/dist/assets.d.ts +2 -2
- package/dist/assets.js +5 -1
- package/dist/browser/controller.d.ts +2 -1
- package/dist/browser/controller.js +156 -32
- package/dist/browser/source-feedback.d.ts +4 -0
- package/dist/browser/source-feedback.js +40 -0
- package/dist/browser/toolbar-focus.d.ts +2 -0
- package/dist/browser/toolbar-focus.js +46 -0
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +2 -2
- package/dist/config/options.d.ts +1 -1
- package/dist/contracts/types.d.ts +479 -1
- package/dist/contracts/xml.d.ts +11 -0
- package/dist/export.d.ts +10 -3
- package/dist/export.js +9 -2
- package/dist/exportDocument.d.ts +12 -1
- package/dist/exportDocument.js +31 -2
- package/dist/features/document/dom/anchors.d.ts +1 -1
- package/dist/features/document/dom/index.d.ts +5 -5
- package/dist/features/document/dom/index.js +3 -3
- package/dist/features/document/dom/providers.d.ts +1 -1
- package/dist/features/document/events.d.ts +2 -2
- package/dist/features/document/events.js +3 -3
- package/dist/features/document/fit.d.ts +1 -1
- package/dist/features/document/fit.js +1 -1
- package/dist/features/document/imageHandTool.d.ts +3 -0
- package/dist/features/document/imageHandTool.js +141 -0
- package/dist/features/document/model.d.ts +1 -1
- package/dist/features/document/search.d.ts +1 -1
- package/dist/features/document/search.js +2 -2
- package/dist/features/document/viewState.d.ts +1 -1
- package/dist/features/document/viewState.js +2 -2
- package/dist/features/document/zoom.d.ts +1 -1
- package/dist/features/document/zoom.js +3 -3
- package/dist/features/document/zoomGestures.d.ts +2 -2
- package/dist/features/document/zoomGestures.js +2 -0
- package/dist/features/printMask.d.ts +1 -1
- package/dist/features/printMaskDesigner.d.ts +6 -2
- package/dist/features/printMaskDesigner.js +11 -2
- package/dist/features/printMaskLoader.d.ts +7 -1
- package/dist/features/printMaskLoader.js +7 -1
- package/dist/features/watermark.d.ts +1 -1
- package/dist/headless.d.ts +29 -29
- package/dist/headless.js +21 -16
- package/dist/i18n/messages.d.ts +1 -1
- package/dist/i18n/messages.de.d.ts +5 -1
- package/dist/i18n/messages.de.js +4 -0
- package/dist/i18n/messages.ja.d.ts +6 -1
- package/dist/i18n/messages.ja.js +5 -0
- package/dist/i18n/messages.js +2 -2
- package/dist/index.d.ts +77 -75
- package/dist/index.js +39 -37
- package/dist/lifecycle/facade.d.ts +4 -4
- package/dist/lifecycle/facade.js +2 -2
- package/dist/lifecycle/operations.d.ts +6 -4
- package/dist/lifecycle/operations.js +21 -4
- package/dist/output/canvasImage.d.ts +2 -1
- package/dist/output/canvasImage.js +4 -1
- package/dist/output/export.d.ts +3 -1
- package/dist/output/export.js +12 -0
- package/dist/output/printLayout.d.ts +5 -0
- package/dist/output/printLayout.js +4 -0
- package/dist/output/snapshotDownload.d.ts +2 -1
- package/dist/output/snapshotDownload.js +3 -2
- package/dist/platform/assets.d.ts +9 -2
- package/dist/platform/assets.js +15 -1
- package/dist/platform/copiedAssets.d.ts +1 -0
- package/dist/platform/copiedAssets.js +2 -1
- package/dist/presentation/colorScheme.d.ts +3 -0
- package/dist/presentation/colorScheme.js +40 -0
- package/dist/presentation/state.d.ts +1 -1
- package/dist/presentation/state.js +3 -3
- package/dist/print-mask.d.ts +2 -2
- package/dist/print-mask.js +1 -1
- package/dist/registry/capabilities.d.ts +14 -1
- package/dist/registry/capabilities.js +15 -2
- package/dist/registry/formats.d.ts +1 -1
- package/dist/registry/formats.generated.d.ts +1 -0
- package/dist/registry/formats.generated.js +1 -0
- package/dist/registry/formats.js +2 -2
- package/dist/registry/registry.d.ts +15 -1
- package/dist/registry/registry.js +10 -2
- package/dist/renderers/image.d.ts +1 -1
- package/dist/renderers/image.js +13 -7
- package/dist/renderers/index.d.ts +1 -1
- package/dist/renderers/index.js +11 -5
- package/dist/rendering/dispatcher.d.ts +6 -1
- package/dist/rendering/dispatcher.js +3 -3
- package/dist/rendering/handler.d.ts +13 -3
- package/dist/rendering/handler.js +21 -9
- package/dist/security/svgResources.d.ts +10 -0
- package/dist/security/svgResources.js +12 -0
- package/dist/source/index.d.ts +3 -3
- package/dist/source/index.js +1 -1
- package/dist/source/loading.d.ts +12 -7
- package/dist/source/loading.js +22 -17
- package/dist/source/precheck.d.ts +13 -1
- package/dist/source/precheck.js +9 -2
- package/dist/source/textEncoding.d.ts +18 -0
- package/dist/source/textEncoding.js +15 -0
- package/dist/viewer/createViewer.d.ts +1 -1
- package/dist/viewer/createViewer.js +30 -17
- package/dist/viewer/loading.d.ts +6 -2
- package/dist/viewer/loading.js +9 -1
- package/dist/viewer/operations.d.ts +3 -3
- package/dist/viewer/operations.js +14 -6
- package/dist/viewer/state.d.ts +6 -2
- package/dist/viewer/state.js +8 -3
- package/package.json +14 -5
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { appendFileViewerStyle, cloneFileViewerViewState, applyFileViewerZoomAvailability, createViewer, isFileViewerShadowRoot, normalizeFileViewerUiDensity, normalizeFileViewerTheme, normalizeFileViewerStyleIsolation, syncFileViewerRenderSurfaceBackground, } from
|
|
2
|
-
import { DEFAULT_FILE_VIEWER_SOURCE_FILENAME, createFileViewerTranslator, getExtension, normalizeFileViewerSourceUrl, hasVisibleFileViewerToolbarActions, isFileViewerZoomButtonDisabled, normalizeFileViewerToolbar, normalizeFilename, readFileViewerBuffer, resolveFileViewerSourceFilename, resolveFileViewerColorScheme, resolveFileViewerToolbarOrder, resolveFileViewerToolbarPosition, resolveVisibleFileViewerToolbar, toggleFileViewerColorScheme, wrapFileViewerFileRef, } from
|
|
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: (
|
|
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: (
|
|
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: (
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
832
|
-
|
|
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
|
-
|
|
839
|
-
|
|
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:
|
|
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 (
|
|
955
|
+
if (!isCurrent()) {
|
|
849
956
|
return null;
|
|
850
957
|
}
|
|
851
|
-
|
|
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 (
|
|
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
|
-
(
|
|
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 (
|
|
866
|
-
|
|
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,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
|
|
2
|
-
export * from
|
|
1
|
+
export * from './index.js';
|
|
2
|
+
export * from './browser/controller.js';
|
package/dist/browser.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from './index.js';
|
|
2
|
+
export * from './browser/controller.js';
|
package/dist/config/options.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FileViewerArchiveOptions, FileViewerCadOptions, FileViewerOptions, FileViewerResolvedThemeMode, FileViewerThemeMode, FileViewerToolbarOptions, FileViewerUiDensity } from
|
|
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;
|