@sdcorejs/angular 21.1.3 → 21.1.5
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.md +19 -14
- package/fesm2022/sdcorejs-angular-components-anchor.mjs +14 -14
- package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-audit-diff.mjs +332 -0
- package/fesm2022/sdcorejs-angular-components-audit-diff.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-badge.mjs +1 -0
- package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs +121 -0
- package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-data-state.mjs +68 -0
- package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs +18 -23
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-editor.mjs +6 -4
- package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs +495 -838
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs +24 -34
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-job-progress.mjs +97 -0
- package/fesm2022/sdcorejs-angular-components-job-progress.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-mini-editor.mjs +10 -20
- package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal.mjs +61 -4
- package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-preview.mjs +1438 -218
- package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs +4 -4
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +39 -2
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs +16 -22
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab.mjs +48 -4
- package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-table.mjs +96 -63
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tree.mjs +186 -17
- package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs +15 -22
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components.mjs +4 -0
- package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-directives.mjs +29 -41
- package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +7 -7
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +20 -71
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +6 -3
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip.mjs +6 -3
- package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs +16 -13
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date.mjs +42 -65
- package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs +51 -66
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +384 -0
- package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs +7 -5
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input.mjs +290 -56
- package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-models.mjs +155 -2
- package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-radio.mjs +44 -66
- package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-select.mjs +6 -20
- package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-switch.mjs +19 -64
- package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs +6 -5
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-time-range.mjs +277 -0
- package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms-time.mjs +307 -0
- package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +210 -0
- package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms.mjs +73 -4
- package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-i18n.mjs +375 -10
- package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-interceptors.mjs +12 -17
- package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-layout.mjs +994 -168
- package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-pipes.mjs +5 -7
- package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-api.mjs +407 -137
- package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-cache.mjs +515 -105
- package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-confirm.mjs +5 -7
- package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-docx.mjs +7 -10
- package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-loading.mjs +339 -74
- package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-notify.mjs +7 -10
- package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-persistence.mjs +899 -0
- package/fesm2022/sdcorejs-angular-services-persistence.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services-storage.mjs +324 -122
- package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-task.mjs +389 -0
- package/fesm2022/sdcorejs-angular-services-task.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs +255 -0
- package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services-viewport.mjs +76 -0
- package/fesm2022/sdcorejs-angular-services-viewport.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services.mjs +4 -0
- package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
- package/package.json +49 -1
- package/types/sdcorejs-angular-components-audit-diff.d.ts +74 -0
- package/types/sdcorejs-angular-components-badge.d.ts +1 -1
- package/types/sdcorejs-angular-components-breadcrumb.d.ts +53 -0
- package/types/sdcorejs-angular-components-data-state.d.ts +44 -0
- package/types/sdcorejs-angular-components-document-builder.d.ts +5 -5
- package/types/sdcorejs-angular-components-form-generic.d.ts +67 -104
- package/types/sdcorejs-angular-components-import-excel.d.ts +4 -14
- package/types/sdcorejs-angular-components-job-progress.d.ts +38 -0
- package/types/sdcorejs-angular-components-mini-editor.d.ts +9 -9
- package/types/sdcorejs-angular-components-modal.d.ts +7 -1
- package/types/sdcorejs-angular-components-preview.d.ts +181 -28
- package/types/sdcorejs-angular-components-side-drawer.d.ts +7 -1
- package/types/sdcorejs-angular-components-tab.d.ts +9 -2
- package/types/sdcorejs-angular-components-table.d.ts +8 -7
- package/types/sdcorejs-angular-components-tree.d.ts +24 -2
- package/types/sdcorejs-angular-components-upload-file.d.ts +5 -4
- package/types/sdcorejs-angular-components.d.ts +4 -0
- package/types/sdcorejs-angular-directives.d.ts +10 -8
- package/types/sdcorejs-angular-forms-autocomplete.d.ts +3 -4
- package/types/sdcorejs-angular-forms-checkbox.d.ts +4 -8
- package/types/sdcorejs-angular-forms-date-range.d.ts +6 -7
- package/types/sdcorejs-angular-forms-date.d.ts +2 -2
- package/types/sdcorejs-angular-forms-datetime.d.ts +2 -2
- package/types/sdcorejs-angular-forms-entity-picker.d.ts +128 -0
- package/types/sdcorejs-angular-forms-input-number.d.ts +1 -1
- package/types/sdcorejs-angular-forms-input.d.ts +74 -11
- package/types/sdcorejs-angular-forms-models.d.ts +86 -3
- package/types/sdcorejs-angular-forms-radio.d.ts +12 -8
- package/types/sdcorejs-angular-forms-switch.d.ts +4 -7
- package/types/sdcorejs-angular-forms-time-range.d.ts +98 -0
- package/types/sdcorejs-angular-forms-time.d.ts +117 -0
- package/types/sdcorejs-angular-forms-tree-select.d.ts +76 -0
- package/types/sdcorejs-angular-forms.d.ts +15 -5
- package/types/sdcorejs-angular-i18n.d.ts +72 -2
- package/types/sdcorejs-angular-interceptors.d.ts +5 -6
- package/types/sdcorejs-angular-modules-layout.d.ts +354 -17
- package/types/sdcorejs-angular-pipes.d.ts +2 -2
- package/types/sdcorejs-angular-services-api.d.ts +68 -24
- package/types/sdcorejs-angular-services-cache.d.ts +52 -16
- package/types/sdcorejs-angular-services-confirm.d.ts +3 -2
- package/types/sdcorejs-angular-services-docx.d.ts +2 -3
- package/types/sdcorejs-angular-services-loading.d.ts +16 -8
- package/types/sdcorejs-angular-services-persistence.d.ts +159 -0
- package/types/sdcorejs-angular-services-storage.d.ts +26 -8
- package/types/sdcorejs-angular-services-task.d.ts +104 -0
- package/types/sdcorejs-angular-services-unsaved-changes.d.ts +83 -0
- package/types/sdcorejs-angular-services-viewport.d.ts +42 -0
- package/types/sdcorejs-angular-services.d.ts +4 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i1 from '@angular/common';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { CommonModule, isPlatformBrowser, DOCUMENT } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { inject, ElementRef, DestroyRef, input, computed, output, signal, effect, afterNextRender, HostListener, ChangeDetectionStrategy, Component, InjectionToken, viewChild, viewChildren } from '@angular/core';
|
|
4
|
+
import { inject, ElementRef, DestroyRef, input, computed, output, signal, effect, afterNextRender, HostListener, ChangeDetectionStrategy, Component, InjectionToken, PLATFORM_ID, viewChild, viewChildren } from '@angular/core';
|
|
5
5
|
import { Utilities } from '@sdcorejs/utils/fns';
|
|
6
6
|
import { TranslatePipe } from '@sdcorejs/angular/i18n';
|
|
7
7
|
import { SdIcon } from '@sdcorejs/angular/modules/icon';
|
|
@@ -657,24 +657,302 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
657
657
|
args: ['keydown', ['$event']]
|
|
658
658
|
}] } });
|
|
659
659
|
|
|
660
|
-
|
|
660
|
+
class SdPdfDocumentBrowserAdapter {
|
|
661
|
+
document;
|
|
662
|
+
isBrowser;
|
|
663
|
+
canDownloadUrl;
|
|
664
|
+
canDownloadBlob;
|
|
665
|
+
canFullscreen;
|
|
666
|
+
constructor(document, platformId) {
|
|
667
|
+
this.document = document;
|
|
668
|
+
this.isBrowser = isPlatformBrowser(platformId) && !!document.defaultView;
|
|
669
|
+
this.canDownloadUrl = this.isBrowser && !!document.body && 'download' in document.createElement('a');
|
|
670
|
+
const view = document.defaultView;
|
|
671
|
+
this.canDownloadBlob =
|
|
672
|
+
this.canDownloadUrl &&
|
|
673
|
+
typeof view?.Blob === 'function' &&
|
|
674
|
+
typeof view.URL?.createObjectURL === 'function' &&
|
|
675
|
+
typeof view.URL?.revokeObjectURL === 'function';
|
|
676
|
+
this.canFullscreen =
|
|
677
|
+
this.isBrowser && typeof document.documentElement?.requestFullscreen === 'function' && typeof document.exitFullscreen === 'function';
|
|
678
|
+
}
|
|
679
|
+
isFile(value) {
|
|
680
|
+
const FileCtor = this.document.defaultView?.File;
|
|
681
|
+
return this.isBrowser && !!FileCtor && value instanceof FileCtor;
|
|
682
|
+
}
|
|
683
|
+
isBlob(value) {
|
|
684
|
+
const BlobCtor = this.document.defaultView?.Blob;
|
|
685
|
+
return this.isBrowser && !!BlobCtor && value instanceof BlobCtor;
|
|
686
|
+
}
|
|
687
|
+
createPdfBlob(data) {
|
|
688
|
+
const BlobCtor = this.document.defaultView?.Blob;
|
|
689
|
+
if (!this.isBrowser || !BlobCtor)
|
|
690
|
+
return null;
|
|
691
|
+
return new BlobCtor([new Uint8Array(data)], { type: 'application/pdf' });
|
|
692
|
+
}
|
|
693
|
+
createObjectUrl(blob) {
|
|
694
|
+
const urlApi = this.document.defaultView?.URL;
|
|
695
|
+
return this.isBrowser && urlApi?.createObjectURL ? urlApi.createObjectURL(blob) : null;
|
|
696
|
+
}
|
|
697
|
+
revokeObjectUrl(url) {
|
|
698
|
+
if (!this.isBrowser)
|
|
699
|
+
return;
|
|
700
|
+
this.document.defaultView?.URL?.revokeObjectURL?.(url);
|
|
701
|
+
}
|
|
702
|
+
download(href, filename) {
|
|
703
|
+
if (!this.canDownloadUrl || !this.document.body)
|
|
704
|
+
return false;
|
|
705
|
+
const anchor = this.document.createElement('a');
|
|
706
|
+
anchor.href = href;
|
|
707
|
+
anchor.download = filename;
|
|
708
|
+
anchor.hidden = true;
|
|
709
|
+
this.document.body.appendChild(anchor);
|
|
710
|
+
anchor.click();
|
|
711
|
+
anchor.remove();
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
createElement(tagName) {
|
|
715
|
+
return this.isBrowser ? this.document.createElement(tagName) : null;
|
|
716
|
+
}
|
|
717
|
+
createImage() {
|
|
718
|
+
return this.createElement('img');
|
|
719
|
+
}
|
|
720
|
+
listenFullscreen(host, listener) {
|
|
721
|
+
if (!this.isBrowser)
|
|
722
|
+
return () => undefined;
|
|
723
|
+
const onChange = () => listener(this.document.fullscreenElement === host);
|
|
724
|
+
this.document.addEventListener('fullscreenchange', onChange);
|
|
725
|
+
onChange();
|
|
726
|
+
return () => this.document.removeEventListener('fullscreenchange', onChange);
|
|
727
|
+
}
|
|
728
|
+
async toggleFullscreen(host) {
|
|
729
|
+
if (!this.canFullscreen)
|
|
730
|
+
return;
|
|
731
|
+
if (this.document.fullscreenElement) {
|
|
732
|
+
await this.document.exitFullscreen?.();
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
await host.requestFullscreen?.();
|
|
736
|
+
}
|
|
737
|
+
observeResize(element, listener) {
|
|
738
|
+
const ResizeObserverCtor = this.document.defaultView?.ResizeObserver;
|
|
739
|
+
if (!this.isBrowser || !ResizeObserverCtor)
|
|
740
|
+
return () => undefined;
|
|
741
|
+
const observer = new ResizeObserverCtor(() => listener());
|
|
742
|
+
observer.observe(element);
|
|
743
|
+
return () => observer.disconnect();
|
|
744
|
+
}
|
|
745
|
+
observeIntersections(elements, listener, options) {
|
|
746
|
+
const IntersectionObserverCtor = this.document.defaultView?.IntersectionObserver;
|
|
747
|
+
if (!this.isBrowser || elements.length === 0)
|
|
748
|
+
return () => undefined;
|
|
749
|
+
if (!IntersectionObserverCtor) {
|
|
750
|
+
listener(elements.map(target => ({ target, isIntersecting: true })));
|
|
751
|
+
return () => undefined;
|
|
752
|
+
}
|
|
753
|
+
const observer = new IntersectionObserverCtor(entries => listener(entries), options);
|
|
754
|
+
for (const element of elements)
|
|
755
|
+
observer.observe(element);
|
|
756
|
+
return () => observer.disconnect();
|
|
757
|
+
}
|
|
758
|
+
scheduleFrame(callback) {
|
|
759
|
+
const view = this.document.defaultView;
|
|
760
|
+
if (!this.isBrowser || !view)
|
|
761
|
+
return null;
|
|
762
|
+
return view.requestAnimationFrame ? view.requestAnimationFrame(callback) : view.setTimeout(() => callback(view.performance.now()), 0);
|
|
763
|
+
}
|
|
764
|
+
cancelFrame(handle) {
|
|
765
|
+
if (handle === null || !this.isBrowser)
|
|
766
|
+
return;
|
|
767
|
+
this.document.defaultView?.cancelAnimationFrame?.(handle);
|
|
768
|
+
this.document.defaultView?.clearTimeout(handle);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const SD_PDF_BROWSER_ADAPTER = new InjectionToken('SD_PDF_BROWSER_ADAPTER', {
|
|
772
|
+
providedIn: 'root',
|
|
773
|
+
factory: () => new SdPdfDocumentBrowserAdapter(inject(DOCUMENT), inject(PLATFORM_ID)),
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
class SdPdfIframePrintAdapter {
|
|
777
|
+
document;
|
|
778
|
+
#isBrowser;
|
|
779
|
+
isSupported;
|
|
780
|
+
constructor(document, platformId) {
|
|
781
|
+
this.document = document;
|
|
782
|
+
this.#isBrowser = isPlatformBrowser(platformId) && !!document.defaultView;
|
|
783
|
+
const view = document.defaultView;
|
|
784
|
+
this.isSupported =
|
|
785
|
+
this.#isBrowser &&
|
|
786
|
+
!!view?.Blob &&
|
|
787
|
+
typeof view.URL?.createObjectURL === 'function' &&
|
|
788
|
+
typeof view.URL?.revokeObjectURL === 'function' &&
|
|
789
|
+
!!document.body;
|
|
790
|
+
}
|
|
791
|
+
start(data, filename) {
|
|
792
|
+
const view = this.document.defaultView;
|
|
793
|
+
const BlobCtor = view?.Blob;
|
|
794
|
+
const urlApi = view?.URL;
|
|
795
|
+
if (!this.isSupported ||
|
|
796
|
+
!view ||
|
|
797
|
+
!BlobCtor ||
|
|
798
|
+
typeof urlApi?.createObjectURL !== 'function' ||
|
|
799
|
+
typeof urlApi.revokeObjectURL !== 'function' ||
|
|
800
|
+
!this.document.body) {
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
const blob = new BlobCtor([new Uint8Array(data)], { type: 'application/pdf' });
|
|
804
|
+
const objectUrl = urlApi.createObjectURL(blob);
|
|
805
|
+
const frame = this.document.createElement('iframe');
|
|
806
|
+
frame.hidden = true;
|
|
807
|
+
frame.title = filename;
|
|
808
|
+
let settled = false;
|
|
809
|
+
let timeoutHandle = null;
|
|
810
|
+
let resolveFinished = () => undefined;
|
|
811
|
+
let rejectFinished = () => undefined;
|
|
812
|
+
const finished = new Promise((resolve, reject) => {
|
|
813
|
+
resolveFinished = resolve;
|
|
814
|
+
rejectFinished = reject;
|
|
815
|
+
});
|
|
816
|
+
const attemptCleanup = (operation) => {
|
|
817
|
+
try {
|
|
818
|
+
operation();
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
// Cleanup is best-effort; every remaining step and promise settlement must still run.
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
const cleanup = () => {
|
|
825
|
+
if (timeoutHandle !== null)
|
|
826
|
+
attemptCleanup(() => view.clearTimeout(timeoutHandle));
|
|
827
|
+
attemptCleanup(() => frame.contentWindow?.removeEventListener('afterprint', complete));
|
|
828
|
+
attemptCleanup(() => frame.removeEventListener('load', onLoad));
|
|
829
|
+
attemptCleanup(() => frame.removeEventListener('error', onError));
|
|
830
|
+
attemptCleanup(() => frame.remove());
|
|
831
|
+
attemptCleanup(() => urlApi.revokeObjectURL(objectUrl));
|
|
832
|
+
};
|
|
833
|
+
const complete = () => {
|
|
834
|
+
if (settled)
|
|
835
|
+
return;
|
|
836
|
+
settled = true;
|
|
837
|
+
cleanup();
|
|
838
|
+
resolveFinished();
|
|
839
|
+
};
|
|
840
|
+
const fail = (message) => {
|
|
841
|
+
if (settled)
|
|
842
|
+
return;
|
|
843
|
+
settled = true;
|
|
844
|
+
cleanup();
|
|
845
|
+
rejectFinished(new Error(message));
|
|
846
|
+
};
|
|
847
|
+
const onError = () => fail('Unable to load the PDF print frame.');
|
|
848
|
+
const onLoad = () => {
|
|
849
|
+
const printWindow = frame.contentWindow;
|
|
850
|
+
if (!printWindow?.print) {
|
|
851
|
+
fail('Printing is unavailable in this browser.');
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
printWindow.addEventListener('afterprint', complete, { once: true });
|
|
855
|
+
timeoutHandle = view.setTimeout(complete, 30_000);
|
|
856
|
+
try {
|
|
857
|
+
printWindow.focus();
|
|
858
|
+
printWindow.print();
|
|
859
|
+
}
|
|
860
|
+
catch {
|
|
861
|
+
fail('The browser blocked the PDF print dialog.');
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
frame.addEventListener('load', onLoad, { once: true });
|
|
865
|
+
frame.addEventListener('error', onError, { once: true });
|
|
866
|
+
frame.src = objectUrl;
|
|
867
|
+
this.document.body.appendChild(frame);
|
|
868
|
+
return {
|
|
869
|
+
finished,
|
|
870
|
+
cancel: complete,
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const SD_PDF_PRINT_ADAPTER = new InjectionToken('SD_PDF_PRINT_ADAPTER', {
|
|
875
|
+
providedIn: 'root',
|
|
876
|
+
factory: () => new SdPdfIframePrintAdapter(inject(DOCUMENT), inject(PLATFORM_ID)),
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
function isSdPdfJsLib(value) {
|
|
880
|
+
if (!value || typeof value !== 'object')
|
|
881
|
+
return false;
|
|
882
|
+
const candidate = value;
|
|
883
|
+
return typeof candidate.getDocument === 'function' && typeof candidate.GlobalWorkerOptions?.workerSrc === 'string';
|
|
884
|
+
}
|
|
661
885
|
const SD_PDFJS_LIB = new InjectionToken('SD_PDFJS_LIB', {
|
|
662
886
|
providedIn: 'root',
|
|
663
887
|
factory: () => {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
// import.meta.url can't be resolved or the worker bundle isn't shipped.
|
|
667
|
-
try {
|
|
668
|
-
if (pdfjsLib?.GlobalWorkerOptions && !pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
|
669
|
-
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
|
670
|
-
}
|
|
888
|
+
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
|
889
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
|
671
890
|
}
|
|
672
|
-
|
|
673
|
-
|
|
891
|
+
const candidate = pdfjsLib;
|
|
892
|
+
if (!isSdPdfJsLib(candidate)) {
|
|
893
|
+
throw new Error('The installed pdfjs-dist build does not expose getDocument().');
|
|
674
894
|
}
|
|
675
|
-
return
|
|
895
|
+
return candidate;
|
|
676
896
|
},
|
|
677
897
|
});
|
|
898
|
+
|
|
899
|
+
var _a;
|
|
900
|
+
let nextPdfPreviewInstanceId = 0;
|
|
901
|
+
class PdfHeightIndex {
|
|
902
|
+
#gap;
|
|
903
|
+
#tree = [0];
|
|
904
|
+
#values = [];
|
|
905
|
+
constructor(gap) {
|
|
906
|
+
this.#gap = gap;
|
|
907
|
+
}
|
|
908
|
+
reset(heights) {
|
|
909
|
+
this.#values = heights.map((height, index) => height + (index < heights.length - 1 ? this.#gap : 0));
|
|
910
|
+
this.#tree = Array.from({ length: this.#values.length + 1 }, () => 0);
|
|
911
|
+
for (let index = 1; index < this.#tree.length; index++) {
|
|
912
|
+
this.#tree[index] += this.#values[index - 1];
|
|
913
|
+
const parent = index + (index & -index);
|
|
914
|
+
if (parent < this.#tree.length)
|
|
915
|
+
this.#tree[parent] += this.#tree[index];
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
updateHeight(index, height) {
|
|
919
|
+
if (index < 0 || index >= this.#values.length)
|
|
920
|
+
return;
|
|
921
|
+
const next = height + (index < this.#values.length - 1 ? this.#gap : 0);
|
|
922
|
+
const delta = next - this.#values[index];
|
|
923
|
+
this.#values[index] = next;
|
|
924
|
+
for (let cursor = index + 1; cursor < this.#tree.length; cursor += cursor & -cursor)
|
|
925
|
+
this.#tree[cursor] += delta;
|
|
926
|
+
}
|
|
927
|
+
offsetAt(index) {
|
|
928
|
+
let sum = 0;
|
|
929
|
+
for (let cursor = Math.max(0, Math.min(index, this.#values.length)); cursor > 0; cursor -= cursor & -cursor) {
|
|
930
|
+
sum += this.#tree[cursor];
|
|
931
|
+
}
|
|
932
|
+
return sum;
|
|
933
|
+
}
|
|
934
|
+
total() {
|
|
935
|
+
return this.offsetAt(this.#values.length);
|
|
936
|
+
}
|
|
937
|
+
indexAt(offset) {
|
|
938
|
+
if (this.#values.length === 0)
|
|
939
|
+
return 0;
|
|
940
|
+
const bounded = Math.max(0, Math.min(offset, this.total()));
|
|
941
|
+
let index = 0;
|
|
942
|
+
let prefix = 0;
|
|
943
|
+
let bit = 1;
|
|
944
|
+
while (bit * 2 < this.#tree.length)
|
|
945
|
+
bit *= 2;
|
|
946
|
+
for (; bit > 0; bit >>= 1) {
|
|
947
|
+
const next = index + bit;
|
|
948
|
+
if (next < this.#tree.length && prefix + this.#tree[next] <= bounded) {
|
|
949
|
+
index = next;
|
|
950
|
+
prefix += this.#tree[next];
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return Math.min(index, this.#values.length - 1);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
678
956
|
class SdPreviewPdf {
|
|
679
957
|
// ==========================================
|
|
680
958
|
// CONSTANTS
|
|
@@ -685,12 +963,25 @@ class SdPreviewPdf {
|
|
|
685
963
|
static MIN_ZOOM = 0.25;
|
|
686
964
|
static MAX_ZOOM = 4;
|
|
687
965
|
static ZOOM_STEP = 0.1;
|
|
966
|
+
static OUTLINE_MAX_DEPTH = 64;
|
|
967
|
+
static OUTLINE_MAX_NODES = 10_000;
|
|
968
|
+
static MAX_SEARCH_RESULTS = 1000;
|
|
969
|
+
static MAX_PAGE_TEXT_CACHE_PAGES = 128;
|
|
970
|
+
static THUMBNAIL_WINDOW_SIZE = 48;
|
|
971
|
+
static THUMBNAIL_ITEM_HEIGHT = 220;
|
|
972
|
+
static MAX_THUMBNAIL_CACHE_ENTRIES = 96;
|
|
973
|
+
static MAX_CONCURRENT_THUMBNAIL_WORK = 4;
|
|
974
|
+
static MAX_CANVAS_DIMENSION = 8192;
|
|
975
|
+
static MAX_CANVAS_PIXELS = 16_777_216;
|
|
688
976
|
// ==========================================
|
|
689
977
|
// DI
|
|
690
978
|
// ==========================================
|
|
691
979
|
#hostEl = inject(ElementRef);
|
|
692
980
|
#destroyRef = inject(DestroyRef);
|
|
693
981
|
#pdfjs = inject(SD_PDFJS_LIB);
|
|
982
|
+
#browser = inject(SD_PDF_BROWSER_ADAPTER);
|
|
983
|
+
#printAdapter = inject(SD_PDF_PRINT_ADAPTER);
|
|
984
|
+
#instanceId = `sd-preview-pdf-${++nextPdfPreviewInstanceId}`;
|
|
694
985
|
// ==========================================
|
|
695
986
|
// VIEW CHILDREN
|
|
696
987
|
// WHY `protected readonly` (not `#private`): Angular's `viewChild()` query
|
|
@@ -702,6 +993,10 @@ class SdPreviewPdf {
|
|
|
702
993
|
stageEl = viewChild('stageRef', ...(ngDevMode ? [{ debugName: "stageEl" }] : /* istanbul ignore next */ []));
|
|
703
994
|
pageInputRef = viewChild('pageInput', ...(ngDevMode ? [{ debugName: "pageInputRef" }] : /* istanbul ignore next */ []));
|
|
704
995
|
searchInputRef = viewChild('searchInput', ...(ngDevMode ? [{ debugName: "searchInputRef" }] : /* istanbul ignore next */ []));
|
|
996
|
+
sidebarContentRef = viewChild('sidebarContent', ...(ngDevMode ? [{ debugName: "sidebarContentRef" }] : /* istanbul ignore next */ []));
|
|
997
|
+
outlineItemRefs = viewChildren('outlineItem', ...(ngDevMode ? [{ debugName: "outlineItemRefs" }] : /* istanbul ignore next */ []));
|
|
998
|
+
sidebarTabRefs = viewChildren('sidebarTab', ...(ngDevMode ? [{ debugName: "sidebarTabRefs" }] : /* istanbul ignore next */ []));
|
|
999
|
+
continuousCanvases = viewChildren('continuousCanvas', ...(ngDevMode ? [{ debugName: "continuousCanvases" }] : /* istanbul ignore next */ []));
|
|
705
1000
|
// Every thumbnail <canvas> in the sidebar — populated when sidebar is open +
|
|
706
1001
|
// mode==='thumbnails'. We feed these to the IntersectionObserver below so a
|
|
707
1002
|
// thumb only renders when it scrolls into the viewport (lazy fill).
|
|
@@ -740,6 +1035,22 @@ class SdPreviewPdf {
|
|
|
740
1035
|
autoIdTabThumbnails = computed(() => this.#childAutoId('tab-thumbnails'), ...(ngDevMode ? [{ debugName: "autoIdTabThumbnails" }] : /* istanbul ignore next */ []));
|
|
741
1036
|
autoIdTabOutline = computed(() => this.#childAutoId('tab-outline'), ...(ngDevMode ? [{ debugName: "autoIdTabOutline" }] : /* istanbul ignore next */ []));
|
|
742
1037
|
autoIdTabSearch = computed(() => this.#childAutoId('tab-search'), ...(ngDevMode ? [{ debugName: "autoIdTabSearch" }] : /* istanbul ignore next */ []));
|
|
1038
|
+
sidebarPanelId = computed(() => this.#childAutoId('sidebar-panel') ?? `${this.#instanceId}-sidebar-panel`, ...(ngDevMode ? [{ debugName: "sidebarPanelId" }] : /* istanbul ignore next */ []));
|
|
1039
|
+
sidebarRegionId = computed(() => this.#childAutoId('sidebar') ?? `${this.#instanceId}-sidebar`, ...(ngDevMode ? [{ debugName: "sidebarRegionId" }] : /* istanbul ignore next */ []));
|
|
1040
|
+
searchBarId = computed(() => this.#childAutoId('searchbar') ?? `${this.#instanceId}-searchbar`, ...(ngDevMode ? [{ debugName: "searchBarId" }] : /* istanbul ignore next */ []));
|
|
1041
|
+
tabThumbnailsId = computed(() => this.#childAutoId('tab-thumbnails') ?? `${this.#instanceId}-tab-thumbnails`, ...(ngDevMode ? [{ debugName: "tabThumbnailsId" }] : /* istanbul ignore next */ []));
|
|
1042
|
+
tabOutlineId = computed(() => this.#childAutoId('tab-outline') ?? `${this.#instanceId}-tab-outline`, ...(ngDevMode ? [{ debugName: "tabOutlineId" }] : /* istanbul ignore next */ []));
|
|
1043
|
+
tabSearchId = computed(() => this.#childAutoId('tab-search') ?? `${this.#instanceId}-tab-search`, ...(ngDevMode ? [{ debugName: "tabSearchId" }] : /* istanbul ignore next */ []));
|
|
1044
|
+
sidebarActiveTabId = computed(() => {
|
|
1045
|
+
switch (this.#sidebarModeInternal()) {
|
|
1046
|
+
case 'outline':
|
|
1047
|
+
return this.tabOutlineId();
|
|
1048
|
+
case 'search':
|
|
1049
|
+
return this.tabSearchId();
|
|
1050
|
+
default:
|
|
1051
|
+
return this.tabThumbnailsId();
|
|
1052
|
+
}
|
|
1053
|
+
}, ...(ngDevMode ? [{ debugName: "sidebarActiveTabId" }] : /* istanbul ignore next */ []));
|
|
743
1054
|
// Search bar
|
|
744
1055
|
autoIdSearchInput = computed(() => this.#childAutoId('search-input'), ...(ngDevMode ? [{ debugName: "autoIdSearchInput" }] : /* istanbul ignore next */ []));
|
|
745
1056
|
autoIdSearchNext = computed(() => this.#childAutoId('search-next'), ...(ngDevMode ? [{ debugName: "autoIdSearchNext" }] : /* istanbul ignore next */ []));
|
|
@@ -797,9 +1108,18 @@ class SdPreviewPdf {
|
|
|
797
1108
|
#isFullscreen = signal(false, ...(ngDevMode ? [{ debugName: "#isFullscreen" }] : /* istanbul ignore next */ []));
|
|
798
1109
|
#filename = signal('document.pdf', ...(ngDevMode ? [{ debugName: "#filename" }] : /* istanbul ignore next */ []));
|
|
799
1110
|
#fileSize = signal(0, ...(ngDevMode ? [{ debugName: "#fileSize" }] : /* istanbul ignore next */ []));
|
|
1111
|
+
#continuousPages = signal([], ...(ngDevMode ? [{ debugName: "#continuousPages" }] : /* istanbul ignore next */ []));
|
|
1112
|
+
#continuousTopSpacer = signal(0, ...(ngDevMode ? [{ debugName: "#continuousTopSpacer" }] : /* istanbul ignore next */ []));
|
|
1113
|
+
#continuousBottomSpacer = signal(0, ...(ngDevMode ? [{ debugName: "#continuousBottomSpacer" }] : /* istanbul ignore next */ []));
|
|
1114
|
+
#continuousHeightVersion = signal(0, ...(ngDevMode ? [{ debugName: "#continuousHeightVersion" }] : /* istanbul ignore next */ []));
|
|
1115
|
+
#thumbnailWindowStart = signal(0, ...(ngDevMode ? [{ debugName: "#thumbnailWindowStart" }] : /* istanbul ignore next */ []));
|
|
1116
|
+
#outline = signal([], ...(ngDevMode ? [{ debugName: "#outline" }] : /* istanbul ignore next */ []));
|
|
1117
|
+
#outlineExpanded = signal(new Set(), ...(ngDevMode ? [{ debugName: "#outlineExpanded" }] : /* istanbul ignore next */ []));
|
|
1118
|
+
#outlineFocusId = signal(null, ...(ngDevMode ? [{ debugName: "#outlineFocusId" }] : /* istanbul ignore next */ []));
|
|
800
1119
|
// Thumbnail cache: page number → data URL of mini-render. Lazy-populated as
|
|
801
1120
|
// the user opens the sidebar / scrolls thumbnails into view.
|
|
802
1121
|
#thumbCache = signal({}, ...(ngDevMode ? [{ debugName: "#thumbCache" }] : /* istanbul ignore next */ []));
|
|
1122
|
+
#thumbnailCacheLru = new Map();
|
|
803
1123
|
// ==========================================
|
|
804
1124
|
// SEARCH STATE
|
|
805
1125
|
// ==========================================
|
|
@@ -814,6 +1134,7 @@ class SdPreviewPdf {
|
|
|
814
1134
|
#searchWholeWord = signal(false, ...(ngDevMode ? [{ debugName: "#searchWholeWord" }] : /* istanbul ignore next */ []));
|
|
815
1135
|
#searchTerm = signal('', ...(ngDevMode ? [{ debugName: "#searchTerm" }] : /* istanbul ignore next */ []));
|
|
816
1136
|
#searchResults = signal([], ...(ngDevMode ? [{ debugName: "#searchResults" }] : /* istanbul ignore next */ []));
|
|
1137
|
+
#searchTruncated = signal(false, ...(ngDevMode ? [{ debugName: "#searchTruncated" }] : /* istanbul ignore next */ []));
|
|
817
1138
|
// -1 sentinel means "no result focused yet" (e.g. right after clearSearch
|
|
818
1139
|
// or before the first searchNext). Highlight rendering uses this to skip
|
|
819
1140
|
// the `--active` class altogether.
|
|
@@ -822,25 +1143,41 @@ class SdPreviewPdf {
|
|
|
822
1143
|
// `getTextContent()` cost once per page per document, then reuse across
|
|
823
1144
|
// re-runs (e.g. toggling case-sensitive). Cleared on every source change.
|
|
824
1145
|
#pageTextCache = new Map();
|
|
1146
|
+
#pageTextCacheSize = signal(0, ...(ngDevMode ? [{ debugName: "#pageTextCacheSize" }] : /* istanbul ignore next */ []));
|
|
825
1147
|
// Token to abort an in-flight `search()` call when a newer one supersedes it
|
|
826
1148
|
// — same pattern as #loadToken / #renderToken upstream.
|
|
827
1149
|
#searchToken = 0;
|
|
1150
|
+
#searchWorkerActive = false;
|
|
1151
|
+
#queuedSearchRequest = null;
|
|
828
1152
|
// The IntersectionObserver instance that drives lazy thumb rendering. Lives
|
|
829
1153
|
// for the component's lifetime; we just rebind to the latest canvas refs
|
|
830
1154
|
// when the sidebar tab switches.
|
|
831
|
-
#
|
|
1155
|
+
#thumbObserverCleanup = null;
|
|
832
1156
|
// Pages currently being rendered as thumbs — guards against double work
|
|
833
1157
|
// when a thumb scrolls in/out/in rapidly.
|
|
834
|
-
#
|
|
1158
|
+
#thumbnailWork = new Map();
|
|
1159
|
+
#thumbnailWorkCount = signal(0, ...(ngDevMode ? [{ debugName: "#thumbnailWorkCount" }] : /* istanbul ignore next */ []));
|
|
1160
|
+
#thumbnailSlotQueue = [];
|
|
1161
|
+
#activeThumbnailSlots = 0;
|
|
1162
|
+
#continuousTasks = new Map();
|
|
1163
|
+
#continuousReservations = new Map();
|
|
1164
|
+
#continuousFrame = null;
|
|
1165
|
+
#layoutGeneration = 0;
|
|
1166
|
+
#pageHeights = [];
|
|
1167
|
+
#heightIndex = new PdfHeightIndex(24);
|
|
835
1168
|
// Cached resolved source object that getDocument actually consumed — used
|
|
836
1169
|
// for download fallback when caller passed a non-URL source.
|
|
837
|
-
#lastBlobUrl = null;
|
|
838
1170
|
// Track ALL blob URLs we created so we can revoke on source change + destroy.
|
|
839
1171
|
#ownedBlobUrls = new Set();
|
|
840
1172
|
// Race guards mirror preview-image's #loadToken pattern.
|
|
841
1173
|
#loadToken = 0;
|
|
1174
|
+
#currentLoadingTask = null;
|
|
842
1175
|
#currentRenderTask = null;
|
|
1176
|
+
#currentPrintJob = null;
|
|
1177
|
+
#printGeneration = 0;
|
|
1178
|
+
#downloadGeneration = 0;
|
|
843
1179
|
#renderToken = 0;
|
|
1180
|
+
#destroyed = false;
|
|
844
1181
|
// ==========================================
|
|
845
1182
|
// COMPUTED (template-readable)
|
|
846
1183
|
// ==========================================
|
|
@@ -857,6 +1194,30 @@ class SdPreviewPdf {
|
|
|
857
1194
|
fileSize = this.#fileSize.asReadonly();
|
|
858
1195
|
loadError$ = this.#loadError.asReadonly();
|
|
859
1196
|
thumbCache = this.#thumbCache.asReadonly();
|
|
1197
|
+
thumbnailWorkCount = this.#thumbnailWorkCount.asReadonly();
|
|
1198
|
+
pageTextCacheSize = this.#pageTextCacheSize.asReadonly();
|
|
1199
|
+
continuousPages = this.#continuousPages.asReadonly();
|
|
1200
|
+
continuousTopSpacer = this.#continuousTopSpacer.asReadonly();
|
|
1201
|
+
continuousBottomSpacer = this.#continuousBottomSpacer.asReadonly();
|
|
1202
|
+
continuousPageHeights = computed(() => {
|
|
1203
|
+
this.#continuousHeightVersion();
|
|
1204
|
+
return Object.fromEntries(this.#continuousPages().map(page => [page, this.#pageHeights[page - 1] ?? 1]));
|
|
1205
|
+
}, ...(ngDevMode ? [{ debugName: "continuousPageHeights" }] : /* istanbul ignore next */ []));
|
|
1206
|
+
outline = this.#outline.asReadonly();
|
|
1207
|
+
outlineFocusId = this.#outlineFocusId.asReadonly();
|
|
1208
|
+
visibleOutline = computed(() => {
|
|
1209
|
+
const rows = [];
|
|
1210
|
+
const expanded = this.#outlineExpanded();
|
|
1211
|
+
const visit = (items, level, parentId) => {
|
|
1212
|
+
for (const item of items) {
|
|
1213
|
+
rows.push({ item, level, parentId });
|
|
1214
|
+
if (item.children.length > 0 && expanded.has(item.id))
|
|
1215
|
+
visit(item.children, level + 1, item.id);
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
visit(this.#outline(), 1, null);
|
|
1219
|
+
return rows;
|
|
1220
|
+
}, ...(ngDevMode ? [{ debugName: "visibleOutline" }] : /* istanbul ignore next */ []));
|
|
860
1221
|
numPages = computed(() => this.#meta()?.numPages ?? 0, ...(ngDevMode ? [{ debugName: "numPages" }] : /* istanbul ignore next */ []));
|
|
861
1222
|
canPrev = computed(() => this.#activePage() > 1, ...(ngDevMode ? [{ debugName: "canPrev" }] : /* istanbul ignore next */ []));
|
|
862
1223
|
canNext = computed(() => {
|
|
@@ -871,13 +1232,37 @@ class SdPreviewPdf {
|
|
|
871
1232
|
return Math.min(100, Math.round((p.loaded / p.total) * 100));
|
|
872
1233
|
}, ...(ngDevMode ? [{ debugName: "loadPercent" }] : /* istanbul ignore next */ []));
|
|
873
1234
|
isSidebarOpen = computed(() => this.#sidebarOpenInternal() && this.#sidebarModeInternal() !== 'none', ...(ngDevMode ? [{ debugName: "isSidebarOpen" }] : /* istanbul ignore next */ []));
|
|
1235
|
+
canDownload = computed(() => {
|
|
1236
|
+
const source = this.source();
|
|
1237
|
+
if (!this.downloadable() || !source)
|
|
1238
|
+
return false;
|
|
1239
|
+
if (this.#requiresLoadedDocumentBytes(source)) {
|
|
1240
|
+
return this.#browser.canDownloadBlob && this.#stage() === 'ready' && this.#pdfDoc() !== null;
|
|
1241
|
+
}
|
|
1242
|
+
if (typeof source === 'string' || ('url' in source && typeof source.url === 'string')) {
|
|
1243
|
+
return this.#browser.canDownloadUrl;
|
|
1244
|
+
}
|
|
1245
|
+
return this.#browser.canDownloadBlob;
|
|
1246
|
+
}, ...(ngDevMode ? [{ debugName: "canDownload" }] : /* istanbul ignore next */ []));
|
|
1247
|
+
canFullscreen = computed(() => this.#browser.canFullscreen, ...(ngDevMode ? [{ debugName: "canFullscreen" }] : /* istanbul ignore next */ []));
|
|
1248
|
+
canPrint = computed(() => this.#printAdapter.isSupported && this.#stage() === 'ready' && this.#pdfDoc() !== null, ...(ngDevMode ? [{ debugName: "canPrint" }] : /* istanbul ignore next */ []));
|
|
874
1249
|
pageList = computed(() => {
|
|
875
1250
|
const n = this.numPages();
|
|
876
1251
|
return n > 0 ? Array.from({ length: n }, (_, i) => i + 1) : [];
|
|
877
1252
|
}, ...(ngDevMode ? [{ debugName: "pageList" }] : /* istanbul ignore next */ []));
|
|
878
1253
|
// Alias used by the template's thumbnail @for — keeps the JSX-y `pageNumbers`
|
|
879
1254
|
// name from the design handoff while reusing the same computed.
|
|
880
|
-
pageNumbers =
|
|
1255
|
+
pageNumbers = computed(() => {
|
|
1256
|
+
const count = this.numPages();
|
|
1257
|
+
const start = Math.max(0, Math.min(this.#thumbnailWindowStart(), Math.max(0, count - _a.THUMBNAIL_WINDOW_SIZE)));
|
|
1258
|
+
const length = Math.min(_a.THUMBNAIL_WINDOW_SIZE, count - start);
|
|
1259
|
+
return Array.from({ length }, (_, index) => start + index + 1);
|
|
1260
|
+
}, ...(ngDevMode ? [{ debugName: "pageNumbers" }] : /* istanbul ignore next */ []));
|
|
1261
|
+
thumbnailTopSpacer = computed(() => this.#thumbnailWindowStart() * _a.THUMBNAIL_ITEM_HEIGHT, ...(ngDevMode ? [{ debugName: "thumbnailTopSpacer" }] : /* istanbul ignore next */ []));
|
|
1262
|
+
thumbnailBottomSpacer = computed(() => {
|
|
1263
|
+
const renderedEnd = this.#thumbnailWindowStart() + this.pageNumbers().length;
|
|
1264
|
+
return Math.max(0, this.numPages() - renderedEnd) * _a.THUMBNAIL_ITEM_HEIGHT;
|
|
1265
|
+
}, ...(ngDevMode ? [{ debugName: "thumbnailBottomSpacer" }] : /* istanbul ignore next */ []));
|
|
881
1266
|
// Search readonly accessors for the template + tests. Read-only by design;
|
|
882
1267
|
// mutations go through `search()` / `searchNext()` etc.
|
|
883
1268
|
searchBarOpen = this.#searchBarOpen.asReadonly();
|
|
@@ -887,6 +1272,7 @@ class SdPreviewPdf {
|
|
|
887
1272
|
searchCaseSensitive = this.#searchCaseSensitive.asReadonly();
|
|
888
1273
|
searchWholeWord = this.#searchWholeWord.asReadonly();
|
|
889
1274
|
searchTotal = computed(() => this.#searchResults().length, ...(ngDevMode ? [{ debugName: "searchTotal" }] : /* istanbul ignore next */ []));
|
|
1275
|
+
searchTruncated = this.#searchTruncated.asReadonly();
|
|
890
1276
|
// 1-based "X of N" indicator for the search bar counter. Reads 0 when
|
|
891
1277
|
// there are no results so the template can hide / dim the counter.
|
|
892
1278
|
searchCurrent = computed(() => {
|
|
@@ -908,6 +1294,7 @@ class SdPreviewPdf {
|
|
|
908
1294
|
wholeWord: this.#searchWholeWord(),
|
|
909
1295
|
results: this.#searchResults(),
|
|
910
1296
|
activeIndex: this.#searchActiveIndex(),
|
|
1297
|
+
truncated: this.#searchTruncated(),
|
|
911
1298
|
}), ...(ngDevMode ? [{ debugName: "searchState" }] : /* istanbul ignore next */ []));
|
|
912
1299
|
// ==========================================
|
|
913
1300
|
// CONSTRUCTOR — declarative wiring
|
|
@@ -921,34 +1308,25 @@ class SdPreviewPdf {
|
|
|
921
1308
|
});
|
|
922
1309
|
effect(() => {
|
|
923
1310
|
const m = this.sidebar();
|
|
924
|
-
// 'outline' + 'search' are deferred — selecting them through the input
|
|
925
|
-
// is allowed (we still toggle the tab UI) but the body shows the deferred
|
|
926
|
-
// placeholder. Don't force-fallback to 'thumbnails' so the input value
|
|
927
|
-
// is honored visibly.
|
|
928
1311
|
this.#sidebarModeInternal.set(m);
|
|
929
1312
|
});
|
|
1313
|
+
let previousScrollModeInput;
|
|
930
1314
|
effect(() => {
|
|
931
1315
|
const m = this.scrollMode();
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
console.warn('[sd-preview-pdf] scrollMode="continuous" is deferred; falling back to single-page rendering.' // @i18n-ignore
|
|
936
|
-
);
|
|
937
|
-
}
|
|
1316
|
+
if (m === previousScrollModeInput)
|
|
1317
|
+
return;
|
|
1318
|
+
previousScrollModeInput = m;
|
|
938
1319
|
this.#scrollModeInternal.set(m);
|
|
1320
|
+
queueMicrotask(() => this.#onScrollModeChanged());
|
|
939
1321
|
});
|
|
940
1322
|
effect(() => {
|
|
941
1323
|
// Reset zoom mode when initialZoom input changes — only fires when the
|
|
942
1324
|
// input itself updates (not when user clicks zoom +/-).
|
|
943
1325
|
this.#zoomMode.set(this.initialZoom());
|
|
944
1326
|
});
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
};
|
|
949
|
-
document.addEventListener('fullscreenchange', onFullscreenChange);
|
|
950
|
-
this.#destroyRef.onDestroy(() => {
|
|
951
|
-
document.removeEventListener('fullscreenchange', onFullscreenChange);
|
|
1327
|
+
const removeFullscreenListener = this.#browser.listenFullscreen(this.#hostEl.nativeElement, active => {
|
|
1328
|
+
if (!this.#destroyed)
|
|
1329
|
+
this.#isFullscreen.set(active);
|
|
952
1330
|
});
|
|
953
1331
|
// React to source changes — destroy previous doc, load new one.
|
|
954
1332
|
// WHY untracked-via-queueMicrotask: `effect()` tracks every signal READ
|
|
@@ -963,6 +1341,8 @@ class SdPreviewPdf {
|
|
|
963
1341
|
});
|
|
964
1342
|
// Auto-focus host after first render so keyboard works immediately.
|
|
965
1343
|
afterNextRender(() => {
|
|
1344
|
+
if (!this.#browser.isBrowser || this.#destroyed)
|
|
1345
|
+
return;
|
|
966
1346
|
try {
|
|
967
1347
|
this.#hostEl.nativeElement.focus({ preventScroll: true });
|
|
968
1348
|
}
|
|
@@ -979,6 +1359,24 @@ class SdPreviewPdf {
|
|
|
979
1359
|
const refs = this.thumbCanvases();
|
|
980
1360
|
this.#syncThumbObserver(refs.map(r => r.nativeElement));
|
|
981
1361
|
});
|
|
1362
|
+
effect(() => {
|
|
1363
|
+
const refs = this.continuousCanvases();
|
|
1364
|
+
this.#scheduleContinuousRenders(refs.map(ref => ref.nativeElement));
|
|
1365
|
+
});
|
|
1366
|
+
effect(onCleanup => {
|
|
1367
|
+
const stage = this.stageEl()?.nativeElement;
|
|
1368
|
+
if (!stage)
|
|
1369
|
+
return;
|
|
1370
|
+
const cleanup = this.#browser.observeResize(stage, () => {
|
|
1371
|
+
if (this.#destroyed || this.#stage() !== 'ready')
|
|
1372
|
+
return;
|
|
1373
|
+
if (this.#scrollModeInternal() === 'continuous')
|
|
1374
|
+
this.#invalidateContinuousLayout();
|
|
1375
|
+
else
|
|
1376
|
+
void this.#renderActivePage();
|
|
1377
|
+
});
|
|
1378
|
+
onCleanup(cleanup);
|
|
1379
|
+
});
|
|
982
1380
|
// Re-apply highlights when the rendered page changes OR when the search
|
|
983
1381
|
// term / active hit changes. WHY a separate effect: text-layer overlay
|
|
984
1382
|
// doesn't exist in this commit, so we hook the page canvas wrapper and
|
|
@@ -995,22 +1393,26 @@ class SdPreviewPdf {
|
|
|
995
1393
|
});
|
|
996
1394
|
// Cleanup on destroy.
|
|
997
1395
|
this.#destroyRef.onDestroy(() => {
|
|
1396
|
+
this.#destroyed = true;
|
|
1397
|
+
this.#loadToken++;
|
|
1398
|
+
this.#searchToken++;
|
|
1399
|
+
this.#cancelQueuedSearch();
|
|
1400
|
+
this.#downloadGeneration++;
|
|
1401
|
+
this.#renderToken++;
|
|
1402
|
+
this.#cancelLoadingTask();
|
|
998
1403
|
this.#cancelRender();
|
|
1404
|
+
this.#cancelContinuousRenders();
|
|
1405
|
+
this.#cancelThumbnailRenders();
|
|
1406
|
+
this.#cancelPrint();
|
|
999
1407
|
this.#destroyDoc();
|
|
1000
1408
|
this.#revokeAllBlobs();
|
|
1001
|
-
this.#
|
|
1002
|
-
this.#
|
|
1003
|
-
this.#
|
|
1004
|
-
this.#
|
|
1005
|
-
|
|
1006
|
-
document.exitFullscreen?.().catch(() => undefined);
|
|
1007
|
-
}
|
|
1409
|
+
this.#thumbObserverCleanup?.();
|
|
1410
|
+
this.#thumbObserverCleanup = null;
|
|
1411
|
+
this.#thumbnailCacheLru.clear();
|
|
1412
|
+
this.#clearPageTextCache();
|
|
1413
|
+
removeFullscreenListener();
|
|
1008
1414
|
});
|
|
1009
1415
|
}
|
|
1010
|
-
ngOnDestroy() {
|
|
1011
|
-
// Cleanup logic dời vào DestroyRef.onDestroy ở constructor — giữ hook để
|
|
1012
|
-
// tương thích với `implements OnDestroy` (giúp test code `spyOn(comp, 'ngOnDestroy')`).
|
|
1013
|
-
}
|
|
1014
1416
|
// ==========================================
|
|
1015
1417
|
// PUBLIC API
|
|
1016
1418
|
// ==========================================
|
|
@@ -1019,11 +1421,18 @@ class SdPreviewPdf {
|
|
|
1019
1421
|
if (n <= 0)
|
|
1020
1422
|
return;
|
|
1021
1423
|
const target = Math.max(1, Math.min(page, n));
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1424
|
+
const changed = target !== this.#activePage();
|
|
1425
|
+
if (changed) {
|
|
1426
|
+
this.#activePage.set(target);
|
|
1427
|
+
this.pageChange.emit(target);
|
|
1428
|
+
}
|
|
1429
|
+
this.#ensureThumbnailPageVisible(target);
|
|
1430
|
+
if (this.#scrollModeInternal() === 'continuous') {
|
|
1431
|
+
this.#positionContinuousPage(target);
|
|
1432
|
+
}
|
|
1433
|
+
else if (changed) {
|
|
1434
|
+
void this.#renderActivePage();
|
|
1435
|
+
}
|
|
1027
1436
|
}
|
|
1028
1437
|
nextPage() {
|
|
1029
1438
|
this.goToPage(this.#activePage() + 1);
|
|
@@ -1051,15 +1460,19 @@ class SdPreviewPdf {
|
|
|
1051
1460
|
this.#setZoom(mode);
|
|
1052
1461
|
}
|
|
1053
1462
|
else {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1463
|
+
if (this.#scrollModeInternal() === 'continuous')
|
|
1464
|
+
this.#invalidateContinuousLayout();
|
|
1465
|
+
else
|
|
1466
|
+
void this.#renderActivePage();
|
|
1057
1467
|
}
|
|
1058
1468
|
}
|
|
1059
1469
|
rotate(direction) {
|
|
1060
1470
|
const delta = direction === 'right' ? 90 : -90;
|
|
1061
1471
|
this.#rotation.update(r => (((r + delta) % 360) + 360) % 360);
|
|
1062
|
-
this.#
|
|
1472
|
+
if (this.#scrollModeInternal() === 'continuous')
|
|
1473
|
+
this.#invalidateContinuousLayout();
|
|
1474
|
+
else
|
|
1475
|
+
void this.#renderActivePage();
|
|
1063
1476
|
}
|
|
1064
1477
|
toggleSidebar() {
|
|
1065
1478
|
this.#sidebarOpenInternal.update(v => !v);
|
|
@@ -1075,59 +1488,114 @@ class SdPreviewPdf {
|
|
|
1075
1488
|
this.openSearch();
|
|
1076
1489
|
}
|
|
1077
1490
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1491
|
+
onSidebarTabKeyDown(event, currentMode) {
|
|
1492
|
+
const modes = ['thumbnails', 'outline', 'search'];
|
|
1493
|
+
const currentIndex = modes.indexOf(currentMode);
|
|
1494
|
+
let targetIndex;
|
|
1495
|
+
switch (event.key) {
|
|
1496
|
+
case 'ArrowRight':
|
|
1497
|
+
targetIndex = (currentIndex + 1) % modes.length;
|
|
1498
|
+
break;
|
|
1499
|
+
case 'ArrowLeft':
|
|
1500
|
+
targetIndex = (currentIndex - 1 + modes.length) % modes.length;
|
|
1501
|
+
break;
|
|
1502
|
+
case 'Home':
|
|
1503
|
+
targetIndex = 0;
|
|
1504
|
+
break;
|
|
1505
|
+
case 'End':
|
|
1506
|
+
targetIndex = modes.length - 1;
|
|
1507
|
+
break;
|
|
1508
|
+
default:
|
|
1509
|
+
return;
|
|
1085
1510
|
}
|
|
1511
|
+
event.preventDefault();
|
|
1512
|
+
event.stopPropagation();
|
|
1513
|
+
const target = modes[targetIndex];
|
|
1514
|
+
this.setSidebarMode(target);
|
|
1515
|
+
queueMicrotask(() => {
|
|
1516
|
+
const tab = this.sidebarTabRefs().find(ref => ref.nativeElement.dataset['sidebarMode'] === target);
|
|
1517
|
+
tab?.nativeElement.focus();
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
onThumbnailSidebarScroll(event) {
|
|
1521
|
+
if (this.#sidebarModeInternal() !== 'thumbnails')
|
|
1522
|
+
return;
|
|
1523
|
+
const element = event.currentTarget;
|
|
1524
|
+
if (!element)
|
|
1525
|
+
return;
|
|
1526
|
+
const firstVisible = Math.floor(element.scrollTop / _a.THUMBNAIL_ITEM_HEIGHT);
|
|
1527
|
+
const start = Math.max(0, Math.min(firstVisible - 8, Math.max(0, this.numPages() - _a.THUMBNAIL_WINDOW_SIZE)));
|
|
1528
|
+
this.#thumbnailWindowStart.set(start);
|
|
1529
|
+
}
|
|
1530
|
+
setScrollMode(mode) {
|
|
1531
|
+
if (this.#scrollModeInternal() === mode)
|
|
1532
|
+
return;
|
|
1086
1533
|
this.#scrollModeInternal.set(mode);
|
|
1534
|
+
this.#onScrollModeChanged();
|
|
1087
1535
|
}
|
|
1088
1536
|
downloadFile() {
|
|
1089
|
-
|
|
1090
|
-
|
|
1537
|
+
void this.downloadFileAsync();
|
|
1538
|
+
}
|
|
1539
|
+
async downloadFileAsync() {
|
|
1540
|
+
const generation = ++this.#downloadGeneration;
|
|
1541
|
+
if (!this.canDownload() || this.#destroyed)
|
|
1542
|
+
return false;
|
|
1091
1543
|
const src = this.source();
|
|
1544
|
+
if (!src)
|
|
1545
|
+
return false;
|
|
1092
1546
|
const filename = this.#filename();
|
|
1093
|
-
|
|
1094
|
-
// Content-Disposition; for File/Blob/buffer sources we re-emit the blob.
|
|
1547
|
+
const doc = this.#pdfDoc();
|
|
1095
1548
|
let href = null;
|
|
1096
|
-
|
|
1097
|
-
|
|
1549
|
+
let temporaryHref = null;
|
|
1550
|
+
if (this.#requiresLoadedDocumentBytes(src)) {
|
|
1551
|
+
if (!doc)
|
|
1552
|
+
return false;
|
|
1553
|
+
let data;
|
|
1554
|
+
try {
|
|
1555
|
+
data = new Uint8Array(await doc.getData());
|
|
1556
|
+
}
|
|
1557
|
+
catch {
|
|
1558
|
+
return false;
|
|
1559
|
+
}
|
|
1560
|
+
if (!this.#isCurrentDownload(generation, src, doc))
|
|
1561
|
+
return false;
|
|
1562
|
+
const blob = this.#browser.createPdfBlob(data);
|
|
1563
|
+
href = blob ? this.#browser.createObjectUrl(blob) : null;
|
|
1564
|
+
temporaryHref = this.#trackTemporaryDownloadUrl(href);
|
|
1098
1565
|
}
|
|
1099
|
-
else if (
|
|
1100
|
-
href =
|
|
1101
|
-
this.#ownedBlobUrls.add(href);
|
|
1566
|
+
else if (typeof src === 'string') {
|
|
1567
|
+
href = src;
|
|
1102
1568
|
}
|
|
1103
|
-
else if (this.#
|
|
1104
|
-
href = this.#
|
|
1569
|
+
else if (this.#browser.isFile(src) || this.#browser.isBlob(src)) {
|
|
1570
|
+
href = this.#browser.createObjectUrl(src);
|
|
1571
|
+
temporaryHref = this.#trackTemporaryDownloadUrl(href);
|
|
1105
1572
|
}
|
|
1106
1573
|
else if (src && typeof src === 'object' && 'url' in src && typeof src.url === 'string') {
|
|
1107
1574
|
href = src.url;
|
|
1108
1575
|
}
|
|
1109
|
-
else if (src instanceof ArrayBuffer || src instanceof Uint8Array) {
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1112
|
-
this.#
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1576
|
+
else if (src instanceof ArrayBuffer || src instanceof Uint8Array || (src && typeof src === 'object' && 'data' in src)) {
|
|
1577
|
+
const raw = src instanceof ArrayBuffer || src instanceof Uint8Array ? src : src.data;
|
|
1578
|
+
const data = raw instanceof Uint8Array ? new Uint8Array(raw) : new Uint8Array(raw.slice(0));
|
|
1579
|
+
const blob = this.#browser.createPdfBlob(data);
|
|
1580
|
+
href = blob ? this.#browser.createObjectUrl(blob) : null;
|
|
1581
|
+
temporaryHref = this.#trackTemporaryDownloadUrl(href);
|
|
1582
|
+
}
|
|
1583
|
+
if (!href || !this.#isCurrentDownload(generation, src, doc)) {
|
|
1584
|
+
if (temporaryHref)
|
|
1585
|
+
this.#scheduleTemporaryDownloadUrlRelease(temporaryHref);
|
|
1586
|
+
return false;
|
|
1587
|
+
}
|
|
1588
|
+
const downloaded = this.#browser.download(href, filename);
|
|
1589
|
+
if (temporaryHref)
|
|
1590
|
+
this.#scheduleTemporaryDownloadUrlRelease(temporaryHref);
|
|
1591
|
+
if (downloaded && !this.#destroyed)
|
|
1592
|
+
this.download.emit({ filename });
|
|
1593
|
+
return downloaded;
|
|
1123
1594
|
}
|
|
1124
1595
|
toggleFullscreen() {
|
|
1125
|
-
if (!
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
else {
|
|
1129
|
-
document.exitFullscreen?.().catch(() => undefined);
|
|
1130
|
-
}
|
|
1596
|
+
if (!this.canFullscreen())
|
|
1597
|
+
return;
|
|
1598
|
+
void this.#browser.toggleFullscreen(this.#hostEl.nativeElement).catch(() => undefined);
|
|
1131
1599
|
}
|
|
1132
1600
|
/** Programmatic equivalent of clicking the X — emits the close output. */
|
|
1133
1601
|
requestClose() {
|
|
@@ -1137,11 +1605,125 @@ class SdPreviewPdf {
|
|
|
1137
1605
|
retryLoad() {
|
|
1138
1606
|
this.#loadDocument(this.source());
|
|
1139
1607
|
}
|
|
1140
|
-
// TODO(preview-pdf): printFile + scroll-mode continuous remain deferred.
|
|
1141
|
-
// Search is now implemented below.
|
|
1142
1608
|
printFile() {
|
|
1143
|
-
|
|
1144
|
-
|
|
1609
|
+
void this.print();
|
|
1610
|
+
}
|
|
1611
|
+
async print() {
|
|
1612
|
+
const generation = ++this.#printGeneration;
|
|
1613
|
+
this.#cancelPrintJob();
|
|
1614
|
+
const doc = this.#pdfDoc();
|
|
1615
|
+
if (!doc || this.#destroyed || !this.canPrint())
|
|
1616
|
+
return;
|
|
1617
|
+
const loadToken = this.#loadToken;
|
|
1618
|
+
let data;
|
|
1619
|
+
try {
|
|
1620
|
+
data = new Uint8Array(await doc.getData());
|
|
1621
|
+
}
|
|
1622
|
+
catch {
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
if (generation !== this.#printGeneration || !this.#isActiveLoad(loadToken) || this.#pdfDoc() !== doc)
|
|
1626
|
+
return;
|
|
1627
|
+
const job = this.#printAdapter.start(data, this.#filename());
|
|
1628
|
+
if (!job)
|
|
1629
|
+
return;
|
|
1630
|
+
this.#currentPrintJob = job;
|
|
1631
|
+
try {
|
|
1632
|
+
await job.finished;
|
|
1633
|
+
}
|
|
1634
|
+
catch {
|
|
1635
|
+
job.cancel();
|
|
1636
|
+
}
|
|
1637
|
+
finally {
|
|
1638
|
+
if (this.#currentPrintJob === job)
|
|
1639
|
+
this.#currentPrintJob = null;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
isOutlineExpanded(id) {
|
|
1643
|
+
return this.#outlineExpanded().has(id);
|
|
1644
|
+
}
|
|
1645
|
+
toggleOutlineItem(id, event) {
|
|
1646
|
+
event?.stopPropagation();
|
|
1647
|
+
const item = this.#findOutlineItem(id);
|
|
1648
|
+
if (!item || item.children.length === 0) {
|
|
1649
|
+
this.#outlineFocusId.set(id);
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
this.#outlineExpanded.update(current => {
|
|
1653
|
+
const next = new Set(current);
|
|
1654
|
+
if (next.has(id))
|
|
1655
|
+
next.delete(id);
|
|
1656
|
+
else
|
|
1657
|
+
next.add(id);
|
|
1658
|
+
return next;
|
|
1659
|
+
});
|
|
1660
|
+
this.#outlineFocusId.set(id);
|
|
1661
|
+
}
|
|
1662
|
+
focusOutlineItem(id) {
|
|
1663
|
+
this.#outlineFocusId.set(id);
|
|
1664
|
+
}
|
|
1665
|
+
activateOutlineItem(id) {
|
|
1666
|
+
const item = this.#findOutlineItem(id);
|
|
1667
|
+
if (!item)
|
|
1668
|
+
return;
|
|
1669
|
+
this.#outlineFocusId.set(id);
|
|
1670
|
+
if (item.page !== null) {
|
|
1671
|
+
this.goToPage(item.page);
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
if (item.children.length > 0)
|
|
1675
|
+
this.toggleOutlineItem(id);
|
|
1676
|
+
}
|
|
1677
|
+
onOutlineKeyDown(event, id) {
|
|
1678
|
+
const rows = this.visibleOutline();
|
|
1679
|
+
const index = rows.findIndex(row => row.item.id === id);
|
|
1680
|
+
if (index < 0)
|
|
1681
|
+
return;
|
|
1682
|
+
const row = rows[index];
|
|
1683
|
+
const item = row.item;
|
|
1684
|
+
switch (event.key) {
|
|
1685
|
+
case 'ArrowDown':
|
|
1686
|
+
event.preventDefault();
|
|
1687
|
+
this.#focusOutlineRow(rows[Math.min(rows.length - 1, index + 1)].item.id);
|
|
1688
|
+
break;
|
|
1689
|
+
case 'ArrowUp':
|
|
1690
|
+
event.preventDefault();
|
|
1691
|
+
this.#focusOutlineRow(rows[Math.max(0, index - 1)].item.id);
|
|
1692
|
+
break;
|
|
1693
|
+
case 'ArrowRight':
|
|
1694
|
+
event.preventDefault();
|
|
1695
|
+
if (item.children.length === 0)
|
|
1696
|
+
break;
|
|
1697
|
+
if (!this.isOutlineExpanded(id))
|
|
1698
|
+
this.toggleOutlineItem(id);
|
|
1699
|
+
else
|
|
1700
|
+
this.#focusOutlineRow(item.children[0].id);
|
|
1701
|
+
break;
|
|
1702
|
+
case 'ArrowLeft':
|
|
1703
|
+
event.preventDefault();
|
|
1704
|
+
if (item.children.length > 0 && this.isOutlineExpanded(id))
|
|
1705
|
+
this.toggleOutlineItem(id);
|
|
1706
|
+
else if (row.parentId)
|
|
1707
|
+
this.#focusOutlineRow(row.parentId);
|
|
1708
|
+
break;
|
|
1709
|
+
case 'Home':
|
|
1710
|
+
event.preventDefault();
|
|
1711
|
+
this.#focusOutlineRow(rows[0].item.id);
|
|
1712
|
+
break;
|
|
1713
|
+
case 'End':
|
|
1714
|
+
event.preventDefault();
|
|
1715
|
+
this.#focusOutlineRow(rows[rows.length - 1].item.id);
|
|
1716
|
+
break;
|
|
1717
|
+
case 'Enter':
|
|
1718
|
+
case ' ':
|
|
1719
|
+
if (item.url && item.page === null)
|
|
1720
|
+
break;
|
|
1721
|
+
event.preventDefault();
|
|
1722
|
+
this.activateOutlineItem(id);
|
|
1723
|
+
break;
|
|
1724
|
+
default:
|
|
1725
|
+
break;
|
|
1726
|
+
}
|
|
1145
1727
|
}
|
|
1146
1728
|
/**
|
|
1147
1729
|
* Run a full-document search for `term`. Returns the result count.
|
|
@@ -1161,7 +1743,7 @@ class SdPreviewPdf {
|
|
|
1161
1743
|
*/
|
|
1162
1744
|
async search(term, options) {
|
|
1163
1745
|
const token = ++this.#searchToken;
|
|
1164
|
-
const cleanTerm = (term ?? '').trim();
|
|
1746
|
+
const cleanTerm = (term ?? '').trim().normalize('NFC');
|
|
1165
1747
|
if (options?.caseSensitive !== undefined) {
|
|
1166
1748
|
this.#searchCaseSensitive.set(options.caseSensitive);
|
|
1167
1749
|
}
|
|
@@ -1169,7 +1751,9 @@ class SdPreviewPdf {
|
|
|
1169
1751
|
this.#searchWholeWord.set(options.wholeWord);
|
|
1170
1752
|
}
|
|
1171
1753
|
this.#searchTerm.set(cleanTerm);
|
|
1754
|
+
this.#searchTruncated.set(false);
|
|
1172
1755
|
if (!cleanTerm) {
|
|
1756
|
+
this.#cancelQueuedSearch();
|
|
1173
1757
|
this.#searchResults.set([]);
|
|
1174
1758
|
this.#searchActiveIndex.set(-1);
|
|
1175
1759
|
this.#emitSearchChange();
|
|
@@ -1177,6 +1761,7 @@ class SdPreviewPdf {
|
|
|
1177
1761
|
}
|
|
1178
1762
|
const doc = this.#pdfDoc();
|
|
1179
1763
|
if (!doc) {
|
|
1764
|
+
this.#cancelQueuedSearch();
|
|
1180
1765
|
this.#searchResults.set([]);
|
|
1181
1766
|
this.#searchActiveIndex.set(-1);
|
|
1182
1767
|
this.#emitSearchChange();
|
|
@@ -1184,35 +1769,51 @@ class SdPreviewPdf {
|
|
|
1184
1769
|
}
|
|
1185
1770
|
const re = this.#buildSearchRegex(cleanTerm, this.#searchCaseSensitive(), this.#searchWholeWord());
|
|
1186
1771
|
if (!re) {
|
|
1772
|
+
this.#cancelQueuedSearch();
|
|
1187
1773
|
this.#searchResults.set([]);
|
|
1188
1774
|
this.#searchActiveIndex.set(-1);
|
|
1189
1775
|
this.#emitSearchChange();
|
|
1190
1776
|
return 0;
|
|
1191
1777
|
}
|
|
1778
|
+
return new Promise(resolve => {
|
|
1779
|
+
this.#enqueueSearchRequest({ token, doc, expression: re, resolve });
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
async #executeSearchRequest(request) {
|
|
1783
|
+
const { token, doc, expression } = request;
|
|
1192
1784
|
const results = [];
|
|
1193
|
-
|
|
1785
|
+
const textScan = {
|
|
1786
|
+
snapshot: new Map(this.#pageTextCache),
|
|
1787
|
+
staged: new Map(),
|
|
1788
|
+
};
|
|
1789
|
+
pageLoop: for (let p = 1; p <= doc.numPages; p++) {
|
|
1194
1790
|
if (token !== this.#searchToken)
|
|
1195
1791
|
return 0; // newer search superseded us
|
|
1196
|
-
const text = await this.#getPageText(doc, p);
|
|
1792
|
+
const text = await this.#getPageText(doc, p, token, textScan);
|
|
1197
1793
|
if (token !== this.#searchToken)
|
|
1198
1794
|
return 0;
|
|
1199
1795
|
// re is /g — must be re-created or `lastIndex` reset between pages.
|
|
1200
|
-
|
|
1796
|
+
expression.lastIndex = 0;
|
|
1201
1797
|
let m;
|
|
1202
|
-
while ((m =
|
|
1798
|
+
while ((m = expression.exec(text)) !== null) {
|
|
1203
1799
|
const start = m.index;
|
|
1204
1800
|
const matched = m[0];
|
|
1205
1801
|
const end = start + matched.length;
|
|
1206
1802
|
const before = text.slice(Math.max(0, start - 30), start);
|
|
1207
1803
|
const after = text.slice(end, end + 30);
|
|
1804
|
+
if (results.length >= _a.MAX_SEARCH_RESULTS) {
|
|
1805
|
+
this.#searchTruncated.set(true);
|
|
1806
|
+
break pageLoop;
|
|
1807
|
+
}
|
|
1208
1808
|
results.push({ page: p, before, term: matched, after });
|
|
1209
1809
|
// Defensive: zero-length match would loop forever.
|
|
1210
1810
|
if (matched.length === 0)
|
|
1211
|
-
|
|
1811
|
+
expression.lastIndex++;
|
|
1212
1812
|
}
|
|
1213
1813
|
}
|
|
1214
1814
|
if (token !== this.#searchToken)
|
|
1215
1815
|
return 0;
|
|
1816
|
+
this.#commitPageTextScan(textScan);
|
|
1216
1817
|
this.#searchResults.set(results);
|
|
1217
1818
|
// Activate the first result so the user sees something immediately;
|
|
1218
1819
|
// tests can assert this via `searchActiveIndex()`. Skip when empty.
|
|
@@ -1224,6 +1825,35 @@ class SdPreviewPdf {
|
|
|
1224
1825
|
this.#emitSearchChange();
|
|
1225
1826
|
return results.length;
|
|
1226
1827
|
}
|
|
1828
|
+
#enqueueSearchRequest(request) {
|
|
1829
|
+
if (this.#searchWorkerActive) {
|
|
1830
|
+
this.#queuedSearchRequest?.resolve(0);
|
|
1831
|
+
this.#queuedSearchRequest = request;
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
this.#searchWorkerActive = true;
|
|
1835
|
+
void this.#drainSearchRequests(request);
|
|
1836
|
+
}
|
|
1837
|
+
async #drainSearchRequests(initial) {
|
|
1838
|
+
let request = initial;
|
|
1839
|
+
while (request) {
|
|
1840
|
+
let resultCount = 0;
|
|
1841
|
+
try {
|
|
1842
|
+
resultCount = await this.#executeSearchRequest(request);
|
|
1843
|
+
}
|
|
1844
|
+
catch {
|
|
1845
|
+
resultCount = 0;
|
|
1846
|
+
}
|
|
1847
|
+
request.resolve(resultCount);
|
|
1848
|
+
request = this.#queuedSearchRequest;
|
|
1849
|
+
this.#queuedSearchRequest = null;
|
|
1850
|
+
}
|
|
1851
|
+
this.#searchWorkerActive = false;
|
|
1852
|
+
}
|
|
1853
|
+
#cancelQueuedSearch() {
|
|
1854
|
+
this.#queuedSearchRequest?.resolve(0);
|
|
1855
|
+
this.#queuedSearchRequest = null;
|
|
1856
|
+
}
|
|
1227
1857
|
searchNext() {
|
|
1228
1858
|
const results = this.#searchResults();
|
|
1229
1859
|
if (results.length === 0)
|
|
@@ -1243,11 +1873,21 @@ class SdPreviewPdf {
|
|
|
1243
1873
|
this.goToPage(results[prev].page);
|
|
1244
1874
|
this.#emitSearchChange();
|
|
1245
1875
|
}
|
|
1876
|
+
activateSearchResult(index) {
|
|
1877
|
+
const result = this.#searchResults()[index];
|
|
1878
|
+
if (!result)
|
|
1879
|
+
return;
|
|
1880
|
+
this.#searchActiveIndex.set(index);
|
|
1881
|
+
this.goToPage(result.page);
|
|
1882
|
+
this.#emitSearchChange();
|
|
1883
|
+
}
|
|
1246
1884
|
clearSearch() {
|
|
1247
1885
|
this.#searchToken++;
|
|
1886
|
+
this.#cancelQueuedSearch();
|
|
1248
1887
|
this.#searchTerm.set('');
|
|
1249
1888
|
this.#searchResults.set([]);
|
|
1250
1889
|
this.#searchActiveIndex.set(-1);
|
|
1890
|
+
this.#searchTruncated.set(false);
|
|
1251
1891
|
this.#emitSearchChange();
|
|
1252
1892
|
}
|
|
1253
1893
|
/** Open the search bar and focus its input. Idempotent. */
|
|
@@ -1278,8 +1918,11 @@ class SdPreviewPdf {
|
|
|
1278
1918
|
}
|
|
1279
1919
|
}
|
|
1280
1920
|
/** Live binding from the search bar's `(input)` event. */
|
|
1281
|
-
onSearchInput(
|
|
1282
|
-
|
|
1921
|
+
onSearchInput(event) {
|
|
1922
|
+
const target = event.target;
|
|
1923
|
+
if (!target || typeof target !== 'object' || !('value' in target) || typeof target.value !== 'string')
|
|
1924
|
+
return;
|
|
1925
|
+
void this.search(target.value);
|
|
1283
1926
|
}
|
|
1284
1927
|
// ==========================================
|
|
1285
1928
|
// KEYBOARD — bound to HOST (not document)
|
|
@@ -1296,6 +1939,11 @@ class SdPreviewPdf {
|
|
|
1296
1939
|
this.openSearch();
|
|
1297
1940
|
return;
|
|
1298
1941
|
}
|
|
1942
|
+
if ((event.ctrlKey || event.metaKey) && (event.key === 'p' || event.key === 'P') && this.canPrint()) {
|
|
1943
|
+
event.preventDefault();
|
|
1944
|
+
this.printFile();
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1299
1947
|
// Keys while the search input is focused: Enter / Shift+Enter, F3 / Shift+F3
|
|
1300
1948
|
// cycle results; Esc closes the bar. Everything else (typing) passes through.
|
|
1301
1949
|
if (isSearchInput) {
|
|
@@ -1439,22 +2087,155 @@ class SdPreviewPdf {
|
|
|
1439
2087
|
// ==========================================
|
|
1440
2088
|
// INTERNALS
|
|
1441
2089
|
// ==========================================
|
|
2090
|
+
#findOutlineItem(id, items = this.#outline()) {
|
|
2091
|
+
for (const item of items) {
|
|
2092
|
+
if (item.id === id)
|
|
2093
|
+
return item;
|
|
2094
|
+
const nested = this.#findOutlineItem(id, item.children);
|
|
2095
|
+
if (nested)
|
|
2096
|
+
return nested;
|
|
2097
|
+
}
|
|
2098
|
+
return null;
|
|
2099
|
+
}
|
|
2100
|
+
#focusOutlineRow(id) {
|
|
2101
|
+
this.#outlineFocusId.set(id);
|
|
2102
|
+
queueMicrotask(() => {
|
|
2103
|
+
if (this.#destroyed)
|
|
2104
|
+
return;
|
|
2105
|
+
const ref = this.outlineItemRefs().find(candidate => candidate.nativeElement.dataset['outlineId'] === id);
|
|
2106
|
+
ref?.nativeElement.focus();
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
#setOutline(items) {
|
|
2110
|
+
const expanded = new Set();
|
|
2111
|
+
const collect = (nodes) => {
|
|
2112
|
+
for (const item of nodes) {
|
|
2113
|
+
if (item.children.length > 0)
|
|
2114
|
+
expanded.add(item.id);
|
|
2115
|
+
collect(item.children);
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
collect(items);
|
|
2119
|
+
this.#outline.set(items);
|
|
2120
|
+
this.#outlineExpanded.set(expanded);
|
|
2121
|
+
this.#outlineFocusId.set(items[0]?.id ?? null);
|
|
2122
|
+
}
|
|
2123
|
+
async #loadOutline(doc, token) {
|
|
2124
|
+
let rawItems;
|
|
2125
|
+
try {
|
|
2126
|
+
rawItems = await doc.getOutline();
|
|
2127
|
+
}
|
|
2128
|
+
catch {
|
|
2129
|
+
return this.#isActiveLoad(token) ? [] : null;
|
|
2130
|
+
}
|
|
2131
|
+
if (!this.#isActiveLoad(token))
|
|
2132
|
+
return null;
|
|
2133
|
+
return this.#resolveOutlineItems(doc, rawItems ?? [], token, [], 0, { nodes: 0 }, new Set());
|
|
2134
|
+
}
|
|
2135
|
+
async #resolveOutlineItems(doc, rawItems, token, parentPath, depth, traversal, ancestors) {
|
|
2136
|
+
const items = [];
|
|
2137
|
+
if (depth >= _a.OUTLINE_MAX_DEPTH)
|
|
2138
|
+
return items;
|
|
2139
|
+
for (let index = 0; index < rawItems.length; index++) {
|
|
2140
|
+
if (!this.#isActiveLoad(token))
|
|
2141
|
+
return null;
|
|
2142
|
+
const raw = rawItems[index];
|
|
2143
|
+
if (!raw || typeof raw !== 'object' || ancestors.has(raw))
|
|
2144
|
+
continue;
|
|
2145
|
+
if (traversal.nodes >= _a.OUTLINE_MAX_NODES)
|
|
2146
|
+
break;
|
|
2147
|
+
traversal.nodes++;
|
|
2148
|
+
const path = [...parentPath, index];
|
|
2149
|
+
const page = await this.#resolveOutlinePage(doc, raw, token);
|
|
2150
|
+
if (!this.#isActiveLoad(token))
|
|
2151
|
+
return null;
|
|
2152
|
+
const rawChildren = Array.isArray(raw.items) ? raw.items : [];
|
|
2153
|
+
const childAncestors = new Set(ancestors);
|
|
2154
|
+
childAncestors.add(raw);
|
|
2155
|
+
const children = await this.#resolveOutlineItems(doc, rawChildren, token, path, depth + 1, traversal, childAncestors);
|
|
2156
|
+
if (!children || !this.#isActiveLoad(token))
|
|
2157
|
+
return null;
|
|
2158
|
+
const safeUrl = this.#safeOutlineUrl(raw.url);
|
|
2159
|
+
items.push({
|
|
2160
|
+
id: `outline-${path.join('-')}`,
|
|
2161
|
+
title: raw.title?.trim() || `Page ${page ?? ''}`.trim(),
|
|
2162
|
+
page,
|
|
2163
|
+
...(safeUrl ? { url: safeUrl } : {}),
|
|
2164
|
+
children,
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
return items;
|
|
2168
|
+
}
|
|
2169
|
+
async #resolveOutlinePage(doc, item, token) {
|
|
2170
|
+
let destination = item.dest;
|
|
2171
|
+
if (typeof destination === 'string') {
|
|
2172
|
+
try {
|
|
2173
|
+
destination = await doc.getDestination(destination);
|
|
2174
|
+
}
|
|
2175
|
+
catch {
|
|
2176
|
+
return null;
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
if (!this.#isActiveLoad(token) || !Array.isArray(destination) || destination.length === 0)
|
|
2180
|
+
return null;
|
|
2181
|
+
const target = destination[0];
|
|
2182
|
+
if (typeof target === 'number')
|
|
2183
|
+
return this.#validOutlinePage(Math.floor(target) + 1, doc.numPages);
|
|
2184
|
+
if (!this.#isPdfReference(target))
|
|
2185
|
+
return null;
|
|
2186
|
+
const cached = doc.cachedPageNumber(target);
|
|
2187
|
+
if (cached !== null)
|
|
2188
|
+
return this.#validOutlinePage(cached, doc.numPages);
|
|
2189
|
+
try {
|
|
2190
|
+
const index = await doc.getPageIndex(target);
|
|
2191
|
+
return this.#isActiveLoad(token) ? this.#validOutlinePage(index + 1, doc.numPages) : null;
|
|
2192
|
+
}
|
|
2193
|
+
catch {
|
|
2194
|
+
return null;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
#isPdfReference(value) {
|
|
2198
|
+
return (!!value &&
|
|
2199
|
+
typeof value === 'object' &&
|
|
2200
|
+
'num' in value &&
|
|
2201
|
+
'gen' in value &&
|
|
2202
|
+
typeof value.num === 'number' &&
|
|
2203
|
+
typeof value.gen === 'number');
|
|
2204
|
+
}
|
|
2205
|
+
#validOutlinePage(page, pageCount) {
|
|
2206
|
+
return Number.isInteger(page) && page >= 1 && page <= pageCount ? page : null;
|
|
2207
|
+
}
|
|
2208
|
+
#safeOutlineUrl(url) {
|
|
2209
|
+
const value = url?.trim();
|
|
2210
|
+
return value && /^(?:https?:\/\/|mailto:)/i.test(value) ? value : undefined;
|
|
2211
|
+
}
|
|
1442
2212
|
async #loadDocument(src) {
|
|
1443
2213
|
const token = ++this.#loadToken;
|
|
2214
|
+
this.#searchToken++;
|
|
2215
|
+
this.#cancelQueuedSearch();
|
|
2216
|
+
this.#downloadGeneration++;
|
|
2217
|
+
this.#cancelLoadingTask();
|
|
1444
2218
|
this.#cancelRender();
|
|
2219
|
+
this.#cancelContinuousRenders();
|
|
2220
|
+
this.#cancelThumbnailRenders();
|
|
2221
|
+
this.#cancelPrint();
|
|
1445
2222
|
await this.#destroyDoc();
|
|
2223
|
+
if (!this.#isActiveLoad(token))
|
|
2224
|
+
return;
|
|
1446
2225
|
this.#revokeAllBlobs();
|
|
2226
|
+
this.#thumbnailCacheLru.clear();
|
|
1447
2227
|
this.#thumbCache.set({});
|
|
1448
|
-
this.#
|
|
1449
|
-
this.#
|
|
2228
|
+
this.#setOutline([]);
|
|
2229
|
+
this.#clearPageTextCache();
|
|
1450
2230
|
// Reset search state — results are per-document, but keep the term so a
|
|
1451
2231
|
// newly-loaded source with the same term gets re-indexed automatically
|
|
1452
2232
|
// on the next user keystroke. Active index resets unconditionally.
|
|
1453
2233
|
this.#searchResults.set([]);
|
|
1454
2234
|
this.#searchActiveIndex.set(-1);
|
|
2235
|
+
this.#searchTruncated.set(false);
|
|
1455
2236
|
this.#loadError.set(null);
|
|
1456
2237
|
this.#loadProgress.set({ loaded: 0, total: 0 });
|
|
1457
|
-
if (!src) {
|
|
2238
|
+
if (!src || !this.#browser.isBrowser) {
|
|
1458
2239
|
this.#stage.set('empty');
|
|
1459
2240
|
this.#pdfDoc.set(null);
|
|
1460
2241
|
this.#meta.set(null);
|
|
@@ -1467,46 +2248,47 @@ class SdPreviewPdf {
|
|
|
1467
2248
|
spec = await this.#normalizeSource(src);
|
|
1468
2249
|
}
|
|
1469
2250
|
catch (err) {
|
|
1470
|
-
if (
|
|
2251
|
+
if (!this.#isActiveLoad(token))
|
|
1471
2252
|
return;
|
|
1472
2253
|
this.#emitError(this.#classifyError(err), this.#errorMessage(err));
|
|
1473
2254
|
return;
|
|
1474
2255
|
}
|
|
2256
|
+
if (!this.#isActiveLoad(token))
|
|
2257
|
+
return;
|
|
1475
2258
|
if (this.password()) {
|
|
1476
|
-
spec
|
|
2259
|
+
spec.password = this.password();
|
|
1477
2260
|
}
|
|
1478
2261
|
let task;
|
|
1479
2262
|
try {
|
|
1480
2263
|
task = this.#pdfjs.getDocument(spec);
|
|
1481
|
-
|
|
2264
|
+
this.#currentLoadingTask = task;
|
|
1482
2265
|
task.onProgress = (p) => {
|
|
1483
|
-
if (token
|
|
2266
|
+
if (!this.#isActiveLoad(token) || this.#currentLoadingTask !== task)
|
|
1484
2267
|
return;
|
|
1485
2268
|
this.#loadProgress.set({ loaded: p.loaded ?? 0, total: p.total ?? 0 });
|
|
1486
2269
|
};
|
|
1487
2270
|
}
|
|
1488
2271
|
catch (err) {
|
|
1489
|
-
|
|
2272
|
+
if (this.#isActiveLoad(token))
|
|
2273
|
+
this.#emitError(this.#classifyError(err), this.#errorMessage(err));
|
|
1490
2274
|
return;
|
|
1491
2275
|
}
|
|
1492
2276
|
let pdfDoc;
|
|
1493
2277
|
try {
|
|
1494
|
-
pdfDoc =
|
|
2278
|
+
pdfDoc = await task.promise;
|
|
1495
2279
|
}
|
|
1496
2280
|
catch (err) {
|
|
1497
|
-
if (
|
|
2281
|
+
if (this.#currentLoadingTask === task)
|
|
2282
|
+
this.#currentLoadingTask = null;
|
|
2283
|
+
if (!this.#isActiveLoad(token))
|
|
1498
2284
|
return;
|
|
1499
2285
|
this.#emitError(this.#classifyError(err), this.#errorMessage(err));
|
|
1500
2286
|
return;
|
|
1501
2287
|
}
|
|
1502
|
-
if (
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
}
|
|
1507
|
-
catch {
|
|
1508
|
-
/* ignore */
|
|
1509
|
-
}
|
|
2288
|
+
if (this.#currentLoadingTask === task)
|
|
2289
|
+
this.#currentLoadingTask = null;
|
|
2290
|
+
if (!this.#isActiveLoad(token)) {
|
|
2291
|
+
await this.#destroyDocument(pdfDoc);
|
|
1510
2292
|
return;
|
|
1511
2293
|
}
|
|
1512
2294
|
let meta;
|
|
@@ -1523,16 +2305,35 @@ class SdPreviewPdf {
|
|
|
1523
2305
|
catch {
|
|
1524
2306
|
meta = { numPages: pdfDoc.numPages };
|
|
1525
2307
|
}
|
|
2308
|
+
if (!this.#isActiveLoad(token)) {
|
|
2309
|
+
await this.#destroyDocument(pdfDoc);
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
const outline = await this.#loadOutline(pdfDoc, token);
|
|
2313
|
+
if (!outline || !this.#isActiveLoad(token)) {
|
|
2314
|
+
await this.#destroyDocument(pdfDoc);
|
|
2315
|
+
return;
|
|
2316
|
+
}
|
|
1526
2317
|
this.#pdfDoc.set(pdfDoc);
|
|
1527
2318
|
this.#meta.set(meta);
|
|
2319
|
+
this.#setOutline(outline);
|
|
1528
2320
|
this.#activePage.set(Math.max(1, Math.min(this.startPage(), pdfDoc.numPages)));
|
|
2321
|
+
this.#ensureThumbnailPageVisible(this.#activePage());
|
|
1529
2322
|
this.#stage.set('ready');
|
|
1530
2323
|
this.#zoom.set(1);
|
|
1531
2324
|
this.#rotation.set(0);
|
|
1532
2325
|
this.#zoomMode.set(this.initialZoom());
|
|
1533
|
-
this
|
|
1534
|
-
|
|
1535
|
-
|
|
2326
|
+
this.#resetContinuousLayout(pdfDoc.numPages);
|
|
2327
|
+
if (!this.#destroyed) {
|
|
2328
|
+
this.loaded.emit({ totalPages: pdfDoc.numPages, meta });
|
|
2329
|
+
this.pageChange.emit(this.#activePage());
|
|
2330
|
+
}
|
|
2331
|
+
if (this.#scrollModeInternal() === 'continuous') {
|
|
2332
|
+
this.#positionContinuousPage(this.#activePage());
|
|
2333
|
+
}
|
|
2334
|
+
else {
|
|
2335
|
+
await this.#renderActivePage();
|
|
2336
|
+
}
|
|
1536
2337
|
}
|
|
1537
2338
|
async #renderActivePage() {
|
|
1538
2339
|
const pdfDoc = this.#pdfDoc();
|
|
@@ -1547,41 +2348,48 @@ class SdPreviewPdf {
|
|
|
1547
2348
|
catch {
|
|
1548
2349
|
return;
|
|
1549
2350
|
}
|
|
1550
|
-
if (token !== this.#renderToken)
|
|
2351
|
+
if (token !== this.#renderToken || this.#destroyed || this.#pdfDoc() !== pdfDoc) {
|
|
2352
|
+
page.cleanup();
|
|
1551
2353
|
return;
|
|
2354
|
+
}
|
|
1552
2355
|
const canvas = this.pageCanvasRef()?.nativeElement;
|
|
1553
|
-
if (!canvas)
|
|
2356
|
+
if (!canvas) {
|
|
2357
|
+
page.cleanup();
|
|
1554
2358
|
return;
|
|
1555
|
-
|
|
2359
|
+
}
|
|
1556
2360
|
const rotation = this.#rotation();
|
|
1557
2361
|
const baseViewport = page.getViewport({ scale: 1, rotation });
|
|
1558
2362
|
const scale = this.#resolveScale(baseViewport);
|
|
1559
2363
|
this.#zoom.set(scale);
|
|
1560
|
-
const
|
|
2364
|
+
const { logicalViewport, renderViewport } = this.#canvasViewports(page, scale, rotation);
|
|
1561
2365
|
const ctx = canvas.getContext('2d');
|
|
1562
|
-
if (!ctx)
|
|
2366
|
+
if (!ctx) {
|
|
2367
|
+
page.cleanup();
|
|
1563
2368
|
return;
|
|
1564
|
-
|
|
1565
|
-
canvas.
|
|
1566
|
-
canvas.
|
|
1567
|
-
canvas.style.
|
|
2369
|
+
}
|
|
2370
|
+
canvas.width = Math.floor(renderViewport.width);
|
|
2371
|
+
canvas.height = Math.floor(renderViewport.height);
|
|
2372
|
+
canvas.style.width = `${logicalViewport.width}px`;
|
|
2373
|
+
canvas.style.height = `${logicalViewport.height}px`;
|
|
1568
2374
|
this.#cancelRender();
|
|
1569
|
-
const renderTask = page.render({ canvasContext: ctx, viewport });
|
|
2375
|
+
const renderTask = page.render({ canvasContext: ctx, viewport: renderViewport });
|
|
1570
2376
|
this.#currentRenderTask = renderTask;
|
|
2377
|
+
let completed = false;
|
|
1571
2378
|
try {
|
|
1572
2379
|
await renderTask.promise;
|
|
2380
|
+
completed = token === this.#renderToken && !this.#destroyed && this.#pdfDoc() === pdfDoc;
|
|
1573
2381
|
}
|
|
1574
2382
|
catch {
|
|
1575
|
-
|
|
1576
|
-
return;
|
|
2383
|
+
completed = false;
|
|
1577
2384
|
}
|
|
1578
2385
|
finally {
|
|
1579
2386
|
if (this.#currentRenderTask === renderTask) {
|
|
1580
2387
|
this.#currentRenderTask = null;
|
|
1581
2388
|
}
|
|
2389
|
+
page.cleanup();
|
|
1582
2390
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
2391
|
+
if (completed)
|
|
2392
|
+
this.zoomChange.emit(scale);
|
|
1585
2393
|
}
|
|
1586
2394
|
#resolveScale(baseViewport) {
|
|
1587
2395
|
const mode = this.#zoomMode();
|
|
@@ -1604,6 +2412,16 @@ class SdPreviewPdf {
|
|
|
1604
2412
|
// page-fit
|
|
1605
2413
|
return Math.min(availW / baseViewport.width, availH / baseViewport.height);
|
|
1606
2414
|
}
|
|
2415
|
+
#canvasViewports(page, scale, rotation) {
|
|
2416
|
+
const logicalViewport = page.getViewport({ scale, rotation });
|
|
2417
|
+
const width = Math.max(1, logicalViewport.width);
|
|
2418
|
+
const height = Math.max(1, logicalViewport.height);
|
|
2419
|
+
const factor = Math.min(1, _a.MAX_CANVAS_DIMENSION / width, _a.MAX_CANVAS_DIMENSION / height, Math.sqrt(_a.MAX_CANVAS_PIXELS / (width * height)));
|
|
2420
|
+
return {
|
|
2421
|
+
logicalViewport,
|
|
2422
|
+
renderViewport: factor < 1 ? page.getViewport({ scale: scale * factor, rotation }) : logicalViewport,
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
1607
2425
|
#cancelRender() {
|
|
1608
2426
|
if (this.#currentRenderTask) {
|
|
1609
2427
|
try {
|
|
@@ -1615,22 +2433,59 @@ class SdPreviewPdf {
|
|
|
1615
2433
|
this.#currentRenderTask = null;
|
|
1616
2434
|
}
|
|
1617
2435
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
2436
|
+
onStageScroll() {
|
|
2437
|
+
if (this.#scrollModeInternal() === 'continuous')
|
|
2438
|
+
this.#updateContinuousWindow(true);
|
|
2439
|
+
}
|
|
2440
|
+
#cancelLoadingTask() {
|
|
2441
|
+
const task = this.#currentLoadingTask;
|
|
2442
|
+
this.#currentLoadingTask = null;
|
|
2443
|
+
if (!task?.destroy)
|
|
1621
2444
|
return;
|
|
1622
|
-
|
|
2445
|
+
try {
|
|
2446
|
+
void Promise.resolve(task.destroy()).catch(() => undefined);
|
|
2447
|
+
}
|
|
2448
|
+
catch {
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
#cancelPrint() {
|
|
2453
|
+
this.#printGeneration++;
|
|
2454
|
+
this.#cancelPrintJob();
|
|
2455
|
+
}
|
|
2456
|
+
#cancelPrintJob() {
|
|
2457
|
+
const job = this.#currentPrintJob;
|
|
2458
|
+
this.#currentPrintJob = null;
|
|
2459
|
+
job?.cancel();
|
|
2460
|
+
}
|
|
2461
|
+
#isActiveLoad(token) {
|
|
2462
|
+
return !this.#destroyed && token === this.#loadToken;
|
|
2463
|
+
}
|
|
2464
|
+
#isCurrentDownload(generation, source, doc) {
|
|
2465
|
+
return !this.#destroyed && generation === this.#downloadGeneration && this.source() === source && this.#pdfDoc() === doc;
|
|
2466
|
+
}
|
|
2467
|
+
async #destroyDocument(doc) {
|
|
1623
2468
|
try {
|
|
1624
2469
|
await doc.destroy();
|
|
1625
2470
|
}
|
|
1626
2471
|
catch {
|
|
1627
|
-
|
|
2472
|
+
return;
|
|
1628
2473
|
}
|
|
1629
2474
|
}
|
|
2475
|
+
async #destroyDoc() {
|
|
2476
|
+
const doc = this.#pdfDoc();
|
|
2477
|
+
if (!doc)
|
|
2478
|
+
return;
|
|
2479
|
+
this.#pdfDoc.set(null);
|
|
2480
|
+
await this.#destroyDocument(doc);
|
|
2481
|
+
}
|
|
1630
2482
|
#setZoom(value) {
|
|
1631
2483
|
const clamped = Math.min(_a.MAX_ZOOM, Math.max(_a.MIN_ZOOM, value));
|
|
1632
2484
|
this.#zoom.set(clamped);
|
|
1633
|
-
this.#
|
|
2485
|
+
if (this.#scrollModeInternal() === 'continuous')
|
|
2486
|
+
this.#invalidateContinuousLayout();
|
|
2487
|
+
else
|
|
2488
|
+
void this.#renderActivePage();
|
|
1634
2489
|
}
|
|
1635
2490
|
async #normalizeSource(src) {
|
|
1636
2491
|
if (typeof src === 'string') {
|
|
@@ -1638,16 +2493,16 @@ class SdPreviewPdf {
|
|
|
1638
2493
|
const headers = this.httpHeaders();
|
|
1639
2494
|
const spec = { url: src };
|
|
1640
2495
|
if (headers)
|
|
1641
|
-
spec
|
|
2496
|
+
spec.httpHeaders = headers;
|
|
1642
2497
|
return spec;
|
|
1643
2498
|
}
|
|
1644
|
-
if (src
|
|
2499
|
+
if (this.#browser.isFile(src)) {
|
|
1645
2500
|
this.#filename.set(src.name);
|
|
1646
2501
|
this.#fileSize.set(src.size);
|
|
1647
2502
|
const buf = await src.arrayBuffer();
|
|
1648
2503
|
return { data: new Uint8Array(buf) };
|
|
1649
2504
|
}
|
|
1650
|
-
if (src
|
|
2505
|
+
if (this.#browser.isBlob(src)) {
|
|
1651
2506
|
this.#filename.set('document.pdf');
|
|
1652
2507
|
this.#fileSize.set(src.size);
|
|
1653
2508
|
const buf = await src.arrayBuffer();
|
|
@@ -1656,12 +2511,12 @@ class SdPreviewPdf {
|
|
|
1656
2511
|
if (src instanceof ArrayBuffer) {
|
|
1657
2512
|
this.#filename.set('document.pdf');
|
|
1658
2513
|
this.#fileSize.set(src.byteLength);
|
|
1659
|
-
return { data: new Uint8Array(src) };
|
|
2514
|
+
return { data: new Uint8Array(src.slice(0)) };
|
|
1660
2515
|
}
|
|
1661
2516
|
if (src instanceof Uint8Array) {
|
|
1662
2517
|
this.#filename.set('document.pdf');
|
|
1663
2518
|
this.#fileSize.set(src.byteLength);
|
|
1664
|
-
return { data: src };
|
|
2519
|
+
return { data: new Uint8Array(src) };
|
|
1665
2520
|
}
|
|
1666
2521
|
if (src && typeof src === 'object') {
|
|
1667
2522
|
if ('url' in src && typeof src.url === 'string') {
|
|
@@ -1670,14 +2525,14 @@ class SdPreviewPdf {
|
|
|
1670
2525
|
// Caller-provided headers win over the [httpHeaders] input.
|
|
1671
2526
|
const headers = src.httpHeaders ?? this.httpHeaders();
|
|
1672
2527
|
if (headers)
|
|
1673
|
-
spec
|
|
2528
|
+
spec.httpHeaders = headers;
|
|
1674
2529
|
if (src.withCredentials)
|
|
1675
|
-
spec
|
|
2530
|
+
spec.withCredentials = true;
|
|
1676
2531
|
return spec;
|
|
1677
2532
|
}
|
|
1678
2533
|
if ('data' in src && (src.data instanceof ArrayBuffer || src.data instanceof Uint8Array)) {
|
|
1679
2534
|
this.#filename.set('document.pdf');
|
|
1680
|
-
const data = src.data instanceof ArrayBuffer ? new Uint8Array(src.data) : src.data;
|
|
2535
|
+
const data = src.data instanceof ArrayBuffer ? new Uint8Array(src.data.slice(0)) : new Uint8Array(src.data);
|
|
1681
2536
|
this.#fileSize.set(data.byteLength);
|
|
1682
2537
|
return { data };
|
|
1683
2538
|
}
|
|
@@ -1721,10 +2576,30 @@ class SdPreviewPdf {
|
|
|
1721
2576
|
}
|
|
1722
2577
|
#revokeAllBlobs() {
|
|
1723
2578
|
for (const url of this.#ownedBlobUrls) {
|
|
1724
|
-
|
|
2579
|
+
this.#browser.revokeObjectUrl(url);
|
|
1725
2580
|
}
|
|
1726
2581
|
this.#ownedBlobUrls.clear();
|
|
1727
|
-
|
|
2582
|
+
}
|
|
2583
|
+
#requiresLoadedDocumentBytes(source) {
|
|
2584
|
+
if (typeof source === 'string')
|
|
2585
|
+
return !!this.httpHeaders();
|
|
2586
|
+
if (!('url' in source) || typeof source.url !== 'string')
|
|
2587
|
+
return false;
|
|
2588
|
+
return !!source.httpHeaders || !!source.withCredentials || !!this.httpHeaders();
|
|
2589
|
+
}
|
|
2590
|
+
#trackTemporaryDownloadUrl(url) {
|
|
2591
|
+
if (url)
|
|
2592
|
+
this.#ownedBlobUrls.add(url);
|
|
2593
|
+
return url;
|
|
2594
|
+
}
|
|
2595
|
+
#scheduleTemporaryDownloadUrlRelease(url) {
|
|
2596
|
+
const release = () => {
|
|
2597
|
+
if (!this.#ownedBlobUrls.delete(url))
|
|
2598
|
+
return;
|
|
2599
|
+
this.#browser.revokeObjectUrl(url);
|
|
2600
|
+
};
|
|
2601
|
+
if (this.#browser.scheduleFrame(() => release()) === null)
|
|
2602
|
+
queueMicrotask(release);
|
|
1728
2603
|
}
|
|
1729
2604
|
// ==========================================
|
|
1730
2605
|
// SEARCH HELPERS
|
|
@@ -1741,7 +2616,8 @@ class SdPreviewPdf {
|
|
|
1741
2616
|
return null;
|
|
1742
2617
|
const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1743
2618
|
const flags = (caseSensitive ? 'g' : 'gi') + 'u';
|
|
1744
|
-
const
|
|
2619
|
+
const wordCharacter = '[\\p{L}\\p{M}\\p{N}\\p{Pc}]';
|
|
2620
|
+
const pattern = wholeWord ? '(?<!' + wordCharacter + ')' + escaped + '(?!' + wordCharacter + ')' : escaped;
|
|
1745
2621
|
try {
|
|
1746
2622
|
return new RegExp(pattern, flags);
|
|
1747
2623
|
}
|
|
@@ -1755,10 +2631,12 @@ class SdPreviewPdf {
|
|
|
1755
2631
|
* (NFD) matches the same search term — pdfjs has been seen to return NFD
|
|
1756
2632
|
* in some PDFs.
|
|
1757
2633
|
*/
|
|
1758
|
-
async #getPageText(doc, pageNum) {
|
|
1759
|
-
const cached =
|
|
1760
|
-
if (cached !== undefined)
|
|
2634
|
+
async #getPageText(doc, pageNum, searchToken, scan) {
|
|
2635
|
+
const cached = scan.staged.get(pageNum) ?? scan.snapshot.get(pageNum);
|
|
2636
|
+
if (cached !== undefined) {
|
|
2637
|
+
this.#stagePageText(scan, pageNum, cached);
|
|
1761
2638
|
return cached;
|
|
2639
|
+
}
|
|
1762
2640
|
let page;
|
|
1763
2641
|
try {
|
|
1764
2642
|
page = await doc.getPage(pageNum);
|
|
@@ -1768,13 +2646,12 @@ class SdPreviewPdf {
|
|
|
1768
2646
|
}
|
|
1769
2647
|
let text = '';
|
|
1770
2648
|
try {
|
|
1771
|
-
const
|
|
1772
|
-
const items = Array.isArray(
|
|
2649
|
+
const content = await page.getTextContent();
|
|
2650
|
+
const items = content && typeof content === 'object' && 'items' in content && Array.isArray(content.items) ? content.items : [];
|
|
1773
2651
|
const parts = [];
|
|
1774
2652
|
for (const it of items) {
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
parts.push(str);
|
|
2653
|
+
if (it && typeof it === 'object' && 'str' in it && typeof it.str === 'string')
|
|
2654
|
+
parts.push(it.str);
|
|
1778
2655
|
}
|
|
1779
2656
|
// Space-join so multi-word matches across textItem boundaries work.
|
|
1780
2657
|
text = parts.join(' ').normalize('NFC');
|
|
@@ -1782,14 +2659,49 @@ class SdPreviewPdf {
|
|
|
1782
2659
|
catch {
|
|
1783
2660
|
text = '';
|
|
1784
2661
|
}
|
|
1785
|
-
|
|
2662
|
+
finally {
|
|
2663
|
+
page.cleanup();
|
|
2664
|
+
}
|
|
2665
|
+
if (searchToken === this.#searchToken && this.#pdfDoc() === doc && !this.#destroyed) {
|
|
2666
|
+
this.#stagePageText(scan, pageNum, text);
|
|
2667
|
+
}
|
|
1786
2668
|
return text;
|
|
1787
2669
|
}
|
|
2670
|
+
#stagePageText(scan, pageNum, text) {
|
|
2671
|
+
scan.staged.delete(pageNum);
|
|
2672
|
+
scan.staged.set(pageNum, text);
|
|
2673
|
+
while (scan.staged.size > _a.MAX_PAGE_TEXT_CACHE_PAGES) {
|
|
2674
|
+
const oldestPage = scan.staged.keys().next().value;
|
|
2675
|
+
if (oldestPage === undefined)
|
|
2676
|
+
break;
|
|
2677
|
+
scan.staged.delete(oldestPage);
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
#commitPageTextScan(scan) {
|
|
2681
|
+
for (const [pageNum, text] of scan.staged)
|
|
2682
|
+
this.#cachePageText(pageNum, text);
|
|
2683
|
+
}
|
|
2684
|
+
#cachePageText(pageNum, text) {
|
|
2685
|
+
this.#pageTextCache.delete(pageNum);
|
|
2686
|
+
this.#pageTextCache.set(pageNum, text);
|
|
2687
|
+
while (this.#pageTextCache.size > _a.MAX_PAGE_TEXT_CACHE_PAGES) {
|
|
2688
|
+
const oldestPage = this.#pageTextCache.keys().next().value;
|
|
2689
|
+
if (oldestPage === undefined)
|
|
2690
|
+
break;
|
|
2691
|
+
this.#pageTextCache.delete(oldestPage);
|
|
2692
|
+
}
|
|
2693
|
+
this.#pageTextCacheSize.set(this.#pageTextCache.size);
|
|
2694
|
+
}
|
|
2695
|
+
#clearPageTextCache() {
|
|
2696
|
+
this.#pageTextCache.clear();
|
|
2697
|
+
this.#pageTextCacheSize.set(0);
|
|
2698
|
+
}
|
|
1788
2699
|
#emitSearchChange() {
|
|
1789
2700
|
this.searchChange.emit({
|
|
1790
2701
|
term: this.#searchTerm(),
|
|
1791
2702
|
total: this.#searchResults().length,
|
|
1792
2703
|
current: this.#searchActiveIndex() + 1, // 1-based; 0 when no active
|
|
2704
|
+
truncated: this.#searchTruncated(),
|
|
1793
2705
|
});
|
|
1794
2706
|
}
|
|
1795
2707
|
/**
|
|
@@ -1819,10 +2731,14 @@ class SdPreviewPdf {
|
|
|
1819
2731
|
const pageHits = results.map((r, i) => ({ r, i })).filter(({ r }) => r.page === activePage);
|
|
1820
2732
|
if (pageHits.length === 0)
|
|
1821
2733
|
return;
|
|
1822
|
-
const overlay =
|
|
2734
|
+
const overlay = this.#browser.createElement('div');
|
|
2735
|
+
if (!overlay)
|
|
2736
|
+
return;
|
|
1823
2737
|
overlay.className = 'sd-pdf-search-overlay';
|
|
1824
2738
|
for (const { r, i } of pageHits) {
|
|
1825
|
-
const m =
|
|
2739
|
+
const m = this.#browser.createElement('mark');
|
|
2740
|
+
if (!m)
|
|
2741
|
+
continue;
|
|
1826
2742
|
m.className = i === activeIdx ? 'sd-pdf-search-hi sd-pdf-search-hi--active' : 'sd-pdf-search-hi';
|
|
1827
2743
|
m.textContent = r.term;
|
|
1828
2744
|
overlay.appendChild(m);
|
|
@@ -1839,6 +2755,186 @@ class SdPreviewPdf {
|
|
|
1839
2755
|
}
|
|
1840
2756
|
}
|
|
1841
2757
|
}
|
|
2758
|
+
#onScrollModeChanged() {
|
|
2759
|
+
if (this.#destroyed || this.#stage() !== 'ready')
|
|
2760
|
+
return;
|
|
2761
|
+
if (this.#scrollModeInternal() === 'continuous') {
|
|
2762
|
+
this.#cancelRender();
|
|
2763
|
+
this.#resetContinuousLayout(this.numPages());
|
|
2764
|
+
this.#positionContinuousPage(this.#activePage());
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
this.#cancelContinuousRenders();
|
|
2768
|
+
this.#continuousPages.set([]);
|
|
2769
|
+
this.#continuousTopSpacer.set(0);
|
|
2770
|
+
this.#continuousBottomSpacer.set(0);
|
|
2771
|
+
void this.#renderActivePage();
|
|
2772
|
+
}
|
|
2773
|
+
#estimatedContinuousHeight() {
|
|
2774
|
+
const rotated = this.#rotation() % 180 !== 0;
|
|
2775
|
+
const baseViewport = rotated ? { width: 792, height: 612 } : { width: 612, height: 792 };
|
|
2776
|
+
return Math.max(1, baseViewport.height * this.#resolveScale(baseViewport));
|
|
2777
|
+
}
|
|
2778
|
+
#resetContinuousLayout(pageCount) {
|
|
2779
|
+
const estimatedHeight = this.#estimatedContinuousHeight();
|
|
2780
|
+
this.#pageHeights = Array.from({ length: pageCount }, () => estimatedHeight);
|
|
2781
|
+
this.#heightIndex.reset(this.#pageHeights);
|
|
2782
|
+
this.#continuousHeightVersion.update(version => version + 1);
|
|
2783
|
+
}
|
|
2784
|
+
#continuousPageIndexAt(offset) {
|
|
2785
|
+
return this.#heightIndex.indexAt(offset);
|
|
2786
|
+
}
|
|
2787
|
+
#updateContinuousWindow(updateActivePage) {
|
|
2788
|
+
const stage = this.stageEl()?.nativeElement;
|
|
2789
|
+
const pageCount = this.#pageHeights.length;
|
|
2790
|
+
if (!stage || pageCount === 0 || this.#scrollModeInternal() !== 'continuous')
|
|
2791
|
+
return;
|
|
2792
|
+
const viewportHeight = Math.max(1, stage.clientHeight || stage.getBoundingClientRect().height || 1);
|
|
2793
|
+
const firstVisible = this.#continuousPageIndexAt(stage.scrollTop);
|
|
2794
|
+
const lastVisible = this.#continuousPageIndexAt(stage.scrollTop + viewportHeight - 1);
|
|
2795
|
+
const first = Math.max(0, firstVisible - 1);
|
|
2796
|
+
const last = Math.min(pageCount - 1, lastVisible + 1);
|
|
2797
|
+
const pages = Array.from({ length: last - first + 1 }, (_, index) => first + index + 1);
|
|
2798
|
+
this.#continuousPages.set(pages);
|
|
2799
|
+
this.#continuousTopSpacer.set(this.#heightIndex.offsetAt(first));
|
|
2800
|
+
const renderedEnd = this.#heightIndex.offsetAt(last) + this.#pageHeights[last];
|
|
2801
|
+
this.#continuousBottomSpacer.set(Math.max(0, this.#heightIndex.total() - renderedEnd));
|
|
2802
|
+
const retained = new Set(pages);
|
|
2803
|
+
for (const [pageNumber, task] of this.#continuousTasks) {
|
|
2804
|
+
if (retained.has(pageNumber))
|
|
2805
|
+
continue;
|
|
2806
|
+
task.cancel();
|
|
2807
|
+
this.#continuousTasks.delete(pageNumber);
|
|
2808
|
+
}
|
|
2809
|
+
if (!updateActivePage)
|
|
2810
|
+
return;
|
|
2811
|
+
const midpointPage = this.#continuousPageIndexAt(stage.scrollTop + viewportHeight / 2) + 1;
|
|
2812
|
+
if (midpointPage === this.#activePage())
|
|
2813
|
+
return;
|
|
2814
|
+
this.#activePage.set(midpointPage);
|
|
2815
|
+
if (!this.#destroyed)
|
|
2816
|
+
this.pageChange.emit(midpointPage);
|
|
2817
|
+
}
|
|
2818
|
+
#positionContinuousPage(pageNumber) {
|
|
2819
|
+
const stage = this.stageEl()?.nativeElement;
|
|
2820
|
+
if (stage)
|
|
2821
|
+
stage.scrollTop = this.#heightIndex.offsetAt(pageNumber - 1);
|
|
2822
|
+
this.#updateContinuousWindow(false);
|
|
2823
|
+
this.#rescheduleContinuousRenders();
|
|
2824
|
+
}
|
|
2825
|
+
#invalidateContinuousLayout() {
|
|
2826
|
+
const stage = this.stageEl()?.nativeElement;
|
|
2827
|
+
const anchorIndex = stage ? this.#continuousPageIndexAt(stage.scrollTop) : 0;
|
|
2828
|
+
const oldHeight = this.#pageHeights[anchorIndex] || 1;
|
|
2829
|
+
const oldOffset = this.#heightIndex.offsetAt(anchorIndex);
|
|
2830
|
+
const anchorRatio = stage ? Math.max(0, Math.min(1, (stage.scrollTop - oldOffset) / oldHeight)) : 0;
|
|
2831
|
+
this.#cancelContinuousRenders();
|
|
2832
|
+
this.#resetContinuousLayout(this.numPages());
|
|
2833
|
+
if (stage)
|
|
2834
|
+
stage.scrollTop = this.#heightIndex.offsetAt(anchorIndex) + this.#pageHeights[anchorIndex] * anchorRatio;
|
|
2835
|
+
this.#updateContinuousWindow(false);
|
|
2836
|
+
this.#rescheduleContinuousRenders();
|
|
2837
|
+
}
|
|
2838
|
+
#rescheduleContinuousRenders() {
|
|
2839
|
+
queueMicrotask(() => {
|
|
2840
|
+
if (this.#destroyed || this.#scrollModeInternal() !== 'continuous')
|
|
2841
|
+
return;
|
|
2842
|
+
this.#scheduleContinuousRenders(this.continuousCanvases().map(ref => ref.nativeElement));
|
|
2843
|
+
});
|
|
2844
|
+
}
|
|
2845
|
+
#scheduleContinuousRenders(canvases) {
|
|
2846
|
+
if (this.#continuousFrame !== null)
|
|
2847
|
+
this.#browser.cancelFrame(this.#continuousFrame);
|
|
2848
|
+
if (canvases.length === 0 || this.#scrollModeInternal() !== 'continuous') {
|
|
2849
|
+
this.#continuousFrame = null;
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
this.#continuousFrame = this.#browser.scheduleFrame(() => {
|
|
2853
|
+
this.#continuousFrame = null;
|
|
2854
|
+
for (const canvas of canvases)
|
|
2855
|
+
void this.#renderContinuousCanvas(canvas);
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
async #renderContinuousCanvas(canvas) {
|
|
2859
|
+
const pageNumber = Number(canvas.getAttribute('data-page'));
|
|
2860
|
+
const doc = this.#pdfDoc();
|
|
2861
|
+
const generation = this.#layoutGeneration;
|
|
2862
|
+
if (!doc ||
|
|
2863
|
+
!Number.isInteger(pageNumber) ||
|
|
2864
|
+
pageNumber < 1 ||
|
|
2865
|
+
this.#continuousTasks.has(pageNumber) ||
|
|
2866
|
+
this.#continuousReservations.has(pageNumber))
|
|
2867
|
+
return;
|
|
2868
|
+
const reservation = { generation };
|
|
2869
|
+
this.#continuousReservations.set(pageNumber, reservation);
|
|
2870
|
+
let page = null;
|
|
2871
|
+
try {
|
|
2872
|
+
page = await doc.getPage(pageNumber);
|
|
2873
|
+
if (!this.#isCurrentContinuousPage(doc, generation, pageNumber))
|
|
2874
|
+
return;
|
|
2875
|
+
const rotation = this.#rotation();
|
|
2876
|
+
const baseViewport = page.getViewport({ scale: 1, rotation });
|
|
2877
|
+
const scale = this.#resolveScale(baseViewport);
|
|
2878
|
+
const { logicalViewport, renderViewport } = this.#canvasViewports(page, scale, rotation);
|
|
2879
|
+
const context = canvas.getContext('2d');
|
|
2880
|
+
if (!context)
|
|
2881
|
+
return;
|
|
2882
|
+
canvas.width = Math.floor(renderViewport.width);
|
|
2883
|
+
canvas.height = Math.floor(renderViewport.height);
|
|
2884
|
+
canvas.style.width = `${logicalViewport.width}px`;
|
|
2885
|
+
canvas.style.height = `${logicalViewport.height}px`;
|
|
2886
|
+
const task = page.render({ canvasContext: context, viewport: renderViewport });
|
|
2887
|
+
this.#continuousTasks.set(pageNumber, task);
|
|
2888
|
+
try {
|
|
2889
|
+
await task.promise;
|
|
2890
|
+
}
|
|
2891
|
+
catch {
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2894
|
+
finally {
|
|
2895
|
+
if (this.#continuousTasks.get(pageNumber) === task)
|
|
2896
|
+
this.#continuousTasks.delete(pageNumber);
|
|
2897
|
+
}
|
|
2898
|
+
if (!this.#isCurrentContinuousPage(doc, generation, pageNumber))
|
|
2899
|
+
return;
|
|
2900
|
+
this.#updateContinuousMeasurement(pageNumber, logicalViewport.height);
|
|
2901
|
+
this.#zoom.set(scale);
|
|
2902
|
+
this.zoomChange.emit(scale);
|
|
2903
|
+
}
|
|
2904
|
+
finally {
|
|
2905
|
+
if (this.#continuousReservations.get(pageNumber) === reservation)
|
|
2906
|
+
this.#continuousReservations.delete(pageNumber);
|
|
2907
|
+
page?.cleanup();
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
#isCurrentContinuousPage(doc, generation, pageNumber) {
|
|
2911
|
+
return (!this.#destroyed && this.#pdfDoc() === doc && this.#layoutGeneration === generation && this.#continuousPages().includes(pageNumber));
|
|
2912
|
+
}
|
|
2913
|
+
#updateContinuousMeasurement(pageNumber, height) {
|
|
2914
|
+
const index = pageNumber - 1;
|
|
2915
|
+
if (!Number.isFinite(height) || height <= 0 || Math.abs((this.#pageHeights[index] ?? 0) - height) < 0.5)
|
|
2916
|
+
return;
|
|
2917
|
+
const stage = this.stageEl()?.nativeElement;
|
|
2918
|
+
const anchorIndex = stage ? this.#continuousPageIndexAt(stage.scrollTop) : 0;
|
|
2919
|
+
const anchorDelta = stage ? stage.scrollTop - this.#heightIndex.offsetAt(anchorIndex) : 0;
|
|
2920
|
+
this.#pageHeights[index] = height;
|
|
2921
|
+
this.#heightIndex.updateHeight(index, height);
|
|
2922
|
+
this.#continuousHeightVersion.update(version => version + 1);
|
|
2923
|
+
if (stage)
|
|
2924
|
+
stage.scrollTop = this.#heightIndex.offsetAt(anchorIndex) + anchorDelta;
|
|
2925
|
+
this.#updateContinuousWindow(false);
|
|
2926
|
+
}
|
|
2927
|
+
#cancelContinuousRenders() {
|
|
2928
|
+
this.#layoutGeneration++;
|
|
2929
|
+
if (this.#continuousFrame !== null) {
|
|
2930
|
+
this.#browser.cancelFrame(this.#continuousFrame);
|
|
2931
|
+
this.#continuousFrame = null;
|
|
2932
|
+
}
|
|
2933
|
+
for (const task of this.#continuousTasks.values())
|
|
2934
|
+
task.cancel();
|
|
2935
|
+
this.#continuousTasks.clear();
|
|
2936
|
+
this.#continuousReservations.clear();
|
|
2937
|
+
}
|
|
1842
2938
|
// ==========================================
|
|
1843
2939
|
// THUMBNAIL HELPERS
|
|
1844
2940
|
// ==========================================
|
|
@@ -1847,42 +2943,46 @@ class SdPreviewPdf {
|
|
|
1847
2943
|
* currently mounted. Called from an effect that tracks `thumbCanvases()`.
|
|
1848
2944
|
*/
|
|
1849
2945
|
#syncThumbObserver(canvases) {
|
|
1850
|
-
|
|
1851
|
-
this.#
|
|
2946
|
+
this.#thumbObserverCleanup?.();
|
|
2947
|
+
this.#thumbObserverCleanup = null;
|
|
2948
|
+
const currentPages = new Set(canvases.map(canvas => Number(canvas.getAttribute('data-page'))).filter(page => Number.isFinite(page)));
|
|
2949
|
+
this.#cancelThumbnailWorkOutside(currentPages);
|
|
1852
2950
|
if (canvases.length === 0) {
|
|
1853
|
-
this.#thumbObserver = null;
|
|
1854
|
-
return;
|
|
1855
|
-
}
|
|
1856
|
-
// Lazily create an IntersectionObserver scoped to the sidebar scroll
|
|
1857
|
-
// container so we only render thumbs the user can actually see. Falls
|
|
1858
|
-
// back to "render everything" when IntersectionObserver isn't available
|
|
1859
|
-
// (very old runtime / SSR shim).
|
|
1860
|
-
if (typeof IntersectionObserver !== 'function') {
|
|
1861
|
-
for (const c of canvases) {
|
|
1862
|
-
const n = Number(c.getAttribute('data-page'));
|
|
1863
|
-
if (Number.isFinite(n))
|
|
1864
|
-
void this.#renderThumbnail(n, c);
|
|
1865
|
-
}
|
|
1866
2951
|
return;
|
|
1867
2952
|
}
|
|
1868
|
-
this.#
|
|
2953
|
+
this.#thumbObserverCleanup = this.#browser.observeIntersections(canvases, entries => {
|
|
1869
2954
|
for (const entry of entries) {
|
|
1870
2955
|
if (!entry.isIntersecting)
|
|
1871
2956
|
continue;
|
|
1872
|
-
const
|
|
1873
|
-
|
|
2957
|
+
const canvas = canvases.find(candidate => candidate === entry.target);
|
|
2958
|
+
if (!canvas)
|
|
2959
|
+
continue;
|
|
2960
|
+
const n = Number(canvas.getAttribute('data-page'));
|
|
1874
2961
|
if (!Number.isFinite(n))
|
|
1875
2962
|
continue;
|
|
1876
|
-
this.#
|
|
1877
|
-
void this.#renderThumbnail(n, c);
|
|
2963
|
+
void this.#renderThumbnail(n, canvas);
|
|
1878
2964
|
}
|
|
1879
2965
|
}, { rootMargin: '120px 0px 120px 0px', threshold: 0.01 });
|
|
1880
|
-
|
|
1881
|
-
|
|
2966
|
+
}
|
|
2967
|
+
#ensureThumbnailPageVisible(pageNumber) {
|
|
2968
|
+
const start = this.#thumbnailWindowStart();
|
|
2969
|
+
const end = start + _a.THUMBNAIL_WINDOW_SIZE;
|
|
2970
|
+
if (pageNumber > start && pageNumber <= end)
|
|
2971
|
+
return;
|
|
2972
|
+
const centered = pageNumber - 1 - Math.floor(_a.THUMBNAIL_WINDOW_SIZE / 2);
|
|
2973
|
+
const next = Math.max(0, Math.min(centered, Math.max(0, this.numPages() - _a.THUMBNAIL_WINDOW_SIZE)));
|
|
2974
|
+
this.#thumbnailWindowStart.set(next);
|
|
2975
|
+
queueMicrotask(() => {
|
|
2976
|
+
const content = this.sidebarContentRef()?.nativeElement;
|
|
2977
|
+
if (content && this.#sidebarModeInternal() === 'thumbnails') {
|
|
2978
|
+
content.scrollTop = next * _a.THUMBNAIL_ITEM_HEIGHT;
|
|
2979
|
+
}
|
|
2980
|
+
});
|
|
1882
2981
|
}
|
|
1883
2982
|
/**
|
|
1884
2983
|
* Render a single page into a sidebar thumbnail canvas at ~140px width.
|
|
1885
|
-
*
|
|
2984
|
+
* Reserved in `#thumbnailWork` before getPage() so rapid scroll bounces
|
|
2985
|
+
* cannot start duplicate work for the same mounted page.
|
|
1886
2986
|
*
|
|
1887
2987
|
* Visible for testing — tests can stub `getPage()` on the fake doc to
|
|
1888
2988
|
* assert this method calls `render(...)` with the right page argument.
|
|
@@ -1899,60 +2999,180 @@ class SdPreviewPdf {
|
|
|
1899
2999
|
const doc = this.#pdfDoc();
|
|
1900
3000
|
if (!doc)
|
|
1901
3001
|
return;
|
|
1902
|
-
if (this.#
|
|
3002
|
+
if (this.#thumbnailWork.has(pageNum))
|
|
1903
3003
|
return;
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
this.#paintCachedThumb(canvas, this.#
|
|
3004
|
+
const cachedThumbnail = this.#getCachedThumbnail(pageNum);
|
|
3005
|
+
if (cachedThumbnail) {
|
|
3006
|
+
this.#paintCachedThumb(canvas, cachedThumbnail, this.#loadToken);
|
|
1907
3007
|
return;
|
|
1908
3008
|
}
|
|
1909
|
-
this.#
|
|
3009
|
+
const loadToken = this.#loadToken;
|
|
3010
|
+
const work = {
|
|
3011
|
+
loadToken,
|
|
3012
|
+
cancelled: false,
|
|
3013
|
+
renderTask: null,
|
|
3014
|
+
slotState: 'new',
|
|
3015
|
+
resumeSlot: null,
|
|
3016
|
+
};
|
|
3017
|
+
this.#thumbnailWork.set(pageNum, work);
|
|
3018
|
+
this.#thumbnailWorkCount.set(this.#thumbnailWork.size);
|
|
3019
|
+
let page = null;
|
|
3020
|
+
let acquiredSlot = false;
|
|
1910
3021
|
try {
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
3022
|
+
acquiredSlot = await this.#acquireThumbnailSlot(work);
|
|
3023
|
+
if (!acquiredSlot || !this.#isThumbnailWorkActive(pageNum, work, doc))
|
|
3024
|
+
return;
|
|
3025
|
+
page = await doc.getPage(pageNum);
|
|
3026
|
+
if (!this.#isThumbnailWorkActive(pageNum, work, doc))
|
|
3027
|
+
return;
|
|
1916
3028
|
const baseViewport = page.getViewport({ scale: 1 });
|
|
1917
3029
|
const scale = 140 / Math.max(1, baseViewport.width);
|
|
1918
|
-
const
|
|
1919
|
-
canvas.width = Math.floor(
|
|
1920
|
-
canvas.height = Math.floor(
|
|
1921
|
-
canvas.style.width = `${
|
|
1922
|
-
canvas.style.height = `${
|
|
3030
|
+
const { logicalViewport, renderViewport } = this.#canvasViewports(page, scale);
|
|
3031
|
+
canvas.width = Math.floor(renderViewport.width);
|
|
3032
|
+
canvas.height = Math.floor(renderViewport.height);
|
|
3033
|
+
canvas.style.width = `${logicalViewport.width}px`;
|
|
3034
|
+
canvas.style.height = `${logicalViewport.height}px`;
|
|
1923
3035
|
const ctx = canvas.getContext('2d');
|
|
1924
3036
|
if (!ctx)
|
|
1925
3037
|
return;
|
|
1926
|
-
const renderTask = page.render({ canvasContext: ctx, viewport });
|
|
3038
|
+
const renderTask = page.render({ canvasContext: ctx, viewport: renderViewport });
|
|
3039
|
+
work.renderTask = renderTask;
|
|
1927
3040
|
try {
|
|
1928
3041
|
await renderTask.promise;
|
|
1929
3042
|
}
|
|
1930
3043
|
catch {
|
|
1931
|
-
return;
|
|
3044
|
+
return;
|
|
1932
3045
|
}
|
|
3046
|
+
if (!this.#isThumbnailWorkActive(pageNum, work, doc))
|
|
3047
|
+
return;
|
|
1933
3048
|
try {
|
|
1934
3049
|
const dataUrl = canvas.toDataURL('image/png');
|
|
1935
|
-
this.#
|
|
3050
|
+
this.#cacheThumbnail(pageNum, dataUrl);
|
|
1936
3051
|
}
|
|
1937
3052
|
catch {
|
|
1938
|
-
|
|
3053
|
+
return;
|
|
1939
3054
|
}
|
|
1940
|
-
page.cleanup();
|
|
1941
3055
|
}
|
|
1942
3056
|
catch {
|
|
1943
|
-
|
|
3057
|
+
return;
|
|
1944
3058
|
}
|
|
1945
3059
|
finally {
|
|
1946
|
-
|
|
3060
|
+
if (acquiredSlot)
|
|
3061
|
+
this.#releaseThumbnailSlot(work);
|
|
3062
|
+
if (this.#thumbnailWork.get(pageNum) === work) {
|
|
3063
|
+
this.#thumbnailWork.delete(pageNum);
|
|
3064
|
+
this.#thumbnailWorkCount.set(this.#thumbnailWork.size);
|
|
3065
|
+
}
|
|
3066
|
+
page?.cleanup();
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
#isThumbnailWorkActive(pageNum, work, doc) {
|
|
3070
|
+
return (!work.cancelled &&
|
|
3071
|
+
work.loadToken === this.#loadToken &&
|
|
3072
|
+
this.#isActiveLoad(work.loadToken) &&
|
|
3073
|
+
this.#pdfDoc() === doc &&
|
|
3074
|
+
this.#thumbnailWork.get(pageNum) === work &&
|
|
3075
|
+
this.pageNumbers().includes(pageNum));
|
|
3076
|
+
}
|
|
3077
|
+
#getCachedThumbnail(pageNum) {
|
|
3078
|
+
const cached = this.#thumbnailCacheLru.get(pageNum);
|
|
3079
|
+
if (cached === undefined)
|
|
3080
|
+
return undefined;
|
|
3081
|
+
this.#thumbnailCacheLru.delete(pageNum);
|
|
3082
|
+
this.#thumbnailCacheLru.set(pageNum, cached);
|
|
3083
|
+
return cached;
|
|
3084
|
+
}
|
|
3085
|
+
#cacheThumbnail(pageNum, dataUrl) {
|
|
3086
|
+
this.#thumbnailCacheLru.delete(pageNum);
|
|
3087
|
+
this.#thumbnailCacheLru.set(pageNum, dataUrl);
|
|
3088
|
+
while (this.#thumbnailCacheLru.size > _a.MAX_THUMBNAIL_CACHE_ENTRIES) {
|
|
3089
|
+
const oldestPage = this.#thumbnailCacheLru.keys().next().value;
|
|
3090
|
+
if (oldestPage === undefined)
|
|
3091
|
+
break;
|
|
3092
|
+
this.#thumbnailCacheLru.delete(oldestPage);
|
|
3093
|
+
}
|
|
3094
|
+
this.#thumbCache.set(Object.fromEntries(this.#thumbnailCacheLru));
|
|
3095
|
+
}
|
|
3096
|
+
#cancelThumbnailRenders() {
|
|
3097
|
+
for (const work of this.#thumbnailWork.values())
|
|
3098
|
+
this.#cancelThumbnailWork(work);
|
|
3099
|
+
this.#thumbnailWork.clear();
|
|
3100
|
+
this.#thumbnailWorkCount.set(0);
|
|
3101
|
+
}
|
|
3102
|
+
#cancelThumbnailWorkOutside(currentPages) {
|
|
3103
|
+
for (const [pageNumber, work] of this.#thumbnailWork) {
|
|
3104
|
+
if (currentPages.has(pageNumber))
|
|
3105
|
+
continue;
|
|
3106
|
+
this.#cancelThumbnailWork(work);
|
|
3107
|
+
this.#thumbnailWork.delete(pageNumber);
|
|
3108
|
+
}
|
|
3109
|
+
this.#thumbnailWorkCount.set(this.#thumbnailWork.size);
|
|
3110
|
+
}
|
|
3111
|
+
#cancelThumbnailWork(work) {
|
|
3112
|
+
work.cancelled = true;
|
|
3113
|
+
if (work.slotState === 'queued') {
|
|
3114
|
+
const queueIndex = this.#thumbnailSlotQueue.indexOf(work);
|
|
3115
|
+
if (queueIndex >= 0)
|
|
3116
|
+
this.#thumbnailSlotQueue.splice(queueIndex, 1);
|
|
3117
|
+
work.slotState = 'released';
|
|
3118
|
+
const resume = work.resumeSlot;
|
|
3119
|
+
work.resumeSlot = null;
|
|
3120
|
+
resume?.(false);
|
|
3121
|
+
}
|
|
3122
|
+
try {
|
|
3123
|
+
work.renderTask?.cancel();
|
|
3124
|
+
}
|
|
3125
|
+
catch {
|
|
3126
|
+
// Cancellation is best-effort; the identity guard still blocks late cache writes.
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
#acquireThumbnailSlot(work) {
|
|
3130
|
+
if (work.cancelled || this.#destroyed)
|
|
3131
|
+
return Promise.resolve(false);
|
|
3132
|
+
if (this.#activeThumbnailSlots < _a.MAX_CONCURRENT_THUMBNAIL_WORK) {
|
|
3133
|
+
this.#activeThumbnailSlots++;
|
|
3134
|
+
work.slotState = 'active';
|
|
3135
|
+
return Promise.resolve(true);
|
|
3136
|
+
}
|
|
3137
|
+
work.slotState = 'queued';
|
|
3138
|
+
return new Promise(resolve => {
|
|
3139
|
+
work.resumeSlot = resolve;
|
|
3140
|
+
this.#thumbnailSlotQueue.push(work);
|
|
3141
|
+
});
|
|
3142
|
+
}
|
|
3143
|
+
#releaseThumbnailSlot(work) {
|
|
3144
|
+
if (work.slotState !== 'active')
|
|
3145
|
+
return;
|
|
3146
|
+
work.slotState = 'released';
|
|
3147
|
+
this.#activeThumbnailSlots = Math.max(0, this.#activeThumbnailSlots - 1);
|
|
3148
|
+
while (this.#thumbnailSlotQueue.length > 0) {
|
|
3149
|
+
const next = this.#thumbnailSlotQueue.shift();
|
|
3150
|
+
if (next.cancelled || next.slotState !== 'queued') {
|
|
3151
|
+
const resumeCancelled = next.resumeSlot;
|
|
3152
|
+
next.resumeSlot = null;
|
|
3153
|
+
next.slotState = 'released';
|
|
3154
|
+
resumeCancelled?.(false);
|
|
3155
|
+
continue;
|
|
3156
|
+
}
|
|
3157
|
+
this.#activeThumbnailSlots++;
|
|
3158
|
+
next.slotState = 'active';
|
|
3159
|
+
const resume = next.resumeSlot;
|
|
3160
|
+
next.resumeSlot = null;
|
|
3161
|
+
resume?.(true);
|
|
3162
|
+
break;
|
|
1947
3163
|
}
|
|
1948
3164
|
}
|
|
1949
3165
|
/**
|
|
1950
3166
|
* Paint a previously-rendered dataURL onto a freshly-mounted canvas so the
|
|
1951
3167
|
* mini-page reappears without re-rasterizing.
|
|
1952
3168
|
*/
|
|
1953
|
-
#paintCachedThumb(canvas, dataUrl) {
|
|
1954
|
-
const img =
|
|
3169
|
+
#paintCachedThumb(canvas, dataUrl, loadToken) {
|
|
3170
|
+
const img = this.#browser.createImage();
|
|
3171
|
+
if (!img)
|
|
3172
|
+
return;
|
|
1955
3173
|
img.onload = () => {
|
|
3174
|
+
if (!this.#isActiveLoad(loadToken))
|
|
3175
|
+
return;
|
|
1956
3176
|
canvas.width = img.naturalWidth;
|
|
1957
3177
|
canvas.height = img.naturalHeight;
|
|
1958
3178
|
canvas.style.width = `${img.naturalWidth}px`;
|
|
@@ -1963,7 +3183,7 @@ class SdPreviewPdf {
|
|
|
1963
3183
|
img.src = dataUrl;
|
|
1964
3184
|
}
|
|
1965
3185
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdPreviewPdf, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1966
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SdPreviewPdf, isStandalone: true, selector: "sd-preview-pdf", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, startPage: { classPropertyName: "startPage", publicName: "startPage", isSignal: true, isRequired: false, transformFunction: null }, initialZoom: { classPropertyName: "initialZoom", publicName: "initialZoom", isSignal: true, isRequired: false, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: false, transformFunction: null }, sidebarOpen: { classPropertyName: "sidebarOpen", publicName: "sidebarOpen", isSignal: true, isRequired: false, transformFunction: null }, scrollMode: { classPropertyName: "scrollMode", publicName: "scrollMode", isSignal: true, isRequired: false, transformFunction: null }, showToolbar: { classPropertyName: "showToolbar", publicName: "showToolbar", isSignal: true, isRequired: false, transformFunction: null }, downloadable: { classPropertyName: "downloadable", publicName: "downloadable", isSignal: true, isRequired: false, transformFunction: null }, password: { classPropertyName: "password", publicName: "password", isSignal: true, isRequired: false, transformFunction: null }, httpHeaders: { classPropertyName: "httpHeaders", publicName: "httpHeaders", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close", loaded: "loaded", pageChange: "pageChange", zoomChange: "zoomChange", download: "download", loadError: "loadError", searchChange: "searchChange" }, host: { attributes: { "tabindex": "0" }, listeners: { "keydown": "onKeyDown($event)" }, properties: { "attr.data-theme": "theme()", "class.sd-preview-pdf-host--fullscreen": "isFullscreen()" }, classAttribute: "sd-preview-pdf-host" }, viewQueries: [{ propertyName: "pageCanvasRef", first: true, predicate: ["pageCanvas"], descendants: true, isSignal: true }, { propertyName: "stageEl", first: true, predicate: ["stageRef"], descendants: true, isSignal: true }, { propertyName: "pageInputRef", first: true, predicate: ["pageInput"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "thumbCanvases", predicate: ["thumbCanvas"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n sd-preview-pdf shell. Self-contained \u2014 sizes to its parent. Consumer wraps\n in <sd-modal>/<sd-side-drawer>/page section. Header + sidebar + stage +\n floating toolbar mirror Artboards A / C / E from the design handoff.\n-->\n<div class=\"sd-preview-pdf-shell\" role=\"region\">\n @let _stage = stage();\n @let _numPages = numPages();\n @let _activePage = activePage();\n @let _isSidebarOpen = isSidebarOpen();\n @let _searchBarOpen = searchBarOpen();\n @let _searchTerm = searchTerm();\n @let _searchTotal = searchTotal();\n\n <!-- ============================================\n HEADER BAR \u2014 hidden in empty + loading per design\n ============================================ -->\n @if (_stage === 'ready') {\n <div class=\"sd-preview-pdf-header\">\n <div class=\"sd-preview-pdf-header__left\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"_isSidebarOpen\"\n [attr.aria-label]=\"'core.component.preview-pdf.toggle-sidebar' | translate\"\n [attr.title]=\"'core.component.preview-pdf.toggle-sidebar' | translate\"\n [attr.data-autoId]=\"autoIdSidebarToggle()\"\n (click)=\"toggleSidebar()\">\n <sd-icon fontIcon=\"menu\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-header__divider\"></span>\n <div class=\"sd-preview-pdf-fileicon\" aria-hidden=\"true\">PDF</div>\n <div class=\"sd-preview-pdf-header__meta\">\n <div class=\"sd-preview-pdf-header__name\" [title]=\"title() || filename()\">\n {{ title() || filename() }}\n </div>\n <div class=\"sd-preview-pdf-header__sub\">\n {{ 'core.component.preview-pdf.pages-count' | translate: { count: _numPages } }}\n </div>\n </div>\n </div>\n\n <div class=\"sd-preview-pdf-header__right\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"_searchBarOpen\"\n [attr.aria-label]=\"'core.component.preview-pdf.search' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search' | translate\"\n [attr.data-autoId]=\"autoIdSearchToggle()\"\n (click)=\"searchBarOpen() ? closeSearch() : openSearch()\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n </button>\n @if (downloadable()) {\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.download' | translate\"\n [attr.title]=\"'core.component.preview-pdf.download' | translate\"\n [attr.data-autoId]=\"autoIdDownload()\"\n (click)=\"downloadFile()\">\n <sd-icon fontIcon=\"download\"></sd-icon>\n </button>\n }\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.fullscreen' | translate\"\n [attr.title]=\"'core.component.preview-pdf.fullscreen' | translate\"\n [attr.data-autoId]=\"autoIdFullscreen()\"\n (click)=\"toggleFullscreen()\">\n <sd-icon [fontIcon]=\"isFullscreen() ? 'fullscreen_exit' : 'fullscreen'\"></sd-icon>\n </button>\n </div>\n </div>\n\n <!-- ============================================\n SEARCH BAR \u2014 between header + body, only when bar is open\n ============================================ -->\n @if (_searchBarOpen) {\n <div class=\"sd-preview-pdf-searchbar\" role=\"search\">\n <sd-icon class=\"sd-preview-pdf-searchbar__icon\" fontIcon=\"search\"></sd-icon>\n <input\n #searchInput\n type=\"text\"\n class=\"sd-preview-pdf-searchbar__input\"\n [attr.placeholder]=\"'core.component.preview-pdf.search-placeholder' | translate\"\n [value]=\"_searchTerm\"\n [attr.data-autoId]=\"autoIdSearchInput()\"\n (input)=\"onSearchInput($any($event.target).value)\" />\n <span class=\"sd-preview-pdf-searchbar__counter\" aria-live=\"polite\"> {{ searchCurrent() }} / {{ _searchTotal }} </span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [disabled]=\"_searchTotal === 0\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-prev' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-prev' | translate\"\n [attr.data-autoId]=\"autoIdSearchPrev()\"\n (click)=\"searchPrev()\">\n <sd-icon fontIcon=\"keyboard_arrow_up\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [disabled]=\"_searchTotal === 0\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-next' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-next' | translate\"\n [attr.data-autoId]=\"autoIdSearchNext()\"\n (click)=\"searchNext()\">\n <sd-icon fontIcon=\"keyboard_arrow_down\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-searchbar__divider\"></span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"searchCaseSensitive()\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-case' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-case' | translate\"\n [attr.data-autoId]=\"autoIdSearchCase()\"\n (click)=\"toggleSearchCaseSensitive()\">\n <sd-icon fontIcon=\"match_case\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"searchWholeWord()\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-whole' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-whole' | translate\"\n [attr.data-autoId]=\"autoIdSearchWhole()\"\n (click)=\"toggleSearchWholeWord()\">\n <sd-icon fontIcon=\"text_fields\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-searchbar__spacer\"></span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-close' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-close' | translate\"\n [attr.data-autoId]=\"autoIdSearchClose()\"\n (click)=\"closeSearch()\">\n <sd-icon fontIcon=\"close\"></sd-icon>\n </button>\n </div>\n }\n }\n\n <!-- ============================================\n BODY \u2014 sidebar + stage\n ============================================ -->\n <div class=\"sd-preview-pdf-body\">\n <!-- SIDEBAR ............................................................. -->\n @if (_stage === 'ready' && _isSidebarOpen) {\n <aside class=\"sd-preview-pdf-sidebar\" role=\"complementary\">\n @let _sidebarMode = sidebarMode();\n <div class=\"sd-preview-pdf-sidebar__tabs\" role=\"tablist\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'thumbnails'\"\n [attr.data-autoId]=\"autoIdTabThumbnails()\"\n (click)=\"setSidebarMode('thumbnails')\">\n <sd-icon fontIcon=\"view_carousel\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.thumbnails' | translate }}</span>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'outline'\"\n [attr.data-autoId]=\"autoIdTabOutline()\"\n (click)=\"setSidebarMode('outline')\">\n <sd-icon fontIcon=\"list\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.outline' | translate }}</span>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'search'\"\n [attr.data-autoId]=\"autoIdTabSearch()\"\n (click)=\"setSidebarMode('search')\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.search' | translate }}</span>\n </button>\n </div>\n\n <div class=\"sd-preview-pdf-sidebar__content\">\n @switch (_sidebarMode) {\n @case ('thumbnails') {\n <ul class=\"sd-preview-pdf-thumb-list\">\n @for (p of pageNumbers(); track trackByPage($index, p); let pi = $index) {\n <li>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-thumb\"\n [class.sd-preview-pdf-thumb--active]=\"p === _activePage\"\n [attr.aria-current]=\"p === _activePage ? 'page' : null\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: p, total: _numPages }\"\n [attr.data-autoId]=\"autoIdThumb(pi)\"\n (click)=\"goToPage(p)\">\n <div class=\"sd-preview-pdf-thumb__page\">\n <canvas #thumbCanvas class=\"sd-preview-pdf-thumb__canvas\" [attr.data-page]=\"p\" aria-hidden=\"true\"></canvas>\n </div>\n <span class=\"sd-preview-pdf-thumb__label\">{{ p }}</span>\n </button>\n </li>\n }\n </ul>\n }\n @case ('outline') {\n <!-- TODO(preview-pdf): outline tree (pdfDoc.getOutline()) deferred to follow-up commit. -->\n <div class=\"sd-preview-pdf-sidebar__deferred\">\n <sd-icon fontIcon=\"list\"></sd-icon>\n <p>{{ 'core.component.preview-pdf.deferred.outline' | translate }}</p>\n </div>\n }\n @case ('search') {\n @if (!_searchTerm) {\n <div class=\"sd-preview-pdf-sidebar__deferred\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n <p>{{ 'core.component.preview-pdf.search-placeholder' | translate }}</p>\n </div>\n } @else if (_searchTotal === 0) {\n <div class=\"sd-preview-pdf-sidebar__deferred\">\n <sd-icon fontIcon=\"search_off\"></sd-icon>\n <p>{{ 'core.component.preview-pdf.search-no-results' | translate }}</p>\n </div>\n } @else {\n <ul class=\"sd-preview-pdf-search-list\">\n @for (r of searchResults(); track $index; let i = $index) {\n <li>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-search-result\"\n [class.sd-preview-pdf-search-result--active]=\"i === searchActiveIndex()\"\n [attr.data-autoId]=\"autoIdResult(i)\"\n (click)=\"goToPage(r.page)\">\n <span class=\"sd-preview-pdf-search-result__snippet\">\n {{ r.before }}<mark>{{ r.term }}</mark\n >{{ r.after }}\n </span>\n <span class=\"sd-preview-pdf-search-result__page\">{{ r.page }}</span>\n </button>\n </li>\n }\n </ul>\n }\n }\n @default {\n <div class=\"sd-preview-pdf-sidebar__deferred\"></div>\n }\n }\n </div>\n </aside>\n }\n\n <!-- STAGE ............................................................... -->\n <div class=\"sd-preview-pdf-stage\" #stageRef (wheel)=\"onWheel($event)\">\n @switch (_stage) {\n @case ('empty') {\n <div class=\"sd-preview-pdf-status\">\n <div class=\"sd-preview-pdf-status__tile sd-preview-pdf-status__tile--muted\">\n <sd-icon fontIcon=\"picture_as_pdf\"></sd-icon>\n </div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.empty.title' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__hint\">\n {{ 'core.component.preview-pdf.empty.subtitle' | translate }}\n </div>\n </div>\n }\n @case ('loading') {\n @let _loadPercent = loadPercent();\n <div class=\"sd-preview-pdf-status\">\n <div class=\"sd-preview-pdf-spinner\" role=\"progressbar\"></div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.loading' | translate }}\n @if (_loadPercent > 0) {\n <span class=\"sd-preview-pdf-progress-pct\"> {{ _loadPercent }}%</span>\n }\n </div>\n <div class=\"sd-preview-pdf-progress\">\n <div class=\"sd-preview-pdf-progress__fill\" [style.width.%]=\"_loadPercent\"></div>\n </div>\n </div>\n }\n @case ('error') {\n @if (loadError$(); as err) {\n <div class=\"sd-preview-pdf-status\">\n <div class=\"sd-preview-pdf-status__tile sd-preview-pdf-status__tile--danger\">\n <sd-icon fontIcon=\"error\"></sd-icon>\n </div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.error.' + err.reason + '.title' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__hint\">\n {{ 'core.component.preview-pdf.error.' + err.reason + '.body' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__actions\">\n <button type=\"button\" class=\"sd-preview-pdf-btn-primary\" (click)=\"retryLoad()\" [attr.data-autoId]=\"autoIdRetry()\">\n <sd-icon fontIcon=\"refresh\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.error.retry' | translate }}</span>\n </button>\n @if (downloadable()) {\n <button type=\"button\" class=\"sd-preview-pdf-btn-secondary\" (click)=\"downloadFile()\">\n <sd-icon fontIcon=\"download\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.error.download-raw' | translate }}</span>\n </button>\n }\n </div>\n </div>\n }\n }\n @default {\n <div class=\"sd-preview-pdf-page-wrap\">\n <canvas\n #pageCanvas\n class=\"sd-preview-pdf-page\"\n role=\"img\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: _activePage, total: _numPages }\">\n </canvas>\n </div>\n }\n }\n\n <!-- FLOATING TOOLBAR ........................................ -->\n @if (showToolbar() && _stage === 'ready') {\n @let _canPrev = canPrev();\n @let _canNext = canNext();\n @let _zoomMode = zoomMode();\n @let _scrollModeCurrent = scrollModeCurrent();\n <div class=\"sd-preview-pdf-toolbar\" role=\"toolbar\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canPrev\"\n [attr.aria-label]=\"'core.component.preview-pdf.first-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.first-page' | translate\"\n [attr.data-autoId]=\"autoIdFirst()\"\n (click)=\"firstPage()\">\n <sd-icon fontIcon=\"first_page\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canPrev\"\n [attr.aria-label]=\"'core.component.preview-pdf.prev-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.prev-page' | translate\"\n [attr.data-autoId]=\"autoIdPrev()\"\n (click)=\"prevPage()\">\n <sd-icon fontIcon=\"chevron_left\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-toolbar__page\">\n <input\n type=\"text\"\n inputmode=\"numeric\"\n class=\"sd-preview-pdf-toolbar__page-input\"\n [value]=\"_activePage\"\n #pageInput\n (keydown.enter)=\"onPageInputEnter($event)\"\n (blur)=\"onPageInputBlur($event)\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: _activePage, total: _numPages }\"\n [attr.data-autoId]=\"autoIdPageInput()\" />\n <span class=\"sd-preview-pdf-toolbar__page-total\">/ {{ _numPages }}</span>\n </span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canNext\"\n [attr.aria-label]=\"'core.component.preview-pdf.next-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.next-page' | translate\"\n [attr.data-autoId]=\"autoIdNext()\"\n (click)=\"nextPage()\">\n <sd-icon fontIcon=\"chevron_right\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canNext\"\n [attr.aria-label]=\"'core.component.preview-pdf.last-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.last-page' | translate\"\n [attr.data-autoId]=\"autoIdLast()\"\n (click)=\"lastPage()\">\n <sd-icon fontIcon=\"last_page\"></sd-icon>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.zoom-out' | translate\"\n [attr.title]=\"'core.component.preview-pdf.zoom-out' | translate\"\n [attr.data-autoId]=\"autoIdZoomOut()\"\n (click)=\"zoomOut()\">\n <sd-icon fontIcon=\"remove\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-toolbar__zoom\" aria-live=\"polite\" [attr.data-autoId]=\"autoIdZoomReadout()\">{{ zoomPercent() }}%</span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.zoom-in' | translate\"\n [attr.title]=\"'core.component.preview-pdf.zoom-in' | translate\"\n [attr.data-autoId]=\"autoIdZoomIn()\"\n (click)=\"zoomIn()\">\n <sd-icon fontIcon=\"add\"></sd-icon>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [class.sd-preview-pdf-toolbar__btn--active]=\"_zoomMode === 'page-fit'\"\n [attr.data-autoId]=\"autoIdFitPage()\"\n (click)=\"setZoom('page-fit')\">\n <sd-icon fontIcon=\"fit_screen\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.fit-page' | translate }}</span>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [class.sd-preview-pdf-toolbar__btn--active]=\"_zoomMode === 'page-width'\"\n [attr.data-autoId]=\"autoIdFitWidth()\"\n (click)=\"setZoom('page-width')\">\n <sd-icon fontIcon=\"swap_horiz\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.fit-width' | translate }}</span>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.rotate' | translate\"\n [attr.title]=\"'core.component.preview-pdf.rotate' | translate\"\n [attr.data-autoId]=\"autoIdRotate()\"\n (click)=\"rotate('right')\">\n <sd-icon fontIcon=\"rotate_right\"></sd-icon>\n </button>\n\n <!-- TODO(preview-pdf): scroll mode toggle is signature-only;\n clicking switches the state but rendering stays single-page\n until the continuous scroll feature lands. -->\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [attr.data-autoId]=\"autoIdScrollMode()\"\n (click)=\"setScrollMode(_scrollModeCurrent === 'page' ? 'continuous' : 'page')\">\n <sd-icon [fontIcon]=\"_scrollModeCurrent === 'page' ? 'description' : 'view_day'\"></sd-icon>\n <span>\n {{\n (_scrollModeCurrent === 'page' ? 'core.component.preview-pdf.scroll-page' : 'core.component.preview-pdf.scroll-continuous')\n | translate\n }}\n </span>\n </button>\n </div>\n }\n </div>\n </div>\n</div>\n", styles: [":host{--sd-pdf-shell-bg: #1a1c1f;--sd-pdf-sidebar-bg: #22252a;--sd-pdf-toolbar-bg: rgba(20, 22, 25, .94);--sd-pdf-page-bg: #ffffff;--sd-pdf-page-shadow: 0 4px 16px rgba(0, 0, 0, .4);--sd-pdf-divider: rgba(255, 255, 255, .08);--sd-pdf-fg: #f5f6f7;--sd-pdf-fg-muted: rgba(255, 255, 255, .6);--sd-pdf-fg-subtle: rgba(255, 255, 255, .4);--sd-pdf-primary: var(--sd-pdf-accent, var(--sd-primary, #005cbb));--sd-pdf-primary-soft: var(--sd-pdf-accent-soft, rgba(0, 92, 187, .18));--sd-pdf-danger: var(--sd-error, #ba1a1a);--sd-pdf-success: var(--sd-success, #2e7d32);--sd-pdf-danger-soft: rgba(186, 26, 26, .1);--sd-pdf-danger-border: rgba(186, 26, 26, .3);--sd-pdf-danger-tint: rgba(186, 26, 26, .15);--sd-pdf-hover-bg: rgba(255, 255, 255, .1);--sd-pdf-search-hi: rgba(250, 204, 21, .55);--sd-pdf-search-hi-active: rgba(249, 115, 22, .85);display:block;width:100%;height:100%;min-height:320px;outline:none}:host([data-theme=light]){--sd-pdf-shell-bg: #f7f8fa;--sd-pdf-sidebar-bg: #ffffff;--sd-pdf-toolbar-bg: rgba(255, 255, 255, .94);--sd-pdf-page-bg: #ffffff;--sd-pdf-page-shadow: 0 4px 16px rgba(0, 0, 0, .12);--sd-pdf-divider: rgba(0, 0, 0, .08);--sd-pdf-fg: #1a1c1f;--sd-pdf-fg-muted: rgba(0, 0, 0, .62);--sd-pdf-fg-subtle: rgba(0, 0, 0, .42);--sd-pdf-primary: var(--sd-pdf-accent, var(--sd-primary, #005cbb));--sd-pdf-primary-soft: var(--sd-pdf-accent-soft, rgba(0, 92, 187, .16));--sd-pdf-danger: var(--sd-error, #ba1a1a);--sd-pdf-success: var(--sd-success, #2e7d32);--sd-pdf-danger-soft: rgba(186, 26, 26, .08);--sd-pdf-danger-border: rgba(186, 26, 26, .25);--sd-pdf-danger-tint: rgba(186, 26, 26, .12);--sd-pdf-hover-bg: rgba(0, 0, 0, .06);--sd-pdf-search-hi: rgba(250, 204, 21, .55);--sd-pdf-search-hi-active: rgba(250, 204, 21, .9)}:host(.sd-preview-pdf-host--fullscreen){width:100vw;height:100vh;min-height:0}.sd-preview-pdf-shell{position:relative;display:flex;flex-direction:column;width:100%;height:100%;background:var(--sd-pdf-shell-bg);color:var(--sd-pdf-fg);border-radius:12px;overflow:hidden;box-shadow:0 30px 80px #0000008c,0 0 0 1px #ffffff0a;font-family:inherit}.sd-preview-pdf-header{display:flex;align-items:center;justify-content:space-between;gap:12px;height:57px;padding:0 12px;background:var(--sd-pdf-toolbar-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--sd-pdf-divider);flex:0 0 auto;z-index:2}.sd-preview-pdf-header__left{display:flex;align-items:center;gap:8px;min-width:0;flex:1 1 auto}.sd-preview-pdf-header__right{display:flex;align-items:center;gap:4px;flex-shrink:0}.sd-preview-pdf-header__divider{display:inline-block;width:1px;height:20px;background:var(--sd-pdf-divider)}.sd-preview-pdf-header__meta{display:flex;flex-direction:column;min-width:0}.sd-preview-pdf-header__name{font-size:14px;font-weight:500;color:var(--sd-pdf-fg);max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-preview-pdf-header__sub{font-size:11px;color:var(--sd-pdf-fg-muted);margin-top:2px}.sd-preview-pdf-fileicon{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:6px;background:var(--sd-pdf-danger-tint);border:1px solid var(--sd-pdf-danger-border);color:var(--sd-error-light, #f87171);font:700 10px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.sd-preview-pdf-iconbtn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-width:32px;height:32px;padding:0 8px;background:transparent;border:none;color:var(--sd-pdf-fg);border-radius:6px;cursor:pointer;transition:background .12s,color .12s;white-space:nowrap;flex-shrink:0}.sd-preview-pdf-iconbtn sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-iconbtn__label{font-size:13px;font-weight:500}.sd-preview-pdf-iconbtn:hover:not(:disabled){background:var(--sd-pdf-hover-bg)}.sd-preview-pdf-iconbtn:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-iconbtn:disabled{opacity:.35;cursor:not-allowed}.sd-preview-pdf-iconbtn--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-searchbar{display:flex;align-items:center;gap:6px;height:46px;padding:0 12px;background:var(--sd-pdf-toolbar-bg);border-bottom:1px solid var(--sd-pdf-divider);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);flex:0 0 auto;z-index:2}.sd-preview-pdf-searchbar__icon{color:var(--sd-pdf-fg-muted);font-size:18px;width:18px;height:18px;flex-shrink:0}.sd-preview-pdf-searchbar__input{flex:0 1 360px;min-width:160px;height:30px;background:#ffffff0f;border:1px solid rgba(255,255,255,.08);color:var(--sd-pdf-fg);border-radius:6px;padding:0 10px;font-size:13px;outline:none}.sd-preview-pdf-searchbar__input::placeholder{color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-searchbar__input:focus{border-color:var(--sd-pdf-primary);background:#ffffff17}.sd-preview-pdf-searchbar__counter{color:var(--sd-pdf-fg-muted);font-size:12px;font-variant-numeric:tabular-nums;min-width:48px;text-align:center;flex-shrink:0}.sd-preview-pdf-searchbar__divider{width:1px;height:20px;background:var(--sd-pdf-divider);flex-shrink:0}.sd-preview-pdf-searchbar__spacer{flex:1 1 auto}.sd-pdf-search-overlay{position:absolute;top:8px;left:50%;transform:translate(-50%);display:flex;flex-wrap:wrap;gap:4px;max-width:80%;pointer-events:none;z-index:5}.sd-pdf-search-hi{display:inline-block;padding:1px 4px;border-radius:2px;background:var(--sd-pdf-search-hi);color:#000;font-size:12px;font-weight:500}.sd-pdf-search-hi--active{background:var(--sd-pdf-search-hi-active);color:#fff;box-shadow:0 0 0 2px #fff6}.sd-preview-pdf-body{position:relative;flex:1 1 auto;min-height:0;display:flex;flex-direction:row;overflow:hidden}.sd-preview-pdf-sidebar{width:240px;display:flex;flex-direction:column;background:var(--sd-pdf-sidebar-bg);border-right:1px solid var(--sd-pdf-divider);flex-shrink:0;min-height:0}.sd-preview-pdf-sidebar__tabs{display:flex;gap:4px;padding:8px;border-bottom:1px solid var(--sd-pdf-divider)}.sd-preview-pdf-sidebar__tab{flex:1 1 0;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:32px;padding:0 4px;background:transparent;border:none;color:var(--sd-pdf-fg-muted);border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;white-space:nowrap;flex-shrink:0;transition:background .12s,color .12s}.sd-preview-pdf-sidebar__tab sd-icon{font-size:14px;width:14px;height:14px}.sd-preview-pdf-sidebar__tab:hover{background:#ffffff0f;color:var(--sd-pdf-fg)}.sd-preview-pdf-sidebar__tab--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-sidebar__content{flex:1 1 auto;overflow:auto;padding:12px}.sd-preview-pdf-sidebar__deferred{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:32px 16px;text-align:center;color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-sidebar__deferred sd-icon{font-size:28px;width:28px;height:28px;opacity:.6}.sd-preview-pdf-sidebar__deferred p{margin:0;font-size:12px;line-height:1.5}.sd-preview-pdf-thumb-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px}.sd-preview-pdf-thumb{display:flex;flex-direction:column;align-items:center;gap:4px;width:100%;padding:0;background:transparent;border:none;cursor:pointer}.sd-preview-pdf-thumb__page{width:152px;aspect-ratio:612/792;background:var(--sd-pdf-page-bg);border-radius:2px;outline:1px solid rgba(255,255,255,.08);outline-offset:-1px;box-shadow:0 2px 6px #0000004d;display:flex;align-items:center;justify-content:center;overflow:hidden;-webkit-user-select:none;user-select:none;background-image:linear-gradient(90deg,#0000,#0000000a,#0000);background-size:200% 100%;animation:sd-preview-pdf-thumb-skel 1.6s ease-in-out infinite}.sd-preview-pdf-thumb__canvas{display:block;max-width:100%;max-height:100%;background:var(--sd-pdf-page-bg)}.sd-preview-pdf-thumb__label{font-size:11px;color:var(--sd-pdf-fg-muted);font-variant-numeric:tabular-nums}.sd-preview-pdf-thumb--active .sd-preview-pdf-thumb__page{outline:2px solid var(--sd-pdf-primary);outline-offset:-2px}.sd-preview-pdf-thumb--active .sd-preview-pdf-thumb__label{color:var(--sd-pdf-primary);font-weight:600}.sd-preview-pdf-thumb:focus-visible .sd-preview-pdf-thumb__page{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-stage{position:relative;flex:1 1 auto;min-width:0;min-height:0;background:var(--sd-pdf-shell-bg);background-image:linear-gradient(45deg,rgba(255,255,255,.015) 25%,transparent 25%),linear-gradient(-45deg,rgba(255,255,255,.015) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(255,255,255,.015) 75%),linear-gradient(-45deg,transparent 75%,rgba(255,255,255,.015) 75%);background-size:24px 24px;background-position:0 0,0 12px,12px -12px,-12px 0;overflow:auto;padding:32px 24px 96px;display:flex;justify-content:center;align-items:flex-start}:host([data-theme=light]) .sd-preview-pdf-stage{background-image:linear-gradient(45deg,rgba(0,0,0,.015) 25%,transparent 25%),linear-gradient(-45deg,rgba(0,0,0,.015) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(0,0,0,.015) 75%),linear-gradient(-45deg,transparent 75%,rgba(0,0,0,.015) 75%)}.sd-preview-pdf-page-wrap{display:inline-block;position:relative}.sd-preview-pdf-page{display:block;background:var(--sd-pdf-page-bg);border-radius:2px;box-shadow:var(--sd-pdf-page-shadow);max-width:100%}.sd-preview-pdf-toolbar{position:absolute;bottom:20px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:2px;padding:5px 6px;background:var(--sd-pdf-toolbar-bg);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid var(--sd-pdf-divider);border-radius:10px;box-shadow:0 8px 30px #0006;z-index:4;max-width:calc(100% - 32px);overflow-x:auto}.sd-preview-pdf-toolbar__btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;min-width:30px;height:28px;padding:0 6px;background:transparent;border:none;color:var(--sd-pdf-fg);border-radius:6px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background .12s,color .12s}.sd-preview-pdf-toolbar__btn sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-toolbar__btn span{font-size:13px;font-weight:500}.sd-preview-pdf-toolbar__btn--with-label{padding:0 10px}.sd-preview-pdf-toolbar__btn--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-toolbar__btn:hover:not(:disabled):not(.sd-preview-pdf-toolbar__btn--active){background:var(--sd-pdf-hover-bg)}.sd-preview-pdf-toolbar__btn:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-toolbar__btn:disabled{opacity:.35;cursor:not-allowed}.sd-preview-pdf-toolbar__page{display:inline-flex;align-items:center;gap:4px;padding:0 6px;flex-shrink:0}.sd-preview-pdf-toolbar__page-input{width:36px;height:24px;background:#ffffff14;border:1px solid rgba(255,255,255,.08);color:var(--sd-pdf-fg);border-radius:4px;text-align:center;font-size:13px;font-variant-numeric:tabular-nums;outline:none}.sd-preview-pdf-toolbar__page-input:focus{border-color:var(--sd-pdf-primary)}.sd-preview-pdf-toolbar__page-total{font-size:13px;color:var(--sd-pdf-fg-muted);font-variant-numeric:tabular-nums}.sd-preview-pdf-toolbar__zoom{min-width:56px;text-align:center;color:var(--sd-pdf-fg);font-size:13px;font-weight:500;font-variant-numeric:tabular-nums;background:#ffffff14;border-radius:4px;padding:2px 8px}.sd-preview-pdf-toolbar__divider{width:1px;height:20px;background:var(--sd-pdf-divider);margin:0 4px;flex-shrink:0}.sd-preview-pdf-status{margin:auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;text-align:center;padding:32px;max-width:420px}.sd-preview-pdf-status__tile{width:96px;height:96px;border-radius:24px;display:flex;align-items:center;justify-content:center;border:1px solid var(--sd-pdf-divider)}.sd-preview-pdf-status__tile sd-icon{font-size:48px;width:48px;height:48px}.sd-preview-pdf-status__tile--muted{background:#ffffff0d}.sd-preview-pdf-status__tile--muted sd-icon{color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-status__tile--danger{background:var(--sd-pdf-danger-soft);border-color:var(--sd-pdf-danger-border)}.sd-preview-pdf-status__tile--danger sd-icon{color:var(--sd-pdf-danger)}.sd-preview-pdf-status__title{font-size:16px;font-weight:500;color:var(--sd-pdf-fg)}.sd-preview-pdf-status__hint{font-size:13px;color:var(--sd-pdf-fg-muted);line-height:1.5}.sd-preview-pdf-status__actions{display:flex;gap:8px;margin-top:4px}.sd-preview-pdf-progress-pct{font-variant-numeric:tabular-nums;color:var(--sd-pdf-fg-muted)}.sd-preview-pdf-progress{width:240px;height:4px;background:#ffffff14;border-radius:999px;overflow:hidden}.sd-preview-pdf-progress__fill{height:100%;background:var(--sd-pdf-primary);transition:width .12s linear}.sd-preview-pdf-spinner{width:56px;height:56px;border-radius:999px;border:3px solid rgba(255,255,255,.08);border-top-color:var(--sd-pdf-primary);animation:sd-preview-pdf-spin .9s linear infinite}@keyframes sd-preview-pdf-spin{to{transform:rotate(360deg)}}@keyframes sd-preview-pdf-thumb-skel{0%{background-position:200% 0}to{background-position:-200% 0}}.sd-preview-pdf-search-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.sd-preview-pdf-search-result{width:100%;display:flex;align-items:flex-start;gap:8px;padding:8px 10px;background:#ffffff0a;border:1px solid transparent;border-radius:6px;color:var(--sd-pdf-fg);cursor:pointer;text-align:left;transition:background .12s,border-color .12s}.sd-preview-pdf-search-result:hover{background:#ffffff12}.sd-preview-pdf-search-result--active{background:var(--sd-pdf-primary-soft);border-color:#005cbb73}.sd-preview-pdf-search-result__snippet{flex:1 1 auto;font-size:12px;line-height:1.4;color:var(--sd-pdf-fg);word-break:break-word}.sd-preview-pdf-search-result__snippet mark{background:var(--sd-pdf-search-hi);color:#000;padding:0 2px;border-radius:2px}.sd-preview-pdf-search-result__page{flex:0 0 auto;font-size:11px;font-variant-numeric:tabular-nums;color:var(--sd-pdf-fg-muted);background:#ffffff0f;padding:2px 6px;border-radius:4px}.sd-preview-pdf-btn-primary{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 16px;background:var(--sd-pdf-primary);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;transition:filter .12s}.sd-preview-pdf-btn-primary sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-btn-primary:hover{filter:brightness(1.1)}.sd-preview-pdf-btn-primary:focus-visible{outline:2px solid #fff;outline-offset:2px}.sd-preview-pdf-btn-secondary{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 16px;background:transparent;color:var(--sd-pdf-fg);border:1px solid var(--sd-pdf-divider);border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;transition:background .12s}.sd-preview-pdf-btn-secondary sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-btn-secondary:hover{background:#ffffff0f}.sd-preview-pdf-btn-secondary:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3186
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SdPreviewPdf, isStandalone: true, selector: "sd-preview-pdf", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, startPage: { classPropertyName: "startPage", publicName: "startPage", isSignal: true, isRequired: false, transformFunction: null }, initialZoom: { classPropertyName: "initialZoom", publicName: "initialZoom", isSignal: true, isRequired: false, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: false, transformFunction: null }, sidebarOpen: { classPropertyName: "sidebarOpen", publicName: "sidebarOpen", isSignal: true, isRequired: false, transformFunction: null }, scrollMode: { classPropertyName: "scrollMode", publicName: "scrollMode", isSignal: true, isRequired: false, transformFunction: null }, showToolbar: { classPropertyName: "showToolbar", publicName: "showToolbar", isSignal: true, isRequired: false, transformFunction: null }, downloadable: { classPropertyName: "downloadable", publicName: "downloadable", isSignal: true, isRequired: false, transformFunction: null }, password: { classPropertyName: "password", publicName: "password", isSignal: true, isRequired: false, transformFunction: null }, httpHeaders: { classPropertyName: "httpHeaders", publicName: "httpHeaders", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close", loaded: "loaded", pageChange: "pageChange", zoomChange: "zoomChange", download: "download", loadError: "loadError", searchChange: "searchChange" }, host: { attributes: { "tabindex": "0" }, listeners: { "keydown": "onKeyDown($event)" }, properties: { "attr.data-theme": "theme()", "class.sd-preview-pdf-host--fullscreen": "isFullscreen()" }, classAttribute: "sd-preview-pdf-host" }, viewQueries: [{ propertyName: "pageCanvasRef", first: true, predicate: ["pageCanvas"], descendants: true, isSignal: true }, { propertyName: "stageEl", first: true, predicate: ["stageRef"], descendants: true, isSignal: true }, { propertyName: "pageInputRef", first: true, predicate: ["pageInput"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "sidebarContentRef", first: true, predicate: ["sidebarContent"], descendants: true, isSignal: true }, { propertyName: "outlineItemRefs", predicate: ["outlineItem"], descendants: true, isSignal: true }, { propertyName: "sidebarTabRefs", predicate: ["sidebarTab"], descendants: true, isSignal: true }, { propertyName: "continuousCanvases", predicate: ["continuousCanvas"], descendants: true, isSignal: true }, { propertyName: "thumbCanvases", predicate: ["thumbCanvas"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n sd-preview-pdf shell. Self-contained \u2014 sizes to its parent. Consumer wraps\n in <sd-modal>/<sd-side-drawer>/page section. Header + sidebar + stage +\n floating toolbar mirror Artboards A / C / E from the design handoff.\n-->\n<div class=\"sd-preview-pdf-shell\" role=\"region\">\n @let _stage = stage();\n @let _numPages = numPages();\n @let _activePage = activePage();\n @let _isSidebarOpen = isSidebarOpen();\n @let _searchBarOpen = searchBarOpen();\n @let _searchTerm = searchTerm();\n @let _searchTotal = searchTotal();\n\n <!-- ============================================\n HEADER BAR \u2014 hidden in empty + loading per design\n ============================================ -->\n @if (_stage === 'ready') {\n <div class=\"sd-preview-pdf-header\">\n <div class=\"sd-preview-pdf-header__left\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"_isSidebarOpen\"\n [attr.aria-expanded]=\"_isSidebarOpen\"\n [attr.aria-controls]=\"sidebarRegionId()\"\n [attr.aria-label]=\"'core.component.preview-pdf.toggle-sidebar' | translate\"\n [attr.title]=\"'core.component.preview-pdf.toggle-sidebar' | translate\"\n [attr.data-autoId]=\"autoIdSidebarToggle()\"\n (click)=\"toggleSidebar()\">\n <sd-icon fontIcon=\"menu\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-header__divider\"></span>\n <div class=\"sd-preview-pdf-fileicon\" aria-hidden=\"true\">PDF</div>\n <div class=\"sd-preview-pdf-header__meta\">\n <div class=\"sd-preview-pdf-header__name\" [title]=\"title() || filename()\">\n {{ title() || filename() }}\n </div>\n <div class=\"sd-preview-pdf-header__sub\">\n {{ 'core.component.preview-pdf.pages-count' | translate: { count: _numPages } }}\n </div>\n </div>\n </div>\n\n <div class=\"sd-preview-pdf-header__right\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"_searchBarOpen\"\n [attr.aria-expanded]=\"_searchBarOpen\"\n [attr.aria-controls]=\"searchBarId()\"\n [attr.aria-label]=\"'core.component.preview-pdf.search' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search' | translate\"\n [attr.data-autoId]=\"autoIdSearchToggle()\"\n (click)=\"searchBarOpen() ? closeSearch() : openSearch()\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n </button>\n @if (canPrint()) {\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.print' | translate\"\n [attr.title]=\"'core.component.preview-pdf.print' | translate\"\n [attr.data-autoId]=\"autoIdPrint()\"\n (click)=\"printFile()\">\n <sd-icon fontIcon=\"print\"></sd-icon>\n </button>\n }\n @if (canDownload()) {\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.download' | translate\"\n [attr.title]=\"'core.component.preview-pdf.download' | translate\"\n [attr.data-autoId]=\"autoIdDownload()\"\n (click)=\"downloadFile()\">\n <sd-icon fontIcon=\"download\"></sd-icon>\n </button>\n }\n @if (canFullscreen()) {\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.fullscreen' | translate\"\n [attr.title]=\"'core.component.preview-pdf.fullscreen' | translate\"\n [attr.data-autoId]=\"autoIdFullscreen()\"\n (click)=\"toggleFullscreen()\">\n <sd-icon [fontIcon]=\"isFullscreen() ? 'fullscreen_exit' : 'fullscreen'\"></sd-icon>\n </button>\n }\n </div>\n </div>\n\n <!-- ============================================\n SEARCH BAR \u2014 between header + body, only when bar is open\n ============================================ -->\n <div\n class=\"sd-preview-pdf-searchbar\"\n role=\"search\"\n [id]=\"searchBarId()\"\n [hidden]=\"!_searchBarOpen\"\n [style.display]=\"_searchBarOpen ? null : 'none'\">\n @if (_searchBarOpen) {\n <sd-icon class=\"sd-preview-pdf-searchbar__icon\" fontIcon=\"search\"></sd-icon>\n <input\n #searchInput\n type=\"text\"\n class=\"sd-preview-pdf-searchbar__input\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-placeholder' | translate\"\n [attr.placeholder]=\"'core.component.preview-pdf.search-placeholder' | translate\"\n [value]=\"_searchTerm\"\n [attr.data-autoId]=\"autoIdSearchInput()\"\n (input)=\"onSearchInput($event)\" />\n <span class=\"sd-preview-pdf-searchbar__counter\" aria-live=\"polite\">\n {{ searchCurrent() }} / {{ _searchTotal }}{{ searchTruncated() ? '+' : '' }}\n </span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [disabled]=\"_searchTotal === 0\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-prev' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-prev' | translate\"\n [attr.data-autoId]=\"autoIdSearchPrev()\"\n (click)=\"searchPrev()\">\n <sd-icon fontIcon=\"keyboard_arrow_up\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [disabled]=\"_searchTotal === 0\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-next' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-next' | translate\"\n [attr.data-autoId]=\"autoIdSearchNext()\"\n (click)=\"searchNext()\">\n <sd-icon fontIcon=\"keyboard_arrow_down\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-searchbar__divider\"></span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"searchCaseSensitive()\"\n [attr.aria-pressed]=\"searchCaseSensitive()\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-case' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-case' | translate\"\n [attr.data-autoId]=\"autoIdSearchCase()\"\n (click)=\"toggleSearchCaseSensitive()\">\n <sd-icon fontIcon=\"match_case\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"searchWholeWord()\"\n [attr.aria-pressed]=\"searchWholeWord()\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-whole' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-whole' | translate\"\n [attr.data-autoId]=\"autoIdSearchWhole()\"\n (click)=\"toggleSearchWholeWord()\">\n <sd-icon fontIcon=\"text_fields\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-searchbar__spacer\"></span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-close' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-close' | translate\"\n [attr.data-autoId]=\"autoIdSearchClose()\"\n (click)=\"closeSearch()\">\n <sd-icon fontIcon=\"close\"></sd-icon>\n </button>\n }\n </div>\n }\n\n <!-- ============================================\n BODY \u2014 sidebar + stage\n ============================================ -->\n <div class=\"sd-preview-pdf-body\">\n <!-- SIDEBAR ............................................................. -->\n @if (_stage === 'ready') {\n <aside\n class=\"sd-preview-pdf-sidebar\"\n role=\"complementary\"\n [id]=\"sidebarRegionId()\"\n [hidden]=\"!_isSidebarOpen\"\n [style.display]=\"_isSidebarOpen ? null : 'none'\">\n @if (_isSidebarOpen) {\n @let _sidebarMode = sidebarMode();\n <div class=\"sd-preview-pdf-sidebar__tabs\" role=\"tablist\">\n <button\n #sidebarTab\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [id]=\"tabThumbnailsId()\"\n [attr.aria-selected]=\"_sidebarMode === 'thumbnails'\"\n [attr.aria-controls]=\"sidebarPanelId()\"\n [attr.tabindex]=\"_sidebarMode === 'thumbnails' ? 0 : -1\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'thumbnails'\"\n data-sidebar-mode=\"thumbnails\"\n [attr.data-autoId]=\"autoIdTabThumbnails()\"\n (keydown)=\"onSidebarTabKeyDown($event, 'thumbnails')\"\n (click)=\"setSidebarMode('thumbnails')\">\n <sd-icon fontIcon=\"view_carousel\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.thumbnails' | translate }}</span>\n </button>\n <button\n #sidebarTab\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [id]=\"tabOutlineId()\"\n [attr.aria-selected]=\"_sidebarMode === 'outline'\"\n [attr.aria-controls]=\"sidebarPanelId()\"\n [attr.tabindex]=\"_sidebarMode === 'outline' ? 0 : -1\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'outline'\"\n data-sidebar-mode=\"outline\"\n [attr.data-autoId]=\"autoIdTabOutline()\"\n (keydown)=\"onSidebarTabKeyDown($event, 'outline')\"\n (click)=\"setSidebarMode('outline')\">\n <sd-icon fontIcon=\"list\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.outline' | translate }}</span>\n </button>\n <button\n #sidebarTab\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [id]=\"tabSearchId()\"\n [attr.aria-selected]=\"_sidebarMode === 'search'\"\n [attr.aria-controls]=\"sidebarPanelId()\"\n [attr.tabindex]=\"_sidebarMode === 'search' ? 0 : -1\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'search'\"\n data-sidebar-mode=\"search\"\n [attr.data-autoId]=\"autoIdTabSearch()\"\n (keydown)=\"onSidebarTabKeyDown($event, 'search')\"\n (click)=\"setSidebarMode('search')\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.search' | translate }}</span>\n </button>\n </div>\n\n <div\n #sidebarContent\n class=\"sd-preview-pdf-sidebar__content\"\n role=\"tabpanel\"\n [id]=\"sidebarPanelId()\"\n [attr.aria-labelledby]=\"sidebarActiveTabId()\"\n (scroll)=\"onThumbnailSidebarScroll($event)\">\n @switch (_sidebarMode) {\n @case ('thumbnails') {\n <ul class=\"sd-preview-pdf-thumb-list\">\n <li class=\"sd-preview-pdf-thumb-list__spacer\" aria-hidden=\"true\" [style.height.px]=\"thumbnailTopSpacer()\"></li>\n @for (p of pageNumbers(); track trackByPage($index, p)) {\n <li>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-thumb\"\n [class.sd-preview-pdf-thumb--active]=\"p === _activePage\"\n [attr.aria-current]=\"p === _activePage ? 'page' : null\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: p, total: _numPages }\"\n [attr.data-autoId]=\"autoIdThumb(p - 1)\"\n (click)=\"goToPage(p)\">\n <div class=\"sd-preview-pdf-thumb__page\">\n <canvas #thumbCanvas class=\"sd-preview-pdf-thumb__canvas\" [attr.data-page]=\"p\" aria-hidden=\"true\"></canvas>\n </div>\n <span class=\"sd-preview-pdf-thumb__label\">{{ p }}</span>\n </button>\n </li>\n }\n <li class=\"sd-preview-pdf-thumb-list__spacer\" aria-hidden=\"true\" [style.height.px]=\"thumbnailBottomSpacer()\"></li>\n </ul>\n }\n @case ('outline') {\n <ul\n class=\"sd-preview-pdf-outline-tree\"\n role=\"tree\"\n [attr.aria-label]=\"'core.component.preview-pdf.tab.outline' | translate\">\n @for (row of visibleOutline(); track row.item.id) {\n <li class=\"sd-preview-pdf-outline-tree__node\" role=\"none\">\n <div class=\"sd-preview-pdf-outline-tree__row\" [style.padding-left.px]=\"(row.level - 1) * 16\">\n @if (row.item.children.length > 0) {\n <button\n type=\"button\"\n class=\"sd-preview-pdf-outline-tree__toggle\"\n tabindex=\"-1\"\n [attr.aria-label]=\"row.item.title\"\n [attr.aria-expanded]=\"isOutlineExpanded(row.item.id)\"\n (click)=\"toggleOutlineItem(row.item.id, $event)\">\n <sd-icon [fontIcon]=\"isOutlineExpanded(row.item.id) ? 'expand_more' : 'chevron_right'\"></sd-icon>\n </button>\n } @else {\n <span class=\"sd-preview-pdf-outline-tree__toggle-placeholder\" aria-hidden=\"true\"></span>\n }\n\n @if (row.item.url && row.item.page === null) {\n <a\n #outlineItem\n class=\"sd-preview-pdf-outline-tree__item\"\n role=\"treeitem\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n [href]=\"row.item.url\"\n [attr.data-outline-id]=\"row.item.id\"\n [attr.aria-level]=\"row.level\"\n [attr.aria-expanded]=\"row.item.children.length > 0 ? isOutlineExpanded(row.item.id) : null\"\n [attr.aria-current]=\"row.item.page === _activePage ? 'page' : null\"\n [attr.tabindex]=\"outlineFocusId() === row.item.id ? 0 : -1\"\n (focus)=\"focusOutlineItem(row.item.id)\"\n (keydown)=\"onOutlineKeyDown($event, row.item.id)\">\n {{ row.item.title }}\n </a>\n } @else {\n <button\n #outlineItem\n type=\"button\"\n class=\"sd-preview-pdf-outline-tree__item\"\n role=\"treeitem\"\n [attr.data-outline-id]=\"row.item.id\"\n [attr.aria-level]=\"row.level\"\n [attr.aria-expanded]=\"row.item.children.length > 0 ? isOutlineExpanded(row.item.id) : null\"\n [attr.aria-current]=\"row.item.page === _activePage ? 'page' : null\"\n [attr.tabindex]=\"outlineFocusId() === row.item.id ? 0 : -1\"\n (focus)=\"focusOutlineItem(row.item.id)\"\n (click)=\"activateOutlineItem(row.item.id)\"\n (keydown)=\"onOutlineKeyDown($event, row.item.id)\">\n {{ row.item.title }}\n </button>\n }\n </div>\n </li>\n }\n </ul>\n }\n @case ('search') {\n @if (!_searchTerm) {\n <div class=\"sd-preview-pdf-sidebar__message\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n <p>{{ 'core.component.preview-pdf.search-placeholder' | translate }}</p>\n </div>\n } @else if (_searchTotal === 0) {\n <div class=\"sd-preview-pdf-sidebar__message\">\n <sd-icon fontIcon=\"search_off\"></sd-icon>\n <p>{{ 'core.component.preview-pdf.search-no-results' | translate }}</p>\n </div>\n } @else {\n @if (searchTruncated()) {\n <p class=\"sd-preview-pdf-search-limit\" role=\"status\">\n {{ 'core.component.preview-pdf.search-truncated' | translate: { count: _searchTotal } }}\n </p>\n }\n <ul class=\"sd-preview-pdf-search-list\">\n @for (r of searchResults(); track $index; let i = $index) {\n <li>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-search-result\"\n [class.sd-preview-pdf-search-result--active]=\"i === searchActiveIndex()\"\n [attr.data-autoId]=\"autoIdResult(i)\"\n (click)=\"activateSearchResult(i)\">\n <span class=\"sd-preview-pdf-search-result__snippet\">\n {{ r.before }}<mark>{{ r.term }}</mark\n >{{ r.after }}\n </span>\n <span class=\"sd-preview-pdf-search-result__page\">{{ r.page }}</span>\n </button>\n </li>\n }\n </ul>\n }\n }\n }\n </div>\n }\n </aside>\n }\n\n <!-- STAGE ............................................................... -->\n <div class=\"sd-preview-pdf-stage\" #stageRef (wheel)=\"onWheel($event)\" (scroll)=\"onStageScroll()\">\n @switch (_stage) {\n @case ('empty') {\n <div class=\"sd-preview-pdf-status\">\n <div class=\"sd-preview-pdf-status__tile sd-preview-pdf-status__tile--muted\">\n <sd-icon fontIcon=\"picture_as_pdf\"></sd-icon>\n </div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.empty.title' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__hint\">\n {{ 'core.component.preview-pdf.empty.subtitle' | translate }}\n </div>\n </div>\n }\n @case ('loading') {\n @let _loadPercent = loadPercent();\n <div class=\"sd-preview-pdf-status\">\n <div\n class=\"sd-preview-pdf-spinner\"\n role=\"progressbar\"\n [attr.aria-label]=\"'core.component.preview-pdf.loading' | translate\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n [attr.aria-valuenow]=\"_loadPercent\"></div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.loading' | translate }}\n @if (_loadPercent > 0) {\n <span class=\"sd-preview-pdf-progress-pct\"> {{ _loadPercent }}%</span>\n }\n </div>\n <div class=\"sd-preview-pdf-progress\">\n <div class=\"sd-preview-pdf-progress__fill\" [style.width.%]=\"_loadPercent\"></div>\n </div>\n </div>\n }\n @case ('error') {\n @if (loadError$(); as err) {\n <div class=\"sd-preview-pdf-status\" role=\"alert\" aria-live=\"assertive\">\n <div class=\"sd-preview-pdf-status__tile sd-preview-pdf-status__tile--danger\">\n <sd-icon fontIcon=\"error\"></sd-icon>\n </div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.error.' + err.reason + '.title' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__hint\">\n {{ 'core.component.preview-pdf.error.' + err.reason + '.body' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__actions\">\n <button type=\"button\" class=\"sd-preview-pdf-btn-primary\" (click)=\"retryLoad()\" [attr.data-autoId]=\"autoIdRetry()\">\n <sd-icon fontIcon=\"refresh\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.error.retry' | translate }}</span>\n </button>\n @if (canDownload()) {\n <button type=\"button\" class=\"sd-preview-pdf-btn-secondary\" (click)=\"downloadFile()\">\n <sd-icon fontIcon=\"download\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.error.download-raw' | translate }}</span>\n </button>\n }\n </div>\n </div>\n }\n }\n @default {\n @if (scrollModeCurrent() === 'continuous') {\n <div class=\"sd-preview-pdf-continuous-content\">\n <div class=\"sd-preview-pdf-continuous-spacer\" [style.height.px]=\"continuousTopSpacer()\"></div>\n @for (pageNumber of continuousPages(); track pageNumber) {\n <div class=\"sd-preview-pdf-continuous-slot\" [style.min-height.px]=\"continuousPageHeights()[pageNumber]\">\n <canvas\n #continuousCanvas\n class=\"sd-preview-pdf-continuous-page\"\n role=\"img\"\n [attr.data-page]=\"pageNumber\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: pageNumber, total: _numPages }\">\n </canvas>\n </div>\n }\n <div class=\"sd-preview-pdf-continuous-spacer\" [style.height.px]=\"continuousBottomSpacer()\"></div>\n </div>\n } @else {\n <div class=\"sd-preview-pdf-page-wrap\">\n <canvas\n #pageCanvas\n class=\"sd-preview-pdf-page\"\n role=\"img\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: _activePage, total: _numPages }\">\n </canvas>\n </div>\n }\n }\n }\n\n <!-- FLOATING TOOLBAR ........................................ -->\n @if (showToolbar() && _stage === 'ready') {\n @let _canPrev = canPrev();\n @let _canNext = canNext();\n @let _zoomMode = zoomMode();\n @let _scrollModeCurrent = scrollModeCurrent();\n <div class=\"sd-preview-pdf-toolbar\" role=\"toolbar\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canPrev\"\n [attr.aria-label]=\"'core.component.preview-pdf.first-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.first-page' | translate\"\n [attr.data-autoId]=\"autoIdFirst()\"\n (click)=\"firstPage()\">\n <sd-icon fontIcon=\"first_page\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canPrev\"\n [attr.aria-label]=\"'core.component.preview-pdf.prev-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.prev-page' | translate\"\n [attr.data-autoId]=\"autoIdPrev()\"\n (click)=\"prevPage()\">\n <sd-icon fontIcon=\"chevron_left\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-toolbar__page\">\n <input\n type=\"text\"\n inputmode=\"numeric\"\n class=\"sd-preview-pdf-toolbar__page-input\"\n [value]=\"_activePage\"\n #pageInput\n (keydown.enter)=\"onPageInputEnter($event)\"\n (blur)=\"onPageInputBlur($event)\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: _activePage, total: _numPages }\"\n [attr.data-autoId]=\"autoIdPageInput()\" />\n <span class=\"sd-preview-pdf-toolbar__page-total\">/ {{ _numPages }}</span>\n </span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canNext\"\n [attr.aria-label]=\"'core.component.preview-pdf.next-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.next-page' | translate\"\n [attr.data-autoId]=\"autoIdNext()\"\n (click)=\"nextPage()\">\n <sd-icon fontIcon=\"chevron_right\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canNext\"\n [attr.aria-label]=\"'core.component.preview-pdf.last-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.last-page' | translate\"\n [attr.data-autoId]=\"autoIdLast()\"\n (click)=\"lastPage()\">\n <sd-icon fontIcon=\"last_page\"></sd-icon>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.zoom-out' | translate\"\n [attr.title]=\"'core.component.preview-pdf.zoom-out' | translate\"\n [attr.data-autoId]=\"autoIdZoomOut()\"\n (click)=\"zoomOut()\">\n <sd-icon fontIcon=\"remove\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-toolbar__zoom\" aria-live=\"polite\" [attr.data-autoId]=\"autoIdZoomReadout()\">{{ zoomPercent() }}%</span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.zoom-in' | translate\"\n [attr.title]=\"'core.component.preview-pdf.zoom-in' | translate\"\n [attr.data-autoId]=\"autoIdZoomIn()\"\n (click)=\"zoomIn()\">\n <sd-icon fontIcon=\"add\"></sd-icon>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [class.sd-preview-pdf-toolbar__btn--active]=\"_zoomMode === 'page-fit'\"\n [attr.data-autoId]=\"autoIdFitPage()\"\n (click)=\"setZoom('page-fit')\">\n <sd-icon fontIcon=\"fit_screen\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.fit-page' | translate }}</span>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [class.sd-preview-pdf-toolbar__btn--active]=\"_zoomMode === 'page-width'\"\n [attr.data-autoId]=\"autoIdFitWidth()\"\n (click)=\"setZoom('page-width')\">\n <sd-icon fontIcon=\"swap_horiz\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.fit-width' | translate }}</span>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.rotate' | translate\"\n [attr.title]=\"'core.component.preview-pdf.rotate' | translate\"\n [attr.data-autoId]=\"autoIdRotate()\"\n (click)=\"rotate('right')\">\n <sd-icon fontIcon=\"rotate_right\"></sd-icon>\n </button>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [attr.data-autoId]=\"autoIdScrollMode()\"\n (click)=\"setScrollMode(_scrollModeCurrent === 'page' ? 'continuous' : 'page')\">\n <sd-icon [fontIcon]=\"_scrollModeCurrent === 'page' ? 'description' : 'view_day'\"></sd-icon>\n <span>\n {{\n (_scrollModeCurrent === 'page' ? 'core.component.preview-pdf.scroll-page' : 'core.component.preview-pdf.scroll-continuous')\n | translate\n }}\n </span>\n </button>\n </div>\n }\n </div>\n </div>\n</div>\n", styles: [":host{--sd-pdf-shell-bg: #1a1c1f;--sd-pdf-sidebar-bg: #22252a;--sd-pdf-toolbar-bg: rgba(20, 22, 25, .94);--sd-pdf-page-bg: #ffffff;--sd-pdf-page-shadow: 0 4px 16px rgba(0, 0, 0, .4);--sd-pdf-divider: rgba(255, 255, 255, .08);--sd-pdf-fg: #f5f6f7;--sd-pdf-fg-muted: rgba(255, 255, 255, .6);--sd-pdf-fg-subtle: rgba(255, 255, 255, .4);--sd-pdf-primary: var(--sd-pdf-accent, var(--sd-primary, #005cbb));--sd-pdf-primary-soft: var(--sd-pdf-accent-soft, rgba(0, 92, 187, .18));--sd-pdf-danger: var(--sd-error, #ba1a1a);--sd-pdf-success: var(--sd-success, #2e7d32);--sd-pdf-danger-soft: rgba(186, 26, 26, .1);--sd-pdf-danger-border: rgba(186, 26, 26, .3);--sd-pdf-danger-tint: rgba(186, 26, 26, .15);--sd-pdf-hover-bg: rgba(255, 255, 255, .1);--sd-pdf-search-hi: rgba(250, 204, 21, .55);--sd-pdf-search-hi-active: rgba(249, 115, 22, .85);display:block;width:100%;height:100%;min-height:320px;outline:none}:host:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}:host([data-theme=light]){--sd-pdf-shell-bg: #f7f8fa;--sd-pdf-sidebar-bg: #ffffff;--sd-pdf-toolbar-bg: rgba(255, 255, 255, .94);--sd-pdf-page-bg: #ffffff;--sd-pdf-page-shadow: 0 4px 16px rgba(0, 0, 0, .12);--sd-pdf-divider: rgba(0, 0, 0, .08);--sd-pdf-fg: #1a1c1f;--sd-pdf-fg-muted: rgba(0, 0, 0, .62);--sd-pdf-fg-subtle: rgba(0, 0, 0, .42);--sd-pdf-primary: var(--sd-pdf-accent, var(--sd-primary, #005cbb));--sd-pdf-primary-soft: var(--sd-pdf-accent-soft, rgba(0, 92, 187, .16));--sd-pdf-danger: var(--sd-error, #ba1a1a);--sd-pdf-success: var(--sd-success, #2e7d32);--sd-pdf-danger-soft: rgba(186, 26, 26, .08);--sd-pdf-danger-border: rgba(186, 26, 26, .25);--sd-pdf-danger-tint: rgba(186, 26, 26, .12);--sd-pdf-hover-bg: rgba(0, 0, 0, .06);--sd-pdf-search-hi: rgba(250, 204, 21, .55);--sd-pdf-search-hi-active: rgba(250, 204, 21, .9)}:host(.sd-preview-pdf-host--fullscreen){width:100vw;height:100vh;min-height:0}.sd-preview-pdf-shell{position:relative;display:flex;flex-direction:column;width:100%;height:100%;background:var(--sd-pdf-shell-bg);color:var(--sd-pdf-fg);border-radius:12px;overflow:hidden;box-shadow:0 30px 80px #0000008c,0 0 0 1px #ffffff0a;font-family:inherit}.sd-preview-pdf-header{display:flex;align-items:center;justify-content:space-between;gap:12px;height:57px;padding:0 12px;background:var(--sd-pdf-toolbar-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--sd-pdf-divider);flex:0 0 auto;z-index:2}.sd-preview-pdf-header__left{display:flex;align-items:center;gap:8px;min-width:0;flex:1 1 auto}.sd-preview-pdf-header__right{display:flex;align-items:center;gap:4px;flex-shrink:0}.sd-preview-pdf-header__divider{display:inline-block;width:1px;height:20px;background:var(--sd-pdf-divider)}.sd-preview-pdf-header__meta{display:flex;flex-direction:column;min-width:0}.sd-preview-pdf-header__name{font-size:14px;font-weight:500;color:var(--sd-pdf-fg);max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-preview-pdf-header__sub{font-size:11px;color:var(--sd-pdf-fg-muted);margin-top:2px}.sd-preview-pdf-fileicon{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:6px;background:var(--sd-pdf-danger-tint);border:1px solid var(--sd-pdf-danger-border);color:var(--sd-error-light, #f87171);font:700 10px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.sd-preview-pdf-iconbtn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-width:32px;height:32px;padding:0 8px;background:transparent;border:none;color:var(--sd-pdf-fg);border-radius:6px;cursor:pointer;transition:background .12s,color .12s;white-space:nowrap;flex-shrink:0}.sd-preview-pdf-iconbtn sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-iconbtn__label{font-size:13px;font-weight:500}.sd-preview-pdf-iconbtn:hover:not(:disabled){background:var(--sd-pdf-hover-bg)}.sd-preview-pdf-iconbtn:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-iconbtn:disabled{opacity:.35;cursor:not-allowed}.sd-preview-pdf-iconbtn--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-searchbar{display:flex;align-items:center;gap:6px;height:46px;padding:0 12px;background:var(--sd-pdf-toolbar-bg);border-bottom:1px solid var(--sd-pdf-divider);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);flex:0 0 auto;z-index:2}.sd-preview-pdf-searchbar__icon{color:var(--sd-pdf-fg-muted);font-size:18px;width:18px;height:18px;flex-shrink:0}.sd-preview-pdf-searchbar__input{flex:0 1 360px;min-width:160px;height:30px;background:#ffffff0f;border:1px solid rgba(255,255,255,.08);color:var(--sd-pdf-fg);border-radius:6px;padding:0 10px;font-size:13px;outline:none}.sd-preview-pdf-searchbar__input::placeholder{color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-searchbar__input:focus{border-color:var(--sd-pdf-primary);background:#ffffff17}.sd-preview-pdf-searchbar__counter{color:var(--sd-pdf-fg-muted);font-size:12px;font-variant-numeric:tabular-nums;min-width:48px;text-align:center;flex-shrink:0}.sd-preview-pdf-searchbar__divider{width:1px;height:20px;background:var(--sd-pdf-divider);flex-shrink:0}.sd-preview-pdf-searchbar__spacer{flex:1 1 auto}.sd-pdf-search-overlay{position:absolute;top:8px;left:50%;transform:translate(-50%);display:flex;flex-wrap:wrap;gap:4px;max-width:80%;pointer-events:none;z-index:5}.sd-pdf-search-hi{display:inline-block;padding:1px 4px;border-radius:2px;background:var(--sd-pdf-search-hi);color:#000;font-size:12px;font-weight:500}.sd-pdf-search-hi--active{background:var(--sd-pdf-search-hi-active);color:#fff;box-shadow:0 0 0 2px #fff6}.sd-preview-pdf-body{position:relative;flex:1 1 auto;min-height:0;display:flex;flex-direction:row;overflow:hidden}.sd-preview-pdf-sidebar{width:240px;display:flex;flex-direction:column;background:var(--sd-pdf-sidebar-bg);border-right:1px solid var(--sd-pdf-divider);flex-shrink:0;min-height:0}.sd-preview-pdf-sidebar__tabs{display:flex;gap:4px;padding:8px;border-bottom:1px solid var(--sd-pdf-divider)}.sd-preview-pdf-sidebar__tab{flex:1 1 0;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:32px;padding:0 4px;background:transparent;border:none;color:var(--sd-pdf-fg-muted);border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;white-space:nowrap;flex-shrink:0;transition:background .12s,color .12s}.sd-preview-pdf-sidebar__tab sd-icon{font-size:14px;width:14px;height:14px}.sd-preview-pdf-sidebar__tab:hover{background:#ffffff0f;color:var(--sd-pdf-fg)}.sd-preview-pdf-sidebar__tab--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-sidebar__content{flex:1 1 auto;overflow:auto;padding:12px}.sd-preview-pdf-sidebar__message{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:32px 16px;text-align:center;color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-sidebar__message sd-icon{font-size:28px;width:28px;height:28px;opacity:.6}.sd-preview-pdf-sidebar__message p{margin:0;font-size:12px;line-height:1.5}.sd-preview-pdf-outline-tree{list-style:none;margin:0;padding:0}.sd-preview-pdf-outline-tree__node{margin:0;padding:0}.sd-preview-pdf-outline-tree__row{display:flex;align-items:center;min-width:0}.sd-preview-pdf-outline-tree__toggle,.sd-preview-pdf-outline-tree__toggle-placeholder{width:24px;height:28px;flex:0 0 24px}.sd-preview-pdf-outline-tree__toggle{display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:4px;background:transparent;color:var(--sd-pdf-fg-muted);cursor:pointer}.sd-preview-pdf-outline-tree__toggle:hover{background:#ffffff0f;color:var(--sd-pdf-fg)}.sd-preview-pdf-outline-tree__toggle sd-icon{width:16px;height:16px;font-size:16px}.sd-preview-pdf-outline-tree__item{min-width:0;flex:1 1 auto;overflow:hidden;padding:6px 8px;border:0;border-radius:5px;background:transparent;color:var(--sd-pdf-fg-muted);font:inherit;font-size:12px;line-height:1.35;text-align:left;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.sd-preview-pdf-outline-tree__item:hover{background:#ffffff0f;color:var(--sd-pdf-fg)}.sd-preview-pdf-outline-tree__item[aria-current=page]{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary);font-weight:600}.sd-preview-pdf-outline-tree__item:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:-2px}.sd-preview-pdf-thumb-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px}.sd-preview-pdf-thumb-list__spacer{flex:0 0 auto;pointer-events:none}.sd-preview-pdf-search-limit{margin:0 0 8px;color:var(--sd-pdf-fg-muted);font-size:11px;line-height:1.4}.sd-preview-pdf-thumb{display:flex;flex-direction:column;align-items:center;gap:4px;width:100%;padding:0;background:transparent;border:none;cursor:pointer}.sd-preview-pdf-thumb__page{width:152px;aspect-ratio:612/792;background:var(--sd-pdf-page-bg);border-radius:2px;outline:1px solid rgba(255,255,255,.08);outline-offset:-1px;box-shadow:0 2px 6px #0000004d;display:flex;align-items:center;justify-content:center;overflow:hidden;-webkit-user-select:none;user-select:none;background-image:linear-gradient(90deg,#0000,#0000000a,#0000);background-size:200% 100%;animation:sd-preview-pdf-thumb-skel 1.6s ease-in-out infinite}.sd-preview-pdf-thumb__canvas{display:block;max-width:100%;max-height:100%;background:var(--sd-pdf-page-bg)}.sd-preview-pdf-thumb__label{font-size:11px;color:var(--sd-pdf-fg-muted);font-variant-numeric:tabular-nums}.sd-preview-pdf-thumb--active .sd-preview-pdf-thumb__page{outline:2px solid var(--sd-pdf-primary);outline-offset:-2px}.sd-preview-pdf-thumb--active .sd-preview-pdf-thumb__label{color:var(--sd-pdf-primary);font-weight:600}.sd-preview-pdf-thumb:focus-visible .sd-preview-pdf-thumb__page{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-stage{position:relative;flex:1 1 auto;min-width:0;min-height:0;background:var(--sd-pdf-shell-bg);background-image:linear-gradient(45deg,rgba(255,255,255,.015) 25%,transparent 25%),linear-gradient(-45deg,rgba(255,255,255,.015) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(255,255,255,.015) 75%),linear-gradient(-45deg,transparent 75%,rgba(255,255,255,.015) 75%);background-size:24px 24px;background-position:0 0,0 12px,12px -12px,-12px 0;overflow:auto;padding:32px 24px 96px;display:flex;justify-content:center;align-items:flex-start}:host([data-theme=light]) .sd-preview-pdf-stage{background-image:linear-gradient(45deg,rgba(0,0,0,.015) 25%,transparent 25%),linear-gradient(-45deg,rgba(0,0,0,.015) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(0,0,0,.015) 75%),linear-gradient(-45deg,transparent 75%,rgba(0,0,0,.015) 75%)}.sd-preview-pdf-page-wrap{display:inline-block;position:relative}.sd-preview-pdf-page{display:block;background:var(--sd-pdf-page-bg);border-radius:2px;box-shadow:var(--sd-pdf-page-shadow);max-width:100%}.sd-preview-pdf-continuous-content{width:100%;min-height:100%}.sd-preview-pdf-continuous-spacer{width:1px}.sd-preview-pdf-continuous-slot{display:flex;justify-content:center;align-items:flex-start}.sd-preview-pdf-continuous-slot+.sd-preview-pdf-continuous-slot{margin-top:24px}.sd-preview-pdf-continuous-page{display:block;max-width:100%;background:var(--sd-pdf-page-bg);border-radius:2px;box-shadow:var(--sd-pdf-page-shadow)}.sd-preview-pdf-toolbar{position:absolute;bottom:20px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:2px;padding:5px 6px;background:var(--sd-pdf-toolbar-bg);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid var(--sd-pdf-divider);border-radius:10px;box-shadow:0 8px 30px #0006;z-index:4;max-width:calc(100% - 32px);overflow-x:auto}.sd-preview-pdf-toolbar__btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;min-width:30px;height:28px;padding:0 6px;background:transparent;border:none;color:var(--sd-pdf-fg);border-radius:6px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background .12s,color .12s}.sd-preview-pdf-toolbar__btn sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-toolbar__btn span{font-size:13px;font-weight:500}.sd-preview-pdf-toolbar__btn--with-label{padding:0 10px}.sd-preview-pdf-toolbar__btn--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-toolbar__btn:hover:not(:disabled):not(.sd-preview-pdf-toolbar__btn--active){background:var(--sd-pdf-hover-bg)}.sd-preview-pdf-toolbar__btn:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-toolbar__btn:disabled{opacity:.35;cursor:not-allowed}.sd-preview-pdf-toolbar__page{display:inline-flex;align-items:center;gap:4px;padding:0 6px;flex-shrink:0}.sd-preview-pdf-toolbar__page-input{width:36px;height:24px;background:#ffffff14;border:1px solid rgba(255,255,255,.08);color:var(--sd-pdf-fg);border-radius:4px;text-align:center;font-size:13px;font-variant-numeric:tabular-nums;outline:none}.sd-preview-pdf-toolbar__page-input:focus{border-color:var(--sd-pdf-primary)}.sd-preview-pdf-toolbar__page-total{font-size:13px;color:var(--sd-pdf-fg-muted);font-variant-numeric:tabular-nums}.sd-preview-pdf-toolbar__zoom{min-width:56px;text-align:center;color:var(--sd-pdf-fg);font-size:13px;font-weight:500;font-variant-numeric:tabular-nums;background:#ffffff14;border-radius:4px;padding:2px 8px}.sd-preview-pdf-toolbar__divider{width:1px;height:20px;background:var(--sd-pdf-divider);margin:0 4px;flex-shrink:0}.sd-preview-pdf-status{margin:auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;text-align:center;padding:32px;max-width:420px}.sd-preview-pdf-status__tile{width:96px;height:96px;border-radius:24px;display:flex;align-items:center;justify-content:center;border:1px solid var(--sd-pdf-divider)}.sd-preview-pdf-status__tile sd-icon{font-size:48px;width:48px;height:48px}.sd-preview-pdf-status__tile--muted{background:#ffffff0d}.sd-preview-pdf-status__tile--muted sd-icon{color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-status__tile--danger{background:var(--sd-pdf-danger-soft);border-color:var(--sd-pdf-danger-border)}.sd-preview-pdf-status__tile--danger sd-icon{color:var(--sd-pdf-danger)}.sd-preview-pdf-status__title{font-size:16px;font-weight:500;color:var(--sd-pdf-fg)}.sd-preview-pdf-status__hint{font-size:13px;color:var(--sd-pdf-fg-muted);line-height:1.5}.sd-preview-pdf-status__actions{display:flex;gap:8px;margin-top:4px}.sd-preview-pdf-progress-pct{font-variant-numeric:tabular-nums;color:var(--sd-pdf-fg-muted)}.sd-preview-pdf-progress{width:240px;height:4px;background:#ffffff14;border-radius:999px;overflow:hidden}.sd-preview-pdf-progress__fill{height:100%;background:var(--sd-pdf-primary);transition:width .12s linear}.sd-preview-pdf-spinner{width:56px;height:56px;border-radius:999px;border:3px solid rgba(255,255,255,.08);border-top-color:var(--sd-pdf-primary);animation:sd-preview-pdf-spin .9s linear infinite}@keyframes sd-preview-pdf-spin{to{transform:rotate(360deg)}}@keyframes sd-preview-pdf-thumb-skel{0%{background-position:200% 0}to{background-position:-200% 0}}.sd-preview-pdf-search-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.sd-preview-pdf-search-result{width:100%;display:flex;align-items:flex-start;gap:8px;padding:8px 10px;background:#ffffff0a;border:1px solid transparent;border-radius:6px;color:var(--sd-pdf-fg);cursor:pointer;text-align:left;transition:background .12s,border-color .12s}.sd-preview-pdf-search-result:hover{background:#ffffff12}.sd-preview-pdf-search-result--active{background:var(--sd-pdf-primary-soft);border-color:#005cbb73}.sd-preview-pdf-search-result__snippet{flex:1 1 auto;font-size:12px;line-height:1.4;color:var(--sd-pdf-fg);word-break:break-word}.sd-preview-pdf-search-result__snippet mark{background:var(--sd-pdf-search-hi);color:#000;padding:0 2px;border-radius:2px}.sd-preview-pdf-search-result__page{flex:0 0 auto;font-size:11px;font-variant-numeric:tabular-nums;color:var(--sd-pdf-fg-muted);background:#ffffff0f;padding:2px 6px;border-radius:4px}.sd-preview-pdf-btn-primary{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 16px;background:var(--sd-pdf-primary);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;transition:filter .12s}.sd-preview-pdf-btn-primary sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-btn-primary:hover{filter:brightness(1.1)}.sd-preview-pdf-btn-primary:focus-visible{outline:2px solid #fff;outline-offset:2px}.sd-preview-pdf-btn-secondary{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 16px;background:transparent;color:var(--sd-pdf-fg);border:1px solid var(--sd-pdf-divider);border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;transition:background .12s}.sd-preview-pdf-btn-secondary sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-btn-secondary:hover{background:#ffffff0f}.sd-preview-pdf-btn-secondary:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1967
3187
|
}
|
|
1968
3188
|
_a = SdPreviewPdf;
|
|
1969
3189
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdPreviewPdf, decorators: [{
|
|
@@ -1976,8 +3196,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
1976
3196
|
// collision with consumer styling.
|
|
1977
3197
|
'[attr.data-theme]': 'theme()',
|
|
1978
3198
|
'[class.sd-preview-pdf-host--fullscreen]': 'isFullscreen()',
|
|
1979
|
-
}, template: "<!--\n sd-preview-pdf shell. Self-contained \u2014 sizes to its parent. Consumer wraps\n in <sd-modal>/<sd-side-drawer>/page section. Header + sidebar + stage +\n floating toolbar mirror Artboards A / C / E from the design handoff.\n-->\n<div class=\"sd-preview-pdf-shell\" role=\"region\">\n @let _stage = stage();\n @let _numPages = numPages();\n @let _activePage = activePage();\n @let _isSidebarOpen = isSidebarOpen();\n @let _searchBarOpen = searchBarOpen();\n @let _searchTerm = searchTerm();\n @let _searchTotal = searchTotal();\n\n <!-- ============================================\n HEADER BAR \u2014 hidden in empty + loading per design\n ============================================ -->\n @if (_stage === 'ready') {\n <div class=\"sd-preview-pdf-header\">\n <div class=\"sd-preview-pdf-header__left\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"_isSidebarOpen\"\n [attr.aria-label]=\"'core.component.preview-pdf.toggle-sidebar' | translate\"\n [attr.title]=\"'core.component.preview-pdf.toggle-sidebar' | translate\"\n [attr.data-autoId]=\"autoIdSidebarToggle()\"\n (click)=\"toggleSidebar()\">\n <sd-icon fontIcon=\"menu\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-header__divider\"></span>\n <div class=\"sd-preview-pdf-fileicon\" aria-hidden=\"true\">PDF</div>\n <div class=\"sd-preview-pdf-header__meta\">\n <div class=\"sd-preview-pdf-header__name\" [title]=\"title() || filename()\">\n {{ title() || filename() }}\n </div>\n <div class=\"sd-preview-pdf-header__sub\">\n {{ 'core.component.preview-pdf.pages-count' | translate: { count: _numPages } }}\n </div>\n </div>\n </div>\n\n <div class=\"sd-preview-pdf-header__right\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"_searchBarOpen\"\n [attr.aria-label]=\"'core.component.preview-pdf.search' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search' | translate\"\n [attr.data-autoId]=\"autoIdSearchToggle()\"\n (click)=\"searchBarOpen() ? closeSearch() : openSearch()\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n </button>\n @if (downloadable()) {\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.download' | translate\"\n [attr.title]=\"'core.component.preview-pdf.download' | translate\"\n [attr.data-autoId]=\"autoIdDownload()\"\n (click)=\"downloadFile()\">\n <sd-icon fontIcon=\"download\"></sd-icon>\n </button>\n }\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.fullscreen' | translate\"\n [attr.title]=\"'core.component.preview-pdf.fullscreen' | translate\"\n [attr.data-autoId]=\"autoIdFullscreen()\"\n (click)=\"toggleFullscreen()\">\n <sd-icon [fontIcon]=\"isFullscreen() ? 'fullscreen_exit' : 'fullscreen'\"></sd-icon>\n </button>\n </div>\n </div>\n\n <!-- ============================================\n SEARCH BAR \u2014 between header + body, only when bar is open\n ============================================ -->\n @if (_searchBarOpen) {\n <div class=\"sd-preview-pdf-searchbar\" role=\"search\">\n <sd-icon class=\"sd-preview-pdf-searchbar__icon\" fontIcon=\"search\"></sd-icon>\n <input\n #searchInput\n type=\"text\"\n class=\"sd-preview-pdf-searchbar__input\"\n [attr.placeholder]=\"'core.component.preview-pdf.search-placeholder' | translate\"\n [value]=\"_searchTerm\"\n [attr.data-autoId]=\"autoIdSearchInput()\"\n (input)=\"onSearchInput($any($event.target).value)\" />\n <span class=\"sd-preview-pdf-searchbar__counter\" aria-live=\"polite\"> {{ searchCurrent() }} / {{ _searchTotal }} </span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [disabled]=\"_searchTotal === 0\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-prev' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-prev' | translate\"\n [attr.data-autoId]=\"autoIdSearchPrev()\"\n (click)=\"searchPrev()\">\n <sd-icon fontIcon=\"keyboard_arrow_up\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [disabled]=\"_searchTotal === 0\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-next' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-next' | translate\"\n [attr.data-autoId]=\"autoIdSearchNext()\"\n (click)=\"searchNext()\">\n <sd-icon fontIcon=\"keyboard_arrow_down\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-searchbar__divider\"></span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"searchCaseSensitive()\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-case' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-case' | translate\"\n [attr.data-autoId]=\"autoIdSearchCase()\"\n (click)=\"toggleSearchCaseSensitive()\">\n <sd-icon fontIcon=\"match_case\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"searchWholeWord()\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-whole' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-whole' | translate\"\n [attr.data-autoId]=\"autoIdSearchWhole()\"\n (click)=\"toggleSearchWholeWord()\">\n <sd-icon fontIcon=\"text_fields\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-searchbar__spacer\"></span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-close' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-close' | translate\"\n [attr.data-autoId]=\"autoIdSearchClose()\"\n (click)=\"closeSearch()\">\n <sd-icon fontIcon=\"close\"></sd-icon>\n </button>\n </div>\n }\n }\n\n <!-- ============================================\n BODY \u2014 sidebar + stage\n ============================================ -->\n <div class=\"sd-preview-pdf-body\">\n <!-- SIDEBAR ............................................................. -->\n @if (_stage === 'ready' && _isSidebarOpen) {\n <aside class=\"sd-preview-pdf-sidebar\" role=\"complementary\">\n @let _sidebarMode = sidebarMode();\n <div class=\"sd-preview-pdf-sidebar__tabs\" role=\"tablist\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'thumbnails'\"\n [attr.data-autoId]=\"autoIdTabThumbnails()\"\n (click)=\"setSidebarMode('thumbnails')\">\n <sd-icon fontIcon=\"view_carousel\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.thumbnails' | translate }}</span>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'outline'\"\n [attr.data-autoId]=\"autoIdTabOutline()\"\n (click)=\"setSidebarMode('outline')\">\n <sd-icon fontIcon=\"list\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.outline' | translate }}</span>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'search'\"\n [attr.data-autoId]=\"autoIdTabSearch()\"\n (click)=\"setSidebarMode('search')\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.search' | translate }}</span>\n </button>\n </div>\n\n <div class=\"sd-preview-pdf-sidebar__content\">\n @switch (_sidebarMode) {\n @case ('thumbnails') {\n <ul class=\"sd-preview-pdf-thumb-list\">\n @for (p of pageNumbers(); track trackByPage($index, p); let pi = $index) {\n <li>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-thumb\"\n [class.sd-preview-pdf-thumb--active]=\"p === _activePage\"\n [attr.aria-current]=\"p === _activePage ? 'page' : null\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: p, total: _numPages }\"\n [attr.data-autoId]=\"autoIdThumb(pi)\"\n (click)=\"goToPage(p)\">\n <div class=\"sd-preview-pdf-thumb__page\">\n <canvas #thumbCanvas class=\"sd-preview-pdf-thumb__canvas\" [attr.data-page]=\"p\" aria-hidden=\"true\"></canvas>\n </div>\n <span class=\"sd-preview-pdf-thumb__label\">{{ p }}</span>\n </button>\n </li>\n }\n </ul>\n }\n @case ('outline') {\n <!-- TODO(preview-pdf): outline tree (pdfDoc.getOutline()) deferred to follow-up commit. -->\n <div class=\"sd-preview-pdf-sidebar__deferred\">\n <sd-icon fontIcon=\"list\"></sd-icon>\n <p>{{ 'core.component.preview-pdf.deferred.outline' | translate }}</p>\n </div>\n }\n @case ('search') {\n @if (!_searchTerm) {\n <div class=\"sd-preview-pdf-sidebar__deferred\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n <p>{{ 'core.component.preview-pdf.search-placeholder' | translate }}</p>\n </div>\n } @else if (_searchTotal === 0) {\n <div class=\"sd-preview-pdf-sidebar__deferred\">\n <sd-icon fontIcon=\"search_off\"></sd-icon>\n <p>{{ 'core.component.preview-pdf.search-no-results' | translate }}</p>\n </div>\n } @else {\n <ul class=\"sd-preview-pdf-search-list\">\n @for (r of searchResults(); track $index; let i = $index) {\n <li>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-search-result\"\n [class.sd-preview-pdf-search-result--active]=\"i === searchActiveIndex()\"\n [attr.data-autoId]=\"autoIdResult(i)\"\n (click)=\"goToPage(r.page)\">\n <span class=\"sd-preview-pdf-search-result__snippet\">\n {{ r.before }}<mark>{{ r.term }}</mark\n >{{ r.after }}\n </span>\n <span class=\"sd-preview-pdf-search-result__page\">{{ r.page }}</span>\n </button>\n </li>\n }\n </ul>\n }\n }\n @default {\n <div class=\"sd-preview-pdf-sidebar__deferred\"></div>\n }\n }\n </div>\n </aside>\n }\n\n <!-- STAGE ............................................................... -->\n <div class=\"sd-preview-pdf-stage\" #stageRef (wheel)=\"onWheel($event)\">\n @switch (_stage) {\n @case ('empty') {\n <div class=\"sd-preview-pdf-status\">\n <div class=\"sd-preview-pdf-status__tile sd-preview-pdf-status__tile--muted\">\n <sd-icon fontIcon=\"picture_as_pdf\"></sd-icon>\n </div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.empty.title' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__hint\">\n {{ 'core.component.preview-pdf.empty.subtitle' | translate }}\n </div>\n </div>\n }\n @case ('loading') {\n @let _loadPercent = loadPercent();\n <div class=\"sd-preview-pdf-status\">\n <div class=\"sd-preview-pdf-spinner\" role=\"progressbar\"></div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.loading' | translate }}\n @if (_loadPercent > 0) {\n <span class=\"sd-preview-pdf-progress-pct\"> {{ _loadPercent }}%</span>\n }\n </div>\n <div class=\"sd-preview-pdf-progress\">\n <div class=\"sd-preview-pdf-progress__fill\" [style.width.%]=\"_loadPercent\"></div>\n </div>\n </div>\n }\n @case ('error') {\n @if (loadError$(); as err) {\n <div class=\"sd-preview-pdf-status\">\n <div class=\"sd-preview-pdf-status__tile sd-preview-pdf-status__tile--danger\">\n <sd-icon fontIcon=\"error\"></sd-icon>\n </div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.error.' + err.reason + '.title' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__hint\">\n {{ 'core.component.preview-pdf.error.' + err.reason + '.body' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__actions\">\n <button type=\"button\" class=\"sd-preview-pdf-btn-primary\" (click)=\"retryLoad()\" [attr.data-autoId]=\"autoIdRetry()\">\n <sd-icon fontIcon=\"refresh\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.error.retry' | translate }}</span>\n </button>\n @if (downloadable()) {\n <button type=\"button\" class=\"sd-preview-pdf-btn-secondary\" (click)=\"downloadFile()\">\n <sd-icon fontIcon=\"download\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.error.download-raw' | translate }}</span>\n </button>\n }\n </div>\n </div>\n }\n }\n @default {\n <div class=\"sd-preview-pdf-page-wrap\">\n <canvas\n #pageCanvas\n class=\"sd-preview-pdf-page\"\n role=\"img\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: _activePage, total: _numPages }\">\n </canvas>\n </div>\n }\n }\n\n <!-- FLOATING TOOLBAR ........................................ -->\n @if (showToolbar() && _stage === 'ready') {\n @let _canPrev = canPrev();\n @let _canNext = canNext();\n @let _zoomMode = zoomMode();\n @let _scrollModeCurrent = scrollModeCurrent();\n <div class=\"sd-preview-pdf-toolbar\" role=\"toolbar\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canPrev\"\n [attr.aria-label]=\"'core.component.preview-pdf.first-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.first-page' | translate\"\n [attr.data-autoId]=\"autoIdFirst()\"\n (click)=\"firstPage()\">\n <sd-icon fontIcon=\"first_page\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canPrev\"\n [attr.aria-label]=\"'core.component.preview-pdf.prev-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.prev-page' | translate\"\n [attr.data-autoId]=\"autoIdPrev()\"\n (click)=\"prevPage()\">\n <sd-icon fontIcon=\"chevron_left\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-toolbar__page\">\n <input\n type=\"text\"\n inputmode=\"numeric\"\n class=\"sd-preview-pdf-toolbar__page-input\"\n [value]=\"_activePage\"\n #pageInput\n (keydown.enter)=\"onPageInputEnter($event)\"\n (blur)=\"onPageInputBlur($event)\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: _activePage, total: _numPages }\"\n [attr.data-autoId]=\"autoIdPageInput()\" />\n <span class=\"sd-preview-pdf-toolbar__page-total\">/ {{ _numPages }}</span>\n </span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canNext\"\n [attr.aria-label]=\"'core.component.preview-pdf.next-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.next-page' | translate\"\n [attr.data-autoId]=\"autoIdNext()\"\n (click)=\"nextPage()\">\n <sd-icon fontIcon=\"chevron_right\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canNext\"\n [attr.aria-label]=\"'core.component.preview-pdf.last-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.last-page' | translate\"\n [attr.data-autoId]=\"autoIdLast()\"\n (click)=\"lastPage()\">\n <sd-icon fontIcon=\"last_page\"></sd-icon>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.zoom-out' | translate\"\n [attr.title]=\"'core.component.preview-pdf.zoom-out' | translate\"\n [attr.data-autoId]=\"autoIdZoomOut()\"\n (click)=\"zoomOut()\">\n <sd-icon fontIcon=\"remove\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-toolbar__zoom\" aria-live=\"polite\" [attr.data-autoId]=\"autoIdZoomReadout()\">{{ zoomPercent() }}%</span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.zoom-in' | translate\"\n [attr.title]=\"'core.component.preview-pdf.zoom-in' | translate\"\n [attr.data-autoId]=\"autoIdZoomIn()\"\n (click)=\"zoomIn()\">\n <sd-icon fontIcon=\"add\"></sd-icon>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [class.sd-preview-pdf-toolbar__btn--active]=\"_zoomMode === 'page-fit'\"\n [attr.data-autoId]=\"autoIdFitPage()\"\n (click)=\"setZoom('page-fit')\">\n <sd-icon fontIcon=\"fit_screen\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.fit-page' | translate }}</span>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [class.sd-preview-pdf-toolbar__btn--active]=\"_zoomMode === 'page-width'\"\n [attr.data-autoId]=\"autoIdFitWidth()\"\n (click)=\"setZoom('page-width')\">\n <sd-icon fontIcon=\"swap_horiz\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.fit-width' | translate }}</span>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.rotate' | translate\"\n [attr.title]=\"'core.component.preview-pdf.rotate' | translate\"\n [attr.data-autoId]=\"autoIdRotate()\"\n (click)=\"rotate('right')\">\n <sd-icon fontIcon=\"rotate_right\"></sd-icon>\n </button>\n\n <!-- TODO(preview-pdf): scroll mode toggle is signature-only;\n clicking switches the state but rendering stays single-page\n until the continuous scroll feature lands. -->\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [attr.data-autoId]=\"autoIdScrollMode()\"\n (click)=\"setScrollMode(_scrollModeCurrent === 'page' ? 'continuous' : 'page')\">\n <sd-icon [fontIcon]=\"_scrollModeCurrent === 'page' ? 'description' : 'view_day'\"></sd-icon>\n <span>\n {{\n (_scrollModeCurrent === 'page' ? 'core.component.preview-pdf.scroll-page' : 'core.component.preview-pdf.scroll-continuous')\n | translate\n }}\n </span>\n </button>\n </div>\n }\n </div>\n </div>\n</div>\n", styles: [":host{--sd-pdf-shell-bg: #1a1c1f;--sd-pdf-sidebar-bg: #22252a;--sd-pdf-toolbar-bg: rgba(20, 22, 25, .94);--sd-pdf-page-bg: #ffffff;--sd-pdf-page-shadow: 0 4px 16px rgba(0, 0, 0, .4);--sd-pdf-divider: rgba(255, 255, 255, .08);--sd-pdf-fg: #f5f6f7;--sd-pdf-fg-muted: rgba(255, 255, 255, .6);--sd-pdf-fg-subtle: rgba(255, 255, 255, .4);--sd-pdf-primary: var(--sd-pdf-accent, var(--sd-primary, #005cbb));--sd-pdf-primary-soft: var(--sd-pdf-accent-soft, rgba(0, 92, 187, .18));--sd-pdf-danger: var(--sd-error, #ba1a1a);--sd-pdf-success: var(--sd-success, #2e7d32);--sd-pdf-danger-soft: rgba(186, 26, 26, .1);--sd-pdf-danger-border: rgba(186, 26, 26, .3);--sd-pdf-danger-tint: rgba(186, 26, 26, .15);--sd-pdf-hover-bg: rgba(255, 255, 255, .1);--sd-pdf-search-hi: rgba(250, 204, 21, .55);--sd-pdf-search-hi-active: rgba(249, 115, 22, .85);display:block;width:100%;height:100%;min-height:320px;outline:none}:host([data-theme=light]){--sd-pdf-shell-bg: #f7f8fa;--sd-pdf-sidebar-bg: #ffffff;--sd-pdf-toolbar-bg: rgba(255, 255, 255, .94);--sd-pdf-page-bg: #ffffff;--sd-pdf-page-shadow: 0 4px 16px rgba(0, 0, 0, .12);--sd-pdf-divider: rgba(0, 0, 0, .08);--sd-pdf-fg: #1a1c1f;--sd-pdf-fg-muted: rgba(0, 0, 0, .62);--sd-pdf-fg-subtle: rgba(0, 0, 0, .42);--sd-pdf-primary: var(--sd-pdf-accent, var(--sd-primary, #005cbb));--sd-pdf-primary-soft: var(--sd-pdf-accent-soft, rgba(0, 92, 187, .16));--sd-pdf-danger: var(--sd-error, #ba1a1a);--sd-pdf-success: var(--sd-success, #2e7d32);--sd-pdf-danger-soft: rgba(186, 26, 26, .08);--sd-pdf-danger-border: rgba(186, 26, 26, .25);--sd-pdf-danger-tint: rgba(186, 26, 26, .12);--sd-pdf-hover-bg: rgba(0, 0, 0, .06);--sd-pdf-search-hi: rgba(250, 204, 21, .55);--sd-pdf-search-hi-active: rgba(250, 204, 21, .9)}:host(.sd-preview-pdf-host--fullscreen){width:100vw;height:100vh;min-height:0}.sd-preview-pdf-shell{position:relative;display:flex;flex-direction:column;width:100%;height:100%;background:var(--sd-pdf-shell-bg);color:var(--sd-pdf-fg);border-radius:12px;overflow:hidden;box-shadow:0 30px 80px #0000008c,0 0 0 1px #ffffff0a;font-family:inherit}.sd-preview-pdf-header{display:flex;align-items:center;justify-content:space-between;gap:12px;height:57px;padding:0 12px;background:var(--sd-pdf-toolbar-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--sd-pdf-divider);flex:0 0 auto;z-index:2}.sd-preview-pdf-header__left{display:flex;align-items:center;gap:8px;min-width:0;flex:1 1 auto}.sd-preview-pdf-header__right{display:flex;align-items:center;gap:4px;flex-shrink:0}.sd-preview-pdf-header__divider{display:inline-block;width:1px;height:20px;background:var(--sd-pdf-divider)}.sd-preview-pdf-header__meta{display:flex;flex-direction:column;min-width:0}.sd-preview-pdf-header__name{font-size:14px;font-weight:500;color:var(--sd-pdf-fg);max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-preview-pdf-header__sub{font-size:11px;color:var(--sd-pdf-fg-muted);margin-top:2px}.sd-preview-pdf-fileicon{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:6px;background:var(--sd-pdf-danger-tint);border:1px solid var(--sd-pdf-danger-border);color:var(--sd-error-light, #f87171);font:700 10px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.sd-preview-pdf-iconbtn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-width:32px;height:32px;padding:0 8px;background:transparent;border:none;color:var(--sd-pdf-fg);border-radius:6px;cursor:pointer;transition:background .12s,color .12s;white-space:nowrap;flex-shrink:0}.sd-preview-pdf-iconbtn sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-iconbtn__label{font-size:13px;font-weight:500}.sd-preview-pdf-iconbtn:hover:not(:disabled){background:var(--sd-pdf-hover-bg)}.sd-preview-pdf-iconbtn:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-iconbtn:disabled{opacity:.35;cursor:not-allowed}.sd-preview-pdf-iconbtn--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-searchbar{display:flex;align-items:center;gap:6px;height:46px;padding:0 12px;background:var(--sd-pdf-toolbar-bg);border-bottom:1px solid var(--sd-pdf-divider);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);flex:0 0 auto;z-index:2}.sd-preview-pdf-searchbar__icon{color:var(--sd-pdf-fg-muted);font-size:18px;width:18px;height:18px;flex-shrink:0}.sd-preview-pdf-searchbar__input{flex:0 1 360px;min-width:160px;height:30px;background:#ffffff0f;border:1px solid rgba(255,255,255,.08);color:var(--sd-pdf-fg);border-radius:6px;padding:0 10px;font-size:13px;outline:none}.sd-preview-pdf-searchbar__input::placeholder{color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-searchbar__input:focus{border-color:var(--sd-pdf-primary);background:#ffffff17}.sd-preview-pdf-searchbar__counter{color:var(--sd-pdf-fg-muted);font-size:12px;font-variant-numeric:tabular-nums;min-width:48px;text-align:center;flex-shrink:0}.sd-preview-pdf-searchbar__divider{width:1px;height:20px;background:var(--sd-pdf-divider);flex-shrink:0}.sd-preview-pdf-searchbar__spacer{flex:1 1 auto}.sd-pdf-search-overlay{position:absolute;top:8px;left:50%;transform:translate(-50%);display:flex;flex-wrap:wrap;gap:4px;max-width:80%;pointer-events:none;z-index:5}.sd-pdf-search-hi{display:inline-block;padding:1px 4px;border-radius:2px;background:var(--sd-pdf-search-hi);color:#000;font-size:12px;font-weight:500}.sd-pdf-search-hi--active{background:var(--sd-pdf-search-hi-active);color:#fff;box-shadow:0 0 0 2px #fff6}.sd-preview-pdf-body{position:relative;flex:1 1 auto;min-height:0;display:flex;flex-direction:row;overflow:hidden}.sd-preview-pdf-sidebar{width:240px;display:flex;flex-direction:column;background:var(--sd-pdf-sidebar-bg);border-right:1px solid var(--sd-pdf-divider);flex-shrink:0;min-height:0}.sd-preview-pdf-sidebar__tabs{display:flex;gap:4px;padding:8px;border-bottom:1px solid var(--sd-pdf-divider)}.sd-preview-pdf-sidebar__tab{flex:1 1 0;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:32px;padding:0 4px;background:transparent;border:none;color:var(--sd-pdf-fg-muted);border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;white-space:nowrap;flex-shrink:0;transition:background .12s,color .12s}.sd-preview-pdf-sidebar__tab sd-icon{font-size:14px;width:14px;height:14px}.sd-preview-pdf-sidebar__tab:hover{background:#ffffff0f;color:var(--sd-pdf-fg)}.sd-preview-pdf-sidebar__tab--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-sidebar__content{flex:1 1 auto;overflow:auto;padding:12px}.sd-preview-pdf-sidebar__deferred{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:32px 16px;text-align:center;color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-sidebar__deferred sd-icon{font-size:28px;width:28px;height:28px;opacity:.6}.sd-preview-pdf-sidebar__deferred p{margin:0;font-size:12px;line-height:1.5}.sd-preview-pdf-thumb-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px}.sd-preview-pdf-thumb{display:flex;flex-direction:column;align-items:center;gap:4px;width:100%;padding:0;background:transparent;border:none;cursor:pointer}.sd-preview-pdf-thumb__page{width:152px;aspect-ratio:612/792;background:var(--sd-pdf-page-bg);border-radius:2px;outline:1px solid rgba(255,255,255,.08);outline-offset:-1px;box-shadow:0 2px 6px #0000004d;display:flex;align-items:center;justify-content:center;overflow:hidden;-webkit-user-select:none;user-select:none;background-image:linear-gradient(90deg,#0000,#0000000a,#0000);background-size:200% 100%;animation:sd-preview-pdf-thumb-skel 1.6s ease-in-out infinite}.sd-preview-pdf-thumb__canvas{display:block;max-width:100%;max-height:100%;background:var(--sd-pdf-page-bg)}.sd-preview-pdf-thumb__label{font-size:11px;color:var(--sd-pdf-fg-muted);font-variant-numeric:tabular-nums}.sd-preview-pdf-thumb--active .sd-preview-pdf-thumb__page{outline:2px solid var(--sd-pdf-primary);outline-offset:-2px}.sd-preview-pdf-thumb--active .sd-preview-pdf-thumb__label{color:var(--sd-pdf-primary);font-weight:600}.sd-preview-pdf-thumb:focus-visible .sd-preview-pdf-thumb__page{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-stage{position:relative;flex:1 1 auto;min-width:0;min-height:0;background:var(--sd-pdf-shell-bg);background-image:linear-gradient(45deg,rgba(255,255,255,.015) 25%,transparent 25%),linear-gradient(-45deg,rgba(255,255,255,.015) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(255,255,255,.015) 75%),linear-gradient(-45deg,transparent 75%,rgba(255,255,255,.015) 75%);background-size:24px 24px;background-position:0 0,0 12px,12px -12px,-12px 0;overflow:auto;padding:32px 24px 96px;display:flex;justify-content:center;align-items:flex-start}:host([data-theme=light]) .sd-preview-pdf-stage{background-image:linear-gradient(45deg,rgba(0,0,0,.015) 25%,transparent 25%),linear-gradient(-45deg,rgba(0,0,0,.015) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(0,0,0,.015) 75%),linear-gradient(-45deg,transparent 75%,rgba(0,0,0,.015) 75%)}.sd-preview-pdf-page-wrap{display:inline-block;position:relative}.sd-preview-pdf-page{display:block;background:var(--sd-pdf-page-bg);border-radius:2px;box-shadow:var(--sd-pdf-page-shadow);max-width:100%}.sd-preview-pdf-toolbar{position:absolute;bottom:20px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:2px;padding:5px 6px;background:var(--sd-pdf-toolbar-bg);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid var(--sd-pdf-divider);border-radius:10px;box-shadow:0 8px 30px #0006;z-index:4;max-width:calc(100% - 32px);overflow-x:auto}.sd-preview-pdf-toolbar__btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;min-width:30px;height:28px;padding:0 6px;background:transparent;border:none;color:var(--sd-pdf-fg);border-radius:6px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background .12s,color .12s}.sd-preview-pdf-toolbar__btn sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-toolbar__btn span{font-size:13px;font-weight:500}.sd-preview-pdf-toolbar__btn--with-label{padding:0 10px}.sd-preview-pdf-toolbar__btn--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-toolbar__btn:hover:not(:disabled):not(.sd-preview-pdf-toolbar__btn--active){background:var(--sd-pdf-hover-bg)}.sd-preview-pdf-toolbar__btn:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-toolbar__btn:disabled{opacity:.35;cursor:not-allowed}.sd-preview-pdf-toolbar__page{display:inline-flex;align-items:center;gap:4px;padding:0 6px;flex-shrink:0}.sd-preview-pdf-toolbar__page-input{width:36px;height:24px;background:#ffffff14;border:1px solid rgba(255,255,255,.08);color:var(--sd-pdf-fg);border-radius:4px;text-align:center;font-size:13px;font-variant-numeric:tabular-nums;outline:none}.sd-preview-pdf-toolbar__page-input:focus{border-color:var(--sd-pdf-primary)}.sd-preview-pdf-toolbar__page-total{font-size:13px;color:var(--sd-pdf-fg-muted);font-variant-numeric:tabular-nums}.sd-preview-pdf-toolbar__zoom{min-width:56px;text-align:center;color:var(--sd-pdf-fg);font-size:13px;font-weight:500;font-variant-numeric:tabular-nums;background:#ffffff14;border-radius:4px;padding:2px 8px}.sd-preview-pdf-toolbar__divider{width:1px;height:20px;background:var(--sd-pdf-divider);margin:0 4px;flex-shrink:0}.sd-preview-pdf-status{margin:auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;text-align:center;padding:32px;max-width:420px}.sd-preview-pdf-status__tile{width:96px;height:96px;border-radius:24px;display:flex;align-items:center;justify-content:center;border:1px solid var(--sd-pdf-divider)}.sd-preview-pdf-status__tile sd-icon{font-size:48px;width:48px;height:48px}.sd-preview-pdf-status__tile--muted{background:#ffffff0d}.sd-preview-pdf-status__tile--muted sd-icon{color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-status__tile--danger{background:var(--sd-pdf-danger-soft);border-color:var(--sd-pdf-danger-border)}.sd-preview-pdf-status__tile--danger sd-icon{color:var(--sd-pdf-danger)}.sd-preview-pdf-status__title{font-size:16px;font-weight:500;color:var(--sd-pdf-fg)}.sd-preview-pdf-status__hint{font-size:13px;color:var(--sd-pdf-fg-muted);line-height:1.5}.sd-preview-pdf-status__actions{display:flex;gap:8px;margin-top:4px}.sd-preview-pdf-progress-pct{font-variant-numeric:tabular-nums;color:var(--sd-pdf-fg-muted)}.sd-preview-pdf-progress{width:240px;height:4px;background:#ffffff14;border-radius:999px;overflow:hidden}.sd-preview-pdf-progress__fill{height:100%;background:var(--sd-pdf-primary);transition:width .12s linear}.sd-preview-pdf-spinner{width:56px;height:56px;border-radius:999px;border:3px solid rgba(255,255,255,.08);border-top-color:var(--sd-pdf-primary);animation:sd-preview-pdf-spin .9s linear infinite}@keyframes sd-preview-pdf-spin{to{transform:rotate(360deg)}}@keyframes sd-preview-pdf-thumb-skel{0%{background-position:200% 0}to{background-position:-200% 0}}.sd-preview-pdf-search-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.sd-preview-pdf-search-result{width:100%;display:flex;align-items:flex-start;gap:8px;padding:8px 10px;background:#ffffff0a;border:1px solid transparent;border-radius:6px;color:var(--sd-pdf-fg);cursor:pointer;text-align:left;transition:background .12s,border-color .12s}.sd-preview-pdf-search-result:hover{background:#ffffff12}.sd-preview-pdf-search-result--active{background:var(--sd-pdf-primary-soft);border-color:#005cbb73}.sd-preview-pdf-search-result__snippet{flex:1 1 auto;font-size:12px;line-height:1.4;color:var(--sd-pdf-fg);word-break:break-word}.sd-preview-pdf-search-result__snippet mark{background:var(--sd-pdf-search-hi);color:#000;padding:0 2px;border-radius:2px}.sd-preview-pdf-search-result__page{flex:0 0 auto;font-size:11px;font-variant-numeric:tabular-nums;color:var(--sd-pdf-fg-muted);background:#ffffff0f;padding:2px 6px;border-radius:4px}.sd-preview-pdf-btn-primary{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 16px;background:var(--sd-pdf-primary);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;transition:filter .12s}.sd-preview-pdf-btn-primary sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-btn-primary:hover{filter:brightness(1.1)}.sd-preview-pdf-btn-primary:focus-visible{outline:2px solid #fff;outline-offset:2px}.sd-preview-pdf-btn-secondary{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 16px;background:transparent;color:var(--sd-pdf-fg);border:1px solid var(--sd-pdf-divider);border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;transition:background .12s}.sd-preview-pdf-btn-secondary sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-btn-secondary:hover{background:#ffffff0f}.sd-preview-pdf-btn-secondary:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}\n"] }]
|
|
1980
|
-
}], ctorParameters: () => [], propDecorators: { pageCanvasRef: [{ type: i0.ViewChild, args: ['pageCanvas', { isSignal: true }] }], stageEl: [{ type: i0.ViewChild, args: ['stageRef', { isSignal: true }] }], pageInputRef: [{ type: i0.ViewChild, args: ['pageInput', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], thumbCanvases: [{ type: i0.ViewChildren, args: ['thumbCanvas', { isSignal: true }] }], autoIdInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoId", required: false }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], startPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "startPage", required: false }] }], initialZoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialZoom", required: false }] }], sidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebar", required: false }] }], sidebarOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarOpen", required: false }] }], scrollMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollMode", required: false }] }], showToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToolbar", required: false }] }], downloadable: [{ type: i0.Input, args: [{ isSignal: true, alias: "downloadable", required: false }] }], password: [{ type: i0.Input, args: [{ isSignal: true, alias: "password", required: false }] }], httpHeaders: [{ type: i0.Input, args: [{ isSignal: true, alias: "httpHeaders", required: false }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], close: [{ type: i0.Output, args: ["close"] }], loaded: [{ type: i0.Output, args: ["loaded"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], zoomChange: [{ type: i0.Output, args: ["zoomChange"] }], download: [{ type: i0.Output, args: ["download"] }], loadError: [{ type: i0.Output, args: ["loadError"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], onKeyDown: [{
|
|
3199
|
+
}, template: "<!--\n sd-preview-pdf shell. Self-contained \u2014 sizes to its parent. Consumer wraps\n in <sd-modal>/<sd-side-drawer>/page section. Header + sidebar + stage +\n floating toolbar mirror Artboards A / C / E from the design handoff.\n-->\n<div class=\"sd-preview-pdf-shell\" role=\"region\">\n @let _stage = stage();\n @let _numPages = numPages();\n @let _activePage = activePage();\n @let _isSidebarOpen = isSidebarOpen();\n @let _searchBarOpen = searchBarOpen();\n @let _searchTerm = searchTerm();\n @let _searchTotal = searchTotal();\n\n <!-- ============================================\n HEADER BAR \u2014 hidden in empty + loading per design\n ============================================ -->\n @if (_stage === 'ready') {\n <div class=\"sd-preview-pdf-header\">\n <div class=\"sd-preview-pdf-header__left\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"_isSidebarOpen\"\n [attr.aria-expanded]=\"_isSidebarOpen\"\n [attr.aria-controls]=\"sidebarRegionId()\"\n [attr.aria-label]=\"'core.component.preview-pdf.toggle-sidebar' | translate\"\n [attr.title]=\"'core.component.preview-pdf.toggle-sidebar' | translate\"\n [attr.data-autoId]=\"autoIdSidebarToggle()\"\n (click)=\"toggleSidebar()\">\n <sd-icon fontIcon=\"menu\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-header__divider\"></span>\n <div class=\"sd-preview-pdf-fileicon\" aria-hidden=\"true\">PDF</div>\n <div class=\"sd-preview-pdf-header__meta\">\n <div class=\"sd-preview-pdf-header__name\" [title]=\"title() || filename()\">\n {{ title() || filename() }}\n </div>\n <div class=\"sd-preview-pdf-header__sub\">\n {{ 'core.component.preview-pdf.pages-count' | translate: { count: _numPages } }}\n </div>\n </div>\n </div>\n\n <div class=\"sd-preview-pdf-header__right\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"_searchBarOpen\"\n [attr.aria-expanded]=\"_searchBarOpen\"\n [attr.aria-controls]=\"searchBarId()\"\n [attr.aria-label]=\"'core.component.preview-pdf.search' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search' | translate\"\n [attr.data-autoId]=\"autoIdSearchToggle()\"\n (click)=\"searchBarOpen() ? closeSearch() : openSearch()\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n </button>\n @if (canPrint()) {\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.print' | translate\"\n [attr.title]=\"'core.component.preview-pdf.print' | translate\"\n [attr.data-autoId]=\"autoIdPrint()\"\n (click)=\"printFile()\">\n <sd-icon fontIcon=\"print\"></sd-icon>\n </button>\n }\n @if (canDownload()) {\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.download' | translate\"\n [attr.title]=\"'core.component.preview-pdf.download' | translate\"\n [attr.data-autoId]=\"autoIdDownload()\"\n (click)=\"downloadFile()\">\n <sd-icon fontIcon=\"download\"></sd-icon>\n </button>\n }\n @if (canFullscreen()) {\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.fullscreen' | translate\"\n [attr.title]=\"'core.component.preview-pdf.fullscreen' | translate\"\n [attr.data-autoId]=\"autoIdFullscreen()\"\n (click)=\"toggleFullscreen()\">\n <sd-icon [fontIcon]=\"isFullscreen() ? 'fullscreen_exit' : 'fullscreen'\"></sd-icon>\n </button>\n }\n </div>\n </div>\n\n <!-- ============================================\n SEARCH BAR \u2014 between header + body, only when bar is open\n ============================================ -->\n <div\n class=\"sd-preview-pdf-searchbar\"\n role=\"search\"\n [id]=\"searchBarId()\"\n [hidden]=\"!_searchBarOpen\"\n [style.display]=\"_searchBarOpen ? null : 'none'\">\n @if (_searchBarOpen) {\n <sd-icon class=\"sd-preview-pdf-searchbar__icon\" fontIcon=\"search\"></sd-icon>\n <input\n #searchInput\n type=\"text\"\n class=\"sd-preview-pdf-searchbar__input\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-placeholder' | translate\"\n [attr.placeholder]=\"'core.component.preview-pdf.search-placeholder' | translate\"\n [value]=\"_searchTerm\"\n [attr.data-autoId]=\"autoIdSearchInput()\"\n (input)=\"onSearchInput($event)\" />\n <span class=\"sd-preview-pdf-searchbar__counter\" aria-live=\"polite\">\n {{ searchCurrent() }} / {{ _searchTotal }}{{ searchTruncated() ? '+' : '' }}\n </span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [disabled]=\"_searchTotal === 0\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-prev' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-prev' | translate\"\n [attr.data-autoId]=\"autoIdSearchPrev()\"\n (click)=\"searchPrev()\">\n <sd-icon fontIcon=\"keyboard_arrow_up\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [disabled]=\"_searchTotal === 0\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-next' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-next' | translate\"\n [attr.data-autoId]=\"autoIdSearchNext()\"\n (click)=\"searchNext()\">\n <sd-icon fontIcon=\"keyboard_arrow_down\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-searchbar__divider\"></span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"searchCaseSensitive()\"\n [attr.aria-pressed]=\"searchCaseSensitive()\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-case' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-case' | translate\"\n [attr.data-autoId]=\"autoIdSearchCase()\"\n (click)=\"toggleSearchCaseSensitive()\">\n <sd-icon fontIcon=\"match_case\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [class.sd-preview-pdf-iconbtn--active]=\"searchWholeWord()\"\n [attr.aria-pressed]=\"searchWholeWord()\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-whole' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-whole' | translate\"\n [attr.data-autoId]=\"autoIdSearchWhole()\"\n (click)=\"toggleSearchWholeWord()\">\n <sd-icon fontIcon=\"text_fields\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-searchbar__spacer\"></span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-iconbtn\"\n [attr.aria-label]=\"'core.component.preview-pdf.search-close' | translate\"\n [attr.title]=\"'core.component.preview-pdf.search-close' | translate\"\n [attr.data-autoId]=\"autoIdSearchClose()\"\n (click)=\"closeSearch()\">\n <sd-icon fontIcon=\"close\"></sd-icon>\n </button>\n }\n </div>\n }\n\n <!-- ============================================\n BODY \u2014 sidebar + stage\n ============================================ -->\n <div class=\"sd-preview-pdf-body\">\n <!-- SIDEBAR ............................................................. -->\n @if (_stage === 'ready') {\n <aside\n class=\"sd-preview-pdf-sidebar\"\n role=\"complementary\"\n [id]=\"sidebarRegionId()\"\n [hidden]=\"!_isSidebarOpen\"\n [style.display]=\"_isSidebarOpen ? null : 'none'\">\n @if (_isSidebarOpen) {\n @let _sidebarMode = sidebarMode();\n <div class=\"sd-preview-pdf-sidebar__tabs\" role=\"tablist\">\n <button\n #sidebarTab\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [id]=\"tabThumbnailsId()\"\n [attr.aria-selected]=\"_sidebarMode === 'thumbnails'\"\n [attr.aria-controls]=\"sidebarPanelId()\"\n [attr.tabindex]=\"_sidebarMode === 'thumbnails' ? 0 : -1\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'thumbnails'\"\n data-sidebar-mode=\"thumbnails\"\n [attr.data-autoId]=\"autoIdTabThumbnails()\"\n (keydown)=\"onSidebarTabKeyDown($event, 'thumbnails')\"\n (click)=\"setSidebarMode('thumbnails')\">\n <sd-icon fontIcon=\"view_carousel\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.thumbnails' | translate }}</span>\n </button>\n <button\n #sidebarTab\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [id]=\"tabOutlineId()\"\n [attr.aria-selected]=\"_sidebarMode === 'outline'\"\n [attr.aria-controls]=\"sidebarPanelId()\"\n [attr.tabindex]=\"_sidebarMode === 'outline' ? 0 : -1\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'outline'\"\n data-sidebar-mode=\"outline\"\n [attr.data-autoId]=\"autoIdTabOutline()\"\n (keydown)=\"onSidebarTabKeyDown($event, 'outline')\"\n (click)=\"setSidebarMode('outline')\">\n <sd-icon fontIcon=\"list\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.outline' | translate }}</span>\n </button>\n <button\n #sidebarTab\n type=\"button\"\n class=\"sd-preview-pdf-sidebar__tab\"\n role=\"tab\"\n [id]=\"tabSearchId()\"\n [attr.aria-selected]=\"_sidebarMode === 'search'\"\n [attr.aria-controls]=\"sidebarPanelId()\"\n [attr.tabindex]=\"_sidebarMode === 'search' ? 0 : -1\"\n [class.sd-preview-pdf-sidebar__tab--active]=\"_sidebarMode === 'search'\"\n data-sidebar-mode=\"search\"\n [attr.data-autoId]=\"autoIdTabSearch()\"\n (keydown)=\"onSidebarTabKeyDown($event, 'search')\"\n (click)=\"setSidebarMode('search')\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.tab.search' | translate }}</span>\n </button>\n </div>\n\n <div\n #sidebarContent\n class=\"sd-preview-pdf-sidebar__content\"\n role=\"tabpanel\"\n [id]=\"sidebarPanelId()\"\n [attr.aria-labelledby]=\"sidebarActiveTabId()\"\n (scroll)=\"onThumbnailSidebarScroll($event)\">\n @switch (_sidebarMode) {\n @case ('thumbnails') {\n <ul class=\"sd-preview-pdf-thumb-list\">\n <li class=\"sd-preview-pdf-thumb-list__spacer\" aria-hidden=\"true\" [style.height.px]=\"thumbnailTopSpacer()\"></li>\n @for (p of pageNumbers(); track trackByPage($index, p)) {\n <li>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-thumb\"\n [class.sd-preview-pdf-thumb--active]=\"p === _activePage\"\n [attr.aria-current]=\"p === _activePage ? 'page' : null\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: p, total: _numPages }\"\n [attr.data-autoId]=\"autoIdThumb(p - 1)\"\n (click)=\"goToPage(p)\">\n <div class=\"sd-preview-pdf-thumb__page\">\n <canvas #thumbCanvas class=\"sd-preview-pdf-thumb__canvas\" [attr.data-page]=\"p\" aria-hidden=\"true\"></canvas>\n </div>\n <span class=\"sd-preview-pdf-thumb__label\">{{ p }}</span>\n </button>\n </li>\n }\n <li class=\"sd-preview-pdf-thumb-list__spacer\" aria-hidden=\"true\" [style.height.px]=\"thumbnailBottomSpacer()\"></li>\n </ul>\n }\n @case ('outline') {\n <ul\n class=\"sd-preview-pdf-outline-tree\"\n role=\"tree\"\n [attr.aria-label]=\"'core.component.preview-pdf.tab.outline' | translate\">\n @for (row of visibleOutline(); track row.item.id) {\n <li class=\"sd-preview-pdf-outline-tree__node\" role=\"none\">\n <div class=\"sd-preview-pdf-outline-tree__row\" [style.padding-left.px]=\"(row.level - 1) * 16\">\n @if (row.item.children.length > 0) {\n <button\n type=\"button\"\n class=\"sd-preview-pdf-outline-tree__toggle\"\n tabindex=\"-1\"\n [attr.aria-label]=\"row.item.title\"\n [attr.aria-expanded]=\"isOutlineExpanded(row.item.id)\"\n (click)=\"toggleOutlineItem(row.item.id, $event)\">\n <sd-icon [fontIcon]=\"isOutlineExpanded(row.item.id) ? 'expand_more' : 'chevron_right'\"></sd-icon>\n </button>\n } @else {\n <span class=\"sd-preview-pdf-outline-tree__toggle-placeholder\" aria-hidden=\"true\"></span>\n }\n\n @if (row.item.url && row.item.page === null) {\n <a\n #outlineItem\n class=\"sd-preview-pdf-outline-tree__item\"\n role=\"treeitem\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n [href]=\"row.item.url\"\n [attr.data-outline-id]=\"row.item.id\"\n [attr.aria-level]=\"row.level\"\n [attr.aria-expanded]=\"row.item.children.length > 0 ? isOutlineExpanded(row.item.id) : null\"\n [attr.aria-current]=\"row.item.page === _activePage ? 'page' : null\"\n [attr.tabindex]=\"outlineFocusId() === row.item.id ? 0 : -1\"\n (focus)=\"focusOutlineItem(row.item.id)\"\n (keydown)=\"onOutlineKeyDown($event, row.item.id)\">\n {{ row.item.title }}\n </a>\n } @else {\n <button\n #outlineItem\n type=\"button\"\n class=\"sd-preview-pdf-outline-tree__item\"\n role=\"treeitem\"\n [attr.data-outline-id]=\"row.item.id\"\n [attr.aria-level]=\"row.level\"\n [attr.aria-expanded]=\"row.item.children.length > 0 ? isOutlineExpanded(row.item.id) : null\"\n [attr.aria-current]=\"row.item.page === _activePage ? 'page' : null\"\n [attr.tabindex]=\"outlineFocusId() === row.item.id ? 0 : -1\"\n (focus)=\"focusOutlineItem(row.item.id)\"\n (click)=\"activateOutlineItem(row.item.id)\"\n (keydown)=\"onOutlineKeyDown($event, row.item.id)\">\n {{ row.item.title }}\n </button>\n }\n </div>\n </li>\n }\n </ul>\n }\n @case ('search') {\n @if (!_searchTerm) {\n <div class=\"sd-preview-pdf-sidebar__message\">\n <sd-icon fontIcon=\"search\"></sd-icon>\n <p>{{ 'core.component.preview-pdf.search-placeholder' | translate }}</p>\n </div>\n } @else if (_searchTotal === 0) {\n <div class=\"sd-preview-pdf-sidebar__message\">\n <sd-icon fontIcon=\"search_off\"></sd-icon>\n <p>{{ 'core.component.preview-pdf.search-no-results' | translate }}</p>\n </div>\n } @else {\n @if (searchTruncated()) {\n <p class=\"sd-preview-pdf-search-limit\" role=\"status\">\n {{ 'core.component.preview-pdf.search-truncated' | translate: { count: _searchTotal } }}\n </p>\n }\n <ul class=\"sd-preview-pdf-search-list\">\n @for (r of searchResults(); track $index; let i = $index) {\n <li>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-search-result\"\n [class.sd-preview-pdf-search-result--active]=\"i === searchActiveIndex()\"\n [attr.data-autoId]=\"autoIdResult(i)\"\n (click)=\"activateSearchResult(i)\">\n <span class=\"sd-preview-pdf-search-result__snippet\">\n {{ r.before }}<mark>{{ r.term }}</mark\n >{{ r.after }}\n </span>\n <span class=\"sd-preview-pdf-search-result__page\">{{ r.page }}</span>\n </button>\n </li>\n }\n </ul>\n }\n }\n }\n </div>\n }\n </aside>\n }\n\n <!-- STAGE ............................................................... -->\n <div class=\"sd-preview-pdf-stage\" #stageRef (wheel)=\"onWheel($event)\" (scroll)=\"onStageScroll()\">\n @switch (_stage) {\n @case ('empty') {\n <div class=\"sd-preview-pdf-status\">\n <div class=\"sd-preview-pdf-status__tile sd-preview-pdf-status__tile--muted\">\n <sd-icon fontIcon=\"picture_as_pdf\"></sd-icon>\n </div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.empty.title' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__hint\">\n {{ 'core.component.preview-pdf.empty.subtitle' | translate }}\n </div>\n </div>\n }\n @case ('loading') {\n @let _loadPercent = loadPercent();\n <div class=\"sd-preview-pdf-status\">\n <div\n class=\"sd-preview-pdf-spinner\"\n role=\"progressbar\"\n [attr.aria-label]=\"'core.component.preview-pdf.loading' | translate\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n [attr.aria-valuenow]=\"_loadPercent\"></div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.loading' | translate }}\n @if (_loadPercent > 0) {\n <span class=\"sd-preview-pdf-progress-pct\"> {{ _loadPercent }}%</span>\n }\n </div>\n <div class=\"sd-preview-pdf-progress\">\n <div class=\"sd-preview-pdf-progress__fill\" [style.width.%]=\"_loadPercent\"></div>\n </div>\n </div>\n }\n @case ('error') {\n @if (loadError$(); as err) {\n <div class=\"sd-preview-pdf-status\" role=\"alert\" aria-live=\"assertive\">\n <div class=\"sd-preview-pdf-status__tile sd-preview-pdf-status__tile--danger\">\n <sd-icon fontIcon=\"error\"></sd-icon>\n </div>\n <div class=\"sd-preview-pdf-status__title\">\n {{ 'core.component.preview-pdf.error.' + err.reason + '.title' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__hint\">\n {{ 'core.component.preview-pdf.error.' + err.reason + '.body' | translate }}\n </div>\n <div class=\"sd-preview-pdf-status__actions\">\n <button type=\"button\" class=\"sd-preview-pdf-btn-primary\" (click)=\"retryLoad()\" [attr.data-autoId]=\"autoIdRetry()\">\n <sd-icon fontIcon=\"refresh\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.error.retry' | translate }}</span>\n </button>\n @if (canDownload()) {\n <button type=\"button\" class=\"sd-preview-pdf-btn-secondary\" (click)=\"downloadFile()\">\n <sd-icon fontIcon=\"download\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.error.download-raw' | translate }}</span>\n </button>\n }\n </div>\n </div>\n }\n }\n @default {\n @if (scrollModeCurrent() === 'continuous') {\n <div class=\"sd-preview-pdf-continuous-content\">\n <div class=\"sd-preview-pdf-continuous-spacer\" [style.height.px]=\"continuousTopSpacer()\"></div>\n @for (pageNumber of continuousPages(); track pageNumber) {\n <div class=\"sd-preview-pdf-continuous-slot\" [style.min-height.px]=\"continuousPageHeights()[pageNumber]\">\n <canvas\n #continuousCanvas\n class=\"sd-preview-pdf-continuous-page\"\n role=\"img\"\n [attr.data-page]=\"pageNumber\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: pageNumber, total: _numPages }\">\n </canvas>\n </div>\n }\n <div class=\"sd-preview-pdf-continuous-spacer\" [style.height.px]=\"continuousBottomSpacer()\"></div>\n </div>\n } @else {\n <div class=\"sd-preview-pdf-page-wrap\">\n <canvas\n #pageCanvas\n class=\"sd-preview-pdf-page\"\n role=\"img\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: _activePage, total: _numPages }\">\n </canvas>\n </div>\n }\n }\n }\n\n <!-- FLOATING TOOLBAR ........................................ -->\n @if (showToolbar() && _stage === 'ready') {\n @let _canPrev = canPrev();\n @let _canNext = canNext();\n @let _zoomMode = zoomMode();\n @let _scrollModeCurrent = scrollModeCurrent();\n <div class=\"sd-preview-pdf-toolbar\" role=\"toolbar\">\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canPrev\"\n [attr.aria-label]=\"'core.component.preview-pdf.first-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.first-page' | translate\"\n [attr.data-autoId]=\"autoIdFirst()\"\n (click)=\"firstPage()\">\n <sd-icon fontIcon=\"first_page\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canPrev\"\n [attr.aria-label]=\"'core.component.preview-pdf.prev-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.prev-page' | translate\"\n [attr.data-autoId]=\"autoIdPrev()\"\n (click)=\"prevPage()\">\n <sd-icon fontIcon=\"chevron_left\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-toolbar__page\">\n <input\n type=\"text\"\n inputmode=\"numeric\"\n class=\"sd-preview-pdf-toolbar__page-input\"\n [value]=\"_activePage\"\n #pageInput\n (keydown.enter)=\"onPageInputEnter($event)\"\n (blur)=\"onPageInputBlur($event)\"\n [attr.aria-label]=\"'core.component.preview-pdf.page-of' | translate: { current: _activePage, total: _numPages }\"\n [attr.data-autoId]=\"autoIdPageInput()\" />\n <span class=\"sd-preview-pdf-toolbar__page-total\">/ {{ _numPages }}</span>\n </span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canNext\"\n [attr.aria-label]=\"'core.component.preview-pdf.next-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.next-page' | translate\"\n [attr.data-autoId]=\"autoIdNext()\"\n (click)=\"nextPage()\">\n <sd-icon fontIcon=\"chevron_right\"></sd-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [disabled]=\"!_canNext\"\n [attr.aria-label]=\"'core.component.preview-pdf.last-page' | translate\"\n [attr.title]=\"'core.component.preview-pdf.last-page' | translate\"\n [attr.data-autoId]=\"autoIdLast()\"\n (click)=\"lastPage()\">\n <sd-icon fontIcon=\"last_page\"></sd-icon>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.zoom-out' | translate\"\n [attr.title]=\"'core.component.preview-pdf.zoom-out' | translate\"\n [attr.data-autoId]=\"autoIdZoomOut()\"\n (click)=\"zoomOut()\">\n <sd-icon fontIcon=\"remove\"></sd-icon>\n </button>\n <span class=\"sd-preview-pdf-toolbar__zoom\" aria-live=\"polite\" [attr.data-autoId]=\"autoIdZoomReadout()\">{{ zoomPercent() }}%</span>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.zoom-in' | translate\"\n [attr.title]=\"'core.component.preview-pdf.zoom-in' | translate\"\n [attr.data-autoId]=\"autoIdZoomIn()\"\n (click)=\"zoomIn()\">\n <sd-icon fontIcon=\"add\"></sd-icon>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [class.sd-preview-pdf-toolbar__btn--active]=\"_zoomMode === 'page-fit'\"\n [attr.data-autoId]=\"autoIdFitPage()\"\n (click)=\"setZoom('page-fit')\">\n <sd-icon fontIcon=\"fit_screen\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.fit-page' | translate }}</span>\n </button>\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [class.sd-preview-pdf-toolbar__btn--active]=\"_zoomMode === 'page-width'\"\n [attr.data-autoId]=\"autoIdFitWidth()\"\n (click)=\"setZoom('page-width')\">\n <sd-icon fontIcon=\"swap_horiz\"></sd-icon>\n <span>{{ 'core.component.preview-pdf.fit-width' | translate }}</span>\n </button>\n\n <span class=\"sd-preview-pdf-toolbar__divider\"></span>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn\"\n [attr.aria-label]=\"'core.component.preview-pdf.rotate' | translate\"\n [attr.title]=\"'core.component.preview-pdf.rotate' | translate\"\n [attr.data-autoId]=\"autoIdRotate()\"\n (click)=\"rotate('right')\">\n <sd-icon fontIcon=\"rotate_right\"></sd-icon>\n </button>\n\n <button\n type=\"button\"\n class=\"sd-preview-pdf-toolbar__btn sd-preview-pdf-toolbar__btn--with-label\"\n [attr.data-autoId]=\"autoIdScrollMode()\"\n (click)=\"setScrollMode(_scrollModeCurrent === 'page' ? 'continuous' : 'page')\">\n <sd-icon [fontIcon]=\"_scrollModeCurrent === 'page' ? 'description' : 'view_day'\"></sd-icon>\n <span>\n {{\n (_scrollModeCurrent === 'page' ? 'core.component.preview-pdf.scroll-page' : 'core.component.preview-pdf.scroll-continuous')\n | translate\n }}\n </span>\n </button>\n </div>\n }\n </div>\n </div>\n</div>\n", styles: [":host{--sd-pdf-shell-bg: #1a1c1f;--sd-pdf-sidebar-bg: #22252a;--sd-pdf-toolbar-bg: rgba(20, 22, 25, .94);--sd-pdf-page-bg: #ffffff;--sd-pdf-page-shadow: 0 4px 16px rgba(0, 0, 0, .4);--sd-pdf-divider: rgba(255, 255, 255, .08);--sd-pdf-fg: #f5f6f7;--sd-pdf-fg-muted: rgba(255, 255, 255, .6);--sd-pdf-fg-subtle: rgba(255, 255, 255, .4);--sd-pdf-primary: var(--sd-pdf-accent, var(--sd-primary, #005cbb));--sd-pdf-primary-soft: var(--sd-pdf-accent-soft, rgba(0, 92, 187, .18));--sd-pdf-danger: var(--sd-error, #ba1a1a);--sd-pdf-success: var(--sd-success, #2e7d32);--sd-pdf-danger-soft: rgba(186, 26, 26, .1);--sd-pdf-danger-border: rgba(186, 26, 26, .3);--sd-pdf-danger-tint: rgba(186, 26, 26, .15);--sd-pdf-hover-bg: rgba(255, 255, 255, .1);--sd-pdf-search-hi: rgba(250, 204, 21, .55);--sd-pdf-search-hi-active: rgba(249, 115, 22, .85);display:block;width:100%;height:100%;min-height:320px;outline:none}:host:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}:host([data-theme=light]){--sd-pdf-shell-bg: #f7f8fa;--sd-pdf-sidebar-bg: #ffffff;--sd-pdf-toolbar-bg: rgba(255, 255, 255, .94);--sd-pdf-page-bg: #ffffff;--sd-pdf-page-shadow: 0 4px 16px rgba(0, 0, 0, .12);--sd-pdf-divider: rgba(0, 0, 0, .08);--sd-pdf-fg: #1a1c1f;--sd-pdf-fg-muted: rgba(0, 0, 0, .62);--sd-pdf-fg-subtle: rgba(0, 0, 0, .42);--sd-pdf-primary: var(--sd-pdf-accent, var(--sd-primary, #005cbb));--sd-pdf-primary-soft: var(--sd-pdf-accent-soft, rgba(0, 92, 187, .16));--sd-pdf-danger: var(--sd-error, #ba1a1a);--sd-pdf-success: var(--sd-success, #2e7d32);--sd-pdf-danger-soft: rgba(186, 26, 26, .08);--sd-pdf-danger-border: rgba(186, 26, 26, .25);--sd-pdf-danger-tint: rgba(186, 26, 26, .12);--sd-pdf-hover-bg: rgba(0, 0, 0, .06);--sd-pdf-search-hi: rgba(250, 204, 21, .55);--sd-pdf-search-hi-active: rgba(250, 204, 21, .9)}:host(.sd-preview-pdf-host--fullscreen){width:100vw;height:100vh;min-height:0}.sd-preview-pdf-shell{position:relative;display:flex;flex-direction:column;width:100%;height:100%;background:var(--sd-pdf-shell-bg);color:var(--sd-pdf-fg);border-radius:12px;overflow:hidden;box-shadow:0 30px 80px #0000008c,0 0 0 1px #ffffff0a;font-family:inherit}.sd-preview-pdf-header{display:flex;align-items:center;justify-content:space-between;gap:12px;height:57px;padding:0 12px;background:var(--sd-pdf-toolbar-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--sd-pdf-divider);flex:0 0 auto;z-index:2}.sd-preview-pdf-header__left{display:flex;align-items:center;gap:8px;min-width:0;flex:1 1 auto}.sd-preview-pdf-header__right{display:flex;align-items:center;gap:4px;flex-shrink:0}.sd-preview-pdf-header__divider{display:inline-block;width:1px;height:20px;background:var(--sd-pdf-divider)}.sd-preview-pdf-header__meta{display:flex;flex-direction:column;min-width:0}.sd-preview-pdf-header__name{font-size:14px;font-weight:500;color:var(--sd-pdf-fg);max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-preview-pdf-header__sub{font-size:11px;color:var(--sd-pdf-fg-muted);margin-top:2px}.sd-preview-pdf-fileicon{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:6px;background:var(--sd-pdf-danger-tint);border:1px solid var(--sd-pdf-danger-border);color:var(--sd-error-light, #f87171);font:700 10px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.sd-preview-pdf-iconbtn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-width:32px;height:32px;padding:0 8px;background:transparent;border:none;color:var(--sd-pdf-fg);border-radius:6px;cursor:pointer;transition:background .12s,color .12s;white-space:nowrap;flex-shrink:0}.sd-preview-pdf-iconbtn sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-iconbtn__label{font-size:13px;font-weight:500}.sd-preview-pdf-iconbtn:hover:not(:disabled){background:var(--sd-pdf-hover-bg)}.sd-preview-pdf-iconbtn:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-iconbtn:disabled{opacity:.35;cursor:not-allowed}.sd-preview-pdf-iconbtn--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-searchbar{display:flex;align-items:center;gap:6px;height:46px;padding:0 12px;background:var(--sd-pdf-toolbar-bg);border-bottom:1px solid var(--sd-pdf-divider);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);flex:0 0 auto;z-index:2}.sd-preview-pdf-searchbar__icon{color:var(--sd-pdf-fg-muted);font-size:18px;width:18px;height:18px;flex-shrink:0}.sd-preview-pdf-searchbar__input{flex:0 1 360px;min-width:160px;height:30px;background:#ffffff0f;border:1px solid rgba(255,255,255,.08);color:var(--sd-pdf-fg);border-radius:6px;padding:0 10px;font-size:13px;outline:none}.sd-preview-pdf-searchbar__input::placeholder{color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-searchbar__input:focus{border-color:var(--sd-pdf-primary);background:#ffffff17}.sd-preview-pdf-searchbar__counter{color:var(--sd-pdf-fg-muted);font-size:12px;font-variant-numeric:tabular-nums;min-width:48px;text-align:center;flex-shrink:0}.sd-preview-pdf-searchbar__divider{width:1px;height:20px;background:var(--sd-pdf-divider);flex-shrink:0}.sd-preview-pdf-searchbar__spacer{flex:1 1 auto}.sd-pdf-search-overlay{position:absolute;top:8px;left:50%;transform:translate(-50%);display:flex;flex-wrap:wrap;gap:4px;max-width:80%;pointer-events:none;z-index:5}.sd-pdf-search-hi{display:inline-block;padding:1px 4px;border-radius:2px;background:var(--sd-pdf-search-hi);color:#000;font-size:12px;font-weight:500}.sd-pdf-search-hi--active{background:var(--sd-pdf-search-hi-active);color:#fff;box-shadow:0 0 0 2px #fff6}.sd-preview-pdf-body{position:relative;flex:1 1 auto;min-height:0;display:flex;flex-direction:row;overflow:hidden}.sd-preview-pdf-sidebar{width:240px;display:flex;flex-direction:column;background:var(--sd-pdf-sidebar-bg);border-right:1px solid var(--sd-pdf-divider);flex-shrink:0;min-height:0}.sd-preview-pdf-sidebar__tabs{display:flex;gap:4px;padding:8px;border-bottom:1px solid var(--sd-pdf-divider)}.sd-preview-pdf-sidebar__tab{flex:1 1 0;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:32px;padding:0 4px;background:transparent;border:none;color:var(--sd-pdf-fg-muted);border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;white-space:nowrap;flex-shrink:0;transition:background .12s,color .12s}.sd-preview-pdf-sidebar__tab sd-icon{font-size:14px;width:14px;height:14px}.sd-preview-pdf-sidebar__tab:hover{background:#ffffff0f;color:var(--sd-pdf-fg)}.sd-preview-pdf-sidebar__tab--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-sidebar__content{flex:1 1 auto;overflow:auto;padding:12px}.sd-preview-pdf-sidebar__message{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:32px 16px;text-align:center;color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-sidebar__message sd-icon{font-size:28px;width:28px;height:28px;opacity:.6}.sd-preview-pdf-sidebar__message p{margin:0;font-size:12px;line-height:1.5}.sd-preview-pdf-outline-tree{list-style:none;margin:0;padding:0}.sd-preview-pdf-outline-tree__node{margin:0;padding:0}.sd-preview-pdf-outline-tree__row{display:flex;align-items:center;min-width:0}.sd-preview-pdf-outline-tree__toggle,.sd-preview-pdf-outline-tree__toggle-placeholder{width:24px;height:28px;flex:0 0 24px}.sd-preview-pdf-outline-tree__toggle{display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:4px;background:transparent;color:var(--sd-pdf-fg-muted);cursor:pointer}.sd-preview-pdf-outline-tree__toggle:hover{background:#ffffff0f;color:var(--sd-pdf-fg)}.sd-preview-pdf-outline-tree__toggle sd-icon{width:16px;height:16px;font-size:16px}.sd-preview-pdf-outline-tree__item{min-width:0;flex:1 1 auto;overflow:hidden;padding:6px 8px;border:0;border-radius:5px;background:transparent;color:var(--sd-pdf-fg-muted);font:inherit;font-size:12px;line-height:1.35;text-align:left;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.sd-preview-pdf-outline-tree__item:hover{background:#ffffff0f;color:var(--sd-pdf-fg)}.sd-preview-pdf-outline-tree__item[aria-current=page]{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary);font-weight:600}.sd-preview-pdf-outline-tree__item:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:-2px}.sd-preview-pdf-thumb-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px}.sd-preview-pdf-thumb-list__spacer{flex:0 0 auto;pointer-events:none}.sd-preview-pdf-search-limit{margin:0 0 8px;color:var(--sd-pdf-fg-muted);font-size:11px;line-height:1.4}.sd-preview-pdf-thumb{display:flex;flex-direction:column;align-items:center;gap:4px;width:100%;padding:0;background:transparent;border:none;cursor:pointer}.sd-preview-pdf-thumb__page{width:152px;aspect-ratio:612/792;background:var(--sd-pdf-page-bg);border-radius:2px;outline:1px solid rgba(255,255,255,.08);outline-offset:-1px;box-shadow:0 2px 6px #0000004d;display:flex;align-items:center;justify-content:center;overflow:hidden;-webkit-user-select:none;user-select:none;background-image:linear-gradient(90deg,#0000,#0000000a,#0000);background-size:200% 100%;animation:sd-preview-pdf-thumb-skel 1.6s ease-in-out infinite}.sd-preview-pdf-thumb__canvas{display:block;max-width:100%;max-height:100%;background:var(--sd-pdf-page-bg)}.sd-preview-pdf-thumb__label{font-size:11px;color:var(--sd-pdf-fg-muted);font-variant-numeric:tabular-nums}.sd-preview-pdf-thumb--active .sd-preview-pdf-thumb__page{outline:2px solid var(--sd-pdf-primary);outline-offset:-2px}.sd-preview-pdf-thumb--active .sd-preview-pdf-thumb__label{color:var(--sd-pdf-primary);font-weight:600}.sd-preview-pdf-thumb:focus-visible .sd-preview-pdf-thumb__page{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-stage{position:relative;flex:1 1 auto;min-width:0;min-height:0;background:var(--sd-pdf-shell-bg);background-image:linear-gradient(45deg,rgba(255,255,255,.015) 25%,transparent 25%),linear-gradient(-45deg,rgba(255,255,255,.015) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(255,255,255,.015) 75%),linear-gradient(-45deg,transparent 75%,rgba(255,255,255,.015) 75%);background-size:24px 24px;background-position:0 0,0 12px,12px -12px,-12px 0;overflow:auto;padding:32px 24px 96px;display:flex;justify-content:center;align-items:flex-start}:host([data-theme=light]) .sd-preview-pdf-stage{background-image:linear-gradient(45deg,rgba(0,0,0,.015) 25%,transparent 25%),linear-gradient(-45deg,rgba(0,0,0,.015) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(0,0,0,.015) 75%),linear-gradient(-45deg,transparent 75%,rgba(0,0,0,.015) 75%)}.sd-preview-pdf-page-wrap{display:inline-block;position:relative}.sd-preview-pdf-page{display:block;background:var(--sd-pdf-page-bg);border-radius:2px;box-shadow:var(--sd-pdf-page-shadow);max-width:100%}.sd-preview-pdf-continuous-content{width:100%;min-height:100%}.sd-preview-pdf-continuous-spacer{width:1px}.sd-preview-pdf-continuous-slot{display:flex;justify-content:center;align-items:flex-start}.sd-preview-pdf-continuous-slot+.sd-preview-pdf-continuous-slot{margin-top:24px}.sd-preview-pdf-continuous-page{display:block;max-width:100%;background:var(--sd-pdf-page-bg);border-radius:2px;box-shadow:var(--sd-pdf-page-shadow)}.sd-preview-pdf-toolbar{position:absolute;bottom:20px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:2px;padding:5px 6px;background:var(--sd-pdf-toolbar-bg);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid var(--sd-pdf-divider);border-radius:10px;box-shadow:0 8px 30px #0006;z-index:4;max-width:calc(100% - 32px);overflow-x:auto}.sd-preview-pdf-toolbar__btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;min-width:30px;height:28px;padding:0 6px;background:transparent;border:none;color:var(--sd-pdf-fg);border-radius:6px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background .12s,color .12s}.sd-preview-pdf-toolbar__btn sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-toolbar__btn span{font-size:13px;font-weight:500}.sd-preview-pdf-toolbar__btn--with-label{padding:0 10px}.sd-preview-pdf-toolbar__btn--active{background:var(--sd-pdf-primary-soft);color:var(--sd-pdf-primary)}.sd-preview-pdf-toolbar__btn:hover:not(:disabled):not(.sd-preview-pdf-toolbar__btn--active){background:var(--sd-pdf-hover-bg)}.sd-preview-pdf-toolbar__btn:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}.sd-preview-pdf-toolbar__btn:disabled{opacity:.35;cursor:not-allowed}.sd-preview-pdf-toolbar__page{display:inline-flex;align-items:center;gap:4px;padding:0 6px;flex-shrink:0}.sd-preview-pdf-toolbar__page-input{width:36px;height:24px;background:#ffffff14;border:1px solid rgba(255,255,255,.08);color:var(--sd-pdf-fg);border-radius:4px;text-align:center;font-size:13px;font-variant-numeric:tabular-nums;outline:none}.sd-preview-pdf-toolbar__page-input:focus{border-color:var(--sd-pdf-primary)}.sd-preview-pdf-toolbar__page-total{font-size:13px;color:var(--sd-pdf-fg-muted);font-variant-numeric:tabular-nums}.sd-preview-pdf-toolbar__zoom{min-width:56px;text-align:center;color:var(--sd-pdf-fg);font-size:13px;font-weight:500;font-variant-numeric:tabular-nums;background:#ffffff14;border-radius:4px;padding:2px 8px}.sd-preview-pdf-toolbar__divider{width:1px;height:20px;background:var(--sd-pdf-divider);margin:0 4px;flex-shrink:0}.sd-preview-pdf-status{margin:auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;text-align:center;padding:32px;max-width:420px}.sd-preview-pdf-status__tile{width:96px;height:96px;border-radius:24px;display:flex;align-items:center;justify-content:center;border:1px solid var(--sd-pdf-divider)}.sd-preview-pdf-status__tile sd-icon{font-size:48px;width:48px;height:48px}.sd-preview-pdf-status__tile--muted{background:#ffffff0d}.sd-preview-pdf-status__tile--muted sd-icon{color:var(--sd-pdf-fg-subtle)}.sd-preview-pdf-status__tile--danger{background:var(--sd-pdf-danger-soft);border-color:var(--sd-pdf-danger-border)}.sd-preview-pdf-status__tile--danger sd-icon{color:var(--sd-pdf-danger)}.sd-preview-pdf-status__title{font-size:16px;font-weight:500;color:var(--sd-pdf-fg)}.sd-preview-pdf-status__hint{font-size:13px;color:var(--sd-pdf-fg-muted);line-height:1.5}.sd-preview-pdf-status__actions{display:flex;gap:8px;margin-top:4px}.sd-preview-pdf-progress-pct{font-variant-numeric:tabular-nums;color:var(--sd-pdf-fg-muted)}.sd-preview-pdf-progress{width:240px;height:4px;background:#ffffff14;border-radius:999px;overflow:hidden}.sd-preview-pdf-progress__fill{height:100%;background:var(--sd-pdf-primary);transition:width .12s linear}.sd-preview-pdf-spinner{width:56px;height:56px;border-radius:999px;border:3px solid rgba(255,255,255,.08);border-top-color:var(--sd-pdf-primary);animation:sd-preview-pdf-spin .9s linear infinite}@keyframes sd-preview-pdf-spin{to{transform:rotate(360deg)}}@keyframes sd-preview-pdf-thumb-skel{0%{background-position:200% 0}to{background-position:-200% 0}}.sd-preview-pdf-search-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.sd-preview-pdf-search-result{width:100%;display:flex;align-items:flex-start;gap:8px;padding:8px 10px;background:#ffffff0a;border:1px solid transparent;border-radius:6px;color:var(--sd-pdf-fg);cursor:pointer;text-align:left;transition:background .12s,border-color .12s}.sd-preview-pdf-search-result:hover{background:#ffffff12}.sd-preview-pdf-search-result--active{background:var(--sd-pdf-primary-soft);border-color:#005cbb73}.sd-preview-pdf-search-result__snippet{flex:1 1 auto;font-size:12px;line-height:1.4;color:var(--sd-pdf-fg);word-break:break-word}.sd-preview-pdf-search-result__snippet mark{background:var(--sd-pdf-search-hi);color:#000;padding:0 2px;border-radius:2px}.sd-preview-pdf-search-result__page{flex:0 0 auto;font-size:11px;font-variant-numeric:tabular-nums;color:var(--sd-pdf-fg-muted);background:#ffffff0f;padding:2px 6px;border-radius:4px}.sd-preview-pdf-btn-primary{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 16px;background:var(--sd-pdf-primary);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;transition:filter .12s}.sd-preview-pdf-btn-primary sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-btn-primary:hover{filter:brightness(1.1)}.sd-preview-pdf-btn-primary:focus-visible{outline:2px solid #fff;outline-offset:2px}.sd-preview-pdf-btn-secondary{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 16px;background:transparent;color:var(--sd-pdf-fg);border:1px solid var(--sd-pdf-divider);border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;transition:background .12s}.sd-preview-pdf-btn-secondary sd-icon{font-size:18px;width:18px;height:18px}.sd-preview-pdf-btn-secondary:hover{background:#ffffff0f}.sd-preview-pdf-btn-secondary:focus-visible{outline:2px solid var(--sd-pdf-primary);outline-offset:2px}\n"] }]
|
|
3200
|
+
}], ctorParameters: () => [], propDecorators: { pageCanvasRef: [{ type: i0.ViewChild, args: ['pageCanvas', { isSignal: true }] }], stageEl: [{ type: i0.ViewChild, args: ['stageRef', { isSignal: true }] }], pageInputRef: [{ type: i0.ViewChild, args: ['pageInput', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], sidebarContentRef: [{ type: i0.ViewChild, args: ['sidebarContent', { isSignal: true }] }], outlineItemRefs: [{ type: i0.ViewChildren, args: ['outlineItem', { isSignal: true }] }], sidebarTabRefs: [{ type: i0.ViewChildren, args: ['sidebarTab', { isSignal: true }] }], continuousCanvases: [{ type: i0.ViewChildren, args: ['continuousCanvas', { isSignal: true }] }], thumbCanvases: [{ type: i0.ViewChildren, args: ['thumbCanvas', { isSignal: true }] }], autoIdInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoId", required: false }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], startPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "startPage", required: false }] }], initialZoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialZoom", required: false }] }], sidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebar", required: false }] }], sidebarOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarOpen", required: false }] }], scrollMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollMode", required: false }] }], showToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToolbar", required: false }] }], downloadable: [{ type: i0.Input, args: [{ isSignal: true, alias: "downloadable", required: false }] }], password: [{ type: i0.Input, args: [{ isSignal: true, alias: "password", required: false }] }], httpHeaders: [{ type: i0.Input, args: [{ isSignal: true, alias: "httpHeaders", required: false }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], close: [{ type: i0.Output, args: ["close"] }], loaded: [{ type: i0.Output, args: ["loaded"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], zoomChange: [{ type: i0.Output, args: ["zoomChange"] }], download: [{ type: i0.Output, args: ["download"] }], loadError: [{ type: i0.Output, args: ["loadError"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], onKeyDown: [{
|
|
1981
3201
|
type: HostListener,
|
|
1982
3202
|
args: ['keydown', ['$event']]
|
|
1983
3203
|
}] } });
|
|
@@ -1986,5 +3206,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
1986
3206
|
* Generated bundle index. Do not edit.
|
|
1987
3207
|
*/
|
|
1988
3208
|
|
|
1989
|
-
export { SD_PDFJS_LIB, SdPreviewImage, SdPreviewPdf };
|
|
3209
|
+
export { SD_PDFJS_LIB, SD_PDF_BROWSER_ADAPTER, SD_PDF_PRINT_ADAPTER, SdPreviewImage, SdPreviewPdf };
|
|
1990
3210
|
//# sourceMappingURL=sdcorejs-angular-components-preview.mjs.map
|