@sdcorejs/angular 20.1.3 → 20.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +17 -14
  2. package/components/audit-diff/index.d.ts +74 -0
  3. package/components/badge/index.d.ts +1 -1
  4. package/components/breadcrumb/index.d.ts +53 -0
  5. package/components/data-state/index.d.ts +44 -0
  6. package/components/document-builder/index.d.ts +5 -5
  7. package/components/form-generic/index.d.ts +67 -104
  8. package/components/import-excel/index.d.ts +4 -14
  9. package/components/index.d.ts +4 -0
  10. package/components/job-progress/index.d.ts +38 -0
  11. package/components/mini-editor/index.d.ts +9 -9
  12. package/components/modal/index.d.ts +7 -1
  13. package/components/preview/index.d.ts +181 -28
  14. package/components/side-drawer/index.d.ts +7 -1
  15. package/components/tab/index.d.ts +9 -2
  16. package/components/table/index.d.ts +8 -7
  17. package/components/tree/index.d.ts +24 -2
  18. package/components/upload-file/index.d.ts +5 -4
  19. package/directives/index.d.ts +10 -8
  20. package/fesm2022/sdcorejs-angular-components-anchor.mjs +14 -14
  21. package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
  22. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs +332 -0
  23. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs.map +1 -0
  24. package/fesm2022/sdcorejs-angular-components-badge.mjs +1 -0
  25. package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
  26. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs +121 -0
  27. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs.map +1 -0
  28. package/fesm2022/sdcorejs-angular-components-data-state.mjs +68 -0
  29. package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -0
  30. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +18 -23
  31. package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
  32. package/fesm2022/sdcorejs-angular-components-editor.mjs +6 -4
  33. package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
  34. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +495 -838
  35. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  36. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +24 -34
  37. package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
  38. package/fesm2022/sdcorejs-angular-components-job-progress.mjs +97 -0
  39. package/fesm2022/sdcorejs-angular-components-job-progress.mjs.map +1 -0
  40. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs +10 -20
  41. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
  42. package/fesm2022/sdcorejs-angular-components-modal.mjs +61 -4
  43. package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
  44. package/fesm2022/sdcorejs-angular-components-preview.mjs +1438 -218
  45. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  46. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +4 -4
  47. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  48. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +1 -1
  49. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  50. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +39 -2
  51. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  52. package/fesm2022/sdcorejs-angular-components-tab-router.mjs +16 -22
  53. package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
  54. package/fesm2022/sdcorejs-angular-components-tab.mjs +48 -4
  55. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  56. package/fesm2022/sdcorejs-angular-components-table.mjs +96 -63
  57. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  58. package/fesm2022/sdcorejs-angular-components-tree.mjs +186 -17
  59. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  60. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +15 -22
  61. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  62. package/fesm2022/sdcorejs-angular-components.mjs +4 -0
  63. package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
  64. package/fesm2022/sdcorejs-angular-directives.mjs +29 -41
  65. package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
  66. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +7 -7
  67. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  68. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +20 -83
  69. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  70. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +6 -3
  71. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  72. package/fesm2022/sdcorejs-angular-forms-chip.mjs +6 -3
  73. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  74. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +16 -13
  75. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  76. package/fesm2022/sdcorejs-angular-forms-date.mjs +42 -65
  77. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  78. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +51 -66
  79. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  80. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +384 -0
  81. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -0
  82. package/fesm2022/sdcorejs-angular-forms-input-color.mjs +1 -1
  83. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  84. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +7 -5
  85. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  86. package/fesm2022/sdcorejs-angular-forms-input.mjs +290 -56
  87. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  88. package/fesm2022/sdcorejs-angular-forms-models.mjs +155 -2
  89. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  90. package/fesm2022/sdcorejs-angular-forms-radio.mjs +44 -78
  91. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  92. package/fesm2022/sdcorejs-angular-forms-select.mjs +6 -20
  93. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  94. package/fesm2022/sdcorejs-angular-forms-switch.mjs +19 -76
  95. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  96. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +6 -5
  97. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  98. package/fesm2022/sdcorejs-angular-forms-time-range.mjs +280 -0
  99. package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -0
  100. package/fesm2022/sdcorejs-angular-forms-time.mjs +310 -0
  101. package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -0
  102. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +210 -0
  103. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -0
  104. package/fesm2022/sdcorejs-angular-forms.mjs +73 -4
  105. package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
  106. package/fesm2022/sdcorejs-angular-i18n.mjs +355 -10
  107. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  108. package/fesm2022/sdcorejs-angular-interceptors.mjs +12 -17
  109. package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
  110. package/fesm2022/sdcorejs-angular-modules-layout.mjs +861 -80
  111. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  112. package/fesm2022/sdcorejs-angular-pipes.mjs +5 -7
  113. package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
  114. package/fesm2022/sdcorejs-angular-services-api.mjs +407 -137
  115. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  116. package/fesm2022/sdcorejs-angular-services-cache.mjs +515 -105
  117. package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
  118. package/fesm2022/sdcorejs-angular-services-confirm.mjs +5 -7
  119. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  120. package/fesm2022/sdcorejs-angular-services-docx.mjs +7 -10
  121. package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
  122. package/fesm2022/sdcorejs-angular-services-loading.mjs +339 -74
  123. package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
  124. package/fesm2022/sdcorejs-angular-services-notify.mjs +7 -10
  125. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  126. package/fesm2022/sdcorejs-angular-services-persistence.mjs +899 -0
  127. package/fesm2022/sdcorejs-angular-services-persistence.mjs.map +1 -0
  128. package/fesm2022/sdcorejs-angular-services-storage.mjs +324 -122
  129. package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
  130. package/fesm2022/sdcorejs-angular-services-task.mjs +389 -0
  131. package/fesm2022/sdcorejs-angular-services-task.mjs.map +1 -0
  132. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs +255 -0
  133. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs.map +1 -0
  134. package/fesm2022/sdcorejs-angular-services-viewport.mjs +76 -0
  135. package/fesm2022/sdcorejs-angular-services-viewport.mjs.map +1 -0
  136. package/fesm2022/sdcorejs-angular-services.mjs +4 -0
  137. package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
  138. package/forms/autocomplete/index.d.ts +3 -4
  139. package/forms/checkbox/index.d.ts +4 -8
  140. package/forms/date/index.d.ts +2 -2
  141. package/forms/date-range/index.d.ts +6 -7
  142. package/forms/datetime/index.d.ts +2 -2
  143. package/forms/entity-picker/index.d.ts +128 -0
  144. package/forms/index.d.ts +15 -5
  145. package/forms/input/index.d.ts +74 -11
  146. package/forms/input-number/index.d.ts +1 -1
  147. package/forms/models/index.d.ts +86 -3
  148. package/forms/radio/index.d.ts +12 -8
  149. package/forms/switch/index.d.ts +4 -7
  150. package/forms/time/index.d.ts +117 -0
  151. package/forms/time-range/index.d.ts +98 -0
  152. package/forms/tree-select/index.d.ts +76 -0
  153. package/i18n/index.d.ts +68 -2
  154. package/interceptors/index.d.ts +5 -6
  155. package/modules/layout/index.d.ts +288 -15
  156. package/package.json +49 -1
  157. package/pipes/index.d.ts +2 -2
  158. package/services/api/index.d.ts +68 -24
  159. package/services/cache/index.d.ts +52 -16
  160. package/services/confirm/index.d.ts +3 -2
  161. package/services/docx/index.d.ts +2 -3
  162. package/services/index.d.ts +4 -0
  163. package/services/loading/index.d.ts +16 -8
  164. package/services/persistence/index.d.ts +159 -0
  165. package/services/storage/index.d.ts +26 -8
  166. package/services/task/index.d.ts +104 -0
  167. package/services/unsaved-changes/index.d.ts +83 -0
  168. package/services/viewport/index.d.ts +42 -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: "20.3.25", ngImpo
657
657
  args: ['keydown', ['$event']]
658
658
  }] } });
659
659
 
660
- var _a;
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
- // Try the worker URL one time at first-injection. Guarded so the
665
- // component still imports cleanly in environments (tests, SSR) where
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
- catch {
673
- // Consumer app must set workerSrc manually (documented in sd-preview.md).
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 pdfjsLib;
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" }] : []));
703
994
  pageInputRef = viewChild('pageInput', ...(ngDevMode ? [{ debugName: "pageInputRef" }] : []));
704
995
  searchInputRef = viewChild('searchInput', ...(ngDevMode ? [{ debugName: "searchInputRef" }] : []));
996
+ sidebarContentRef = viewChild('sidebarContent', ...(ngDevMode ? [{ debugName: "sidebarContentRef" }] : []));
997
+ outlineItemRefs = viewChildren('outlineItem', ...(ngDevMode ? [{ debugName: "outlineItemRefs" }] : []));
998
+ sidebarTabRefs = viewChildren('sidebarTab', ...(ngDevMode ? [{ debugName: "sidebarTabRefs" }] : []));
999
+ continuousCanvases = viewChildren('continuousCanvas', ...(ngDevMode ? [{ debugName: "continuousCanvases" }] : []));
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" }] : []));
741
1036
  autoIdTabOutline = computed(() => this.#childAutoId('tab-outline'), ...(ngDevMode ? [{ debugName: "autoIdTabOutline" }] : []));
742
1037
  autoIdTabSearch = computed(() => this.#childAutoId('tab-search'), ...(ngDevMode ? [{ debugName: "autoIdTabSearch" }] : []));
1038
+ sidebarPanelId = computed(() => this.#childAutoId('sidebar-panel') ?? `${this.#instanceId}-sidebar-panel`, ...(ngDevMode ? [{ debugName: "sidebarPanelId" }] : []));
1039
+ sidebarRegionId = computed(() => this.#childAutoId('sidebar') ?? `${this.#instanceId}-sidebar`, ...(ngDevMode ? [{ debugName: "sidebarRegionId" }] : []));
1040
+ searchBarId = computed(() => this.#childAutoId('searchbar') ?? `${this.#instanceId}-searchbar`, ...(ngDevMode ? [{ debugName: "searchBarId" }] : []));
1041
+ tabThumbnailsId = computed(() => this.#childAutoId('tab-thumbnails') ?? `${this.#instanceId}-tab-thumbnails`, ...(ngDevMode ? [{ debugName: "tabThumbnailsId" }] : []));
1042
+ tabOutlineId = computed(() => this.#childAutoId('tab-outline') ?? `${this.#instanceId}-tab-outline`, ...(ngDevMode ? [{ debugName: "tabOutlineId" }] : []));
1043
+ tabSearchId = computed(() => this.#childAutoId('tab-search') ?? `${this.#instanceId}-tab-search`, ...(ngDevMode ? [{ debugName: "tabSearchId" }] : []));
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" }] : []));
743
1054
  // Search bar
744
1055
  autoIdSearchInput = computed(() => this.#childAutoId('search-input'), ...(ngDevMode ? [{ debugName: "autoIdSearchInput" }] : []));
745
1056
  autoIdSearchNext = computed(() => this.#childAutoId('search-next'), ...(ngDevMode ? [{ debugName: "autoIdSearchNext" }] : []));
@@ -797,9 +1108,18 @@ class SdPreviewPdf {
797
1108
  #isFullscreen = signal(false, ...(ngDevMode ? [{ debugName: "#isFullscreen" }] : []));
798
1109
  #filename = signal('document.pdf', ...(ngDevMode ? [{ debugName: "#filename" }] : []));
799
1110
  #fileSize = signal(0, ...(ngDevMode ? [{ debugName: "#fileSize" }] : []));
1111
+ #continuousPages = signal([], ...(ngDevMode ? [{ debugName: "#continuousPages" }] : []));
1112
+ #continuousTopSpacer = signal(0, ...(ngDevMode ? [{ debugName: "#continuousTopSpacer" }] : []));
1113
+ #continuousBottomSpacer = signal(0, ...(ngDevMode ? [{ debugName: "#continuousBottomSpacer" }] : []));
1114
+ #continuousHeightVersion = signal(0, ...(ngDevMode ? [{ debugName: "#continuousHeightVersion" }] : []));
1115
+ #thumbnailWindowStart = signal(0, ...(ngDevMode ? [{ debugName: "#thumbnailWindowStart" }] : []));
1116
+ #outline = signal([], ...(ngDevMode ? [{ debugName: "#outline" }] : []));
1117
+ #outlineExpanded = signal(new Set(), ...(ngDevMode ? [{ debugName: "#outlineExpanded" }] : []));
1118
+ #outlineFocusId = signal(null, ...(ngDevMode ? [{ debugName: "#outlineFocusId" }] : []));
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" }] : []));
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" }] : []));
815
1135
  #searchTerm = signal('', ...(ngDevMode ? [{ debugName: "#searchTerm" }] : []));
816
1136
  #searchResults = signal([], ...(ngDevMode ? [{ debugName: "#searchResults" }] : []));
1137
+ #searchTruncated = signal(false, ...(ngDevMode ? [{ debugName: "#searchTruncated" }] : []));
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" }] : []));
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
- #thumbObserver = null;
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
- #thumbsRendering = new Set();
1158
+ #thumbnailWork = new Map();
1159
+ #thumbnailWorkCount = signal(0, ...(ngDevMode ? [{ debugName: "#thumbnailWorkCount" }] : []));
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" }] : []));
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" }] : []));
860
1221
  numPages = computed(() => this.#meta()?.numPages ?? 0, ...(ngDevMode ? [{ debugName: "numPages" }] : []));
861
1222
  canPrev = computed(() => this.#activePage() > 1, ...(ngDevMode ? [{ debugName: "canPrev" }] : []));
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" }] : []));
873
1234
  isSidebarOpen = computed(() => this.#sidebarOpenInternal() && this.#sidebarModeInternal() !== 'none', ...(ngDevMode ? [{ debugName: "isSidebarOpen" }] : []));
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" }] : []));
1247
+ canFullscreen = computed(() => this.#browser.canFullscreen, ...(ngDevMode ? [{ debugName: "canFullscreen" }] : []));
1248
+ canPrint = computed(() => this.#printAdapter.isSupported && this.#stage() === 'ready' && this.#pdfDoc() !== null, ...(ngDevMode ? [{ debugName: "canPrint" }] : []));
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" }] : []));
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 = this.pageList;
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" }] : []));
1261
+ thumbnailTopSpacer = computed(() => this.#thumbnailWindowStart() * _a.THUMBNAIL_ITEM_HEIGHT, ...(ngDevMode ? [{ debugName: "thumbnailTopSpacer" }] : []));
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" }] : []));
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" }] : []));
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" }] : []));
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
- // Continuous scroll is deferred — warn once if requested but keep the
933
- // input value so consumer code is forward-compatible.
934
- if (m === 'continuous') {
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
- // Sync fullscreen state from the browser (works with Esc, F11, programmatic exit).
946
- const onFullscreenChange = () => {
947
- this.#isFullscreen.set(document.fullscreenElement === this.#hostEl.nativeElement);
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.#thumbObserver?.disconnect();
1002
- this.#thumbObserver = null;
1003
- this.#thumbsRendering.clear();
1004
- this.#pageTextCache.clear();
1005
- if (document.fullscreenElement === this.#hostEl.nativeElement) {
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
- if (target === this.#activePage())
1023
- return;
1024
- this.#activePage.set(target);
1025
- this.pageChange.emit(target);
1026
- this.#renderActivePage();
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
- // Fit modes recompute against stage size + page natural size — done in
1055
- // #renderActivePage which reads zoomMode().
1056
- this.#renderActivePage();
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.#renderActivePage();
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
- // TODO(preview-pdf): setScrollMode is signature-only; continuous + horizontal
1079
- // layout deferred. Currently this just updates internal state + emits a
1080
- // warning when consumer asks for the deferred 'continuous' mode.
1081
- setScrollMode(mode) {
1082
- if (mode === 'continuous') {
1083
- console.warn('[sd-preview-pdf] setScrollMode("continuous") is deferred; staying in single-page mode.' // @i18n-ignore
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
- if (!this.downloadable())
1090
- return;
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
- // Strategy: if caller passed a URL, anchor download honors server's
1094
- // Content-Disposition; for File/Blob/buffer sources we re-emit the blob.
1547
+ const doc = this.#pdfDoc();
1095
1548
  let href = null;
1096
- if (typeof src === 'string') {
1097
- href = src;
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 (src instanceof File || src instanceof Blob) {
1100
- href = URL.createObjectURL(src);
1101
- this.#ownedBlobUrls.add(href);
1566
+ else if (typeof src === 'string') {
1567
+ href = src;
1102
1568
  }
1103
- else if (this.#lastBlobUrl) {
1104
- href = this.#lastBlobUrl;
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 blob = new Blob([src], { type: 'application/pdf' });
1111
- href = URL.createObjectURL(blob);
1112
- this.#ownedBlobUrls.add(href);
1113
- }
1114
- if (!href)
1115
- return;
1116
- const a = document.createElement('a');
1117
- a.href = href;
1118
- a.download = filename;
1119
- document.body.appendChild(a);
1120
- a.click();
1121
- a.remove();
1122
- this.download.emit({ filename });
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 (!document.fullscreenElement) {
1126
- this.#hostEl.nativeElement.requestFullscreen?.().catch(() => undefined);
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
- console.warn('[sd-preview-pdf] printFile() is deferred to a follow-up commit.' // @i18n-ignore
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
- for (let p = 1; p <= doc.numPages; p++) {
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
- re.lastIndex = 0;
1796
+ expression.lastIndex = 0;
1201
1797
  let m;
1202
- while ((m = re.exec(text)) !== null) {
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
- re.lastIndex++;
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(value) {
1282
- void this.search(value);
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.#pageTextCache.clear();
1449
- this.#thumbsRendering.clear();
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 (token !== this.#loadToken)
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['password'] = this.password();
2259
+ spec.password = this.password();
1477
2260
  }
1478
2261
  let task;
1479
2262
  try {
1480
2263
  task = this.#pdfjs.getDocument(spec);
1481
- // pdfjs's loadingTask exposes onProgress as an assignable property.
2264
+ this.#currentLoadingTask = task;
1482
2265
  task.onProgress = (p) => {
1483
- if (token !== this.#loadToken)
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
- this.#emitError(this.#classifyError(err), this.#errorMessage(err));
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 = (await task.promise);
2278
+ pdfDoc = await task.promise;
1495
2279
  }
1496
2280
  catch (err) {
1497
- if (token !== this.#loadToken)
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 (token !== this.#loadToken) {
1503
- // A newer source replaced us mid-load. Cleanup this stale doc.
1504
- try {
1505
- await pdfDoc.destroy();
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.loaded.emit({ totalPages: pdfDoc.numPages, meta });
1534
- this.pageChange.emit(this.#activePage());
1535
- await this.#renderActivePage();
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
- // Resolve scale from current zoom mode.
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 viewport = page.getViewport({ scale, rotation });
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
- canvas.width = Math.floor(viewport.width);
1565
- canvas.height = Math.floor(viewport.height);
1566
- canvas.style.width = `${viewport.width}px`;
1567
- canvas.style.height = `${viewport.height}px`;
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
- // Cancelled mid-flight when user navigated to a new page — fine.
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
- page.cleanup();
1584
- this.zoomChange.emit(scale);
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
- async #destroyDoc() {
1619
- const doc = this.#pdfDoc();
1620
- if (!doc)
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
- this.#pdfDoc.set(null);
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
- /* ignore */
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.#renderActivePage();
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['httpHeaders'] = headers;
2496
+ spec.httpHeaders = headers;
1642
2497
  return spec;
1643
2498
  }
1644
- if (src instanceof File) {
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 instanceof Blob) {
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['httpHeaders'] = headers;
2528
+ spec.httpHeaders = headers;
1674
2529
  if (src.withCredentials)
1675
- spec['withCredentials'] = true;
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
- URL.revokeObjectURL(url);
2579
+ this.#browser.revokeObjectUrl(url);
1725
2580
  }
1726
2581
  this.#ownedBlobUrls.clear();
1727
- this.#lastBlobUrl = null;
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 pattern = wholeWord ? `\\b${escaped}\\b` : escaped;
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 = this.#pageTextCache.get(pageNum);
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 tc = (await page.getTextContent());
1772
- const items = Array.isArray(tc?.items) ? tc.items : [];
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
- const str = it?.str;
1776
- if (typeof str === 'string')
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
- this.#pageTextCache.set(pageNum, text);
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 = document.createElement('div');
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 = document.createElement('mark');
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
- // Disconnect prior observer — refs are stale once the @for re-renders.
1851
- this.#thumbObserver?.disconnect();
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.#thumbObserver = new IntersectionObserver(entries => {
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 c = entry.target;
1873
- const n = Number(c.getAttribute('data-page'));
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.#thumbObserver?.unobserve(c); // one-shot — cached after first paint
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
- for (const c of canvases)
1881
- this.#thumbObserver.observe(c);
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
- * Cached via `#thumbsRendering` so rapid scroll bounces don't double-render.
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.#thumbsRendering.has(pageNum))
3002
+ if (this.#thumbnailWork.has(pageNum))
1903
3003
  return;
1904
- if (this.#thumbCache()[pageNum]) {
1905
- // Already cached → just blit the dataURL onto the canvas.
1906
- this.#paintCachedThumb(canvas, this.#thumbCache()[pageNum]);
3004
+ const cachedThumbnail = this.#getCachedThumbnail(pageNum);
3005
+ if (cachedThumbnail) {
3006
+ this.#paintCachedThumb(canvas, cachedThumbnail, this.#loadToken);
1907
3007
  return;
1908
3008
  }
1909
- this.#thumbsRendering.add(pageNum);
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
- const page = await doc.getPage(pageNum);
1912
- // Target ~140px wide for sidebar thumbs (handoff says 152px, leave a
1913
- // small margin so the canvas itself can fit inside the page wrapper's
1914
- // outline). PDF page viewport at scale=1 612 wide; pick scale so the
1915
- // result lands near 140px.
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 viewport = page.getViewport({ scale });
1919
- canvas.width = Math.floor(viewport.width);
1920
- canvas.height = Math.floor(viewport.height);
1921
- canvas.style.width = `${viewport.width}px`;
1922
- canvas.style.height = `${viewport.height}px`;
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; // cancelled / failed silently — keep the blank thumb
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.#thumbCache.update(prev => ({ ...prev, [pageNum]: dataUrl }));
3050
+ this.#cacheThumbnail(pageNum, dataUrl);
1936
3051
  }
1937
3052
  catch {
1938
- // Tainted canvas (e.g. dev mock) — just skip caching, keep visual.
3053
+ return;
1939
3054
  }
1940
- page.cleanup();
1941
3055
  }
1942
3056
  catch {
1943
- // Page load failed — leave the thumb blank rather than throwing.
3057
+ return;
1944
3058
  }
1945
3059
  finally {
1946
- this.#thumbsRendering.delete(pageNum);
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 = new Image();
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: "20.3.25", ngImport: i0, type: SdPreviewPdf, deps: [], target: i0.ɵɵFactoryTarget.Component });
1966
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", 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\">&nbsp;{{ _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: "20.3.25", 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\">&nbsp;{{ _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: "20.3.25", ngImport: i0, type: SdPreviewPdf, decorators: [{
@@ -1976,8 +3196,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", 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\">&nbsp;{{ _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\">&nbsp;{{ _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: "20.3.25", 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