@ngstarter-ui/components 21.0.33 → 21.0.35

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.
@@ -1,13 +1,153 @@
1
- import { DOCUMENT, isPlatformBrowser } from '@angular/common';
1
+ import { NgTemplateOutlet, DOCUMENT, isPlatformBrowser } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { Injectable, inject, DestroyRef, ElementRef, PLATFORM_ID, viewChild, input, numberAttribute, booleanAttribute, output, signal, computed, effect, untracked, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { input, output, signal, computed, ChangeDetectionStrategy, Component, Directive, Injectable, effect, untracked, inject, DestroyRef, ElementRef, PLATFORM_ID, viewChild, contentChildren, numberAttribute, booleanAttribute, afterNextRender } from '@angular/core';
4
+ import { Rotation, MatchFlag } from '@embedpdf/models';
4
5
  import { BlockLoader } from '@ngstarter-ui/components/block-loader';
5
6
  import { Button } from '@ngstarter-ui/components/button';
7
+ import { Divider } from '@ngstarter-ui/components/divider';
6
8
  import { Icon } from '@ngstarter-ui/components/icon';
7
9
  import { ImagePlaceholder } from '@ngstarter-ui/components/image-placeholder';
8
10
  import { Menu, MenuDivider, MenuHeading, MenuItem, MenuTrigger } from '@ngstarter-ui/components/menu';
9
- import { Panel, PanelContent, PanelHeader, PanelSidebar } from '@ngstarter-ui/components/panel';
11
+ import { Panel, PanelContent, PanelHeader, PanelSubheader, PanelAside, PanelSidebar } from '@ngstarter-ui/components/panel';
12
+ import { Toolbar, ToolbarItem, ToolbarSpacer, ToolbarTitle } from '@ngstarter-ui/components/toolbar';
13
+ import { isObservable } from 'rxjs';
14
+ import { FormField, IconButtonSuffix, IconPrefix } from '@ngstarter-ui/components/form-field';
15
+ import { Input } from '@ngstarter-ui/components/input';
10
16
  import { PluginRegistry } from '@embedpdf/core';
17
+ import { Checkbox } from '@ngstarter-ui/components/checkbox';
18
+
19
+ class PdfViewerAnnotations {
20
+ annotations = input([], ...(ngDevMode ? [{ debugName: "annotations" }] : /* istanbul ignore next */ []));
21
+ annotationDefs = input([], ...(ngDevMode ? [{ debugName: "annotationDefs" }] : /* istanbul ignore next */ []));
22
+ annotationTypeProperty = input('type', ...(ngDevMode ? [{ debugName: "annotationTypeProperty" }] : /* istanbul ignore next */ []));
23
+ closed = output();
24
+ pageSelected = output();
25
+ filterQuery = signal('', ...(ngDevMode ? [{ debugName: "filterQuery" }] : /* istanbul ignore next */ []));
26
+ filteredAnnotations = computed(() => {
27
+ const query = this.filterQuery().trim().toLocaleLowerCase();
28
+ const annotations = this.annotations();
29
+ if (!query) {
30
+ return annotations;
31
+ }
32
+ return annotations.filter((annotation) => this.annotationMatchesFilter(annotation, query));
33
+ }, ...(ngDevMode ? [{ debugName: "filteredAnnotations" }] : /* istanbul ignore next */ []));
34
+ setFilterQuery(event) {
35
+ this.filterQuery.set(event.target.value);
36
+ }
37
+ clearFilterQuery() {
38
+ this.filterQuery.set('');
39
+ }
40
+ getAnnotationTemplate(annotation, index) {
41
+ const annotationDefs = this.annotationDefs();
42
+ const typeProperty = this.annotationTypeProperty();
43
+ const matchingDef = annotationDefs.find((def) => def.matches(annotation, index, typeProperty))
44
+ ?? annotationDefs.find((def) => !def.hasWhen());
45
+ return matchingDef?.template ?? null;
46
+ }
47
+ getAnnotationTemplateContext(annotation, index) {
48
+ return {
49
+ $implicit: annotation,
50
+ annotation,
51
+ index,
52
+ goToPage: (pageNumber) => this.pageSelected.emit(pageNumber),
53
+ };
54
+ }
55
+ getAnnotationTypeLabel(annotation) {
56
+ const label = annotation.label ?? annotation.type;
57
+ return typeof label === 'string' && label.trim().length > 0 ? label : 'Comment';
58
+ }
59
+ getAvatarLabel(annotation) {
60
+ const explicitLabel = annotation.avatarLabel;
61
+ if (typeof explicitLabel === 'string' && explicitLabel.trim().length > 0) {
62
+ return explicitLabel.trim();
63
+ }
64
+ return annotation.author
65
+ .split(/\s+/)
66
+ .filter(Boolean)
67
+ .slice(0, 2)
68
+ .map((part) => part[0]?.toUpperCase())
69
+ .join('');
70
+ }
71
+ getAvatarImage(annotation) {
72
+ return typeof annotation.avatarUrl === 'string' ? annotation.avatarUrl : '';
73
+ }
74
+ getReplyLabel(annotation) {
75
+ return typeof annotation.replyLabel === 'string' && annotation.replyLabel.trim().length > 0
76
+ ? annotation.replyLabel
77
+ : 'Reply';
78
+ }
79
+ annotationMatchesFilter(annotation, query) {
80
+ return [
81
+ annotation.author,
82
+ annotation.time,
83
+ annotation.text,
84
+ annotation.type,
85
+ annotation.label,
86
+ `page ${annotation.pageNumber}`,
87
+ ]
88
+ .filter((value) => typeof value === 'string')
89
+ .some((value) => value.toLocaleLowerCase().includes(query));
90
+ }
91
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotations, deps: [], target: i0.ɵɵFactoryTarget.Component });
92
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: PdfViewerAnnotations, isStandalone: true, selector: "ngs-pdf-viewer-annotations", inputs: { annotations: { classPropertyName: "annotations", publicName: "annotations", isSignal: true, isRequired: false, transformFunction: null }, annotationDefs: { classPropertyName: "annotationDefs", publicName: "annotationDefs", isSignal: true, isRequired: false, transformFunction: null }, annotationTypeProperty: { classPropertyName: "annotationTypeProperty", publicName: "annotationTypeProperty", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", pageSelected: "pageSelected" }, host: { classAttribute: "ngs-pdf-viewer-annotations" }, ngImport: i0, template: "<ngs-panel class=\"pdf-viewer-annotations bg-surface\" aria-label=\"PDF annotations\">\n <ngs-panel-header class=\"p-0\">\n <ngs-toolbar class=\"w-full py-0 pr-2 ps-4\" aria-label=\"PDF annotations toolbar\">\n <ngs-toolbar-title>\n Annotations\n </ngs-toolbar-title>\n <ngs-toolbar-spacer />\n <ngs-toolbar-item>\n <button\n class=\"pdf-viewer-annotations__close\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Close annotations panel\"\n (click)=\"closed.emit()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n </ngs-toolbar-item>\n </ngs-toolbar>\n </ngs-panel-header>\n\n <ngs-panel-subheader autoHeight class=\"px-4 pb-4\">\n <ngs-form-field subscriptHiddenIfEmpty>\n <ngs-icon ngsIconPrefix name=\"fluent:search-24-regular\" />\n <input\n ngsInput\n type=\"text\"\n aria-label=\"Filter annotations\"\n placeholder=\"Filter annotations...\"\n [value]=\"filterQuery()\"\n (input)=\"setFilterQuery($event)\" />\n @if (filterQuery()) {\n <button\n ngsIconButton\n ngsIconButtonSuffix\n type=\"button\"\n aria-label=\"Clear annotation filter\"\n (click)=\"clearFilterQuery()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n }\n </ngs-form-field>\n </ngs-panel-subheader>\n\n <ngs-panel-content>\n <div class=\"flex min-h-full flex-col\">\n <div class=\"flex flex-1 flex-col gap-4 pb-4 px-4\">\n <ng-template #defaultAnnotationTemplate let-annotation let-goToPage=\"goToPage\">\n <article class=\"rounded-2xl border border-border bg-surface p-4 shadow-xs\">\n <header class=\"flex items-start gap-3\">\n <span class=\"flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-full bg-primary-container text-xs font-semibold text-on-primary-container\">\n @if (getAvatarImage(annotation); as avatarImage) {\n <img\n class=\"h-full w-full object-cover\"\n [src]=\"avatarImage\"\n [alt]=\"annotation.author\" />\n } @else {\n {{ getAvatarLabel(annotation) }}\n }\n </span>\n\n <span class=\"min-w-0 flex-1\">\n <span class=\"block truncate text-sm font-semibold text-on-surface\">{{ annotation.author }}</span>\n @if (annotation.time) {\n <span class=\"block truncate text-xs text-on-surface-variant\">{{ annotation.time }}</span>\n }\n </span>\n\n <span class=\"rounded-full bg-surface-container-high px-2 py-1 text-xs font-medium tracking-normal text-on-surface-variant uppercase\">\n {{ getAnnotationTypeLabel(annotation) }}\n </span>\n </header>\n\n <p class=\"mt-3 mb-0 text-base leading-relaxed text-on-surface\">{{ annotation.text }}</p>\n\n <footer class=\"mt-4 flex items-center justify-between gap-2\">\n <button\n ngsButton=\"tonal\"\n type=\"button\"\n (click)=\"goToPage(annotation.pageNumber)\">\n Page {{ annotation.pageNumber }}\n </button>\n\n <button ngsButton=\"text\" type=\"button\">\n {{ getReplyLabel(annotation) }}\n </button>\n </footer>\n </article>\n </ng-template>\n\n @for (annotation of filteredAnnotations(); track annotation.id ?? annotation.pageNumber + '-' + $index) {\n <ng-container\n [ngTemplateOutlet]=\"getAnnotationTemplate(annotation, $index) ?? defaultAnnotationTemplate\"\n [ngTemplateOutletContext]=\"getAnnotationTemplateContext(annotation, $index)\" />\n } @empty {\n <div class=\"p-4 text-center text-sm text-on-surface-variant\">No annotations found.</div>\n }\n </div>\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;min-height:0;height:100%}\n"], dependencies: [{ kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "directive", type: IconButtonSuffix, selector: "[ngsIconButtonSuffix]" }, { kind: "directive", type: IconPrefix, selector: "[ngsIconPrefix]" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: Toolbar, selector: "ngs-toolbar", exportAs: ["ngsToolbar"] }, { kind: "component", type: ToolbarItem, selector: "ngs-toolbar-item", inputs: ["hidden"], outputs: ["hiddenChange"] }, { kind: "component", type: ToolbarSpacer, selector: "ngs-toolbar-spacer" }, { kind: "component", type: ToolbarTitle, selector: "ngs-toolbar-title" }, { kind: "component", type: PanelSubheader, selector: "ngs-panel-subheader", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelSubheader"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
93
+ }
94
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotations, decorators: [{
95
+ type: Component,
96
+ args: [{ selector: 'ngs-pdf-viewer-annotations', standalone: true, imports: [
97
+ Button,
98
+ FormField,
99
+ Icon,
100
+ IconButtonSuffix,
101
+ IconPrefix,
102
+ Input,
103
+ NgTemplateOutlet,
104
+ Panel,
105
+ PanelContent,
106
+ PanelHeader,
107
+ Toolbar,
108
+ ToolbarItem,
109
+ ToolbarSpacer,
110
+ ToolbarTitle,
111
+ PanelSubheader,
112
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
113
+ class: 'ngs-pdf-viewer-annotations',
114
+ }, template: "<ngs-panel class=\"pdf-viewer-annotations bg-surface\" aria-label=\"PDF annotations\">\n <ngs-panel-header class=\"p-0\">\n <ngs-toolbar class=\"w-full py-0 pr-2 ps-4\" aria-label=\"PDF annotations toolbar\">\n <ngs-toolbar-title>\n Annotations\n </ngs-toolbar-title>\n <ngs-toolbar-spacer />\n <ngs-toolbar-item>\n <button\n class=\"pdf-viewer-annotations__close\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Close annotations panel\"\n (click)=\"closed.emit()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n </ngs-toolbar-item>\n </ngs-toolbar>\n </ngs-panel-header>\n\n <ngs-panel-subheader autoHeight class=\"px-4 pb-4\">\n <ngs-form-field subscriptHiddenIfEmpty>\n <ngs-icon ngsIconPrefix name=\"fluent:search-24-regular\" />\n <input\n ngsInput\n type=\"text\"\n aria-label=\"Filter annotations\"\n placeholder=\"Filter annotations...\"\n [value]=\"filterQuery()\"\n (input)=\"setFilterQuery($event)\" />\n @if (filterQuery()) {\n <button\n ngsIconButton\n ngsIconButtonSuffix\n type=\"button\"\n aria-label=\"Clear annotation filter\"\n (click)=\"clearFilterQuery()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n }\n </ngs-form-field>\n </ngs-panel-subheader>\n\n <ngs-panel-content>\n <div class=\"flex min-h-full flex-col\">\n <div class=\"flex flex-1 flex-col gap-4 pb-4 px-4\">\n <ng-template #defaultAnnotationTemplate let-annotation let-goToPage=\"goToPage\">\n <article class=\"rounded-2xl border border-border bg-surface p-4 shadow-xs\">\n <header class=\"flex items-start gap-3\">\n <span class=\"flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-full bg-primary-container text-xs font-semibold text-on-primary-container\">\n @if (getAvatarImage(annotation); as avatarImage) {\n <img\n class=\"h-full w-full object-cover\"\n [src]=\"avatarImage\"\n [alt]=\"annotation.author\" />\n } @else {\n {{ getAvatarLabel(annotation) }}\n }\n </span>\n\n <span class=\"min-w-0 flex-1\">\n <span class=\"block truncate text-sm font-semibold text-on-surface\">{{ annotation.author }}</span>\n @if (annotation.time) {\n <span class=\"block truncate text-xs text-on-surface-variant\">{{ annotation.time }}</span>\n }\n </span>\n\n <span class=\"rounded-full bg-surface-container-high px-2 py-1 text-xs font-medium tracking-normal text-on-surface-variant uppercase\">\n {{ getAnnotationTypeLabel(annotation) }}\n </span>\n </header>\n\n <p class=\"mt-3 mb-0 text-base leading-relaxed text-on-surface\">{{ annotation.text }}</p>\n\n <footer class=\"mt-4 flex items-center justify-between gap-2\">\n <button\n ngsButton=\"tonal\"\n type=\"button\"\n (click)=\"goToPage(annotation.pageNumber)\">\n Page {{ annotation.pageNumber }}\n </button>\n\n <button ngsButton=\"text\" type=\"button\">\n {{ getReplyLabel(annotation) }}\n </button>\n </footer>\n </article>\n </ng-template>\n\n @for (annotation of filteredAnnotations(); track annotation.id ?? annotation.pageNumber + '-' + $index) {\n <ng-container\n [ngTemplateOutlet]=\"getAnnotationTemplate(annotation, $index) ?? defaultAnnotationTemplate\"\n [ngTemplateOutletContext]=\"getAnnotationTemplateContext(annotation, $index)\" />\n } @empty {\n <div class=\"p-4 text-center text-sm text-on-surface-variant\">No annotations found.</div>\n }\n </div>\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;min-height:0;height:100%}\n"] }]
115
+ }], propDecorators: { annotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotations", required: false }] }], annotationDefs: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotationDefs", required: false }] }], annotationTypeProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotationTypeProperty", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], pageSelected: [{ type: i0.Output, args: ["pageSelected"] }] } });
116
+
117
+ class PdfViewerAnnotationDef {
118
+ template;
119
+ annotationWhen = input(undefined, { ...(ngDevMode ? { debugName: "annotationWhen" } : /* istanbul ignore next */ {}), alias: 'ngsPdfViewerAnnotation' });
120
+ defWhen = input(undefined, { ...(ngDevMode ? { debugName: "defWhen" } : /* istanbul ignore next */ {}), alias: 'ngsPdfViewerAnnotationDef' });
121
+ when = input(undefined, { ...(ngDevMode ? { debugName: "when" } : /* istanbul ignore next */ {}), alias: 'ngsPdfViewerAnnotationWhen' });
122
+ constructor(template) {
123
+ this.template = template;
124
+ }
125
+ matches(annotation, index, typeProperty) {
126
+ const whenValue = this.whenValue();
127
+ if (!whenValue) {
128
+ return false;
129
+ }
130
+ if (typeof whenValue === 'function') {
131
+ return whenValue(annotation, index);
132
+ }
133
+ return annotation[typeProperty] === whenValue;
134
+ }
135
+ hasWhen() {
136
+ return !!this.whenValue();
137
+ }
138
+ whenValue() {
139
+ return this.when() ?? this.defWhen() ?? this.annotationWhen();
140
+ }
141
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotationDef, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
142
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.4", type: PdfViewerAnnotationDef, isStandalone: true, selector: "[ngsPdfViewerAnnotationDef], [ngsPdfViewerAnnotation]", inputs: { annotationWhen: { classPropertyName: "annotationWhen", publicName: "ngsPdfViewerAnnotation", isSignal: true, isRequired: false, transformFunction: null }, defWhen: { classPropertyName: "defWhen", publicName: "ngsPdfViewerAnnotationDef", isSignal: true, isRequired: false, transformFunction: null }, when: { classPropertyName: "when", publicName: "ngsPdfViewerAnnotationWhen", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
143
+ }
144
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotationDef, decorators: [{
145
+ type: Directive,
146
+ args: [{
147
+ selector: '[ngsPdfViewerAnnotationDef], [ngsPdfViewerAnnotation]',
148
+ standalone: true,
149
+ }]
150
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { annotationWhen: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngsPdfViewerAnnotation", required: false }] }], defWhen: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngsPdfViewerAnnotationDef", required: false }] }], when: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngsPdfViewerAnnotationWhen", required: false }] }] } });
11
151
 
12
152
  class PdfViewerEngineService {
13
153
  engines = new Map();
@@ -16,7 +156,10 @@ class PdfViewerEngineService {
16
156
  if (cachedEngine) {
17
157
  return cachedEngine;
18
158
  }
19
- const engine = this.createEngine(wasmUrl);
159
+ const engine = this.createEngine(wasmUrl).catch((error) => {
160
+ this.engines.delete(wasmUrl);
161
+ throw error;
162
+ });
20
163
  this.engines.set(wasmUrl, engine);
21
164
  return engine;
22
165
  }
@@ -38,6 +181,145 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
38
181
  }]
39
182
  }] });
40
183
 
184
+ class PdfViewerSearch {
185
+ results = input([], ...(ngDevMode ? [{ debugName: "results" }] : /* istanbul ignore next */ []));
186
+ query = input('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
187
+ closed = output();
188
+ resultSelected = output();
189
+ searchChanged = output();
190
+ caseSensitive = signal(false, ...(ngDevMode ? [{ debugName: "caseSensitive" }] : /* istanbul ignore next */ []));
191
+ wholeWord = signal(false, ...(ngDevMode ? [{ debugName: "wholeWord" }] : /* istanbul ignore next */ []));
192
+ activeResultIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeResultIndex" }] : /* istanbul ignore next */ []));
193
+ queryValue = signal('', ...(ngDevMode ? [{ debugName: "queryValue" }] : /* istanbul ignore next */ []));
194
+ hasQuery = computed(() => this.queryValue().trim().length > 0, ...(ngDevMode ? [{ debugName: "hasQuery" }] : /* istanbul ignore next */ []));
195
+ visibleResultGroups = computed(() => {
196
+ const groups = new Map();
197
+ this.results().forEach((result, index) => {
198
+ const group = groups.get(result.pageNumber) ?? {
199
+ pageNumber: result.pageNumber,
200
+ results: [],
201
+ };
202
+ group.results.push({ result, index });
203
+ groups.set(result.pageNumber, group);
204
+ });
205
+ return [...groups.values()];
206
+ }, ...(ngDevMode ? [{ debugName: "visibleResultGroups" }] : /* istanbul ignore next */ []));
207
+ resultCountLabel = computed(() => {
208
+ const count = this.results().length;
209
+ return `${count} ${count === 1 ? 'result' : 'results'} found`;
210
+ }, ...(ngDevMode ? [{ debugName: "resultCountLabel" }] : /* istanbul ignore next */ []));
211
+ constructor() {
212
+ effect(() => {
213
+ const query = this.query();
214
+ untracked(() => {
215
+ this.queryValue.set(query);
216
+ this.activeResultIndex.set(0);
217
+ });
218
+ });
219
+ }
220
+ setQuery(event) {
221
+ this.queryValue.set(event.target.value);
222
+ this.activeResultIndex.set(0);
223
+ this.emitSearchChanged();
224
+ }
225
+ clearQuery() {
226
+ this.queryValue.set('');
227
+ this.activeResultIndex.set(0);
228
+ this.emitSearchChanged();
229
+ }
230
+ setCaseSensitive(value) {
231
+ this.caseSensitive.set(value);
232
+ this.activeResultIndex.set(0);
233
+ this.emitSearchChanged();
234
+ }
235
+ setWholeWord(value) {
236
+ this.wholeWord.set(value);
237
+ this.activeResultIndex.set(0);
238
+ this.emitSearchChanged();
239
+ }
240
+ previousResult() {
241
+ const count = this.results().length;
242
+ if (count === 0) {
243
+ return;
244
+ }
245
+ this.activeResultIndex.update((index) => (index - 1 + count) % count);
246
+ }
247
+ nextResult() {
248
+ const count = this.results().length;
249
+ if (count === 0) {
250
+ return;
251
+ }
252
+ this.activeResultIndex.update((index) => (index + 1) % count);
253
+ }
254
+ selectResult(result, index) {
255
+ this.activeResultIndex.set(index);
256
+ this.resultSelected.emit(result);
257
+ }
258
+ resultParts(result) {
259
+ const query = this.queryValue().trim();
260
+ if (!query) {
261
+ return [{ text: result.excerpt, highlight: false }];
262
+ }
263
+ const flags = this.caseSensitive() ? 'g' : 'gi';
264
+ const escapedQuery = this.escapeRegExp(query);
265
+ const pattern = this.wholeWord()
266
+ ? new RegExp(`\\b${escapedQuery}\\b`, flags)
267
+ : new RegExp(escapedQuery, flags);
268
+ const parts = [];
269
+ let lastIndex = 0;
270
+ let match;
271
+ while ((match = pattern.exec(result.excerpt)) !== null) {
272
+ if (match.index > lastIndex) {
273
+ parts.push({ text: result.excerpt.slice(lastIndex, match.index), highlight: false });
274
+ }
275
+ parts.push({ text: match[0], highlight: true });
276
+ lastIndex = match.index + match[0].length;
277
+ if (match[0].length === 0) {
278
+ pattern.lastIndex++;
279
+ }
280
+ }
281
+ if (lastIndex < result.excerpt.length) {
282
+ parts.push({ text: result.excerpt.slice(lastIndex), highlight: false });
283
+ }
284
+ return parts.length > 0 ? parts : [{ text: result.excerpt, highlight: false }];
285
+ }
286
+ emitSearchChanged() {
287
+ this.searchChanged.emit({
288
+ query: this.queryValue(),
289
+ options: {
290
+ caseSensitive: this.caseSensitive(),
291
+ wholeWord: this.wholeWord(),
292
+ },
293
+ });
294
+ }
295
+ escapeRegExp(value) {
296
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
297
+ }
298
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerSearch, deps: [], target: i0.ɵɵFactoryTarget.Component });
299
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: PdfViewerSearch, isStandalone: true, selector: "ngs-pdf-viewer-search", inputs: { results: { classPropertyName: "results", publicName: "results", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", resultSelected: "resultSelected", searchChanged: "searchChanged" }, host: { classAttribute: "ngs-pdf-viewer-search" }, ngImport: i0, template: "<ngs-panel class=\"pdf-viewer-search\" aria-label=\"PDF search\">\n <ngs-panel-header class=\"ps-4 pe-3\">\n <ngs-toolbar>\n <ngs-toolbar-title>Search</ngs-toolbar-title>\n <ngs-toolbar-spacer />\n <button ngsIconButton type=\"button\" aria-label=\"Close search panel\" (click)=\"closed.emit()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n </ngs-toolbar>\n </ngs-panel-header>\n\n <ngs-panel-subheader autoHeight class=\"px-4 pb-4\">\n <ngs-form-field class=\"pdf-viewer-search__field\" subscriptHiddenIfEmpty>\n <ngs-icon name=\"fluent:search-24-regular\" ngsIconPrefix />\n <input\n ngsInput\n type=\"text\"\n aria-label=\"Search PDF\"\n placeholder=\"Search\"\n [value]=\"queryValue()\"\n (input)=\"setQuery($event)\" />\n @if (queryValue()) {\n <button ngsIconButtonSuffix ngsIconButton type=\"button\" aria-label=\"Clear search\" (click)=\"clearQuery()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n }\n </ngs-form-field>\n\n <div class=\"pdf-viewer-search__options mt-4 grid gap-3 text-base text-on-surface\">\n <ngs-checkbox [checked]=\"caseSensitive()\" (checkedChange)=\"setCaseSensitive($event)\">\n Case sensitive\n </ngs-checkbox>\n <ngs-checkbox [checked]=\"wholeWord()\" (checkedChange)=\"setWholeWord($event)\">\n Whole word\n </ngs-checkbox>\n </div>\n\n @if (hasQuery()) {\n <div class=\"pdf-viewer-search__summary mt-4 flex items-center justify-between gap-4 text-base text-on-surface-variant\">\n <span>{{ resultCountLabel() }}</span>\n <div class=\"pdf-viewer-search__nav flex items-center gap-3\">\n <button\n class=\"text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Previous search result\"\n [disabled]=\"results().length === 0\"\n (click)=\"previousResult()\">\n <ngs-icon name=\"fluent:chevron-left-24-regular\" />\n </button>\n <button\n class=\"text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Next search result\"\n [disabled]=\"results().length === 0\"\n (click)=\"nextResult()\">\n <ngs-icon name=\"fluent:chevron-right-24-regular\" />\n </button>\n </div>\n </div>\n }\n </ngs-panel-subheader>\n\n <ngs-panel-content>\n <div class=\"pdf-viewer-search__content flex min-h-full flex-col gap-4 px-4 pb-4\">\n @if (hasQuery()) {\n <div class=\"pdf-viewer-search__results grid min-h-0 gap-4\">\n @for (group of visibleResultGroups(); track group.pageNumber) {\n <section class=\"pdf-viewer-search__page-group grid gap-3 bg-surface\">\n <h3 class=\"m-0 text-base font-medium text-on-surface-variant\">Page {{ group.pageNumber }}</h3>\n @for (item of group.results; track item.result.id ?? item.result.pageNumber + '-' + item.index) {\n <button\n class=\"pdf-viewer-search__result w-full cursor-pointer rounded-2xl border border-border p-4 text-left text-base leading-snug text-on-surface\"\n type=\"button\"\n [class.bg-primary-container]=\"item.index === activeResultIndex()\"\n [class.bg-surface]=\"item.index !== activeResultIndex()\"\n (click)=\"selectResult(item.result, item.index)\">\n @for (part of resultParts(item.result); track part.text + '-' + $index) {\n <span\n [class.font-bold]=\"part.highlight\"\n [class.text-primary]=\"part.highlight\">{{ part.text }}</span>\n }\n </button>\n }\n </section>\n } @empty {\n <div class=\"pdf-viewer-search__empty p-4 text-center text-sm text-on-surface-variant\">No results.</div>\n }\n </div>\n }\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;min-height:0;height:100%}\n"], dependencies: [{ kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Checkbox, selector: "ngs-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["checkedChange", "disabledChange", "indeterminateChange", "change"], exportAs: ["ngsCheckbox"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "directive", type: IconButtonSuffix, selector: "[ngsIconButtonSuffix]" }, { kind: "directive", type: IconPrefix, selector: "[ngsIconPrefix]" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: Toolbar, selector: "ngs-toolbar", exportAs: ["ngsToolbar"] }, { kind: "component", type: ToolbarSpacer, selector: "ngs-toolbar-spacer" }, { kind: "component", type: ToolbarTitle, selector: "ngs-toolbar-title" }, { kind: "component", type: PanelSubheader, selector: "ngs-panel-subheader", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelSubheader"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
300
+ }
301
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerSearch, decorators: [{
302
+ type: Component,
303
+ args: [{ selector: 'ngs-pdf-viewer-search', standalone: true, imports: [
304
+ Button,
305
+ Checkbox,
306
+ FormField,
307
+ Icon,
308
+ IconButtonSuffix,
309
+ IconPrefix,
310
+ Input,
311
+ Panel,
312
+ PanelContent,
313
+ PanelHeader,
314
+ Toolbar,
315
+ ToolbarSpacer,
316
+ ToolbarTitle,
317
+ PanelSubheader,
318
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
319
+ class: 'ngs-pdf-viewer-search',
320
+ }, template: "<ngs-panel class=\"pdf-viewer-search\" aria-label=\"PDF search\">\n <ngs-panel-header class=\"ps-4 pe-3\">\n <ngs-toolbar>\n <ngs-toolbar-title>Search</ngs-toolbar-title>\n <ngs-toolbar-spacer />\n <button ngsIconButton type=\"button\" aria-label=\"Close search panel\" (click)=\"closed.emit()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n </ngs-toolbar>\n </ngs-panel-header>\n\n <ngs-panel-subheader autoHeight class=\"px-4 pb-4\">\n <ngs-form-field class=\"pdf-viewer-search__field\" subscriptHiddenIfEmpty>\n <ngs-icon name=\"fluent:search-24-regular\" ngsIconPrefix />\n <input\n ngsInput\n type=\"text\"\n aria-label=\"Search PDF\"\n placeholder=\"Search\"\n [value]=\"queryValue()\"\n (input)=\"setQuery($event)\" />\n @if (queryValue()) {\n <button ngsIconButtonSuffix ngsIconButton type=\"button\" aria-label=\"Clear search\" (click)=\"clearQuery()\">\n <ngs-icon name=\"fluent:dismiss-24-regular\" />\n </button>\n }\n </ngs-form-field>\n\n <div class=\"pdf-viewer-search__options mt-4 grid gap-3 text-base text-on-surface\">\n <ngs-checkbox [checked]=\"caseSensitive()\" (checkedChange)=\"setCaseSensitive($event)\">\n Case sensitive\n </ngs-checkbox>\n <ngs-checkbox [checked]=\"wholeWord()\" (checkedChange)=\"setWholeWord($event)\">\n Whole word\n </ngs-checkbox>\n </div>\n\n @if (hasQuery()) {\n <div class=\"pdf-viewer-search__summary mt-4 flex items-center justify-between gap-4 text-base text-on-surface-variant\">\n <span>{{ resultCountLabel() }}</span>\n <div class=\"pdf-viewer-search__nav flex items-center gap-3\">\n <button\n class=\"text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Previous search result\"\n [disabled]=\"results().length === 0\"\n (click)=\"previousResult()\">\n <ngs-icon name=\"fluent:chevron-left-24-regular\" />\n </button>\n <button\n class=\"text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Next search result\"\n [disabled]=\"results().length === 0\"\n (click)=\"nextResult()\">\n <ngs-icon name=\"fluent:chevron-right-24-regular\" />\n </button>\n </div>\n </div>\n }\n </ngs-panel-subheader>\n\n <ngs-panel-content>\n <div class=\"pdf-viewer-search__content flex min-h-full flex-col gap-4 px-4 pb-4\">\n @if (hasQuery()) {\n <div class=\"pdf-viewer-search__results grid min-h-0 gap-4\">\n @for (group of visibleResultGroups(); track group.pageNumber) {\n <section class=\"pdf-viewer-search__page-group grid gap-3 bg-surface\">\n <h3 class=\"m-0 text-base font-medium text-on-surface-variant\">Page {{ group.pageNumber }}</h3>\n @for (item of group.results; track item.result.id ?? item.result.pageNumber + '-' + item.index) {\n <button\n class=\"pdf-viewer-search__result w-full cursor-pointer rounded-2xl border border-border p-4 text-left text-base leading-snug text-on-surface\"\n type=\"button\"\n [class.bg-primary-container]=\"item.index === activeResultIndex()\"\n [class.bg-surface]=\"item.index !== activeResultIndex()\"\n (click)=\"selectResult(item.result, item.index)\">\n @for (part of resultParts(item.result); track part.text + '-' + $index) {\n <span\n [class.font-bold]=\"part.highlight\"\n [class.text-primary]=\"part.highlight\">{{ part.text }}</span>\n }\n </button>\n }\n </section>\n } @empty {\n <div class=\"pdf-viewer-search__empty p-4 text-center text-sm text-on-surface-variant\">No results.</div>\n }\n </div>\n }\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{display:block;min-height:0;height:100%}\n"] }]
321
+ }], ctorParameters: () => [], propDecorators: { results: [{ type: i0.Input, args: [{ isSignal: true, alias: "results", required: false }] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], resultSelected: [{ type: i0.Output, args: ["resultSelected"] }], searchChanged: [{ type: i0.Output, args: ["searchChanged"] }] } });
322
+
41
323
  class PdfViewer {
42
324
  textSelectionHorizontalPadding = 2;
43
325
  textSelectionLineHeight = 1.2;
@@ -49,16 +331,26 @@ class PdfViewer {
49
331
  isBrowser = isPlatformBrowser(this.platformId);
50
332
  viewerBody = viewChild('viewerBody', ...(ngDevMode ? [{ debugName: "viewerBody" }] : /* istanbul ignore next */ []));
51
333
  pageList = viewChild('pageList', ...(ngDevMode ? [{ debugName: "pageList" }] : /* istanbul ignore next */ []));
334
+ annotationDefs = contentChildren(PdfViewerAnnotationDef, { ...(ngDevMode ? { debugName: "annotationDefs" } : /* istanbul ignore next */ {}), descendants: true });
52
335
  src = input(null, ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
336
+ documentName = input(null, ...(ngDevMode ? [{ debugName: "documentName" }] : /* istanbul ignore next */ []));
53
337
  wasmUrl = input('/assets/embedpdf/pdfium.wasm', ...(ngDevMode ? [{ debugName: "wasmUrl" }] : /* istanbul ignore next */ []));
54
338
  page = input(1, { ...(ngDevMode ? { debugName: "page" } : /* istanbul ignore next */ {}), transform: numberAttribute });
55
339
  scale = input(1, { ...(ngDevMode ? { debugName: "scale" } : /* istanbul ignore next */ {}), transform: numberAttribute });
56
340
  minScale = input(0.2, { ...(ngDevMode ? { debugName: "minScale" } : /* istanbul ignore next */ {}), transform: numberAttribute });
57
341
  maxScale = input(60, { ...(ngDevMode ? { debugName: "maxScale" } : /* istanbul ignore next */ {}), transform: numberAttribute });
58
342
  zoomStep = input(0.1, { ...(ngDevMode ? { debugName: "zoomStep" } : /* istanbul ignore next */ {}), transform: numberAttribute });
343
+ maxRenderPixels = input(128_000_000, { ...(ngDevMode ? { debugName: "maxRenderPixels" } : /* istanbul ignore next */ {}), transform: numberAttribute });
344
+ maxRenderDimension = input(13_000, { ...(ngDevMode ? { debugName: "maxRenderDimension" } : /* istanbul ignore next */ {}), transform: numberAttribute });
59
345
  renderAll = input(true, { ...(ngDevMode ? { debugName: "renderAll" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
60
346
  showToolbar = input(true, { ...(ngDevMode ? { debugName: "showToolbar" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
61
347
  showPageList = input(true, { ...(ngDevMode ? { debugName: "showPageList" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
348
+ showSearchPanel = input(true, { ...(ngDevMode ? { debugName: "showSearchPanel" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
349
+ showAnnotationsPanel = input(false, { ...(ngDevMode ? { debugName: "showAnnotationsPanel" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
350
+ annotations = input([], ...(ngDevMode ? [{ debugName: "annotations" }] : /* istanbul ignore next */ []));
351
+ annotationsDataSource = input(null, ...(ngDevMode ? [{ debugName: "annotationsDataSource" }] : /* istanbul ignore next */ []));
352
+ annotationTypeProperty = input('type', ...(ngDevMode ? [{ debugName: "annotationTypeProperty" }] : /* istanbul ignore next */ []));
353
+ searchQuery = input('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
62
354
  withAnnotations = input(true, { ...(ngDevMode ? { debugName: "withAnnotations" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
63
355
  withForms = input(true, { ...(ngDevMode ? { debugName: "withForms" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
64
356
  loaded = output();
@@ -72,12 +364,22 @@ class PdfViewer {
72
364
  pageCount = signal(0, ...(ngDevMode ? [{ debugName: "pageCount" }] : /* istanbul ignore next */ []));
73
365
  activePage = signal(1, ...(ngDevMode ? [{ debugName: "activePage" }] : /* istanbul ignore next */ []));
74
366
  zoom = signal(1, ...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
75
- pageListVisible = signal(true, ...(ngDevMode ? [{ debugName: "pageListVisible" }] : /* istanbul ignore next */ []));
367
+ pageListVisible = signal(false, ...(ngDevMode ? [{ debugName: "pageListVisible" }] : /* istanbul ignore next */ []));
368
+ searchPanelVisible = signal(false, ...(ngDevMode ? [{ debugName: "searchPanelVisible" }] : /* istanbul ignore next */ []));
369
+ annotationsPanelVisible = signal(false, ...(ngDevMode ? [{ debugName: "annotationsPanelVisible" }] : /* istanbul ignore next */ []));
370
+ asidePanelInteractive = signal(false, ...(ngDevMode ? [{ debugName: "asidePanelInteractive" }] : /* istanbul ignore next */ []));
76
371
  spreadMode = signal('single', ...(ngDevMode ? [{ debugName: "spreadMode" }] : /* istanbul ignore next */ []));
77
- scrollLayout = signal('horizontal', ...(ngDevMode ? [{ debugName: "scrollLayout" }] : /* istanbul ignore next */ []));
372
+ scrollLayout = signal('vertical', ...(ngDevMode ? [{ debugName: "scrollLayout" }] : /* istanbul ignore next */ []));
373
+ pageRotation = signal(Rotation.Degree0, ...(ngDevMode ? [{ debugName: "pageRotation" }] : /* istanbul ignore next */ []));
374
+ zoomMode = signal('custom', ...(ngDevMode ? [{ debugName: "zoomMode" }] : /* istanbul ignore next */ []));
375
+ annotationItems = signal([], ...(ngDevMode ? [{ debugName: "annotationItems" }] : /* istanbul ignore next */ []));
376
+ activeSearchQuery = signal('', ...(ngDevMode ? [{ debugName: "activeSearchQuery" }] : /* istanbul ignore next */ []));
377
+ pdfSearchResults = signal([], ...(ngDevMode ? [{ debugName: "pdfSearchResults" }] : /* istanbul ignore next */ []));
78
378
  selectionRects = signal([], ...(ngDevMode ? [{ debugName: "selectionRects" }] : /* istanbul ignore next */ []));
79
379
  hasDocument = computed(() => this.pageCount() > 0, ...(ngDevMode ? [{ debugName: "hasDocument" }] : /* istanbul ignore next */ []));
80
- isPageListVisible = computed(() => this.showPageList() && this.hasDocument() && this.pageListVisible(), ...(ngDevMode ? [{ debugName: "isPageListVisible" }] : /* istanbul ignore next */ []));
380
+ isPageListVisible = computed(() => this.showPageList() && this.pageListVisible(), ...(ngDevMode ? [{ debugName: "isPageListVisible" }] : /* istanbul ignore next */ []));
381
+ isSearchPanelVisible = computed(() => this.showSearchPanel() && this.searchPanelVisible(), ...(ngDevMode ? [{ debugName: "isSearchPanelVisible" }] : /* istanbul ignore next */ []));
382
+ isAnnotationsPanelVisible = computed(() => this.showAnnotationsPanel() && !this.searchPanelVisible() && this.annotationsPanelVisible(), ...(ngDevMode ? [{ debugName: "isAnnotationsPanelVisible" }] : /* istanbul ignore next */ []));
81
383
  thumbnailPageMap = computed(() => new Map(this.thumbnailPages().map((thumbnail) => [thumbnail.pageNumber, thumbnail])), ...(ngDevMode ? [{ debugName: "thumbnailPageMap" }] : /* istanbul ignore next */ []));
82
384
  pageItems = computed(() => Array.from({ length: this.pageCount() }, (_, index) => {
83
385
  const pageNumber = index + 1;
@@ -91,6 +393,9 @@ class PdfViewer {
91
393
  canZoomOut = computed(() => this.zoom() > this.getScaleBounds().min, ...(ngDevMode ? [{ debugName: "canZoomOut" }] : /* istanbul ignore next */ []));
92
394
  canZoomIn = computed(() => this.zoom() < this.getScaleBounds().max, ...(ngDevMode ? [{ debugName: "canZoomIn" }] : /* istanbul ignore next */ []));
93
395
  zoomLabel = computed(() => `${Math.round(this.zoom() * 100)}%`, ...(ngDevMode ? [{ debugName: "zoomLabel" }] : /* istanbul ignore next */ []));
396
+ displayDocumentName = computed(() => this.documentName() || this.getSourceName(this.src()), ...(ngDevMode ? [{ debugName: "displayDocumentName" }] : /* istanbul ignore next */ []));
397
+ pageSpreads = computed(() => this.groupPagesIntoSpreads(this.renderedPages(), this.spreadMode()), ...(ngDevMode ? [{ debugName: "pageSpreads" }] : /* istanbul ignore next */ []));
398
+ zoomPresets = [0.25, 0.5, 1, 1.25, 1.5, 2, 4, 8, 16];
94
399
  engine = null;
95
400
  pdfDocument = null;
96
401
  registry = null;
@@ -98,21 +403,36 @@ class PdfViewer {
98
403
  visiblePageRatios = new Map();
99
404
  loadToken = 0;
100
405
  renderToken = 0;
406
+ searchToken = 0;
101
407
  scrollSyncFrame = null;
102
408
  programmaticScrollTargetPage = null;
103
409
  programmaticScrollTimeout = null;
104
410
  pageObserverFrame = null;
105
411
  pageObserverTimeout = null;
106
412
  visiblePageRenderFrame = null;
413
+ visiblePageRenderTimeout = null;
107
414
  pageObserverRefreshAttempts = 0;
108
415
  selectionStart = null;
109
416
  isViewInitialized = false;
417
+ annotationDataSourceToken = 0;
418
+ annotationDataSourceCleanup = null;
419
+ lastZoomChangeTime = 0;
110
420
  programmaticScrollMinDuration = 900;
111
421
  programmaticScrollMaxDuration = 6000;
422
+ qualityRenderZoomIdleDelay = 160;
423
+ documentOpenTimeoutMs = 15000;
112
424
  constructor() {
425
+ afterNextRender(() => {
426
+ if (this.isBrowser) {
427
+ this.asidePanelInteractive.set(true);
428
+ }
429
+ });
113
430
  this.destroyRef.onDestroy(() => {
114
431
  this.loadToken++;
115
432
  this.renderToken++;
433
+ this.searchToken++;
434
+ this.annotationDataSourceToken++;
435
+ this.annotationDataSourceCleanup?.();
116
436
  this.cancelScrollSyncFrame();
117
437
  this.clearProgrammaticScrollLock();
118
438
  this.cancelPageObserverFrame();
@@ -123,6 +443,25 @@ class PdfViewer {
123
443
  void this.closeDocument();
124
444
  void this.registry?.destroy();
125
445
  });
446
+ effect((onCleanup) => {
447
+ const dataSource = this.annotationsDataSource();
448
+ const fallbackAnnotations = this.annotations();
449
+ const source = this.src();
450
+ const documentName = this.documentName() || this.getSourceName(source);
451
+ const pageCount = this.pageCount();
452
+ const token = ++this.annotationDataSourceToken;
453
+ const context = {
454
+ source,
455
+ documentName,
456
+ pageCount,
457
+ };
458
+ this.annotationDataSourceCleanup?.();
459
+ this.annotationDataSourceCleanup = untracked(() => this.loadAnnotationsDataSource(dataSource ?? fallbackAnnotations, context, token));
460
+ onCleanup(() => {
461
+ this.annotationDataSourceCleanup?.();
462
+ this.annotationDataSourceCleanup = null;
463
+ });
464
+ });
126
465
  effect(() => {
127
466
  const source = this.src();
128
467
  const wasmUrl = this.wasmUrl();
@@ -145,17 +484,33 @@ class PdfViewer {
145
484
  this.activePage.set(requestedPage);
146
485
  }
147
486
  });
487
+ effect(() => {
488
+ const query = this.searchQuery();
489
+ if (query !== untracked(() => this.activeSearchQuery())) {
490
+ this.activeSearchQuery.set(query);
491
+ untracked(() => {
492
+ void this.searchPdf(query, {
493
+ caseSensitive: false,
494
+ wholeWord: false,
495
+ });
496
+ });
497
+ }
498
+ });
148
499
  effect(() => {
149
500
  const zoom = this.zoom();
150
501
  const renderAll = this.renderAll();
151
502
  const activePage = renderAll ? untracked(() => this.activePage()) : this.activePage();
152
503
  const withAnnotations = this.withAnnotations();
153
504
  const withForms = this.withForms();
505
+ this.pageRotation();
506
+ this.spreadMode();
507
+ this.scrollLayout();
154
508
  if (!this.pdfDocument || !this.engine || this.isLoading()) {
155
509
  return;
156
510
  }
157
511
  untracked(() => {
158
512
  this.applyInstantZoom(zoom);
513
+ this.schedulePageObserverRefresh();
159
514
  this.scheduleVisiblePagesRender({ zoom, activePage, renderAll, withAnnotations, withForms });
160
515
  });
161
516
  });
@@ -163,6 +518,7 @@ class PdfViewer {
163
518
  const withAnnotations = this.withAnnotations();
164
519
  const isLoading = this.isLoading();
165
520
  const pageCount = this.pageCount();
521
+ this.pageRotation();
166
522
  if (isLoading || pageCount === 0 || !this.pdfDocument || !this.engine) {
167
523
  return;
168
524
  }
@@ -190,25 +546,134 @@ class PdfViewer {
190
546
  this.setPage(this.activePage() + 1);
191
547
  }
192
548
  zoomIn() {
549
+ this.zoomMode.set('custom');
193
550
  this.setZoom(this.roundZoom(this.zoom() + this.sanitizeZoomStep()));
194
551
  }
195
552
  zoomOut() {
553
+ this.zoomMode.set('custom');
196
554
  this.setZoom(this.roundZoom(this.zoom() - this.sanitizeZoomStep()));
197
555
  }
198
556
  togglePageList() {
199
557
  this.pageListVisible.update((isVisible) => !isVisible);
200
558
  }
559
+ toggleSearchPanel() {
560
+ const nextVisible = !this.searchPanelVisible();
561
+ this.searchPanelVisible.set(nextVisible);
562
+ if (nextVisible) {
563
+ this.annotationsPanelVisible.set(false);
564
+ const query = this.searchQuery();
565
+ this.activeSearchQuery.set(query);
566
+ void this.searchPdf(query, {
567
+ caseSensitive: false,
568
+ wholeWord: false,
569
+ });
570
+ }
571
+ }
572
+ toggleAnnotationsPanel() {
573
+ const nextVisible = !this.annotationsPanelVisible();
574
+ this.annotationsPanelVisible.set(nextVisible);
575
+ if (nextVisible) {
576
+ this.searchPanelVisible.set(false);
577
+ }
578
+ }
579
+ closeAsidePanel() {
580
+ this.searchPanelVisible.set(false);
581
+ this.annotationsPanelVisible.set(false);
582
+ }
583
+ updatePdfSearch(event) {
584
+ this.activeSearchQuery.set(event.query);
585
+ return this.searchPdf(event.query, event.options);
586
+ }
587
+ selectSearchResult(result) {
588
+ this.setPage(result.pageNumber);
589
+ }
590
+ setZoomPreset(scale) {
591
+ this.zoomMode.set('custom');
592
+ this.setZoom(scale);
593
+ }
594
+ isZoomPresetSelected(scale) {
595
+ return this.zoomMode() === 'custom' && Math.abs(this.zoom() - this.sanitizeScale(scale)) < 0.0001;
596
+ }
597
+ fitToPage() {
598
+ const scale = this.getFitScale('fit-page');
599
+ if (scale === null) {
600
+ return;
601
+ }
602
+ this.zoomMode.set('fit-page');
603
+ this.setZoom(scale);
604
+ }
605
+ fitToWidth() {
606
+ const scale = this.getFitScale('fit-width');
607
+ if (scale === null) {
608
+ return;
609
+ }
610
+ this.zoomMode.set('fit-width');
611
+ this.setZoom(scale);
612
+ }
201
613
  setSpreadMode(mode) {
202
614
  this.spreadMode.set(mode);
615
+ this.refreshLayoutAfterModeChange();
203
616
  }
204
617
  setScrollLayout(layout) {
205
618
  this.scrollLayout.set(layout);
619
+ this.refreshLayoutAfterModeChange();
206
620
  }
207
621
  rotateClockwise() {
208
- this.renderedPages.update((pages) => [...pages]);
622
+ this.setPageRotation(this.rotatePageBy(1));
209
623
  }
210
624
  rotateCounterClockwise() {
211
- this.renderedPages.update((pages) => [...pages]);
625
+ this.setPageRotation(this.rotatePageBy(-1));
626
+ }
627
+ groupPagesIntoSpreads(pages, mode) {
628
+ if (mode === 'single') {
629
+ return pages.map((page) => ({
630
+ id: `single-${page.pageNumber}`,
631
+ leadingPlaceholder: false,
632
+ leadingPlaceholderPage: null,
633
+ pages: [page],
634
+ }));
635
+ }
636
+ const spreads = [];
637
+ let pageIndex = 0;
638
+ if (mode === 'two-even' && pages.length > 0) {
639
+ spreads.push({
640
+ id: 'two-even-cover',
641
+ leadingPlaceholder: true,
642
+ leadingPlaceholderPage: pages[0],
643
+ pages: [pages[0]],
644
+ });
645
+ pageIndex = 1;
646
+ }
647
+ while (pageIndex < pages.length) {
648
+ const spreadPages = pages.slice(pageIndex, pageIndex + 2);
649
+ spreads.push({
650
+ id: `${mode}-${spreadPages.map((page) => page.pageNumber).join('-')}`,
651
+ leadingPlaceholder: false,
652
+ leadingPlaceholderPage: null,
653
+ pages: spreadPages,
654
+ });
655
+ pageIndex += 2;
656
+ }
657
+ return spreads;
658
+ }
659
+ setPageRotation(rotation) {
660
+ this.pageRotation.set(rotation);
661
+ this.applyInstantZoom(this.zoom());
662
+ this.refreshLayoutAfterModeChange();
663
+ }
664
+ rotatePageBy(delta) {
665
+ return this.normalizeRotation(this.pageRotation() + delta);
666
+ }
667
+ refreshLayoutAfterModeChange() {
668
+ this.selectionStart = null;
669
+ this.selectionRects.set([]);
670
+ this.schedulePageObserverRefresh();
671
+ const targetWindow = this.document.defaultView;
672
+ if (!targetWindow) {
673
+ this.scrollToPage(this.activePage());
674
+ return;
675
+ }
676
+ targetWindow.requestAnimationFrame(() => this.scrollToPage(this.activePage()));
212
677
  }
213
678
  toggleFullscreen() {
214
679
  if (!this.isBrowser) {
@@ -252,12 +717,31 @@ class PdfViewer {
252
717
  this.scheduleCurrentVisiblePagesRender();
253
718
  }) ?? null;
254
719
  }
720
+ onViewerWheel(event) {
721
+ if (!event.metaKey && !event.ctrlKey) {
722
+ return;
723
+ }
724
+ if (!this.pdfDocument || this.isLoading()) {
725
+ return;
726
+ }
727
+ event.preventDefault();
728
+ event.stopPropagation();
729
+ const container = this.viewerBody()?.nativeElement;
730
+ const anchor = container ? this.getZoomAnchor(container, event) : null;
731
+ const delta = event.deltaY || event.deltaX;
732
+ const direction = delta < 0 ? 1 : -1;
733
+ const multiplier = Math.max(1, Math.min(6, Math.abs(delta) / 100));
734
+ const nextZoom = this.roundZoom(this.zoom() + direction * this.sanitizeZoomStep() * multiplier);
735
+ if (nextZoom === this.zoom()) {
736
+ return;
737
+ }
738
+ this.zoomMode.set('custom');
739
+ this.setZoom(nextZoom);
740
+ this.restoreZoomAnchor(anchor);
741
+ }
255
742
  selectionRectsForPage(pageNumber) {
256
743
  return this.selectionRects().filter((rect) => rect.pageNumber === pageNumber);
257
744
  }
258
- isPageImageFresh(page) {
259
- return !!page.url && Math.abs((page.renderedScale ?? 0) - page.scale) < 0.0001;
260
- }
261
745
  startTextSelection(event, page) {
262
746
  if (event.button !== 0 || page.textGlyphs.length === 0) {
263
747
  return;
@@ -295,17 +779,148 @@ class PdfViewer {
295
779
  surface.releasePointerCapture?.(event.pointerId);
296
780
  }
297
781
  }
782
+ loadAnnotationsDataSource(dataSource, context, token) {
783
+ if (!dataSource) {
784
+ this.setAnnotationItems([], token);
785
+ return () => { };
786
+ }
787
+ try {
788
+ if (Array.isArray(dataSource)) {
789
+ this.setAnnotationItems(dataSource, token);
790
+ return () => { };
791
+ }
792
+ if (this.isServerAnnotationDataSource(dataSource)) {
793
+ let isActive = true;
794
+ dataSource.getAnnotations({
795
+ ...context,
796
+ successCallback: (annotations) => {
797
+ if (isActive) {
798
+ this.setAnnotationItems(annotations, token);
799
+ }
800
+ },
801
+ failCallback: () => {
802
+ if (isActive) {
803
+ this.setAnnotationItems([], token);
804
+ }
805
+ },
806
+ });
807
+ return () => {
808
+ isActive = false;
809
+ };
810
+ }
811
+ const result = typeof dataSource === 'function' ? dataSource(context) : dataSource;
812
+ return this.applyAnnotationDataSourceResult(result, token);
813
+ }
814
+ catch {
815
+ this.setAnnotationItems([], token);
816
+ return () => { };
817
+ }
818
+ }
819
+ applyAnnotationDataSourceResult(result, token) {
820
+ if (isObservable(result)) {
821
+ const subscription = result.subscribe({
822
+ next: (annotations) => this.setAnnotationItems(annotations, token),
823
+ error: () => this.setAnnotationItems([], token),
824
+ });
825
+ return () => subscription.unsubscribe();
826
+ }
827
+ if (this.isPromiseLike(result)) {
828
+ let isActive = true;
829
+ result
830
+ .then((annotations) => {
831
+ if (isActive) {
832
+ this.setAnnotationItems(annotations, token);
833
+ }
834
+ })
835
+ .catch(() => {
836
+ if (isActive) {
837
+ this.setAnnotationItems([], token);
838
+ }
839
+ });
840
+ return () => {
841
+ isActive = false;
842
+ };
843
+ }
844
+ this.setAnnotationItems(result, token);
845
+ return () => { };
846
+ }
847
+ setAnnotationItems(annotations, token) {
848
+ if (token === this.annotationDataSourceToken) {
849
+ this.annotationItems.set(annotations ?? []);
850
+ }
851
+ }
852
+ isServerAnnotationDataSource(dataSource) {
853
+ return typeof dataSource === 'object'
854
+ && dataSource !== null
855
+ && 'getAnnotations' in dataSource
856
+ && typeof dataSource.getAnnotations === 'function';
857
+ }
858
+ isPromiseLike(value) {
859
+ return typeof value === 'object'
860
+ && value !== null
861
+ && 'then' in value
862
+ && typeof value.then === 'function';
863
+ }
298
864
  cancelTextSelection() {
299
865
  this.selectionStart = null;
300
866
  }
867
+ async searchPdf(query, options) {
868
+ const token = ++this.searchToken;
869
+ const keyword = query.trim();
870
+ if (!keyword || !this.engine || !this.pdfDocument) {
871
+ this.pdfSearchResults.set([]);
872
+ return;
873
+ }
874
+ const flags = [];
875
+ if (options.caseSensitive) {
876
+ flags.push(MatchFlag.MatchCase);
877
+ }
878
+ if (options.wholeWord) {
879
+ flags.push(MatchFlag.MatchWholeWord);
880
+ }
881
+ try {
882
+ const searchResult = await this.engine.searchAllPages(this.pdfDocument, keyword, { flags }).toPromise();
883
+ if (token !== this.searchToken) {
884
+ return;
885
+ }
886
+ this.pdfSearchResults.set(searchResult.results.map((result, index) => this.toSearchResultView(result, index)));
887
+ }
888
+ catch (error) {
889
+ if (token === this.searchToken) {
890
+ this.pdfSearchResults.set([]);
891
+ this.error.emit(error);
892
+ }
893
+ }
894
+ }
895
+ toSearchResultView(result, index) {
896
+ const context = result.context;
897
+ const excerpt = [
898
+ context.truncatedLeft ? '...' : '',
899
+ context.before,
900
+ context.match,
901
+ context.after,
902
+ context.truncatedRight ? '...' : '',
903
+ ]
904
+ .filter((part) => part.length > 0)
905
+ .join(' ')
906
+ .replace(/\s+/g, ' ')
907
+ .trim();
908
+ return {
909
+ id: `${result.pageIndex}-${result.charIndex}-${result.charCount}-${index}`,
910
+ pageNumber: result.pageIndex + 1,
911
+ excerpt,
912
+ };
913
+ }
301
914
  async loadDocument(source, wasmUrl) {
302
915
  const token = ++this.loadToken;
303
916
  this.renderToken++;
304
917
  this.isLoading.set(true);
305
918
  this.errorState.set(null);
306
919
  this.pageCount.set(0);
920
+ this.searchToken++;
307
921
  this.selectionStart = null;
308
922
  this.selectionRects.set([]);
923
+ this.pdfSearchResults.set([]);
309
924
  this.revokeRenderedPages();
310
925
  this.revokeThumbnailPages();
311
926
  await this.closeDocument();
@@ -337,6 +952,10 @@ class PdfViewer {
337
952
  this.loaded.emit({ pageCount: pdfDocument.pageCount });
338
953
  this.initializePageShells(pdfDocument, this.zoom(), this.renderAll(), this.activePage());
339
954
  this.schedulePageObserverRefresh();
955
+ await this.searchPdf(this.activeSearchQuery() || this.searchQuery(), {
956
+ caseSensitive: false,
957
+ wholeWord: false,
958
+ });
340
959
  await this.renderVisiblePages(token, ++this.renderToken, {
341
960
  zoom: this.zoom(),
342
961
  activePage: this.activePage(),
@@ -366,12 +985,17 @@ class PdfViewer {
366
985
  const pdfPage = pdfDocument.pages[pageNumber - 1];
367
986
  const previousPage = previousPageMap.get(pageNumber);
368
987
  const displaySize = this.getPageDisplaySize(pdfPage, nextScale);
369
- const isFresh = previousPage?.url && Math.abs((previousPage.renderedScale ?? 0) - nextScale) < 0.0001;
988
+ const displayRotation = this.getPageDisplayRotation(pdfPage);
989
+ const isFresh = previousPage?.url
990
+ && Math.abs((previousPage.renderedScale ?? 0) - nextScale) < 0.0001
991
+ && previousPage.renderedRotation === displayRotation;
370
992
  return {
371
993
  pageNumber,
372
994
  url: isFresh ? previousPage.url : null,
373
995
  scale: nextScale,
374
996
  renderedScale: isFresh ? previousPage.renderedScale : null,
997
+ rotation: displayRotation,
998
+ renderedRotation: isFresh ? previousPage.renderedRotation : null,
375
999
  width: displaySize.width,
376
1000
  height: displaySize.height,
377
1001
  isRendering: false,
@@ -403,6 +1027,21 @@ class PdfViewer {
403
1027
  this.cancelVisiblePageRenderFrame();
404
1028
  const token = this.loadToken;
405
1029
  const renderToken = ++this.renderToken;
1030
+ const renderDelay = this.getQualityRenderDelay();
1031
+ if (renderDelay > 0) {
1032
+ this.visiblePageRenderTimeout = targetWindow.setTimeout(() => {
1033
+ this.visiblePageRenderTimeout = null;
1034
+ this.queueVisiblePageRenderFrame(token, renderToken, options);
1035
+ }, renderDelay);
1036
+ return;
1037
+ }
1038
+ this.queueVisiblePageRenderFrame(token, renderToken, options);
1039
+ }
1040
+ queueVisiblePageRenderFrame(token, renderToken, options) {
1041
+ const targetWindow = this.document.defaultView;
1042
+ if (!targetWindow) {
1043
+ return;
1044
+ }
406
1045
  this.visiblePageRenderFrame = targetWindow.requestAnimationFrame(() => {
407
1046
  this.visiblePageRenderFrame = null;
408
1047
  void this.renderVisiblePages(token, renderToken, options);
@@ -429,17 +1068,22 @@ class PdfViewer {
429
1068
  return;
430
1069
  }
431
1070
  const currentPage = this.renderedPages().find((page) => page.pageNumber === pageNumber);
1071
+ const page = pdfDocument.pages[pageNumber - 1];
1072
+ const renderRotation = this.getPageDisplayRotation(page);
432
1073
  if (!currentPage || Math.abs(currentPage.scale - renderScale) > 0.0001) {
433
1074
  continue;
434
1075
  }
435
- if (currentPage.url && Math.abs((currentPage.renderedScale ?? 0) - renderScale) < 0.0001) {
1076
+ if (currentPage.url
1077
+ && Math.abs((currentPage.renderedScale ?? 0) - renderScale) < 0.0001
1078
+ && currentPage.renderedRotation === renderRotation) {
436
1079
  continue;
437
1080
  }
438
- this.patchRenderedPage(pageNumber, { isRendering: true });
439
- const page = pdfDocument.pages[pageNumber - 1];
1081
+ this.patchRenderedPage(pageNumber, { isRendering: !currentPage.url });
1082
+ const rasterOptions = this.getPageRasterRenderOptions(page, renderScale);
440
1083
  const blob = await this.engine.renderPage(pdfDocument, page, {
441
- scaleFactor: renderScale,
442
- dpr: this.getDevicePixelRatio(),
1084
+ scaleFactor: rasterOptions.scaleFactor,
1085
+ rotation: renderRotation,
1086
+ dpr: rasterOptions.dpr,
443
1087
  withAnnotations: options.withAnnotations,
444
1088
  withForms: options.withForms,
445
1089
  }).toPromise();
@@ -447,7 +1091,9 @@ class PdfViewer {
447
1091
  return;
448
1092
  }
449
1093
  const displaySize = this.getPageDisplaySize(page, renderScale);
450
- const textGlyphs = await this.getPageTextGlyphs(pdfDocument, page, renderScale);
1094
+ const textGlyphs = this.pageRotation() === Rotation.Degree0
1095
+ ? await this.getPageTextGlyphs(pdfDocument, page, renderScale)
1096
+ : [];
451
1097
  if (!this.isRenderCurrent(token, renderToken)) {
452
1098
  return;
453
1099
  }
@@ -457,6 +1103,8 @@ class PdfViewer {
457
1103
  url,
458
1104
  scale: renderScale,
459
1105
  renderedScale: renderScale,
1106
+ rotation: renderRotation,
1107
+ renderedRotation: renderRotation,
460
1108
  width: displaySize.width,
461
1109
  height: displaySize.height,
462
1110
  isRendering: false,
@@ -471,6 +1119,7 @@ class PdfViewer {
471
1119
  setZoom(scale) {
472
1120
  const nextZoom = this.sanitizeScale(scale);
473
1121
  if (this.zoom() !== nextZoom) {
1122
+ this.lastZoomChangeTime = this.getCurrentTime();
474
1123
  this.zoom.set(nextZoom);
475
1124
  }
476
1125
  this.applyInstantZoom(nextZoom);
@@ -483,18 +1132,27 @@ class PdfViewer {
483
1132
  return;
484
1133
  }
485
1134
  let hasChanges = false;
1135
+ const urlsToRevoke = [];
486
1136
  const nextPages = pages.map((page) => {
487
- if (Math.abs(page.scale - nextScale) < 0.0001) {
1137
+ const pdfPage = pdfDocument.pages[page.pageNumber - 1];
1138
+ const displaySize = this.getPageDisplaySize(pdfPage, nextScale);
1139
+ const displayRotation = this.getPageDisplayRotation(pdfPage);
1140
+ const scaleChanged = Math.abs(page.scale - nextScale) >= 0.0001;
1141
+ const rotationChanged = page.rotation !== displayRotation || page.renderedRotation !== displayRotation;
1142
+ if (!scaleChanged && !rotationChanged && page.width === displaySize.width && page.height === displaySize.height) {
488
1143
  return page;
489
1144
  }
490
1145
  hasChanges = true;
491
- const pdfPage = pdfDocument.pages[page.pageNumber - 1];
492
- const displaySize = this.getPageDisplaySize(pdfPage, nextScale);
1146
+ if (rotationChanged && page.url) {
1147
+ urlsToRevoke.push(page.url);
1148
+ }
493
1149
  return {
494
1150
  ...page,
495
- url: null,
1151
+ url: rotationChanged ? null : page.url,
496
1152
  scale: nextScale,
497
- renderedScale: null,
1153
+ renderedScale: rotationChanged ? null : page.renderedScale,
1154
+ rotation: displayRotation,
1155
+ renderedRotation: rotationChanged ? null : page.renderedRotation,
498
1156
  width: displaySize.width,
499
1157
  height: displaySize.height,
500
1158
  isRendering: false,
@@ -504,14 +1162,12 @@ class PdfViewer {
504
1162
  if (!hasChanges) {
505
1163
  return;
506
1164
  }
507
- for (const page of pages) {
508
- if (page.url) {
509
- this.revokeObjectUrl(page.url);
510
- }
511
- }
512
1165
  this.selectionStart = null;
513
1166
  this.selectionRects.set([]);
514
1167
  this.renderedPages.set(nextPages);
1168
+ for (const url of urlsToRevoke) {
1169
+ this.revokeObjectUrl(url);
1170
+ }
515
1171
  this.schedulePageObserverRefresh();
516
1172
  }
517
1173
  getPriorityPageNumbers(activePage, renderAll) {
@@ -542,15 +1198,18 @@ class PdfViewer {
542
1198
  return [];
543
1199
  }
544
1200
  const containerRect = container.getBoundingClientRect();
545
- const prefetchMargin = containerRect.height;
1201
+ const verticalPrefetchMargin = containerRect.height;
1202
+ const horizontalPrefetchMargin = containerRect.width;
546
1203
  const pages = Array.from(container.querySelectorAll('[data-ngs-pdf-page]'));
547
1204
  const visiblePageNumbers = [];
548
1205
  for (const page of pages) {
549
1206
  const rect = page.getBoundingClientRect();
550
1207
  const pageNumber = Number(page.dataset['ngsPdfPage']);
551
1208
  if (Number.isFinite(pageNumber) &&
552
- rect.bottom >= containerRect.top - prefetchMargin &&
553
- rect.top <= containerRect.bottom + prefetchMargin) {
1209
+ rect.bottom >= containerRect.top - verticalPrefetchMargin &&
1210
+ rect.top <= containerRect.bottom + verticalPrefetchMargin &&
1211
+ rect.right >= containerRect.left - horizontalPrefetchMargin &&
1212
+ rect.left <= containerRect.right + horizontalPrefetchMargin) {
554
1213
  visiblePageNumbers.push(pageNumber);
555
1214
  }
556
1215
  }
@@ -570,6 +1229,7 @@ class PdfViewer {
570
1229
  for (const page of pdfDocument.pages) {
571
1230
  const blob = await this.engine.renderThumbnail(pdfDocument, page, {
572
1231
  scaleFactor: thumbnailScale,
1232
+ rotation: this.getPageDisplayRotation(page),
573
1233
  dpr: this.getDevicePixelRatio(),
574
1234
  withAnnotations: options.withAnnotations,
575
1235
  }).toPromise();
@@ -756,16 +1416,39 @@ class PdfViewer {
756
1416
  }
757
1417
  async openSource(engine, documentId, source) {
758
1418
  if (typeof source === 'string') {
759
- return engine.openDocumentUrl({ id: documentId, url: source }).toPromise();
1419
+ const content = await this.fetchPdfSource(source);
1420
+ return this.openDocumentBuffer(engine, documentId, content);
760
1421
  }
761
1422
  if (source instanceof Blob) {
762
- return engine.openDocumentBuffer({ id: documentId, content: await source.arrayBuffer() }).toPromise();
1423
+ return this.openDocumentBuffer(engine, documentId, await source.arrayBuffer());
763
1424
  }
764
1425
  if (source instanceof Uint8Array) {
765
1426
  const content = new Uint8Array(source).buffer;
766
- return engine.openDocumentBuffer({ id: documentId, content }).toPromise();
1427
+ return this.openDocumentBuffer(engine, documentId, content);
767
1428
  }
768
- return engine.openDocumentBuffer({ id: documentId, content: source }).toPromise();
1429
+ return this.openDocumentBuffer(engine, documentId, source);
1430
+ }
1431
+ async fetchPdfSource(source) {
1432
+ const response = await fetch(source);
1433
+ if (!response.ok) {
1434
+ throw new Error(`PDF request failed with status ${response.status}`);
1435
+ }
1436
+ return response.arrayBuffer();
1437
+ }
1438
+ openDocumentBuffer(engine, documentId, content) {
1439
+ return this.withTimeout(engine.openDocumentBuffer({ id: documentId, content }).toPromise(), this.documentOpenTimeoutMs, 'PDF document opening timed out');
1440
+ }
1441
+ withTimeout(promise, timeoutMs, message) {
1442
+ return new Promise((resolve, reject) => {
1443
+ const timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
1444
+ promise.then((value) => {
1445
+ clearTimeout(timeoutId);
1446
+ resolve(value);
1447
+ }, (error) => {
1448
+ clearTimeout(timeoutId);
1449
+ reject(error);
1450
+ });
1451
+ });
769
1452
  }
770
1453
  async closeDocument() {
771
1454
  if (!this.engine || !this.pdfDocument) {
@@ -805,6 +1488,23 @@ class PdfViewer {
805
1488
  }
806
1489
  URL.revokeObjectURL(url);
807
1490
  }
1491
+ getSourceName(source) {
1492
+ if (typeof File !== 'undefined' && source instanceof File && source.name) {
1493
+ return source.name;
1494
+ }
1495
+ if (typeof source !== 'string' || source.trim().length === 0) {
1496
+ return 'Document.pdf';
1497
+ }
1498
+ try {
1499
+ const url = new URL(source, this.document.baseURI);
1500
+ const pathName = url.pathname.split('/').filter(Boolean).pop();
1501
+ return pathName ? decodeURIComponent(pathName) : 'Document.pdf';
1502
+ }
1503
+ catch {
1504
+ const pathName = source.split('?')[0]?.split('#')[0]?.split('/').filter(Boolean).pop();
1505
+ return pathName ? decodeURIComponent(pathName) : 'Document.pdf';
1506
+ }
1507
+ }
808
1508
  sanitizePage(pageNumber) {
809
1509
  return this.clamp(Math.trunc(Number.isFinite(pageNumber) ? pageNumber : 1), 1, Math.max(this.pageCount(), 1));
810
1510
  }
@@ -815,15 +1515,43 @@ class PdfViewer {
815
1515
  roundZoom(value) {
816
1516
  return this.sanitizeScale(Math.round(this.sanitizeScale(value) * 100) / 100);
817
1517
  }
1518
+ floorFitZoom(value) {
1519
+ return this.sanitizeScale(Math.floor(this.sanitizeScale(value) * 100) / 100);
1520
+ }
818
1521
  clamp(value, min, max) {
819
1522
  return Math.min(Math.max(value, min), max);
820
1523
  }
821
1524
  roundCssPixel(value) {
822
1525
  return Math.round(value * 100) / 100;
823
1526
  }
1527
+ parseCssPixel(value) {
1528
+ const parsed = Number.parseFloat(value);
1529
+ return Number.isFinite(parsed) ? parsed : 0;
1530
+ }
824
1531
  getDevicePixelRatio() {
825
1532
  return this.document.defaultView?.devicePixelRatio || 1;
826
1533
  }
1534
+ getPageRasterRenderOptions(page, scale) {
1535
+ const devicePixelRatio = Math.max(1, this.getDevicePixelRatio());
1536
+ const pageSize = this.getPageBaseSize(page);
1537
+ const maxRenderPixels = this.sanitizePositiveNumber(this.maxRenderPixels(), 128_000_000);
1538
+ const maxRenderDimension = this.sanitizePositiveNumber(this.maxRenderDimension(), 13_000);
1539
+ const targetEffectiveScale = scale * devicePixelRatio;
1540
+ const dimensionEffectiveScale = maxRenderDimension / Math.max(pageSize.width, pageSize.height);
1541
+ const pixelEffectiveScale = Math.sqrt(maxRenderPixels / (pageSize.width * pageSize.height));
1542
+ const effectiveScale = this.clamp(Math.min(targetEffectiveScale, dimensionEffectiveScale, pixelEffectiveScale), 0.05, targetEffectiveScale);
1543
+ const dprAtLayoutScale = effectiveScale / scale;
1544
+ if (dprAtLayoutScale >= 1) {
1545
+ return {
1546
+ scaleFactor: scale,
1547
+ dpr: this.clamp(dprAtLayoutScale, 1, devicePixelRatio),
1548
+ };
1549
+ }
1550
+ return {
1551
+ scaleFactor: effectiveScale,
1552
+ dpr: 1,
1553
+ };
1554
+ }
827
1555
  createDocumentId() {
828
1556
  return `ngs-pdf-${Date.now()}-${Math.random().toString(36).slice(2)}`;
829
1557
  }
@@ -849,12 +1577,82 @@ class PdfViewer {
849
1577
  this.pageObserverFrame = null;
850
1578
  }
851
1579
  cancelVisiblePageRenderFrame() {
1580
+ if (this.visiblePageRenderTimeout !== null) {
1581
+ this.document.defaultView?.clearTimeout(this.visiblePageRenderTimeout);
1582
+ this.visiblePageRenderTimeout = null;
1583
+ }
852
1584
  if (this.visiblePageRenderFrame === null) {
853
1585
  return;
854
1586
  }
855
1587
  this.document.defaultView?.cancelAnimationFrame(this.visiblePageRenderFrame);
856
1588
  this.visiblePageRenderFrame = null;
857
1589
  }
1590
+ getCurrentTime() {
1591
+ return this.document.defaultView?.performance?.now() ?? Date.now();
1592
+ }
1593
+ getQualityRenderDelay() {
1594
+ const elapsed = this.getCurrentTime() - this.lastZoomChangeTime;
1595
+ if (elapsed >= this.qualityRenderZoomIdleDelay) {
1596
+ return 0;
1597
+ }
1598
+ return Math.max(0, this.qualityRenderZoomIdleDelay - elapsed);
1599
+ }
1600
+ getZoomAnchor(container, event) {
1601
+ if (!Number.isFinite(event.clientX) || !Number.isFinite(event.clientY)) {
1602
+ return null;
1603
+ }
1604
+ const targetDocument = container.ownerDocument;
1605
+ const targetElement = targetDocument.elementFromPoint(event.clientX, event.clientY);
1606
+ const pageElement = targetElement?.closest('[data-ngs-pdf-page]');
1607
+ if (!pageElement || !container.contains(pageElement)) {
1608
+ return null;
1609
+ }
1610
+ const containerRect = container.getBoundingClientRect();
1611
+ const pageRect = pageElement.getBoundingClientRect();
1612
+ const pageNumber = Number(pageElement.dataset['ngsPdfPage']);
1613
+ if (!Number.isFinite(pageNumber) || pageRect.width <= 0 || pageRect.height <= 0) {
1614
+ return null;
1615
+ }
1616
+ return {
1617
+ container,
1618
+ pageNumber,
1619
+ relativeX: this.clamp((event.clientX - pageRect.left) / pageRect.width, 0, 1),
1620
+ relativeY: this.clamp((event.clientY - pageRect.top) / pageRect.height, 0, 1),
1621
+ viewportX: event.clientX - containerRect.left,
1622
+ viewportY: event.clientY - containerRect.top,
1623
+ };
1624
+ }
1625
+ restoreZoomAnchor(anchor) {
1626
+ if (!anchor) {
1627
+ return;
1628
+ }
1629
+ const targetWindow = this.document.defaultView;
1630
+ const restore = () => {
1631
+ const pageElement = anchor.container.querySelector(`[data-ngs-pdf-page="${anchor.pageNumber}"]`);
1632
+ if (!pageElement) {
1633
+ return;
1634
+ }
1635
+ const containerRect = anchor.container.getBoundingClientRect();
1636
+ const pageRect = pageElement.getBoundingClientRect();
1637
+ const targetLeft = anchor.container.scrollLeft +
1638
+ pageRect.left -
1639
+ containerRect.left +
1640
+ pageRect.width * anchor.relativeX -
1641
+ anchor.viewportX;
1642
+ const targetTop = anchor.container.scrollTop +
1643
+ pageRect.top -
1644
+ containerRect.top +
1645
+ pageRect.height * anchor.relativeY -
1646
+ anchor.viewportY;
1647
+ anchor.container.scrollLeft = this.clamp(targetLeft, 0, Math.max(0, anchor.container.scrollWidth - anchor.container.clientWidth));
1648
+ anchor.container.scrollTop = this.clamp(targetTop, 0, Math.max(0, anchor.container.scrollHeight - anchor.container.clientHeight));
1649
+ };
1650
+ if (!targetWindow) {
1651
+ restore();
1652
+ return;
1653
+ }
1654
+ targetWindow.requestAnimationFrame(restore);
1655
+ }
858
1656
  disconnectPageObserver() {
859
1657
  this.cancelPageObserverFrame();
860
1658
  this.pageIntersectionObserver?.disconnect();
@@ -924,6 +1722,32 @@ class PdfViewer {
924
1722
  }
925
1723
  return true;
926
1724
  }
1725
+ getFitScale(mode) {
1726
+ const container = this.viewerBody()?.nativeElement;
1727
+ const page = this.pdfDocument?.pages[this.clamp(this.activePage(), 1, Math.max(this.pageCount(), 1)) - 1];
1728
+ if (!container || !page) {
1729
+ return null;
1730
+ }
1731
+ const pageSize = this.getPageBaseSize(page);
1732
+ const pagesElement = container.querySelector('.pdf-viewer-pages');
1733
+ const computedStyle = pagesElement && this.document.defaultView
1734
+ ? this.document.defaultView.getComputedStyle(pagesElement)
1735
+ : null;
1736
+ const horizontalPadding = computedStyle
1737
+ ? this.parseCssPixel(computedStyle.paddingLeft) + this.parseCssPixel(computedStyle.paddingRight)
1738
+ : 0;
1739
+ const verticalPadding = computedStyle
1740
+ ? this.parseCssPixel(computedStyle.paddingTop) + this.parseCssPixel(computedStyle.paddingBottom)
1741
+ : 0;
1742
+ const fitAllowance = 1;
1743
+ const availableWidth = Math.max(1, container.clientWidth - horizontalPadding - fitAllowance);
1744
+ const availableHeight = Math.max(1, container.clientHeight - verticalPadding - fitAllowance);
1745
+ const widthScale = availableWidth / pageSize.width;
1746
+ if (mode === 'fit-width') {
1747
+ return this.floorFitZoom(widthScale);
1748
+ }
1749
+ return this.floorFitZoom(Math.min(widthScale, availableHeight / pageSize.height));
1750
+ }
927
1751
  getScaleBounds() {
928
1752
  const min = this.sanitizePositiveNumber(this.minScale(), 0.2);
929
1753
  const max = this.sanitizePositiveNumber(this.maxScale(), 60);
@@ -936,14 +1760,28 @@ class PdfViewer {
936
1760
  return Number.isFinite(value) && value > 0 ? value : fallback;
937
1761
  }
938
1762
  getPageDisplaySize(page, scale) {
939
- const isRotatedSideways = page.rotation === 1 || page.rotation === 3;
1763
+ const pageSize = this.getPageBaseSize(page);
1764
+ return {
1765
+ width: Math.max(1, Math.round(pageSize.width * scale)),
1766
+ height: Math.max(1, Math.round(pageSize.height * scale)),
1767
+ };
1768
+ }
1769
+ getPageBaseSize(page) {
1770
+ const rotation = this.getPageDisplayRotation(page);
1771
+ const isRotatedSideways = rotation === Rotation.Degree90 || rotation === Rotation.Degree270;
940
1772
  const width = isRotatedSideways ? page.size.height : page.size.width;
941
1773
  const height = isRotatedSideways ? page.size.width : page.size.height;
942
1774
  return {
943
- width: Math.max(1, Math.round(width * scale)),
944
- height: Math.max(1, Math.round(height * scale)),
1775
+ width: Math.max(1, width),
1776
+ height: Math.max(1, height),
945
1777
  };
946
1778
  }
1779
+ getPageDisplayRotation(page) {
1780
+ return this.normalizeRotation(page.rotation + this.pageRotation());
1781
+ }
1782
+ normalizeRotation(rotation) {
1783
+ return (((rotation % 4) + 4) % 4);
1784
+ }
947
1785
  scrollToPage(pageNumber) {
948
1786
  if (!this.renderAll()) {
949
1787
  return;
@@ -954,10 +1792,12 @@ class PdfViewer {
954
1792
  return;
955
1793
  }
956
1794
  const nextScrollTop = this.getPageScrollTop(container, target);
957
- const scrollDistance = Math.abs(container.scrollTop - nextScrollTop);
1795
+ const nextScrollLeft = this.getPageScrollLeft(container, target);
1796
+ const scrollDistance = Math.hypot(container.scrollTop - nextScrollTop, container.scrollLeft - nextScrollLeft);
958
1797
  this.startProgrammaticScrollLock(pageNumber, scrollDistance);
959
1798
  container.scrollTo({
960
1799
  top: nextScrollTop,
1800
+ left: nextScrollLeft,
961
1801
  behavior: 'smooth',
962
1802
  });
963
1803
  }
@@ -997,27 +1837,35 @@ class PdfViewer {
997
1837
  return null;
998
1838
  }
999
1839
  const containerRect = container.getBoundingClientRect();
1000
- const viewportCenter = containerRect.top + containerRect.height / 2;
1840
+ const viewportCenterX = containerRect.left + containerRect.width / 2;
1841
+ const viewportCenterY = containerRect.top + containerRect.height / 2;
1001
1842
  const pages = Array.from(container.querySelectorAll('[data-ngs-pdf-page]'));
1002
1843
  let closestPage = null;
1003
1844
  let closestDistance = Number.POSITIVE_INFINITY;
1004
1845
  let mostVisiblePage = null;
1005
- let mostVisibleHeight = 0;
1846
+ let mostVisibleArea = 0;
1006
1847
  for (const page of pages) {
1007
1848
  const rect = page.getBoundingClientRect();
1008
1849
  const pageNumber = Number(page.dataset['ngsPdfPage']);
1009
1850
  if (!Number.isFinite(pageNumber)) {
1010
1851
  continue;
1011
1852
  }
1012
- if (rect.top <= viewportCenter && rect.bottom >= viewportCenter) {
1853
+ if (rect.left <= viewportCenterX &&
1854
+ rect.right >= viewportCenterX &&
1855
+ rect.top <= viewportCenterY &&
1856
+ rect.bottom >= viewportCenterY) {
1013
1857
  return pageNumber;
1014
1858
  }
1859
+ const visibleWidth = Math.max(0, Math.min(rect.right, containerRect.right) - Math.max(rect.left, containerRect.left));
1015
1860
  const visibleHeight = Math.max(0, Math.min(rect.bottom, containerRect.bottom) - Math.max(rect.top, containerRect.top));
1016
- if (visibleHeight > mostVisibleHeight) {
1017
- mostVisibleHeight = visibleHeight;
1861
+ const visibleArea = visibleWidth * visibleHeight;
1862
+ if (visibleArea > mostVisibleArea) {
1863
+ mostVisibleArea = visibleArea;
1018
1864
  mostVisiblePage = pageNumber;
1019
1865
  }
1020
- const distance = Math.min(Math.abs(rect.top - viewportCenter), Math.abs(rect.bottom - viewportCenter));
1866
+ const pageCenterX = rect.left + rect.width / 2;
1867
+ const pageCenterY = rect.top + rect.height / 2;
1868
+ const distance = Math.hypot(pageCenterX - viewportCenterX, pageCenterY - viewportCenterY);
1021
1869
  if (distance < closestDistance) {
1022
1870
  closestDistance = distance;
1023
1871
  closestPage = pageNumber;
@@ -1046,6 +1894,12 @@ class PdfViewer {
1046
1894
  getPageScrollTop(container, target) {
1047
1895
  return this.getElementScrollTop(container, target, 'start');
1048
1896
  }
1897
+ getPageScrollLeft(container, target) {
1898
+ const containerRect = container.getBoundingClientRect();
1899
+ const targetRect = target.getBoundingClientRect();
1900
+ const targetLeft = container.scrollLeft + targetRect.left - containerRect.left;
1901
+ return this.clamp(targetLeft, 0, Math.max(0, container.scrollWidth - container.clientWidth));
1902
+ }
1049
1903
  getElementScrollTop(container, target, align) {
1050
1904
  const containerRect = container.getBoundingClientRect();
1051
1905
  const targetRect = target.getBoundingClientRect();
@@ -1084,7 +1938,8 @@ class PdfViewer {
1084
1938
  this.completeProgrammaticScroll();
1085
1939
  return;
1086
1940
  }
1087
- if (Math.abs(container.scrollTop - this.getPageScrollTop(container, target)) <= 2) {
1941
+ if (Math.abs(container.scrollTop - this.getPageScrollTop(container, target)) <= 2 &&
1942
+ Math.abs(container.scrollLeft - this.getPageScrollLeft(container, target)) <= 2) {
1088
1943
  this.completeProgrammaticScroll();
1089
1944
  }
1090
1945
  }
@@ -1108,13 +1963,14 @@ class PdfViewer {
1108
1963
  this.programmaticScrollTargetPage = null;
1109
1964
  }
1110
1965
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewer, deps: [], target: i0.ɵɵFactoryTarget.Component });
1111
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: PdfViewer, isStandalone: true, selector: "ngs-pdf-viewer", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, wasmUrl: { classPropertyName: "wasmUrl", publicName: "wasmUrl", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, scale: { classPropertyName: "scale", publicName: "scale", isSignal: true, isRequired: false, transformFunction: null }, minScale: { classPropertyName: "minScale", publicName: "minScale", isSignal: true, isRequired: false, transformFunction: null }, maxScale: { classPropertyName: "maxScale", publicName: "maxScale", isSignal: true, isRequired: false, transformFunction: null }, zoomStep: { classPropertyName: "zoomStep", publicName: "zoomStep", isSignal: true, isRequired: false, transformFunction: null }, renderAll: { classPropertyName: "renderAll", publicName: "renderAll", isSignal: true, isRequired: false, transformFunction: null }, showToolbar: { classPropertyName: "showToolbar", publicName: "showToolbar", isSignal: true, isRequired: false, transformFunction: null }, showPageList: { classPropertyName: "showPageList", publicName: "showPageList", isSignal: true, isRequired: false, transformFunction: null }, withAnnotations: { classPropertyName: "withAnnotations", publicName: "withAnnotations", isSignal: true, isRequired: false, transformFunction: null }, withForms: { classPropertyName: "withForms", publicName: "withForms", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", pageChanged: "pageChanged", pageRendered: "pageRendered", error: "error" }, host: { properties: { "class.is-loading": "isLoading()", "class.has-toolbar": "showToolbar()", "class.has-page-list": "isPageListVisible()", "class.has-error": "errorState()" }, classAttribute: "ngs-pdf-viewer not-prose" }, viewQueries: [{ propertyName: "viewerBody", first: true, predicate: ["viewerBody"], descendants: true, isSignal: true }, { propertyName: "pageList", first: true, predicate: ["pageList"], descendants: true, isSignal: true }], exportAs: ["ngsPdfViewer"], ngImport: i0, template: "<ngs-panel class=\"pdf-viewer-panel\">\n @if (showToolbar()) {\n <ngs-panel-header flex>\n <div class=\"pdf-viewer-toolbar\" aria-label=\"PDF viewer controls\">\n @if (showPageList()) {\n <button\n ngsIconButton\n type=\"button\"\n [attr.aria-label]=\"pageListVisible() ? 'Hide pages panel' : 'Show pages panel'\"\n [attr.aria-pressed]=\"pageListVisible()\"\n (click)=\"togglePageList()\">\n <ngs-icon\n [name]=\"pageListVisible() ? 'fluent:panel-left-contract-24-regular' : 'fluent:panel-left-expand-24-regular'\" />\n </button>\n }\n <button ngsIconButton type=\"button\" aria-label=\"Previous page\" [disabled]=\"!canGoPrevious()\" (click)=\"previousPage()\">\n <ngs-icon name=\"fluent:chevron-up-24-regular\" />\n </button>\n <span class=\"pdf-viewer-toolbar__page\">{{ activePage() }} / {{ pageCount() || 1 }}</span>\n <button ngsIconButton type=\"button\" aria-label=\"Next page\" [disabled]=\"!canGoNext()\" (click)=\"nextPage()\">\n <ngs-icon name=\"fluent:chevron-down-24-regular\" />\n </button>\n <span class=\"pdf-viewer-toolbar__spacer\"></span>\n <button ngsIconButton type=\"button\" aria-label=\"Zoom out\" [disabled]=\"!canZoomOut()\" (click)=\"zoomOut()\">\n <ngs-icon name=\"fluent:zoom-out-24-regular\" />\n </button>\n <span class=\"pdf-viewer-toolbar__zoom\">{{ zoomLabel() }}</span>\n <button ngsIconButton type=\"button\" aria-label=\"Zoom in\" [disabled]=\"!canZoomIn()\" (click)=\"zoomIn()\">\n <ngs-icon name=\"fluent:zoom-in-24-regular\" />\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"PDF viewer settings\" [ngsMenuTriggerFor]=\"settingsMenu\">\n <ngs-icon name=\"fluent:settings-24-regular\" />\n </button>\n </div>\n\n <ngs-menu #settingsMenu=\"ngsMenu\" xPosition=\"before\">\n <ngs-menu-heading>SPREAD MODE</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'single'\"\n (click)=\"setSpreadMode('single')\">\n <ngs-icon name=\"fluent:document-24-regular\" />\n <span>Single Page</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-odd'\"\n (click)=\"setSpreadMode('two-odd')\">\n <ngs-icon name=\"fluent:book-open-24-regular\" />\n <span>Two Page (Odd)</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-even'\"\n (click)=\"setSpreadMode('two-even')\">\n <ngs-icon name=\"fluent:document-text-24-regular\" />\n <span>Two Page (Even)</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>SCROLL LAYOUT</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'vertical'\"\n (click)=\"setScrollLayout('vertical')\">\n <ngs-icon name=\"fluent:arrow-sort-24-regular\" />\n <span>Vertical</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'horizontal'\"\n (click)=\"setScrollLayout('horizontal')\">\n <ngs-icon name=\"fluent:arrow-swap-24-regular\" />\n <span>Horizontal</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>PAGE ROTATION</ngs-menu-heading>\n <button ngs-menu-item type=\"button\" (click)=\"rotateClockwise()\">\n <ngs-icon name=\"fluent:arrow-clockwise-24-regular\" />\n <span>Rotate Clockwise</span>\n </button>\n <button ngs-menu-item type=\"button\" (click)=\"rotateCounterClockwise()\">\n <ngs-icon name=\"fluent:arrow-counterclockwise-24-regular\" />\n <span>Rotate Counter-Clockwise</span>\n </button>\n\n <ngs-menu-divider />\n <button ngs-menu-item type=\"button\" (click)=\"toggleFullscreen()\">\n <ngs-icon name=\"fluent:arrow-maximize-24-regular\" />\n <span>Fullscreen</span>\n </button>\n </ngs-menu>\n </ngs-panel-header>\n }\n\n @if (isPageListVisible()) {\n <ngs-panel-sidebar>\n <div class=\"pdf-viewer-sidebar\" #pageList>\n <div class=\"pdf-viewer-sidebar__title\">Pages</div>\n <div class=\"pdf-viewer-sidebar__pages\" aria-label=\"PDF pages\">\n @for (pageItem of pageItems(); track pageItem.pageNumber) {\n <button\n class=\"pdf-viewer-sidebar__page\"\n type=\"button\"\n [class.is-active]=\"pageItem.pageNumber === activePage()\"\n [attr.aria-current]=\"pageItem.pageNumber === activePage() ? 'page' : null\"\n [attr.aria-label]=\"'Open page ' + pageItem.pageNumber\"\n [attr.data-ngs-pdf-page-button]=\"pageItem.pageNumber\"\n (click)=\"setPage(pageItem.pageNumber)\">\n <span class=\"pdf-viewer-sidebar__thumb\" aria-hidden=\"true\">\n @if (pageItem.thumbnail; as thumbnail) {\n <img\n [src]=\"thumbnail.url\"\n [attr.width]=\"thumbnail.width\"\n [attr.height]=\"thumbnail.height\"\n alt=\"\"\n draggable=\"false\" />\n } @else {\n <ngs-image-placeholder />\n }\n </span>\n <span class=\"pdf-viewer-sidebar__label\">Page {{ pageItem.pageNumber }}</span>\n </button>\n }\n </div>\n </div>\n </ngs-panel-sidebar>\n }\n\n <ngs-panel-content>\n <div class=\"pdf-viewer-body\" #viewerBody (scroll)=\"onViewerScroll()\">\n <ngs-block-loader [loading]=\"isLoading()\">Loading PDF...</ngs-block-loader>\n @if (errorState()) {\n <div class=\"pdf-viewer-state pdf-viewer-state--error\">\n <ngs-icon name=\"fluent:error-circle-24-regular\" />\n <span>PDF could not be loaded.</span>\n </div>\n } @else if (!src()) {\n <div class=\"pdf-viewer-state\">\n <ngs-icon name=\"fluent:document-pdf-24-regular\" />\n <span>No PDF selected.</span>\n </div>\n } @else {\n <div class=\"pdf-viewer-pages\">\n @for (pdfPage of renderedPages(); track pdfPage.pageNumber) {\n <article\n class=\"pdf-viewer-page\"\n [attr.aria-label]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.data-ngs-pdf-page]=\"pdfPage.pageNumber\">\n <div\n class=\"pdf-viewer-page__surface\"\n [style.width.px]=\"pdfPage.width\"\n [style.height.px]=\"pdfPage.height\"\n (pointerdown)=\"startTextSelection($event, pdfPage)\"\n (pointermove)=\"updateTextSelection($event, pdfPage)\"\n (pointerup)=\"finishTextSelection($event, pdfPage)\"\n (pointercancel)=\"cancelTextSelection()\"\n (mousedown)=\"startTextSelection($event, pdfPage)\"\n (mousemove)=\"updateTextSelection($event, pdfPage)\"\n (mouseup)=\"finishTextSelection($event, pdfPage)\">\n @if (isPageImageFresh(pdfPage) && pdfPage.url; as pageUrl) {\n <img\n [src]=\"pageUrl\"\n [alt]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.width]=\"pdfPage.width\"\n [attr.height]=\"pdfPage.height\"\n draggable=\"false\" />\n } @else {\n <div class=\"pdf-viewer-page__placeholder\" aria-hidden=\"true\">\n @if (pdfPage.isRendering) {\n <span></span>\n }\n </div>\n }\n @if (selectionRectsForPage(pdfPage.pageNumber); as selectionRects) {\n @if (selectionRects.length > 0) {\n <div class=\"pdf-viewer-page__selection-layer\" aria-hidden=\"true\">\n @for (selectionRect of selectionRects; track selectionRect.left + '-' + selectionRect.top + '-' + $index) {\n <div\n class=\"pdf-viewer-page__selection-rect\"\n [style.left.px]=\"selectionRect.left\"\n [style.top.px]=\"selectionRect.top\"\n [style.width.px]=\"selectionRect.width\"\n [style.height.px]=\"selectionRect.height\"></div>\n }\n </div>\n }\n }\n </div>\n </article>\n }\n </div>\n }\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{--ngs-pdf-viewer-height: 640px;--ngs-pdf-viewer-background: var(--ngs-color-surface-container-low, #f5f6f8);--ngs-pdf-viewer-page-background: var(--ngs-color-surface, #ffffff);--ngs-pdf-viewer-page-shadow: var(--ngs-shadow-md, 0 8px 24px rgba(15, 23, 42, .12));--ngs-pdf-viewer-border-color: var(--ngs-color-outline-variant, rgba(15, 23, 42, .12));--ngs-pdf-viewer-radius: var(--ngs-radius-md, 10px);--ngs-pdf-viewer-sidebar-width: 152px;--ngs-pdf-viewer-selection-background: rgb(33 150 243 / .34);display:block;height:var(--ngs-pdf-viewer-height);min-height:320px;overflow:hidden;border:1px solid var(--ngs-pdf-viewer-border-color);border-radius:var(--ngs-pdf-viewer-radius);background:var(--ngs-pdf-viewer-background);color:var(--ngs-color-on-surface, #111827)}:host ngs-panel{height:100%;overflow:hidden;border-radius:inherit;background:var(--ngs-pdf-viewer-background)}:host ngs-panel-header{min-width:0;border-bottom:1px solid var(--ngs-pdf-viewer-border-color);background:var(--ngs-color-surface, #ffffff)}:host ngs-panel-sidebar{width:var(--ngs-pdf-viewer-sidebar-width);min-width:var(--ngs-pdf-viewer-sidebar-width);border-right:1px solid var(--ngs-pdf-viewer-border-color);background:var(--ngs-color-surface, #ffffff)}:host ngs-panel-content{min-width:0;min-height:0;overflow:hidden;background:var(--ngs-pdf-viewer-background)}:host .pdf-viewer-toolbar{display:flex;align-items:center;gap:var(--ngs-spacing-1, .25rem);width:100%;padding:0 var(--ngs-spacing-2, .5rem)}:host .pdf-viewer-toolbar__page,:host .pdf-viewer-toolbar__zoom{min-width:76px;text-align:center;font-size:var(--ngs-font-size-sm, .875rem);font-weight:500;color:var(--ngs-color-on-surface-variant, #4b5563)}:host .pdf-viewer-toolbar__spacer{flex:1 1 auto}:host .pdf-viewer-sidebar{display:flex;flex-direction:column;gap:var(--ngs-spacing-2, .5rem);height:100%;min-height:0;padding:var(--ngs-spacing-3, .75rem);overflow:auto}:host .pdf-viewer-sidebar__title{padding:0 var(--ngs-spacing-1, .25rem);font-size:var(--ngs-font-size-xs, .75rem);font-weight:600;color:var(--ngs-color-on-surface-variant, #6b7280)}:host .pdf-viewer-sidebar__pages{display:flex;flex-direction:column;gap:var(--ngs-spacing-2, .5rem);min-height:0}:host .pdf-viewer-sidebar__page{display:flex;flex-direction:column;align-items:stretch;gap:var(--ngs-spacing-1, .25rem);width:100%;padding:var(--ngs-spacing-1, .25rem);border:1px solid transparent;border-radius:calc(var(--ngs-pdf-viewer-radius) - 4px);background:transparent;color:var(--ngs-color-on-surface-variant, #4b5563);cursor:pointer;text-align:left}:host .pdf-viewer-sidebar__page:hover .pdf-viewer-sidebar__thumb{border-color:var(--ngs-color-outline, rgba(15, 23, 42, .24))}:host .pdf-viewer-sidebar__page:focus-visible{outline:2px solid var(--ngs-color-primary, #155eef);outline-offset:2px}:host .pdf-viewer-sidebar__page.is-active{color:var(--ngs-color-on-surface, #111827)}:host .pdf-viewer-sidebar__page.is-active .pdf-viewer-sidebar__thumb{border-color:var(--ngs-color-primary, #155eef);background:var(--ngs-color-primary-container, rgba(21, 94, 239, .08));box-shadow:0 0 0 2px var(--ngs-color-primary-container, rgba(21, 94, 239, .14)),var(--ngs-shadow-xs, 0 1px 2px rgba(15, 23, 42, .08))}:host .pdf-viewer-sidebar__thumb{display:flex;align-items:center;justify-content:center;aspect-ratio:3/4;width:100%;overflow:hidden;border:1px solid var(--ngs-pdf-viewer-border-color);border-radius:calc(var(--ngs-pdf-viewer-radius) - 6px);background:var(--ngs-pdf-viewer-page-background);box-shadow:var(--ngs-shadow-xs, 0 1px 2px rgba(15, 23, 42, .08))}:host .pdf-viewer-sidebar__thumb img{display:block;width:100%;height:100%;object-fit:contain;-webkit-user-select:none;user-select:none}:host .pdf-viewer-sidebar__thumb ngs-image-placeholder{width:100%;height:100%}:host .pdf-viewer-sidebar__thumb ngs-image-placeholder ::ng-deep svg{width:64%;min-width:0}:host .pdf-viewer-sidebar__thumb ngs-icon{font-size:1.375rem;color:var(--ngs-color-on-surface-variant, #6b7280)}:host .pdf-viewer-sidebar__label{display:block;overflow:hidden;font-size:var(--ngs-font-size-xs, .75rem);font-weight:500;line-height:1.25;text-align:center;text-overflow:ellipsis;white-space:nowrap}:host .pdf-viewer-body{position:absolute;inset:0;overflow:auto}:host .pdf-viewer-pages{display:flex;flex-direction:column;align-items:center;gap:var(--ngs-spacing-6, 1.5rem);min-height:100%;padding:var(--ngs-spacing-6, 1.5rem)}:host .pdf-viewer-page{width:max-content;max-width:none}:host .pdf-viewer-page__surface{position:relative;overflow:hidden;cursor:text;-webkit-user-select:none;user-select:none;border-radius:calc(var(--ngs-pdf-viewer-radius) - 4px);background:var(--ngs-pdf-viewer-page-background);box-shadow:var(--ngs-pdf-viewer-page-shadow);touch-action:none}:host .pdf-viewer-page__surface img{display:block;width:100%;height:100%;max-width:none;-webkit-user-select:none;user-select:none;pointer-events:none}:host .pdf-viewer-page__placeholder{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:var(--ngs-pdf-viewer-page-background);pointer-events:none}:host .pdf-viewer-page__placeholder span{width:32px;height:32px;border:2px solid var(--ngs-color-outline-variant, rgba(15, 23, 42, .12));border-top-color:var(--ngs-color-primary, #155eef);border-radius:999px;animation:ngs-pdf-viewer-page-spin .8s linear infinite}:host .pdf-viewer-page__selection-layer{position:absolute;inset:0;z-index:1;overflow:hidden;isolation:isolate;mix-blend-mode:multiply;forced-color-adjust:none;pointer-events:none}:host .pdf-viewer-page__selection-rect{position:absolute;background:var(--ngs-pdf-viewer-selection-background);pointer-events:none}:host .pdf-viewer-state{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--ngs-spacing-3, .75rem);padding:var(--ngs-spacing-6, 1.5rem);color:var(--ngs-color-on-surface-variant, #6b7280);text-align:center}:host .pdf-viewer-state ngs-icon{font-size:2rem}:host .pdf-viewer-state--error{color:var(--ngs-color-danger, #dc2626)}@keyframes ngs-pdf-viewer-page-spin{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "component", type: BlockLoader, selector: "ngs-block-loader", inputs: ["loading", "spinnerDiameter", "spinnerStrokeWidth"], exportAs: ["ngsBlockLoader"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "component", type: ImagePlaceholder, selector: "ngs-image-placeholder", exportAs: ["ngsImagePlaceholder"] }, { kind: "component", type: Menu, selector: "ngs-menu", inputs: ["role", "classList", "xPosition", "yPosition"], outputs: ["closed"], exportAs: ["ngsMenu"] }, { kind: "component", type: MenuDivider, selector: "ngs-menu-divider" }, { kind: "component", type: MenuHeading, selector: "ngs-menu-heading" }, { kind: "component", type: MenuItem, selector: "ngs-menu-item, [ngs-menu-item]", inputs: ["disabled", "role", "selected"], outputs: ["_triggered"], exportAs: ["ngsMenuItem"] }, { kind: "directive", type: MenuTrigger, selector: "[ngsMenuTriggerFor]", inputs: ["ngsMenuTriggerFor", "ngsMenuTriggerData", "ngsMenuDisabled", "xPosition", "yPosition", "ngsMenuTriggerRestoreFocus"], outputs: ["menuOpened", "menuClosed"], exportAs: ["ngsMenuTrigger"] }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: PanelSidebar, selector: "ngs-panel-sidebar" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1966
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: PdfViewer, isStandalone: true, selector: "ngs-pdf-viewer", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, documentName: { classPropertyName: "documentName", publicName: "documentName", isSignal: true, isRequired: false, transformFunction: null }, wasmUrl: { classPropertyName: "wasmUrl", publicName: "wasmUrl", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, scale: { classPropertyName: "scale", publicName: "scale", isSignal: true, isRequired: false, transformFunction: null }, minScale: { classPropertyName: "minScale", publicName: "minScale", isSignal: true, isRequired: false, transformFunction: null }, maxScale: { classPropertyName: "maxScale", publicName: "maxScale", isSignal: true, isRequired: false, transformFunction: null }, zoomStep: { classPropertyName: "zoomStep", publicName: "zoomStep", isSignal: true, isRequired: false, transformFunction: null }, maxRenderPixels: { classPropertyName: "maxRenderPixels", publicName: "maxRenderPixels", isSignal: true, isRequired: false, transformFunction: null }, maxRenderDimension: { classPropertyName: "maxRenderDimension", publicName: "maxRenderDimension", isSignal: true, isRequired: false, transformFunction: null }, renderAll: { classPropertyName: "renderAll", publicName: "renderAll", isSignal: true, isRequired: false, transformFunction: null }, showToolbar: { classPropertyName: "showToolbar", publicName: "showToolbar", isSignal: true, isRequired: false, transformFunction: null }, showPageList: { classPropertyName: "showPageList", publicName: "showPageList", isSignal: true, isRequired: false, transformFunction: null }, showSearchPanel: { classPropertyName: "showSearchPanel", publicName: "showSearchPanel", isSignal: true, isRequired: false, transformFunction: null }, showAnnotationsPanel: { classPropertyName: "showAnnotationsPanel", publicName: "showAnnotationsPanel", isSignal: true, isRequired: false, transformFunction: null }, annotations: { classPropertyName: "annotations", publicName: "annotations", isSignal: true, isRequired: false, transformFunction: null }, annotationsDataSource: { classPropertyName: "annotationsDataSource", publicName: "annotationsDataSource", isSignal: true, isRequired: false, transformFunction: null }, annotationTypeProperty: { classPropertyName: "annotationTypeProperty", publicName: "annotationTypeProperty", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null }, withAnnotations: { classPropertyName: "withAnnotations", publicName: "withAnnotations", isSignal: true, isRequired: false, transformFunction: null }, withForms: { classPropertyName: "withForms", publicName: "withForms", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", pageChanged: "pageChanged", pageRendered: "pageRendered", error: "error" }, host: { properties: { "class.is-loading": "isLoading()", "class.has-toolbar": "showToolbar()", "class.has-page-list": "isPageListVisible()", "class.has-error": "errorState()" }, classAttribute: "ngs-pdf-viewer not-prose" }, queries: [{ propertyName: "annotationDefs", predicate: PdfViewerAnnotationDef, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "viewerBody", first: true, predicate: ["viewerBody"], descendants: true, isSignal: true }, { propertyName: "pageList", first: true, predicate: ["pageList"], descendants: true, isSignal: true }], exportAs: ["ngsPdfViewer"], ngImport: i0, template: "<ngs-panel class=\"pdf-viewer-panel h-full overflow-hidden rounded-lg border border-border bg-surface-container-low text-on-surface\">\n @if (showToolbar()) {\n <ngs-panel-header class=\"min-w-0 border-b border-border bg-surface\" flex>\n <ngs-toolbar class=\"pdf-viewer-toolbar flex h-14 w-full min-w-0 items-center gap-4 overflow-visible px-2 text-on-surface-variant\" aria-label=\"PDF viewer controls\">\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__start flex min-w-0 flex-none items-center gap-2\">\n @if (showPageList()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-label]=\"pageListVisible() ? 'Hide pages panel' : 'Show pages panel'\"\n [attr.aria-pressed]=\"pageListVisible()\"\n (click)=\"togglePageList()\">\n <ngs-icon\n class=\"text-2xl\"\n [name]=\"pageListVisible() ? 'fluent:panel-left-contract-24-regular' : 'fluent:panel-left-expand-24-regular'\" />\n </button>\n }\n <ngs-divider vertical fixedHeight />\n </ngs-toolbar-item>\n <ngs-toolbar-title class=\"min-w-0 flex-1 truncate text-sm! max-w-[20%] font-semibold! text-on-surface-variant\" [title]=\"displayDocumentName()\">\n {{ displayDocumentName() }}\n </ngs-toolbar-title>\n\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__center flex flex-none items-center gap-3\">\n <div class=\"pdf-viewer-toolbar__cluster flex min-w-0 items-center gap-2\" aria-label=\"Zoom controls\">\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Zoom out\"\n [disabled]=\"!canZoomOut()\"\n (click)=\"zoomOut()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:zoom-out-24-regular\" />\n </button>\n <button\n class=\"pdf-viewer-toolbar__zoom min-w-12 cursor-pointer border-0 bg-transparent p-0 text-center text-sm text-on-surface hover:text-primary\"\n type=\"button\"\n aria-label=\"Zoom presets\"\n [ngsMenuTriggerFor]=\"zoomMenu\">\n {{ zoomLabel() }}\n </button>\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Zoom in\"\n [disabled]=\"!canZoomIn()\"\n (click)=\"zoomIn()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:zoom-in-24-regular\" />\n </button>\n </div>\n\n <ngs-divider vertical fixedHeight />\n\n <div class=\"pdf-viewer-toolbar__cluster flex min-w-0 items-center gap-2\" aria-label=\"Page navigation\">\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Previous page\"\n [disabled]=\"!canGoPrevious()\"\n (click)=\"previousPage()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:chevron-left-24-regular\" />\n </button>\n <span class=\"pdf-viewer-toolbar__page inline-flex h-9 min-w-16 items-center justify-center gap-2 rounded bg-surface-container-low text-sm font-bold text-on-surface\">\n <span>{{ activePage() }}</span>\n <span>/</span>\n <span>{{ pageCount() || 1 }}</span>\n </span>\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Next page\"\n [disabled]=\"!canGoNext()\"\n (click)=\"nextPage()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:chevron-right-24-regular\" />\n </button>\n </div>\n </ngs-toolbar-item>\n\n <ngs-toolbar-spacer />\n\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__end flex min-w-0 flex-1 items-center justify-end gap-2\" aria-label=\"PDF tools\">\n @if (showSearchPanel()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-pressed]=\"isSearchPanelVisible()\"\n aria-label=\"Search\"\n [disabled]=\"!asidePanelInteractive()\"\n (click)=\"toggleSearchPanel()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:search-24-regular\" />\n </button>\n }\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"PDF viewer settings\"\n [ngsMenuTriggerFor]=\"settingsMenu\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:settings-24-regular\" />\n </button>\n @if (showAnnotationsPanel()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-pressed]=\"isAnnotationsPanelVisible()\"\n aria-label=\"Toggle annotations panel\"\n [disabled]=\"!asidePanelInteractive()\"\n (click)=\"toggleAnnotationsPanel()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:text-bullet-list-square-edit-24-regular\" />\n </button>\n }\n </ngs-toolbar-item>\n </ngs-toolbar>\n\n <ngs-menu #zoomMenu=\"ngsMenu\">\n @for (zoomPreset of zoomPresets; track zoomPreset) {\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"isZoomPresetSelected(zoomPreset)\"\n (click)=\"setZoomPreset(zoomPreset)\">\n <span>{{ zoomPreset * 100 }}%</span>\n </button>\n }\n\n <ngs-menu-divider />\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"zoomMode() === 'fit-page'\"\n (click)=\"fitToPage()\">\n <ngs-icon name=\"fluent:resize-large-24-regular\" />\n <span>Fit to Page</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"zoomMode() === 'fit-width'\"\n (click)=\"fitToWidth()\">\n <ngs-icon name=\"fluent:resize-24-regular\" />\n <span>Fit to Width</span>\n </button>\n </ngs-menu>\n\n <ngs-menu #settingsMenu=\"ngsMenu\" xPosition=\"before\">\n <ngs-menu-heading>SPREAD MODE</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'single'\"\n (click)=\"setSpreadMode('single')\">\n <ngs-icon name=\"fluent:document-24-regular\" />\n <span>Single Page</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-odd'\"\n (click)=\"setSpreadMode('two-odd')\">\n <ngs-icon name=\"fluent:book-open-24-regular\" />\n <span>Two Page (Odd)</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-even'\"\n (click)=\"setSpreadMode('two-even')\">\n <ngs-icon name=\"fluent:document-text-24-regular\" />\n <span>Two Page (Even)</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>SCROLL LAYOUT</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'vertical'\"\n (click)=\"setScrollLayout('vertical')\">\n <ngs-icon name=\"fluent:arrow-sort-24-regular\" />\n <span>Vertical</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'horizontal'\"\n (click)=\"setScrollLayout('horizontal')\">\n <ngs-icon name=\"fluent:arrow-swap-24-regular\" />\n <span>Horizontal</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>PAGE ROTATION</ngs-menu-heading>\n <button ngs-menu-item type=\"button\" (click)=\"rotateClockwise()\">\n <ngs-icon name=\"fluent:arrow-clockwise-24-regular\" />\n <span>Rotate Clockwise</span>\n </button>\n <button ngs-menu-item type=\"button\" (click)=\"rotateCounterClockwise()\">\n <ngs-icon name=\"fluent:arrow-counterclockwise-24-regular\" />\n <span>Rotate Counter-Clockwise</span>\n </button>\n\n <ngs-menu-divider />\n <button ngs-menu-item type=\"button\" (click)=\"toggleFullscreen()\">\n <ngs-icon name=\"fluent:arrow-maximize-24-regular\" />\n <span>Fullscreen</span>\n </button>\n </ngs-menu>\n </ngs-panel-header>\n }\n\n @if (isPageListVisible()) {\n <ngs-panel-sidebar class=\"w-[var(--ngs-pdf-viewer-sidebar-width)] min-w-[var(--ngs-pdf-viewer-sidebar-width)] border-r border-border bg-surface\">\n <div class=\"pdf-viewer-sidebar flex h-full min-h-0 flex-col gap-2 overflow-auto p-3\" #pageList>\n <div class=\"pdf-viewer-sidebar__title px-1 text-xs font-semibold text-on-surface-variant\">Pages</div>\n <div class=\"pdf-viewer-sidebar__pages flex min-h-0 flex-col gap-2\" aria-label=\"PDF pages\">\n @for (pageItem of pageItems(); track pageItem.pageNumber) {\n <button\n class=\"pdf-viewer-sidebar__page group flex w-full cursor-pointer flex-col items-stretch gap-1 rounded border border-transparent bg-transparent p-1 text-left text-on-surface-variant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary [&.is-active]:text-on-surface\"\n type=\"button\"\n [class.is-active]=\"pageItem.pageNumber === activePage()\"\n [attr.aria-current]=\"pageItem.pageNumber === activePage() ? 'page' : null\"\n [attr.aria-label]=\"'Open page ' + pageItem.pageNumber\"\n [attr.data-ngs-pdf-page-button]=\"pageItem.pageNumber\"\n (click)=\"setPage(pageItem.pageNumber)\">\n <span class=\"pdf-viewer-sidebar__thumb flex aspect-[3/4] w-full items-center justify-center overflow-hidden rounded border border-border bg-surface shadow-xs group-hover:border-border\" aria-hidden=\"true\">\n @if (pageItem.thumbnail; as thumbnail) {\n <img\n class=\"block h-full w-full select-none object-contain\"\n [src]=\"thumbnail.url\"\n [attr.width]=\"thumbnail.width\"\n [attr.height]=\"thumbnail.height\"\n alt=\"\"\n draggable=\"false\" />\n } @else {\n <ngs-image-placeholder class=\"h-full w-full\" />\n }\n </span>\n <span class=\"pdf-viewer-sidebar__label block truncate text-center text-xs font-medium leading-tight\">Page {{ pageItem.pageNumber }}</span>\n </button>\n }\n </div>\n </div>\n </ngs-panel-sidebar>\n }\n\n <ngs-panel-content class=\"min-h-0 min-w-0 overflow-hidden bg-surface-container-low\">\n <div class=\"pdf-viewer-body absolute inset-0 overflow-auto\" #viewerBody (scroll)=\"onViewerScroll()\" (wheel)=\"onViewerWheel($event)\">\n <ngs-block-loader [loading]=\"isLoading()\">Loading PDF...</ngs-block-loader>\n @if (errorState()) {\n <div class=\"pdf-viewer-state pdf-viewer-state--error absolute inset-0 flex flex-col items-center justify-center gap-3 p-6 text-center text-danger\">\n <ngs-icon class=\"text-3xl\" name=\"fluent:error-circle-24-regular\" />\n <span>PDF could not be loaded.</span>\n </div>\n } @else if (!src()) {\n <div class=\"pdf-viewer-state absolute inset-0 flex flex-col items-center justify-center gap-3 p-6 text-center text-on-surface-variant\">\n <ngs-icon class=\"text-3xl\" name=\"fluent:document-pdf-24-regular\" />\n <span>No PDF selected.</span>\n </div>\n } @else {\n <div\n class=\"pdf-viewer-pages\"\n [class.is-horizontal]=\"scrollLayout() === 'horizontal'\"\n [class.is-vertical]=\"scrollLayout() === 'vertical'\"\n [class.is-spread]=\"spreadMode() !== 'single'\">\n @for (spread of pageSpreads(); track spread.id) {\n <div class=\"pdf-viewer-spread\" [attr.data-ngs-pdf-spread]=\"spread.id\">\n @if (spread.leadingPlaceholder) {\n @if (spread.leadingPlaceholderPage; as placeholderPage) {\n <div\n class=\"pdf-viewer-spread__placeholder\"\n [style.width.px]=\"placeholderPage.width\"\n [style.height.px]=\"placeholderPage.height\"\n aria-hidden=\"true\"></div>\n }\n }\n @for (pdfPage of spread.pages; track pdfPage.pageNumber) {\n <article\n class=\"pdf-viewer-page w-max max-w-none\"\n [attr.aria-label]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.data-ngs-pdf-page]=\"pdfPage.pageNumber\">\n <div\n class=\"pdf-viewer-page__surface relative cursor-text overflow-hidden rounded bg-surface shadow-md select-none touch-none\"\n [style.width.px]=\"pdfPage.width\"\n [style.height.px]=\"pdfPage.height\"\n (pointerdown)=\"startTextSelection($event, pdfPage)\"\n (pointermove)=\"updateTextSelection($event, pdfPage)\"\n (pointerup)=\"finishTextSelection($event, pdfPage)\"\n (pointercancel)=\"cancelTextSelection()\"\n (mousedown)=\"startTextSelection($event, pdfPage)\"\n (mousemove)=\"updateTextSelection($event, pdfPage)\"\n (mouseup)=\"finishTextSelection($event, pdfPage)\">\n @if (pdfPage.url; as pageUrl) {\n <img\n class=\"block h-full w-full max-w-none select-none pointer-events-none\"\n [src]=\"pageUrl\"\n [alt]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.width]=\"pdfPage.width\"\n [attr.height]=\"pdfPage.height\"\n draggable=\"false\" />\n } @else {\n <div class=\"pdf-viewer-page__placeholder pointer-events-none absolute inset-0 flex items-center justify-center bg-surface\" aria-hidden=\"true\">\n @if (pdfPage.isRendering) {\n <span class=\"size-8 animate-spin rounded-full border-2 border-border border-t-primary\"></span>\n }\n </div>\n }\n @if (selectionRectsForPage(pdfPage.pageNumber); as selectionRects) {\n @if (selectionRects.length > 0) {\n <div class=\"pdf-viewer-page__selection-layer pointer-events-none absolute inset-0 z-[1] isolate overflow-hidden mix-blend-multiply\" aria-hidden=\"true\">\n @for (selectionRect of selectionRects; track selectionRect.left + '-' + selectionRect.top + '-' + $index) {\n <div\n class=\"pdf-viewer-page__selection-rect pointer-events-none absolute bg-primary/30\"\n [style.left.px]=\"selectionRect.left\"\n [style.top.px]=\"selectionRect.top\"\n [style.width.px]=\"selectionRect.width\"\n [style.height.px]=\"selectionRect.height\"></div>\n }\n </div>\n }\n }\n </div>\n </article>\n }\n </div>\n }\n </div>\n }\n </div>\n </ngs-panel-content>\n\n @if (isSearchPanelVisible() || isAnnotationsPanelVisible()) {\n <ngs-panel-aside class=\"w-[var(--ngs-pdf-viewer-aside-width)] min-w-[var(--ngs-pdf-viewer-aside-width)] border-l border-border bg-surface\">\n @if (isSearchPanelVisible()) {\n <ngs-pdf-viewer-search\n [query]=\"activeSearchQuery()\"\n [results]=\"pdfSearchResults()\"\n (closed)=\"closeAsidePanel()\"\n (searchChanged)=\"updatePdfSearch($event)\"\n (resultSelected)=\"selectSearchResult($event)\" />\n } @else {\n <ngs-pdf-viewer-annotations\n [annotations]=\"annotationItems()\"\n [annotationDefs]=\"annotationDefs()\"\n [annotationTypeProperty]=\"annotationTypeProperty()\"\n (closed)=\"closeAsidePanel()\"\n (pageSelected)=\"setPage($event)\" />\n }\n </ngs-panel-aside>\n }\n</ngs-panel>\n", styles: [":host{--ngs-pdf-viewer-height: 720px;--ngs-pdf-viewer-min-height: 720px;--ngs-pdf-viewer-sidebar-width: 152px;--ngs-pdf-viewer-aside-width: 360px;--ngs-pdf-viewer-page-gap: 24px;--ngs-pdf-viewer-spread-gap: 16px;--ngs-pdf-viewer-page-padding: 24px;display:block;height:var(--ngs-pdf-viewer-height);min-height:var(--ngs-pdf-viewer-min-height);overflow:hidden}:host .pdf-viewer-pages{box-sizing:border-box;display:grid;gap:var(--ngs-pdf-viewer-page-gap);min-height:100%;min-width:100%;padding:var(--ngs-pdf-viewer-page-padding)}:host .pdf-viewer-pages.is-vertical{align-content:start;grid-auto-flow:row;grid-auto-rows:max-content;justify-items:center}:host .pdf-viewer-pages.is-horizontal{align-content:center;align-items:center;grid-auto-columns:max-content;grid-auto-flow:column;justify-content:start;width:max-content}:host .pdf-viewer-spread{align-items:start;display:grid;gap:var(--ngs-pdf-viewer-spread-gap);grid-auto-columns:max-content;grid-auto-flow:column;justify-content:center}:host .pdf-viewer-spread__placeholder{pointer-events:none;visibility:hidden}:host .pdf-viewer-toolbar{position:relative}:host .pdf-viewer-toolbar__center{left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}:host .pdf-viewer-toolbar__start,:host .pdf-viewer-toolbar__end{position:relative;z-index:2}:host .pdf-viewer-sidebar__page.is-active .pdf-viewer-sidebar__thumb{outline:2px solid var(--ngs-color-primary)}:host .pdf-viewer-page__selection-layer{forced-color-adjust:none}\n"], dependencies: [{ kind: "component", type: BlockLoader, selector: "ngs-block-loader", inputs: ["loading", "spinnerDiameter", "spinnerStrokeWidth"], exportAs: ["ngsBlockLoader"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Divider, selector: "ngs-divider", inputs: ["vertical", "inset", "fixedHeight"], exportAs: ["ngsDivider"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "component", type: ImagePlaceholder, selector: "ngs-image-placeholder", exportAs: ["ngsImagePlaceholder"] }, { kind: "component", type: Menu, selector: "ngs-menu", inputs: ["role", "classList", "xPosition", "yPosition"], outputs: ["closed"], exportAs: ["ngsMenu"] }, { kind: "component", type: MenuDivider, selector: "ngs-menu-divider" }, { kind: "component", type: MenuHeading, selector: "ngs-menu-heading" }, { kind: "component", type: MenuItem, selector: "ngs-menu-item, [ngs-menu-item]", inputs: ["disabled", "role", "selected"], outputs: ["_triggered"], exportAs: ["ngsMenuItem"] }, { kind: "directive", type: MenuTrigger, selector: "[ngsMenuTriggerFor]", inputs: ["ngsMenuTriggerFor", "ngsMenuTriggerData", "ngsMenuDisabled", "xPosition", "yPosition", "ngsMenuTriggerRestoreFocus"], outputs: ["menuOpened", "menuClosed"], exportAs: ["ngsMenuTrigger"] }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelAside, selector: "ngs-panel-aside" }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: PanelSidebar, selector: "ngs-panel-sidebar" }, { kind: "component", type: Toolbar, selector: "ngs-toolbar", exportAs: ["ngsToolbar"] }, { kind: "component", type: ToolbarItem, selector: "ngs-toolbar-item", inputs: ["hidden"], outputs: ["hiddenChange"] }, { kind: "component", type: ToolbarSpacer, selector: "ngs-toolbar-spacer" }, { kind: "component", type: ToolbarTitle, selector: "ngs-toolbar-title" }, { kind: "component", type: PdfViewerAnnotations, selector: "ngs-pdf-viewer-annotations", inputs: ["annotations", "annotationDefs", "annotationTypeProperty"], outputs: ["closed", "pageSelected"] }, { kind: "component", type: PdfViewerSearch, selector: "ngs-pdf-viewer-search", inputs: ["results", "query"], outputs: ["closed", "resultSelected", "searchChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1112
1967
  }
1113
1968
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewer, decorators: [{
1114
1969
  type: Component,
1115
- args: [{ selector: 'ngs-pdf-viewer', exportAs: 'ngsPdfViewer', standalone: true, imports: [
1970
+ args: [{ selector: 'ngs-pdf-viewer', exportAs: 'ngsPdfViewer', imports: [
1116
1971
  BlockLoader,
1117
1972
  Button,
1973
+ Divider,
1118
1974
  Icon,
1119
1975
  ImagePlaceholder,
1120
1976
  Menu,
@@ -1123,21 +1979,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
1123
1979
  MenuItem,
1124
1980
  MenuTrigger,
1125
1981
  Panel,
1982
+ PanelAside,
1126
1983
  PanelContent,
1127
1984
  PanelHeader,
1128
1985
  PanelSidebar,
1986
+ Toolbar,
1987
+ ToolbarItem,
1988
+ ToolbarSpacer,
1989
+ ToolbarTitle,
1990
+ PdfViewerAnnotations,
1991
+ PdfViewerSearch,
1129
1992
  ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1130
1993
  class: 'ngs-pdf-viewer not-prose',
1131
1994
  '[class.is-loading]': 'isLoading()',
1132
1995
  '[class.has-toolbar]': 'showToolbar()',
1133
1996
  '[class.has-page-list]': 'isPageListVisible()',
1134
1997
  '[class.has-error]': 'errorState()',
1135
- }, template: "<ngs-panel class=\"pdf-viewer-panel\">\n @if (showToolbar()) {\n <ngs-panel-header flex>\n <div class=\"pdf-viewer-toolbar\" aria-label=\"PDF viewer controls\">\n @if (showPageList()) {\n <button\n ngsIconButton\n type=\"button\"\n [attr.aria-label]=\"pageListVisible() ? 'Hide pages panel' : 'Show pages panel'\"\n [attr.aria-pressed]=\"pageListVisible()\"\n (click)=\"togglePageList()\">\n <ngs-icon\n [name]=\"pageListVisible() ? 'fluent:panel-left-contract-24-regular' : 'fluent:panel-left-expand-24-regular'\" />\n </button>\n }\n <button ngsIconButton type=\"button\" aria-label=\"Previous page\" [disabled]=\"!canGoPrevious()\" (click)=\"previousPage()\">\n <ngs-icon name=\"fluent:chevron-up-24-regular\" />\n </button>\n <span class=\"pdf-viewer-toolbar__page\">{{ activePage() }} / {{ pageCount() || 1 }}</span>\n <button ngsIconButton type=\"button\" aria-label=\"Next page\" [disabled]=\"!canGoNext()\" (click)=\"nextPage()\">\n <ngs-icon name=\"fluent:chevron-down-24-regular\" />\n </button>\n <span class=\"pdf-viewer-toolbar__spacer\"></span>\n <button ngsIconButton type=\"button\" aria-label=\"Zoom out\" [disabled]=\"!canZoomOut()\" (click)=\"zoomOut()\">\n <ngs-icon name=\"fluent:zoom-out-24-regular\" />\n </button>\n <span class=\"pdf-viewer-toolbar__zoom\">{{ zoomLabel() }}</span>\n <button ngsIconButton type=\"button\" aria-label=\"Zoom in\" [disabled]=\"!canZoomIn()\" (click)=\"zoomIn()\">\n <ngs-icon name=\"fluent:zoom-in-24-regular\" />\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"PDF viewer settings\" [ngsMenuTriggerFor]=\"settingsMenu\">\n <ngs-icon name=\"fluent:settings-24-regular\" />\n </button>\n </div>\n\n <ngs-menu #settingsMenu=\"ngsMenu\" xPosition=\"before\">\n <ngs-menu-heading>SPREAD MODE</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'single'\"\n (click)=\"setSpreadMode('single')\">\n <ngs-icon name=\"fluent:document-24-regular\" />\n <span>Single Page</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-odd'\"\n (click)=\"setSpreadMode('two-odd')\">\n <ngs-icon name=\"fluent:book-open-24-regular\" />\n <span>Two Page (Odd)</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-even'\"\n (click)=\"setSpreadMode('two-even')\">\n <ngs-icon name=\"fluent:document-text-24-regular\" />\n <span>Two Page (Even)</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>SCROLL LAYOUT</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'vertical'\"\n (click)=\"setScrollLayout('vertical')\">\n <ngs-icon name=\"fluent:arrow-sort-24-regular\" />\n <span>Vertical</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'horizontal'\"\n (click)=\"setScrollLayout('horizontal')\">\n <ngs-icon name=\"fluent:arrow-swap-24-regular\" />\n <span>Horizontal</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>PAGE ROTATION</ngs-menu-heading>\n <button ngs-menu-item type=\"button\" (click)=\"rotateClockwise()\">\n <ngs-icon name=\"fluent:arrow-clockwise-24-regular\" />\n <span>Rotate Clockwise</span>\n </button>\n <button ngs-menu-item type=\"button\" (click)=\"rotateCounterClockwise()\">\n <ngs-icon name=\"fluent:arrow-counterclockwise-24-regular\" />\n <span>Rotate Counter-Clockwise</span>\n </button>\n\n <ngs-menu-divider />\n <button ngs-menu-item type=\"button\" (click)=\"toggleFullscreen()\">\n <ngs-icon name=\"fluent:arrow-maximize-24-regular\" />\n <span>Fullscreen</span>\n </button>\n </ngs-menu>\n </ngs-panel-header>\n }\n\n @if (isPageListVisible()) {\n <ngs-panel-sidebar>\n <div class=\"pdf-viewer-sidebar\" #pageList>\n <div class=\"pdf-viewer-sidebar__title\">Pages</div>\n <div class=\"pdf-viewer-sidebar__pages\" aria-label=\"PDF pages\">\n @for (pageItem of pageItems(); track pageItem.pageNumber) {\n <button\n class=\"pdf-viewer-sidebar__page\"\n type=\"button\"\n [class.is-active]=\"pageItem.pageNumber === activePage()\"\n [attr.aria-current]=\"pageItem.pageNumber === activePage() ? 'page' : null\"\n [attr.aria-label]=\"'Open page ' + pageItem.pageNumber\"\n [attr.data-ngs-pdf-page-button]=\"pageItem.pageNumber\"\n (click)=\"setPage(pageItem.pageNumber)\">\n <span class=\"pdf-viewer-sidebar__thumb\" aria-hidden=\"true\">\n @if (pageItem.thumbnail; as thumbnail) {\n <img\n [src]=\"thumbnail.url\"\n [attr.width]=\"thumbnail.width\"\n [attr.height]=\"thumbnail.height\"\n alt=\"\"\n draggable=\"false\" />\n } @else {\n <ngs-image-placeholder />\n }\n </span>\n <span class=\"pdf-viewer-sidebar__label\">Page {{ pageItem.pageNumber }}</span>\n </button>\n }\n </div>\n </div>\n </ngs-panel-sidebar>\n }\n\n <ngs-panel-content>\n <div class=\"pdf-viewer-body\" #viewerBody (scroll)=\"onViewerScroll()\">\n <ngs-block-loader [loading]=\"isLoading()\">Loading PDF...</ngs-block-loader>\n @if (errorState()) {\n <div class=\"pdf-viewer-state pdf-viewer-state--error\">\n <ngs-icon name=\"fluent:error-circle-24-regular\" />\n <span>PDF could not be loaded.</span>\n </div>\n } @else if (!src()) {\n <div class=\"pdf-viewer-state\">\n <ngs-icon name=\"fluent:document-pdf-24-regular\" />\n <span>No PDF selected.</span>\n </div>\n } @else {\n <div class=\"pdf-viewer-pages\">\n @for (pdfPage of renderedPages(); track pdfPage.pageNumber) {\n <article\n class=\"pdf-viewer-page\"\n [attr.aria-label]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.data-ngs-pdf-page]=\"pdfPage.pageNumber\">\n <div\n class=\"pdf-viewer-page__surface\"\n [style.width.px]=\"pdfPage.width\"\n [style.height.px]=\"pdfPage.height\"\n (pointerdown)=\"startTextSelection($event, pdfPage)\"\n (pointermove)=\"updateTextSelection($event, pdfPage)\"\n (pointerup)=\"finishTextSelection($event, pdfPage)\"\n (pointercancel)=\"cancelTextSelection()\"\n (mousedown)=\"startTextSelection($event, pdfPage)\"\n (mousemove)=\"updateTextSelection($event, pdfPage)\"\n (mouseup)=\"finishTextSelection($event, pdfPage)\">\n @if (isPageImageFresh(pdfPage) && pdfPage.url; as pageUrl) {\n <img\n [src]=\"pageUrl\"\n [alt]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.width]=\"pdfPage.width\"\n [attr.height]=\"pdfPage.height\"\n draggable=\"false\" />\n } @else {\n <div class=\"pdf-viewer-page__placeholder\" aria-hidden=\"true\">\n @if (pdfPage.isRendering) {\n <span></span>\n }\n </div>\n }\n @if (selectionRectsForPage(pdfPage.pageNumber); as selectionRects) {\n @if (selectionRects.length > 0) {\n <div class=\"pdf-viewer-page__selection-layer\" aria-hidden=\"true\">\n @for (selectionRect of selectionRects; track selectionRect.left + '-' + selectionRect.top + '-' + $index) {\n <div\n class=\"pdf-viewer-page__selection-rect\"\n [style.left.px]=\"selectionRect.left\"\n [style.top.px]=\"selectionRect.top\"\n [style.width.px]=\"selectionRect.width\"\n [style.height.px]=\"selectionRect.height\"></div>\n }\n </div>\n }\n }\n </div>\n </article>\n }\n </div>\n }\n </div>\n </ngs-panel-content>\n</ngs-panel>\n", styles: [":host{--ngs-pdf-viewer-height: 640px;--ngs-pdf-viewer-background: var(--ngs-color-surface-container-low, #f5f6f8);--ngs-pdf-viewer-page-background: var(--ngs-color-surface, #ffffff);--ngs-pdf-viewer-page-shadow: var(--ngs-shadow-md, 0 8px 24px rgba(15, 23, 42, .12));--ngs-pdf-viewer-border-color: var(--ngs-color-outline-variant, rgba(15, 23, 42, .12));--ngs-pdf-viewer-radius: var(--ngs-radius-md, 10px);--ngs-pdf-viewer-sidebar-width: 152px;--ngs-pdf-viewer-selection-background: rgb(33 150 243 / .34);display:block;height:var(--ngs-pdf-viewer-height);min-height:320px;overflow:hidden;border:1px solid var(--ngs-pdf-viewer-border-color);border-radius:var(--ngs-pdf-viewer-radius);background:var(--ngs-pdf-viewer-background);color:var(--ngs-color-on-surface, #111827)}:host ngs-panel{height:100%;overflow:hidden;border-radius:inherit;background:var(--ngs-pdf-viewer-background)}:host ngs-panel-header{min-width:0;border-bottom:1px solid var(--ngs-pdf-viewer-border-color);background:var(--ngs-color-surface, #ffffff)}:host ngs-panel-sidebar{width:var(--ngs-pdf-viewer-sidebar-width);min-width:var(--ngs-pdf-viewer-sidebar-width);border-right:1px solid var(--ngs-pdf-viewer-border-color);background:var(--ngs-color-surface, #ffffff)}:host ngs-panel-content{min-width:0;min-height:0;overflow:hidden;background:var(--ngs-pdf-viewer-background)}:host .pdf-viewer-toolbar{display:flex;align-items:center;gap:var(--ngs-spacing-1, .25rem);width:100%;padding:0 var(--ngs-spacing-2, .5rem)}:host .pdf-viewer-toolbar__page,:host .pdf-viewer-toolbar__zoom{min-width:76px;text-align:center;font-size:var(--ngs-font-size-sm, .875rem);font-weight:500;color:var(--ngs-color-on-surface-variant, #4b5563)}:host .pdf-viewer-toolbar__spacer{flex:1 1 auto}:host .pdf-viewer-sidebar{display:flex;flex-direction:column;gap:var(--ngs-spacing-2, .5rem);height:100%;min-height:0;padding:var(--ngs-spacing-3, .75rem);overflow:auto}:host .pdf-viewer-sidebar__title{padding:0 var(--ngs-spacing-1, .25rem);font-size:var(--ngs-font-size-xs, .75rem);font-weight:600;color:var(--ngs-color-on-surface-variant, #6b7280)}:host .pdf-viewer-sidebar__pages{display:flex;flex-direction:column;gap:var(--ngs-spacing-2, .5rem);min-height:0}:host .pdf-viewer-sidebar__page{display:flex;flex-direction:column;align-items:stretch;gap:var(--ngs-spacing-1, .25rem);width:100%;padding:var(--ngs-spacing-1, .25rem);border:1px solid transparent;border-radius:calc(var(--ngs-pdf-viewer-radius) - 4px);background:transparent;color:var(--ngs-color-on-surface-variant, #4b5563);cursor:pointer;text-align:left}:host .pdf-viewer-sidebar__page:hover .pdf-viewer-sidebar__thumb{border-color:var(--ngs-color-outline, rgba(15, 23, 42, .24))}:host .pdf-viewer-sidebar__page:focus-visible{outline:2px solid var(--ngs-color-primary, #155eef);outline-offset:2px}:host .pdf-viewer-sidebar__page.is-active{color:var(--ngs-color-on-surface, #111827)}:host .pdf-viewer-sidebar__page.is-active .pdf-viewer-sidebar__thumb{border-color:var(--ngs-color-primary, #155eef);background:var(--ngs-color-primary-container, rgba(21, 94, 239, .08));box-shadow:0 0 0 2px var(--ngs-color-primary-container, rgba(21, 94, 239, .14)),var(--ngs-shadow-xs, 0 1px 2px rgba(15, 23, 42, .08))}:host .pdf-viewer-sidebar__thumb{display:flex;align-items:center;justify-content:center;aspect-ratio:3/4;width:100%;overflow:hidden;border:1px solid var(--ngs-pdf-viewer-border-color);border-radius:calc(var(--ngs-pdf-viewer-radius) - 6px);background:var(--ngs-pdf-viewer-page-background);box-shadow:var(--ngs-shadow-xs, 0 1px 2px rgba(15, 23, 42, .08))}:host .pdf-viewer-sidebar__thumb img{display:block;width:100%;height:100%;object-fit:contain;-webkit-user-select:none;user-select:none}:host .pdf-viewer-sidebar__thumb ngs-image-placeholder{width:100%;height:100%}:host .pdf-viewer-sidebar__thumb ngs-image-placeholder ::ng-deep svg{width:64%;min-width:0}:host .pdf-viewer-sidebar__thumb ngs-icon{font-size:1.375rem;color:var(--ngs-color-on-surface-variant, #6b7280)}:host .pdf-viewer-sidebar__label{display:block;overflow:hidden;font-size:var(--ngs-font-size-xs, .75rem);font-weight:500;line-height:1.25;text-align:center;text-overflow:ellipsis;white-space:nowrap}:host .pdf-viewer-body{position:absolute;inset:0;overflow:auto}:host .pdf-viewer-pages{display:flex;flex-direction:column;align-items:center;gap:var(--ngs-spacing-6, 1.5rem);min-height:100%;padding:var(--ngs-spacing-6, 1.5rem)}:host .pdf-viewer-page{width:max-content;max-width:none}:host .pdf-viewer-page__surface{position:relative;overflow:hidden;cursor:text;-webkit-user-select:none;user-select:none;border-radius:calc(var(--ngs-pdf-viewer-radius) - 4px);background:var(--ngs-pdf-viewer-page-background);box-shadow:var(--ngs-pdf-viewer-page-shadow);touch-action:none}:host .pdf-viewer-page__surface img{display:block;width:100%;height:100%;max-width:none;-webkit-user-select:none;user-select:none;pointer-events:none}:host .pdf-viewer-page__placeholder{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:var(--ngs-pdf-viewer-page-background);pointer-events:none}:host .pdf-viewer-page__placeholder span{width:32px;height:32px;border:2px solid var(--ngs-color-outline-variant, rgba(15, 23, 42, .12));border-top-color:var(--ngs-color-primary, #155eef);border-radius:999px;animation:ngs-pdf-viewer-page-spin .8s linear infinite}:host .pdf-viewer-page__selection-layer{position:absolute;inset:0;z-index:1;overflow:hidden;isolation:isolate;mix-blend-mode:multiply;forced-color-adjust:none;pointer-events:none}:host .pdf-viewer-page__selection-rect{position:absolute;background:var(--ngs-pdf-viewer-selection-background);pointer-events:none}:host .pdf-viewer-state{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--ngs-spacing-3, .75rem);padding:var(--ngs-spacing-6, 1.5rem);color:var(--ngs-color-on-surface-variant, #6b7280);text-align:center}:host .pdf-viewer-state ngs-icon{font-size:2rem}:host .pdf-viewer-state--error{color:var(--ngs-color-danger, #dc2626)}@keyframes ngs-pdf-viewer-page-spin{to{transform:rotate(360deg)}}\n"] }]
1136
- }], ctorParameters: () => [], propDecorators: { viewerBody: [{ type: i0.ViewChild, args: ['viewerBody', { isSignal: true }] }], pageList: [{ type: i0.ViewChild, args: ['pageList', { isSignal: true }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], wasmUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "wasmUrl", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], scale: [{ type: i0.Input, args: [{ isSignal: true, alias: "scale", required: false }] }], minScale: [{ type: i0.Input, args: [{ isSignal: true, alias: "minScale", required: false }] }], maxScale: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxScale", required: false }] }], zoomStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoomStep", required: false }] }], renderAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "renderAll", required: false }] }], showToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToolbar", required: false }] }], showPageList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageList", required: false }] }], withAnnotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "withAnnotations", required: false }] }], withForms: [{ type: i0.Input, args: [{ isSignal: true, alias: "withForms", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], pageChanged: [{ type: i0.Output, args: ["pageChanged"] }], pageRendered: [{ type: i0.Output, args: ["pageRendered"] }], error: [{ type: i0.Output, args: ["error"] }] } });
1998
+ }, template: "<ngs-panel class=\"pdf-viewer-panel h-full overflow-hidden rounded-lg border border-border bg-surface-container-low text-on-surface\">\n @if (showToolbar()) {\n <ngs-panel-header class=\"min-w-0 border-b border-border bg-surface\" flex>\n <ngs-toolbar class=\"pdf-viewer-toolbar flex h-14 w-full min-w-0 items-center gap-4 overflow-visible px-2 text-on-surface-variant\" aria-label=\"PDF viewer controls\">\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__start flex min-w-0 flex-none items-center gap-2\">\n @if (showPageList()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-label]=\"pageListVisible() ? 'Hide pages panel' : 'Show pages panel'\"\n [attr.aria-pressed]=\"pageListVisible()\"\n (click)=\"togglePageList()\">\n <ngs-icon\n class=\"text-2xl\"\n [name]=\"pageListVisible() ? 'fluent:panel-left-contract-24-regular' : 'fluent:panel-left-expand-24-regular'\" />\n </button>\n }\n <ngs-divider vertical fixedHeight />\n </ngs-toolbar-item>\n <ngs-toolbar-title class=\"min-w-0 flex-1 truncate text-sm! max-w-[20%] font-semibold! text-on-surface-variant\" [title]=\"displayDocumentName()\">\n {{ displayDocumentName() }}\n </ngs-toolbar-title>\n\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__center flex flex-none items-center gap-3\">\n <div class=\"pdf-viewer-toolbar__cluster flex min-w-0 items-center gap-2\" aria-label=\"Zoom controls\">\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Zoom out\"\n [disabled]=\"!canZoomOut()\"\n (click)=\"zoomOut()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:zoom-out-24-regular\" />\n </button>\n <button\n class=\"pdf-viewer-toolbar__zoom min-w-12 cursor-pointer border-0 bg-transparent p-0 text-center text-sm text-on-surface hover:text-primary\"\n type=\"button\"\n aria-label=\"Zoom presets\"\n [ngsMenuTriggerFor]=\"zoomMenu\">\n {{ zoomLabel() }}\n </button>\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Zoom in\"\n [disabled]=\"!canZoomIn()\"\n (click)=\"zoomIn()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:zoom-in-24-regular\" />\n </button>\n </div>\n\n <ngs-divider vertical fixedHeight />\n\n <div class=\"pdf-viewer-toolbar__cluster flex min-w-0 items-center gap-2\" aria-label=\"Page navigation\">\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Previous page\"\n [disabled]=\"!canGoPrevious()\"\n (click)=\"previousPage()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:chevron-left-24-regular\" />\n </button>\n <span class=\"pdf-viewer-toolbar__page inline-flex h-9 min-w-16 items-center justify-center gap-2 rounded bg-surface-container-low text-sm font-bold text-on-surface\">\n <span>{{ activePage() }}</span>\n <span>/</span>\n <span>{{ pageCount() || 1 }}</span>\n </span>\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"Next page\"\n [disabled]=\"!canGoNext()\"\n (click)=\"nextPage()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:chevron-right-24-regular\" />\n </button>\n </div>\n </ngs-toolbar-item>\n\n <ngs-toolbar-spacer />\n\n <ngs-toolbar-item class=\"pdf-viewer-toolbar__end flex min-w-0 flex-1 items-center justify-end gap-2\" aria-label=\"PDF tools\">\n @if (showSearchPanel()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-pressed]=\"isSearchPanelVisible()\"\n aria-label=\"Search\"\n [disabled]=\"!asidePanelInteractive()\"\n (click)=\"toggleSearchPanel()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:search-24-regular\" />\n </button>\n }\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n aria-label=\"PDF viewer settings\"\n [ngsMenuTriggerFor]=\"settingsMenu\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:settings-24-regular\" />\n </button>\n @if (showAnnotationsPanel()) {\n <button\n class=\"pdf-viewer-toolbar__button size-10 text-on-surface-variant hover:bg-surface-container-low hover:text-on-surface\"\n ngsIconButton\n type=\"button\"\n [attr.aria-pressed]=\"isAnnotationsPanelVisible()\"\n aria-label=\"Toggle annotations panel\"\n [disabled]=\"!asidePanelInteractive()\"\n (click)=\"toggleAnnotationsPanel()\">\n <ngs-icon class=\"text-2xl\" name=\"fluent:text-bullet-list-square-edit-24-regular\" />\n </button>\n }\n </ngs-toolbar-item>\n </ngs-toolbar>\n\n <ngs-menu #zoomMenu=\"ngsMenu\">\n @for (zoomPreset of zoomPresets; track zoomPreset) {\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"isZoomPresetSelected(zoomPreset)\"\n (click)=\"setZoomPreset(zoomPreset)\">\n <span>{{ zoomPreset * 100 }}%</span>\n </button>\n }\n\n <ngs-menu-divider />\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"zoomMode() === 'fit-page'\"\n (click)=\"fitToPage()\">\n <ngs-icon name=\"fluent:resize-large-24-regular\" />\n <span>Fit to Page</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"zoomMode() === 'fit-width'\"\n (click)=\"fitToWidth()\">\n <ngs-icon name=\"fluent:resize-24-regular\" />\n <span>Fit to Width</span>\n </button>\n </ngs-menu>\n\n <ngs-menu #settingsMenu=\"ngsMenu\" xPosition=\"before\">\n <ngs-menu-heading>SPREAD MODE</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'single'\"\n (click)=\"setSpreadMode('single')\">\n <ngs-icon name=\"fluent:document-24-regular\" />\n <span>Single Page</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-odd'\"\n (click)=\"setSpreadMode('two-odd')\">\n <ngs-icon name=\"fluent:book-open-24-regular\" />\n <span>Two Page (Odd)</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"spreadMode() === 'two-even'\"\n (click)=\"setSpreadMode('two-even')\">\n <ngs-icon name=\"fluent:document-text-24-regular\" />\n <span>Two Page (Even)</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>SCROLL LAYOUT</ngs-menu-heading>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'vertical'\"\n (click)=\"setScrollLayout('vertical')\">\n <ngs-icon name=\"fluent:arrow-sort-24-regular\" />\n <span>Vertical</span>\n </button>\n <button\n ngs-menu-item\n type=\"button\"\n [selected]=\"scrollLayout() === 'horizontal'\"\n (click)=\"setScrollLayout('horizontal')\">\n <ngs-icon name=\"fluent:arrow-swap-24-regular\" />\n <span>Horizontal</span>\n </button>\n\n <ngs-menu-divider />\n <ngs-menu-heading>PAGE ROTATION</ngs-menu-heading>\n <button ngs-menu-item type=\"button\" (click)=\"rotateClockwise()\">\n <ngs-icon name=\"fluent:arrow-clockwise-24-regular\" />\n <span>Rotate Clockwise</span>\n </button>\n <button ngs-menu-item type=\"button\" (click)=\"rotateCounterClockwise()\">\n <ngs-icon name=\"fluent:arrow-counterclockwise-24-regular\" />\n <span>Rotate Counter-Clockwise</span>\n </button>\n\n <ngs-menu-divider />\n <button ngs-menu-item type=\"button\" (click)=\"toggleFullscreen()\">\n <ngs-icon name=\"fluent:arrow-maximize-24-regular\" />\n <span>Fullscreen</span>\n </button>\n </ngs-menu>\n </ngs-panel-header>\n }\n\n @if (isPageListVisible()) {\n <ngs-panel-sidebar class=\"w-[var(--ngs-pdf-viewer-sidebar-width)] min-w-[var(--ngs-pdf-viewer-sidebar-width)] border-r border-border bg-surface\">\n <div class=\"pdf-viewer-sidebar flex h-full min-h-0 flex-col gap-2 overflow-auto p-3\" #pageList>\n <div class=\"pdf-viewer-sidebar__title px-1 text-xs font-semibold text-on-surface-variant\">Pages</div>\n <div class=\"pdf-viewer-sidebar__pages flex min-h-0 flex-col gap-2\" aria-label=\"PDF pages\">\n @for (pageItem of pageItems(); track pageItem.pageNumber) {\n <button\n class=\"pdf-viewer-sidebar__page group flex w-full cursor-pointer flex-col items-stretch gap-1 rounded border border-transparent bg-transparent p-1 text-left text-on-surface-variant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary [&.is-active]:text-on-surface\"\n type=\"button\"\n [class.is-active]=\"pageItem.pageNumber === activePage()\"\n [attr.aria-current]=\"pageItem.pageNumber === activePage() ? 'page' : null\"\n [attr.aria-label]=\"'Open page ' + pageItem.pageNumber\"\n [attr.data-ngs-pdf-page-button]=\"pageItem.pageNumber\"\n (click)=\"setPage(pageItem.pageNumber)\">\n <span class=\"pdf-viewer-sidebar__thumb flex aspect-[3/4] w-full items-center justify-center overflow-hidden rounded border border-border bg-surface shadow-xs group-hover:border-border\" aria-hidden=\"true\">\n @if (pageItem.thumbnail; as thumbnail) {\n <img\n class=\"block h-full w-full select-none object-contain\"\n [src]=\"thumbnail.url\"\n [attr.width]=\"thumbnail.width\"\n [attr.height]=\"thumbnail.height\"\n alt=\"\"\n draggable=\"false\" />\n } @else {\n <ngs-image-placeholder class=\"h-full w-full\" />\n }\n </span>\n <span class=\"pdf-viewer-sidebar__label block truncate text-center text-xs font-medium leading-tight\">Page {{ pageItem.pageNumber }}</span>\n </button>\n }\n </div>\n </div>\n </ngs-panel-sidebar>\n }\n\n <ngs-panel-content class=\"min-h-0 min-w-0 overflow-hidden bg-surface-container-low\">\n <div class=\"pdf-viewer-body absolute inset-0 overflow-auto\" #viewerBody (scroll)=\"onViewerScroll()\" (wheel)=\"onViewerWheel($event)\">\n <ngs-block-loader [loading]=\"isLoading()\">Loading PDF...</ngs-block-loader>\n @if (errorState()) {\n <div class=\"pdf-viewer-state pdf-viewer-state--error absolute inset-0 flex flex-col items-center justify-center gap-3 p-6 text-center text-danger\">\n <ngs-icon class=\"text-3xl\" name=\"fluent:error-circle-24-regular\" />\n <span>PDF could not be loaded.</span>\n </div>\n } @else if (!src()) {\n <div class=\"pdf-viewer-state absolute inset-0 flex flex-col items-center justify-center gap-3 p-6 text-center text-on-surface-variant\">\n <ngs-icon class=\"text-3xl\" name=\"fluent:document-pdf-24-regular\" />\n <span>No PDF selected.</span>\n </div>\n } @else {\n <div\n class=\"pdf-viewer-pages\"\n [class.is-horizontal]=\"scrollLayout() === 'horizontal'\"\n [class.is-vertical]=\"scrollLayout() === 'vertical'\"\n [class.is-spread]=\"spreadMode() !== 'single'\">\n @for (spread of pageSpreads(); track spread.id) {\n <div class=\"pdf-viewer-spread\" [attr.data-ngs-pdf-spread]=\"spread.id\">\n @if (spread.leadingPlaceholder) {\n @if (spread.leadingPlaceholderPage; as placeholderPage) {\n <div\n class=\"pdf-viewer-spread__placeholder\"\n [style.width.px]=\"placeholderPage.width\"\n [style.height.px]=\"placeholderPage.height\"\n aria-hidden=\"true\"></div>\n }\n }\n @for (pdfPage of spread.pages; track pdfPage.pageNumber) {\n <article\n class=\"pdf-viewer-page w-max max-w-none\"\n [attr.aria-label]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.data-ngs-pdf-page]=\"pdfPage.pageNumber\">\n <div\n class=\"pdf-viewer-page__surface relative cursor-text overflow-hidden rounded bg-surface shadow-md select-none touch-none\"\n [style.width.px]=\"pdfPage.width\"\n [style.height.px]=\"pdfPage.height\"\n (pointerdown)=\"startTextSelection($event, pdfPage)\"\n (pointermove)=\"updateTextSelection($event, pdfPage)\"\n (pointerup)=\"finishTextSelection($event, pdfPage)\"\n (pointercancel)=\"cancelTextSelection()\"\n (mousedown)=\"startTextSelection($event, pdfPage)\"\n (mousemove)=\"updateTextSelection($event, pdfPage)\"\n (mouseup)=\"finishTextSelection($event, pdfPage)\">\n @if (pdfPage.url; as pageUrl) {\n <img\n class=\"block h-full w-full max-w-none select-none pointer-events-none\"\n [src]=\"pageUrl\"\n [alt]=\"'PDF page ' + pdfPage.pageNumber\"\n [attr.width]=\"pdfPage.width\"\n [attr.height]=\"pdfPage.height\"\n draggable=\"false\" />\n } @else {\n <div class=\"pdf-viewer-page__placeholder pointer-events-none absolute inset-0 flex items-center justify-center bg-surface\" aria-hidden=\"true\">\n @if (pdfPage.isRendering) {\n <span class=\"size-8 animate-spin rounded-full border-2 border-border border-t-primary\"></span>\n }\n </div>\n }\n @if (selectionRectsForPage(pdfPage.pageNumber); as selectionRects) {\n @if (selectionRects.length > 0) {\n <div class=\"pdf-viewer-page__selection-layer pointer-events-none absolute inset-0 z-[1] isolate overflow-hidden mix-blend-multiply\" aria-hidden=\"true\">\n @for (selectionRect of selectionRects; track selectionRect.left + '-' + selectionRect.top + '-' + $index) {\n <div\n class=\"pdf-viewer-page__selection-rect pointer-events-none absolute bg-primary/30\"\n [style.left.px]=\"selectionRect.left\"\n [style.top.px]=\"selectionRect.top\"\n [style.width.px]=\"selectionRect.width\"\n [style.height.px]=\"selectionRect.height\"></div>\n }\n </div>\n }\n }\n </div>\n </article>\n }\n </div>\n }\n </div>\n }\n </div>\n </ngs-panel-content>\n\n @if (isSearchPanelVisible() || isAnnotationsPanelVisible()) {\n <ngs-panel-aside class=\"w-[var(--ngs-pdf-viewer-aside-width)] min-w-[var(--ngs-pdf-viewer-aside-width)] border-l border-border bg-surface\">\n @if (isSearchPanelVisible()) {\n <ngs-pdf-viewer-search\n [query]=\"activeSearchQuery()\"\n [results]=\"pdfSearchResults()\"\n (closed)=\"closeAsidePanel()\"\n (searchChanged)=\"updatePdfSearch($event)\"\n (resultSelected)=\"selectSearchResult($event)\" />\n } @else {\n <ngs-pdf-viewer-annotations\n [annotations]=\"annotationItems()\"\n [annotationDefs]=\"annotationDefs()\"\n [annotationTypeProperty]=\"annotationTypeProperty()\"\n (closed)=\"closeAsidePanel()\"\n (pageSelected)=\"setPage($event)\" />\n }\n </ngs-panel-aside>\n }\n</ngs-panel>\n", styles: [":host{--ngs-pdf-viewer-height: 720px;--ngs-pdf-viewer-min-height: 720px;--ngs-pdf-viewer-sidebar-width: 152px;--ngs-pdf-viewer-aside-width: 360px;--ngs-pdf-viewer-page-gap: 24px;--ngs-pdf-viewer-spread-gap: 16px;--ngs-pdf-viewer-page-padding: 24px;display:block;height:var(--ngs-pdf-viewer-height);min-height:var(--ngs-pdf-viewer-min-height);overflow:hidden}:host .pdf-viewer-pages{box-sizing:border-box;display:grid;gap:var(--ngs-pdf-viewer-page-gap);min-height:100%;min-width:100%;padding:var(--ngs-pdf-viewer-page-padding)}:host .pdf-viewer-pages.is-vertical{align-content:start;grid-auto-flow:row;grid-auto-rows:max-content;justify-items:center}:host .pdf-viewer-pages.is-horizontal{align-content:center;align-items:center;grid-auto-columns:max-content;grid-auto-flow:column;justify-content:start;width:max-content}:host .pdf-viewer-spread{align-items:start;display:grid;gap:var(--ngs-pdf-viewer-spread-gap);grid-auto-columns:max-content;grid-auto-flow:column;justify-content:center}:host .pdf-viewer-spread__placeholder{pointer-events:none;visibility:hidden}:host .pdf-viewer-toolbar{position:relative}:host .pdf-viewer-toolbar__center{left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}:host .pdf-viewer-toolbar__start,:host .pdf-viewer-toolbar__end{position:relative;z-index:2}:host .pdf-viewer-sidebar__page.is-active .pdf-viewer-sidebar__thumb{outline:2px solid var(--ngs-color-primary)}:host .pdf-viewer-page__selection-layer{forced-color-adjust:none}\n"] }]
1999
+ }], ctorParameters: () => [], propDecorators: { viewerBody: [{ type: i0.ViewChild, args: ['viewerBody', { isSignal: true }] }], pageList: [{ type: i0.ViewChild, args: ['pageList', { isSignal: true }] }], annotationDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => PdfViewerAnnotationDef), { ...{ descendants: true }, isSignal: true }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], documentName: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentName", required: false }] }], wasmUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "wasmUrl", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], scale: [{ type: i0.Input, args: [{ isSignal: true, alias: "scale", required: false }] }], minScale: [{ type: i0.Input, args: [{ isSignal: true, alias: "minScale", required: false }] }], maxScale: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxScale", required: false }] }], zoomStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoomStep", required: false }] }], maxRenderPixels: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRenderPixels", required: false }] }], maxRenderDimension: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRenderDimension", required: false }] }], renderAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "renderAll", required: false }] }], showToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToolbar", required: false }] }], showPageList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageList", required: false }] }], showSearchPanel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearchPanel", required: false }] }], showAnnotationsPanel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAnnotationsPanel", required: false }] }], annotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotations", required: false }] }], annotationsDataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotationsDataSource", required: false }] }], annotationTypeProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotationTypeProperty", required: false }] }], searchQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchQuery", required: false }] }], withAnnotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "withAnnotations", required: false }] }], withForms: [{ type: i0.Input, args: [{ isSignal: true, alias: "withForms", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], pageChanged: [{ type: i0.Output, args: ["pageChanged"] }], pageRendered: [{ type: i0.Output, args: ["pageRendered"] }], error: [{ type: i0.Output, args: ["error"] }] } });
1137
2000
 
1138
2001
  /**
1139
2002
  * Generated bundle index. Do not edit.
1140
2003
  */
1141
2004
 
1142
- export { PdfViewer, PdfViewerEngineService };
2005
+ export { PdfViewer, PdfViewerAnnotationDef, PdfViewerAnnotations, PdfViewerEngineService, PdfViewerSearch };
1143
2006
  //# sourceMappingURL=ngstarter-ui-components-pdf-viewer.mjs.map