@ngstarter-ui/components 21.0.33 → 21.0.34

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,161 @@
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 } from '@angular/core';
4
+ import { 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, PanelAside, PanelSidebar } from '@ngstarter-ui/components/panel';
12
+ import { Toolbar, ToolbarSpacer, ToolbarTitle, ToolbarItem } from '@ngstarter-ui/components/toolbar';
13
+ import { isObservable } from 'rxjs';
14
+ import { Avatar } from '@ngstarter-ui/components/avatar';
15
+ import { Card, CardActions, CardAside, CardAvatar, CardContent, CardHeader, CardSubtitle, CardTitle } from '@ngstarter-ui/components/card';
16
+ import { Chip } from '@ngstarter-ui/components/chips';
17
+ import { FormField, IconButtonSuffix, IconPrefix } from '@ngstarter-ui/components/form-field';
18
+ import { Input } from '@ngstarter-ui/components/input';
10
19
  import { PluginRegistry } from '@embedpdf/core';
20
+ import { Checkbox } from '@ngstarter-ui/components/checkbox';
21
+
22
+ class PdfViewerAnnotations {
23
+ annotations = input([], ...(ngDevMode ? [{ debugName: "annotations" }] : /* istanbul ignore next */ []));
24
+ annotationDefs = input([], ...(ngDevMode ? [{ debugName: "annotationDefs" }] : /* istanbul ignore next */ []));
25
+ annotationTypeProperty = input('type', ...(ngDevMode ? [{ debugName: "annotationTypeProperty" }] : /* istanbul ignore next */ []));
26
+ closed = output();
27
+ pageSelected = output();
28
+ filterQuery = signal('', ...(ngDevMode ? [{ debugName: "filterQuery" }] : /* istanbul ignore next */ []));
29
+ filteredAnnotations = computed(() => {
30
+ const query = this.filterQuery().trim().toLocaleLowerCase();
31
+ const annotations = this.annotations();
32
+ if (!query) {
33
+ return annotations;
34
+ }
35
+ return annotations.filter((annotation) => this.annotationMatchesFilter(annotation, query));
36
+ }, ...(ngDevMode ? [{ debugName: "filteredAnnotations" }] : /* istanbul ignore next */ []));
37
+ setFilterQuery(event) {
38
+ this.filterQuery.set(event.target.value);
39
+ }
40
+ clearFilterQuery() {
41
+ this.filterQuery.set('');
42
+ }
43
+ getAnnotationTemplate(annotation, index) {
44
+ const annotationDefs = this.annotationDefs();
45
+ const typeProperty = this.annotationTypeProperty();
46
+ const matchingDef = annotationDefs.find((def) => def.matches(annotation, index, typeProperty))
47
+ ?? annotationDefs.find((def) => !def.hasWhen());
48
+ return matchingDef?.template ?? null;
49
+ }
50
+ getAnnotationTemplateContext(annotation, index) {
51
+ return {
52
+ $implicit: annotation,
53
+ annotation,
54
+ index,
55
+ goToPage: (pageNumber) => this.pageSelected.emit(pageNumber),
56
+ };
57
+ }
58
+ getAnnotationTypeLabel(annotation) {
59
+ const label = annotation.label ?? annotation.type;
60
+ return typeof label === 'string' && label.trim().length > 0 ? label : 'Comment';
61
+ }
62
+ getAvatarLabel(annotation) {
63
+ const explicitLabel = annotation.avatarLabel;
64
+ if (typeof explicitLabel === 'string' && explicitLabel.trim().length > 0) {
65
+ return explicitLabel.trim();
66
+ }
67
+ return annotation.author
68
+ .split(/\s+/)
69
+ .filter(Boolean)
70
+ .slice(0, 2)
71
+ .map((part) => part[0]?.toUpperCase())
72
+ .join('');
73
+ }
74
+ getAvatarImage(annotation) {
75
+ return typeof annotation.avatarUrl === 'string' ? annotation.avatarUrl : '';
76
+ }
77
+ getReplyLabel(annotation) {
78
+ return typeof annotation.replyLabel === 'string' && annotation.replyLabel.trim().length > 0
79
+ ? annotation.replyLabel
80
+ : 'Reply';
81
+ }
82
+ annotationMatchesFilter(annotation, query) {
83
+ return [
84
+ annotation.author,
85
+ annotation.time,
86
+ annotation.text,
87
+ annotation.type,
88
+ annotation.label,
89
+ `page ${annotation.pageNumber}`,
90
+ ]
91
+ .filter((value) => typeof value === 'string')
92
+ .some((value) => value.toLocaleLowerCase().includes(query));
93
+ }
94
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotations, deps: [], target: i0.ɵɵFactoryTarget.Component });
95
+ 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\" aria-label=\"PDF annotations\">\n <ngs-panel-header class=\"pdf-viewer-annotations__toolbar\">\n <h3>Annotations</h3>\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-panel-header>\n\n <ngs-panel-content>\n <div class=\"pdf-viewer-annotations__content\">\n <div class=\"pdf-viewer-annotations__filters\">\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\n <button\n ngsIconButton\n type=\"button\"\n aria-label=\"Annotation filters\">\n <ngs-icon name=\"fluent:filter-24-regular\" />\n </button>\n </div>\n\n <div class=\"pdf-viewer-annotations__list\">\n <ng-template #defaultAnnotationTemplate let-annotation let-goToPage=\"goToPage\">\n <ngs-card class=\"pdf-viewer-annotation-card\">\n <ngs-card-header>\n <ngs-avatar\n ngsCardAvatar\n variant=\"tonal\"\n [image]=\"getAvatarImage(annotation)\"\n [label]=\"getAvatarLabel(annotation)\"\n [alt]=\"annotation.author\" />\n\n <ngs-card-title>{{ annotation.author }}</ngs-card-title>\n @if (annotation.time) {\n <ngs-card-subtitle>{{ annotation.time }}</ngs-card-subtitle>\n }\n\n <ngs-card-aside>\n <ngs-chip appearance=\"tonal\">{{ getAnnotationTypeLabel(annotation) }}</ngs-chip>\n </ngs-card-aside>\n </ngs-card-header>\n\n <ngs-card-content>\n <p>{{ annotation.text }}</p>\n </ngs-card-content>\n\n <ngs-card-actions align=\"between\">\n <button ngsButton=\"tonal\" type=\"button\" (click)=\"goToPage(annotation.pageNumber)\">\n Page {{ annotation.pageNumber }}\n </button>\n\n <button ngsButton=\"text\" type=\"button\">\n <ngs-icon name=\"fluent:arrow-reply-24-regular\" />\n {{ getReplyLabel(annotation) }}\n </button>\n </ngs-card-actions>\n </ngs-card>\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=\"pdf-viewer-annotations__empty\">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%}:host ngs-panel.pdf-viewer-annotations{background:var(--ngs-color-surface)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__toolbar{display:flex;align-items:center;justify-content:space-between;padding:0 calc(var(--spacing, .25rem) * 4);border-bottom:1px solid var(--ngs-color-border)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__toolbar h3{margin:0;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;letter-spacing:0;text-transform:uppercase}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__content{display:flex;flex-direction:column;min-height:100%;background:var(--ngs-color-surface-container-lowest)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__filters{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding:calc(var(--spacing, .25rem) * 3) calc(var(--spacing, .25rem) * 4);border-bottom:1px solid var(--ngs-color-border)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__list{display:flex;flex:1 1 auto;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);padding:calc(var(--spacing, .25rem) * 4)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__empty{padding:calc(var(--spacing, .25rem) * 4);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);text-align:center}:host ngs-card.pdf-viewer-annotation-card{--ngs-card-bg: var(--ngs-color-surface);--ngs-card-radius: var(--ngs-radius-md);--ngs-card-padding: calc(var(--spacing, .25rem) * 4)}:host ngs-card.pdf-viewer-annotation-card ngs-card-header{align-items:flex-start}:host ngs-card.pdf-viewer-annotation-card ngs-card-title{font-weight:600}:host ngs-card.pdf-viewer-annotation-card ngs-card-subtitle{color:var(--ngs-color-on-surface-variant)}:host ngs-card.pdf-viewer-annotation-card ngs-card-content p{margin:0;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);line-height:1.45}:host ngs-card.pdf-viewer-annotation-card ngs-card-actions{padding:0 calc(var(--spacing, .25rem) * 3) calc(var(--spacing, .25rem) * 3)}:host ngs-card.pdf-viewer-annotation-card ngs-chip{text-transform:uppercase}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: Avatar, selector: "ngs-avatar,[ngs-avatar]", inputs: ["image", "variant", "clickable", "label", "alt", "automaticColor", "presenceIndicator"], exportAs: ["ngsAvatar"] }, { 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: Card, selector: "ngs-card", inputs: ["appearance"], exportAs: ["ngsCard"] }, { kind: "component", type: CardActions, selector: "ngs-card-actions, [ngs-card-actions], [ngsCardActions]", inputs: ["align"] }, { kind: "component", type: CardAside, selector: "ngs-card-aside, [ngs-card-aside], ngsCardAside" }, { kind: "directive", type: CardAvatar, selector: "ngs-card-avatar, [ngs-card-avatar], [ngsCardAvatar]" }, { kind: "component", type: CardContent, selector: "ngs-card-content, [ngs-card-content], [ngsCardContent]", inputs: ["withoutPadding"], exportAs: ["ngsCardContent"] }, { kind: "component", type: CardHeader, selector: "ngs-card-header", exportAs: ["ngsCardHeader"] }, { kind: "component", type: CardSubtitle, selector: "ngs-card-subtitle, [ngs-card-subtitle], [ngsCardSubtitle]", exportAs: ["ngsCardSubtitle"] }, { kind: "component", type: CardTitle, selector: "ngs-card-title, [ngs-card-title], [ngsCardTitle]", exportAs: ["ngsCardTitle"] }, { kind: "component", type: Chip, selector: "ngs-chip", inputs: ["appearance", "disabled", "value"], outputs: ["destroyed", "removed"], exportAs: ["ngsChip"] }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
96
+ }
97
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotations, decorators: [{
98
+ type: Component,
99
+ args: [{ selector: 'ngs-pdf-viewer-annotations', standalone: true, imports: [
100
+ Avatar,
101
+ Button,
102
+ Card,
103
+ CardActions,
104
+ CardAside,
105
+ CardAvatar,
106
+ CardContent,
107
+ CardHeader,
108
+ CardSubtitle,
109
+ CardTitle,
110
+ Chip,
111
+ FormField,
112
+ Icon,
113
+ IconButtonSuffix,
114
+ IconPrefix,
115
+ Input,
116
+ NgTemplateOutlet,
117
+ Panel,
118
+ PanelContent,
119
+ PanelHeader,
120
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
121
+ class: 'ngs-pdf-viewer-annotations',
122
+ }, template: "<ngs-panel class=\"pdf-viewer-annotations\" aria-label=\"PDF annotations\">\n <ngs-panel-header class=\"pdf-viewer-annotations__toolbar\">\n <h3>Annotations</h3>\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-panel-header>\n\n <ngs-panel-content>\n <div class=\"pdf-viewer-annotations__content\">\n <div class=\"pdf-viewer-annotations__filters\">\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\n <button\n ngsIconButton\n type=\"button\"\n aria-label=\"Annotation filters\">\n <ngs-icon name=\"fluent:filter-24-regular\" />\n </button>\n </div>\n\n <div class=\"pdf-viewer-annotations__list\">\n <ng-template #defaultAnnotationTemplate let-annotation let-goToPage=\"goToPage\">\n <ngs-card class=\"pdf-viewer-annotation-card\">\n <ngs-card-header>\n <ngs-avatar\n ngsCardAvatar\n variant=\"tonal\"\n [image]=\"getAvatarImage(annotation)\"\n [label]=\"getAvatarLabel(annotation)\"\n [alt]=\"annotation.author\" />\n\n <ngs-card-title>{{ annotation.author }}</ngs-card-title>\n @if (annotation.time) {\n <ngs-card-subtitle>{{ annotation.time }}</ngs-card-subtitle>\n }\n\n <ngs-card-aside>\n <ngs-chip appearance=\"tonal\">{{ getAnnotationTypeLabel(annotation) }}</ngs-chip>\n </ngs-card-aside>\n </ngs-card-header>\n\n <ngs-card-content>\n <p>{{ annotation.text }}</p>\n </ngs-card-content>\n\n <ngs-card-actions align=\"between\">\n <button ngsButton=\"tonal\" type=\"button\" (click)=\"goToPage(annotation.pageNumber)\">\n Page {{ annotation.pageNumber }}\n </button>\n\n <button ngsButton=\"text\" type=\"button\">\n <ngs-icon name=\"fluent:arrow-reply-24-regular\" />\n {{ getReplyLabel(annotation) }}\n </button>\n </ngs-card-actions>\n </ngs-card>\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=\"pdf-viewer-annotations__empty\">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%}:host ngs-panel.pdf-viewer-annotations{background:var(--ngs-color-surface)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__toolbar{display:flex;align-items:center;justify-content:space-between;padding:0 calc(var(--spacing, .25rem) * 4);border-bottom:1px solid var(--ngs-color-border)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__toolbar h3{margin:0;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;letter-spacing:0;text-transform:uppercase}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__content{display:flex;flex-direction:column;min-height:100%;background:var(--ngs-color-surface-container-lowest)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__filters{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding:calc(var(--spacing, .25rem) * 3) calc(var(--spacing, .25rem) * 4);border-bottom:1px solid var(--ngs-color-border)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__list{display:flex;flex:1 1 auto;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);padding:calc(var(--spacing, .25rem) * 4)}:host ngs-panel.pdf-viewer-annotations .pdf-viewer-annotations__empty{padding:calc(var(--spacing, .25rem) * 4);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);text-align:center}:host ngs-card.pdf-viewer-annotation-card{--ngs-card-bg: var(--ngs-color-surface);--ngs-card-radius: var(--ngs-radius-md);--ngs-card-padding: calc(var(--spacing, .25rem) * 4)}:host ngs-card.pdf-viewer-annotation-card ngs-card-header{align-items:flex-start}:host ngs-card.pdf-viewer-annotation-card ngs-card-title{font-weight:600}:host ngs-card.pdf-viewer-annotation-card ngs-card-subtitle{color:var(--ngs-color-on-surface-variant)}:host ngs-card.pdf-viewer-annotation-card ngs-card-content p{margin:0;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);line-height:1.45}:host ngs-card.pdf-viewer-annotation-card ngs-card-actions{padding:0 calc(var(--spacing, .25rem) * 3) calc(var(--spacing, .25rem) * 3)}:host ngs-card.pdf-viewer-annotation-card ngs-chip{text-transform:uppercase}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
123
+ }], 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"] }] } });
124
+
125
+ class PdfViewerAnnotationDef {
126
+ template;
127
+ annotationWhen = input(undefined, { ...(ngDevMode ? { debugName: "annotationWhen" } : /* istanbul ignore next */ {}), alias: 'ngsPdfViewerAnnotation' });
128
+ defWhen = input(undefined, { ...(ngDevMode ? { debugName: "defWhen" } : /* istanbul ignore next */ {}), alias: 'ngsPdfViewerAnnotationDef' });
129
+ when = input(undefined, { ...(ngDevMode ? { debugName: "when" } : /* istanbul ignore next */ {}), alias: 'ngsPdfViewerAnnotationWhen' });
130
+ constructor(template) {
131
+ this.template = template;
132
+ }
133
+ matches(annotation, index, typeProperty) {
134
+ const whenValue = this.whenValue();
135
+ if (!whenValue) {
136
+ return false;
137
+ }
138
+ if (typeof whenValue === 'function') {
139
+ return whenValue(annotation, index);
140
+ }
141
+ return annotation[typeProperty] === whenValue;
142
+ }
143
+ hasWhen() {
144
+ return !!this.whenValue();
145
+ }
146
+ whenValue() {
147
+ return this.when() ?? this.defWhen() ?? this.annotationWhen();
148
+ }
149
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotationDef, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
150
+ 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 });
151
+ }
152
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerAnnotationDef, decorators: [{
153
+ type: Directive,
154
+ args: [{
155
+ selector: '[ngsPdfViewerAnnotationDef], [ngsPdfViewerAnnotation]',
156
+ standalone: true,
157
+ }]
158
+ }], 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
159
 
12
160
  class PdfViewerEngineService {
13
161
  engines = new Map();
@@ -38,6 +186,145 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
38
186
  }]
39
187
  }] });
40
188
 
189
+ class PdfViewerSearch {
190
+ results = input([], ...(ngDevMode ? [{ debugName: "results" }] : /* istanbul ignore next */ []));
191
+ query = input('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
192
+ closed = output();
193
+ resultSelected = output();
194
+ searchChanged = output();
195
+ caseSensitive = signal(false, ...(ngDevMode ? [{ debugName: "caseSensitive" }] : /* istanbul ignore next */ []));
196
+ wholeWord = signal(false, ...(ngDevMode ? [{ debugName: "wholeWord" }] : /* istanbul ignore next */ []));
197
+ activeResultIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeResultIndex" }] : /* istanbul ignore next */ []));
198
+ queryValue = signal('', ...(ngDevMode ? [{ debugName: "queryValue" }] : /* istanbul ignore next */ []));
199
+ hasQuery = computed(() => this.queryValue().trim().length > 0, ...(ngDevMode ? [{ debugName: "hasQuery" }] : /* istanbul ignore next */ []));
200
+ visibleResultGroups = computed(() => {
201
+ const groups = new Map();
202
+ this.results().forEach((result, index) => {
203
+ const group = groups.get(result.pageNumber) ?? {
204
+ pageNumber: result.pageNumber,
205
+ results: [],
206
+ };
207
+ group.results.push({ result, index });
208
+ groups.set(result.pageNumber, group);
209
+ });
210
+ return [...groups.values()];
211
+ }, ...(ngDevMode ? [{ debugName: "visibleResultGroups" }] : /* istanbul ignore next */ []));
212
+ resultCountLabel = computed(() => {
213
+ const count = this.results().length;
214
+ return `${count} ${count === 1 ? 'result' : 'results'} found`;
215
+ }, ...(ngDevMode ? [{ debugName: "resultCountLabel" }] : /* istanbul ignore next */ []));
216
+ constructor() {
217
+ effect(() => {
218
+ const query = this.query();
219
+ untracked(() => {
220
+ this.queryValue.set(query);
221
+ this.activeResultIndex.set(0);
222
+ });
223
+ });
224
+ }
225
+ setQuery(event) {
226
+ this.queryValue.set(event.target.value);
227
+ this.activeResultIndex.set(0);
228
+ this.emitSearchChanged();
229
+ }
230
+ clearQuery() {
231
+ this.queryValue.set('');
232
+ this.activeResultIndex.set(0);
233
+ this.emitSearchChanged();
234
+ }
235
+ setCaseSensitive(value) {
236
+ this.caseSensitive.set(value);
237
+ this.activeResultIndex.set(0);
238
+ this.emitSearchChanged();
239
+ }
240
+ setWholeWord(value) {
241
+ this.wholeWord.set(value);
242
+ this.activeResultIndex.set(0);
243
+ this.emitSearchChanged();
244
+ }
245
+ previousResult() {
246
+ const count = this.results().length;
247
+ if (count === 0) {
248
+ return;
249
+ }
250
+ this.activeResultIndex.update((index) => (index - 1 + count) % count);
251
+ }
252
+ nextResult() {
253
+ const count = this.results().length;
254
+ if (count === 0) {
255
+ return;
256
+ }
257
+ this.activeResultIndex.update((index) => (index + 1) % count);
258
+ }
259
+ selectResult(result, index) {
260
+ this.activeResultIndex.set(index);
261
+ this.resultSelected.emit(result);
262
+ }
263
+ resultParts(result) {
264
+ const query = this.queryValue().trim();
265
+ if (!query) {
266
+ return [{ text: result.excerpt, highlight: false }];
267
+ }
268
+ const flags = this.caseSensitive() ? 'g' : 'gi';
269
+ const escapedQuery = this.escapeRegExp(query);
270
+ const pattern = this.wholeWord()
271
+ ? new RegExp(`\\b${escapedQuery}\\b`, flags)
272
+ : new RegExp(escapedQuery, flags);
273
+ const parts = [];
274
+ let lastIndex = 0;
275
+ let match;
276
+ while ((match = pattern.exec(result.excerpt)) !== null) {
277
+ if (match.index > lastIndex) {
278
+ parts.push({ text: result.excerpt.slice(lastIndex, match.index), highlight: false });
279
+ }
280
+ parts.push({ text: match[0], highlight: true });
281
+ lastIndex = match.index + match[0].length;
282
+ if (match[0].length === 0) {
283
+ pattern.lastIndex++;
284
+ }
285
+ }
286
+ if (lastIndex < result.excerpt.length) {
287
+ parts.push({ text: result.excerpt.slice(lastIndex), highlight: false });
288
+ }
289
+ return parts.length > 0 ? parts : [{ text: result.excerpt, highlight: false }];
290
+ }
291
+ emitSearchChanged() {
292
+ this.searchChanged.emit({
293
+ query: this.queryValue(),
294
+ options: {
295
+ caseSensitive: this.caseSensitive(),
296
+ wholeWord: this.wholeWord(),
297
+ },
298
+ });
299
+ }
300
+ escapeRegExp(value) {
301
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
302
+ }
303
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerSearch, deps: [], target: i0.ɵɵFactoryTarget.Component });
304
+ 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-5 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-content>\n <div class=\"pdf-viewer-search__content flex min-h-full flex-col gap-5 p-6\">\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 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 <ngs-divider />\n\n <div class=\"pdf-viewer-search__summary 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 <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\">\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 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: Divider, selector: "ngs-divider", inputs: ["vertical", "inset", "fixedHeight"], exportAs: ["ngsDivider"] }, { 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" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
305
+ }
306
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewerSearch, decorators: [{
307
+ type: Component,
308
+ args: [{ selector: 'ngs-pdf-viewer-search', standalone: true, imports: [
309
+ Button,
310
+ Checkbox,
311
+ Divider,
312
+ FormField,
313
+ Icon,
314
+ IconButtonSuffix,
315
+ IconPrefix,
316
+ Input,
317
+ Panel,
318
+ PanelContent,
319
+ PanelHeader,
320
+ Toolbar,
321
+ ToolbarSpacer,
322
+ ToolbarTitle,
323
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
324
+ class: 'ngs-pdf-viewer-search',
325
+ }, template: "<ngs-panel class=\"pdf-viewer-search\" aria-label=\"PDF search\">\n <ngs-panel-header class=\"ps-5 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-content>\n <div class=\"pdf-viewer-search__content flex min-h-full flex-col gap-5 p-6\">\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 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 <ngs-divider />\n\n <div class=\"pdf-viewer-search__summary 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 <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\">\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 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"] }]
326
+ }], 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"] }] } });
327
+
41
328
  class PdfViewer {
42
329
  textSelectionHorizontalPadding = 2;
43
330
  textSelectionLineHeight = 1.2;
@@ -49,16 +336,26 @@ class PdfViewer {
49
336
  isBrowser = isPlatformBrowser(this.platformId);
50
337
  viewerBody = viewChild('viewerBody', ...(ngDevMode ? [{ debugName: "viewerBody" }] : /* istanbul ignore next */ []));
51
338
  pageList = viewChild('pageList', ...(ngDevMode ? [{ debugName: "pageList" }] : /* istanbul ignore next */ []));
339
+ annotationDefs = contentChildren(PdfViewerAnnotationDef, { ...(ngDevMode ? { debugName: "annotationDefs" } : /* istanbul ignore next */ {}), descendants: true });
52
340
  src = input(null, ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
341
+ documentName = input(null, ...(ngDevMode ? [{ debugName: "documentName" }] : /* istanbul ignore next */ []));
53
342
  wasmUrl = input('/assets/embedpdf/pdfium.wasm', ...(ngDevMode ? [{ debugName: "wasmUrl" }] : /* istanbul ignore next */ []));
54
343
  page = input(1, { ...(ngDevMode ? { debugName: "page" } : /* istanbul ignore next */ {}), transform: numberAttribute });
55
344
  scale = input(1, { ...(ngDevMode ? { debugName: "scale" } : /* istanbul ignore next */ {}), transform: numberAttribute });
56
345
  minScale = input(0.2, { ...(ngDevMode ? { debugName: "minScale" } : /* istanbul ignore next */ {}), transform: numberAttribute });
57
346
  maxScale = input(60, { ...(ngDevMode ? { debugName: "maxScale" } : /* istanbul ignore next */ {}), transform: numberAttribute });
58
347
  zoomStep = input(0.1, { ...(ngDevMode ? { debugName: "zoomStep" } : /* istanbul ignore next */ {}), transform: numberAttribute });
348
+ maxRenderPixels = input(128_000_000, { ...(ngDevMode ? { debugName: "maxRenderPixels" } : /* istanbul ignore next */ {}), transform: numberAttribute });
349
+ maxRenderDimension = input(12_000, { ...(ngDevMode ? { debugName: "maxRenderDimension" } : /* istanbul ignore next */ {}), transform: numberAttribute });
59
350
  renderAll = input(true, { ...(ngDevMode ? { debugName: "renderAll" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
60
351
  showToolbar = input(true, { ...(ngDevMode ? { debugName: "showToolbar" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
61
352
  showPageList = input(true, { ...(ngDevMode ? { debugName: "showPageList" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
353
+ showSearchPanel = input(true, { ...(ngDevMode ? { debugName: "showSearchPanel" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
354
+ showAnnotationsPanel = input(false, { ...(ngDevMode ? { debugName: "showAnnotationsPanel" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
355
+ annotations = input([], ...(ngDevMode ? [{ debugName: "annotations" }] : /* istanbul ignore next */ []));
356
+ annotationsDataSource = input(null, ...(ngDevMode ? [{ debugName: "annotationsDataSource" }] : /* istanbul ignore next */ []));
357
+ annotationTypeProperty = input('type', ...(ngDevMode ? [{ debugName: "annotationTypeProperty" }] : /* istanbul ignore next */ []));
358
+ searchQuery = input('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
62
359
  withAnnotations = input(true, { ...(ngDevMode ? { debugName: "withAnnotations" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
63
360
  withForms = input(true, { ...(ngDevMode ? { debugName: "withForms" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
64
361
  loaded = output();
@@ -72,12 +369,20 @@ class PdfViewer {
72
369
  pageCount = signal(0, ...(ngDevMode ? [{ debugName: "pageCount" }] : /* istanbul ignore next */ []));
73
370
  activePage = signal(1, ...(ngDevMode ? [{ debugName: "activePage" }] : /* istanbul ignore next */ []));
74
371
  zoom = signal(1, ...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
75
- pageListVisible = signal(true, ...(ngDevMode ? [{ debugName: "pageListVisible" }] : /* istanbul ignore next */ []));
372
+ pageListVisible = signal(false, ...(ngDevMode ? [{ debugName: "pageListVisible" }] : /* istanbul ignore next */ []));
373
+ searchPanelVisible = signal(false, ...(ngDevMode ? [{ debugName: "searchPanelVisible" }] : /* istanbul ignore next */ []));
374
+ annotationsPanelVisible = signal(false, ...(ngDevMode ? [{ debugName: "annotationsPanelVisible" }] : /* istanbul ignore next */ []));
76
375
  spreadMode = signal('single', ...(ngDevMode ? [{ debugName: "spreadMode" }] : /* istanbul ignore next */ []));
77
376
  scrollLayout = signal('horizontal', ...(ngDevMode ? [{ debugName: "scrollLayout" }] : /* istanbul ignore next */ []));
377
+ zoomMode = signal('custom', ...(ngDevMode ? [{ debugName: "zoomMode" }] : /* istanbul ignore next */ []));
378
+ annotationItems = signal([], ...(ngDevMode ? [{ debugName: "annotationItems" }] : /* istanbul ignore next */ []));
379
+ activeSearchQuery = signal('', ...(ngDevMode ? [{ debugName: "activeSearchQuery" }] : /* istanbul ignore next */ []));
380
+ pdfSearchResults = signal([], ...(ngDevMode ? [{ debugName: "pdfSearchResults" }] : /* istanbul ignore next */ []));
78
381
  selectionRects = signal([], ...(ngDevMode ? [{ debugName: "selectionRects" }] : /* istanbul ignore next */ []));
79
382
  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 */ []));
383
+ isPageListVisible = computed(() => this.showPageList() && this.pageListVisible(), ...(ngDevMode ? [{ debugName: "isPageListVisible" }] : /* istanbul ignore next */ []));
384
+ isSearchPanelVisible = computed(() => this.showSearchPanel() && this.searchPanelVisible(), ...(ngDevMode ? [{ debugName: "isSearchPanelVisible" }] : /* istanbul ignore next */ []));
385
+ isAnnotationsPanelVisible = computed(() => this.showAnnotationsPanel() && !this.searchPanelVisible() && this.annotationsPanelVisible(), ...(ngDevMode ? [{ debugName: "isAnnotationsPanelVisible" }] : /* istanbul ignore next */ []));
81
386
  thumbnailPageMap = computed(() => new Map(this.thumbnailPages().map((thumbnail) => [thumbnail.pageNumber, thumbnail])), ...(ngDevMode ? [{ debugName: "thumbnailPageMap" }] : /* istanbul ignore next */ []));
82
387
  pageItems = computed(() => Array.from({ length: this.pageCount() }, (_, index) => {
83
388
  const pageNumber = index + 1;
@@ -91,6 +396,8 @@ class PdfViewer {
91
396
  canZoomOut = computed(() => this.zoom() > this.getScaleBounds().min, ...(ngDevMode ? [{ debugName: "canZoomOut" }] : /* istanbul ignore next */ []));
92
397
  canZoomIn = computed(() => this.zoom() < this.getScaleBounds().max, ...(ngDevMode ? [{ debugName: "canZoomIn" }] : /* istanbul ignore next */ []));
93
398
  zoomLabel = computed(() => `${Math.round(this.zoom() * 100)}%`, ...(ngDevMode ? [{ debugName: "zoomLabel" }] : /* istanbul ignore next */ []));
399
+ displayDocumentName = computed(() => this.documentName() || this.getSourceName(this.src()), ...(ngDevMode ? [{ debugName: "displayDocumentName" }] : /* istanbul ignore next */ []));
400
+ zoomPresets = [0.25, 0.5, 1, 1.25, 1.5, 2, 4, 8, 16];
94
401
  engine = null;
95
402
  pdfDocument = null;
96
403
  registry = null;
@@ -98,21 +405,30 @@ class PdfViewer {
98
405
  visiblePageRatios = new Map();
99
406
  loadToken = 0;
100
407
  renderToken = 0;
408
+ searchToken = 0;
101
409
  scrollSyncFrame = null;
102
410
  programmaticScrollTargetPage = null;
103
411
  programmaticScrollTimeout = null;
104
412
  pageObserverFrame = null;
105
413
  pageObserverTimeout = null;
106
414
  visiblePageRenderFrame = null;
415
+ visiblePageRenderTimeout = null;
107
416
  pageObserverRefreshAttempts = 0;
108
417
  selectionStart = null;
109
418
  isViewInitialized = false;
419
+ annotationDataSourceToken = 0;
420
+ annotationDataSourceCleanup = null;
421
+ lastZoomChangeTime = 0;
110
422
  programmaticScrollMinDuration = 900;
111
423
  programmaticScrollMaxDuration = 6000;
424
+ qualityRenderZoomIdleDelay = 160;
112
425
  constructor() {
113
426
  this.destroyRef.onDestroy(() => {
114
427
  this.loadToken++;
115
428
  this.renderToken++;
429
+ this.searchToken++;
430
+ this.annotationDataSourceToken++;
431
+ this.annotationDataSourceCleanup?.();
116
432
  this.cancelScrollSyncFrame();
117
433
  this.clearProgrammaticScrollLock();
118
434
  this.cancelPageObserverFrame();
@@ -123,6 +439,25 @@ class PdfViewer {
123
439
  void this.closeDocument();
124
440
  void this.registry?.destroy();
125
441
  });
442
+ effect((onCleanup) => {
443
+ const dataSource = this.annotationsDataSource();
444
+ const fallbackAnnotations = this.annotations();
445
+ const source = this.src();
446
+ const documentName = this.documentName() || this.getSourceName(source);
447
+ const pageCount = this.pageCount();
448
+ const token = ++this.annotationDataSourceToken;
449
+ const context = {
450
+ source,
451
+ documentName,
452
+ pageCount,
453
+ };
454
+ this.annotationDataSourceCleanup?.();
455
+ this.annotationDataSourceCleanup = untracked(() => this.loadAnnotationsDataSource(dataSource ?? fallbackAnnotations, context, token));
456
+ onCleanup(() => {
457
+ this.annotationDataSourceCleanup?.();
458
+ this.annotationDataSourceCleanup = null;
459
+ });
460
+ });
126
461
  effect(() => {
127
462
  const source = this.src();
128
463
  const wasmUrl = this.wasmUrl();
@@ -145,6 +480,18 @@ class PdfViewer {
145
480
  this.activePage.set(requestedPage);
146
481
  }
147
482
  });
483
+ effect(() => {
484
+ const query = this.searchQuery();
485
+ if (query !== untracked(() => this.activeSearchQuery())) {
486
+ this.activeSearchQuery.set(query);
487
+ untracked(() => {
488
+ void this.searchPdf(query, {
489
+ caseSensitive: false,
490
+ wholeWord: false,
491
+ });
492
+ });
493
+ }
494
+ });
148
495
  effect(() => {
149
496
  const zoom = this.zoom();
150
497
  const renderAll = this.renderAll();
@@ -190,14 +537,70 @@ class PdfViewer {
190
537
  this.setPage(this.activePage() + 1);
191
538
  }
192
539
  zoomIn() {
540
+ this.zoomMode.set('custom');
193
541
  this.setZoom(this.roundZoom(this.zoom() + this.sanitizeZoomStep()));
194
542
  }
195
543
  zoomOut() {
544
+ this.zoomMode.set('custom');
196
545
  this.setZoom(this.roundZoom(this.zoom() - this.sanitizeZoomStep()));
197
546
  }
198
547
  togglePageList() {
199
548
  this.pageListVisible.update((isVisible) => !isVisible);
200
549
  }
550
+ toggleSearchPanel() {
551
+ const nextVisible = !this.searchPanelVisible();
552
+ this.searchPanelVisible.set(nextVisible);
553
+ if (nextVisible) {
554
+ this.annotationsPanelVisible.set(false);
555
+ const query = this.searchQuery();
556
+ this.activeSearchQuery.set(query);
557
+ void this.searchPdf(query, {
558
+ caseSensitive: false,
559
+ wholeWord: false,
560
+ });
561
+ }
562
+ }
563
+ toggleAnnotationsPanel() {
564
+ const nextVisible = !this.annotationsPanelVisible();
565
+ this.annotationsPanelVisible.set(nextVisible);
566
+ if (nextVisible) {
567
+ this.searchPanelVisible.set(false);
568
+ }
569
+ }
570
+ closeAsidePanel() {
571
+ this.searchPanelVisible.set(false);
572
+ this.annotationsPanelVisible.set(false);
573
+ }
574
+ updatePdfSearch(event) {
575
+ this.activeSearchQuery.set(event.query);
576
+ return this.searchPdf(event.query, event.options);
577
+ }
578
+ selectSearchResult(result) {
579
+ this.setPage(result.pageNumber);
580
+ }
581
+ setZoomPreset(scale) {
582
+ this.zoomMode.set('custom');
583
+ this.setZoom(scale);
584
+ }
585
+ isZoomPresetSelected(scale) {
586
+ return this.zoomMode() === 'custom' && Math.abs(this.zoom() - this.sanitizeScale(scale)) < 0.0001;
587
+ }
588
+ fitToPage() {
589
+ const scale = this.getFitScale('fit-page');
590
+ if (scale === null) {
591
+ return;
592
+ }
593
+ this.zoomMode.set('fit-page');
594
+ this.setZoom(scale);
595
+ }
596
+ fitToWidth() {
597
+ const scale = this.getFitScale('fit-width');
598
+ if (scale === null) {
599
+ return;
600
+ }
601
+ this.zoomMode.set('fit-width');
602
+ this.setZoom(scale);
603
+ }
201
604
  setSpreadMode(mode) {
202
605
  this.spreadMode.set(mode);
203
606
  }
@@ -252,12 +655,31 @@ class PdfViewer {
252
655
  this.scheduleCurrentVisiblePagesRender();
253
656
  }) ?? null;
254
657
  }
658
+ onViewerWheel(event) {
659
+ if (!event.metaKey && !event.ctrlKey) {
660
+ return;
661
+ }
662
+ if (!this.pdfDocument || this.isLoading()) {
663
+ return;
664
+ }
665
+ event.preventDefault();
666
+ event.stopPropagation();
667
+ const container = this.viewerBody()?.nativeElement;
668
+ const anchor = container ? this.getZoomAnchor(container, event) : null;
669
+ const delta = event.deltaY || event.deltaX;
670
+ const direction = delta < 0 ? 1 : -1;
671
+ const multiplier = Math.max(1, Math.min(6, Math.abs(delta) / 100));
672
+ const nextZoom = this.roundZoom(this.zoom() + direction * this.sanitizeZoomStep() * multiplier);
673
+ if (nextZoom === this.zoom()) {
674
+ return;
675
+ }
676
+ this.zoomMode.set('custom');
677
+ this.setZoom(nextZoom);
678
+ this.restoreZoomAnchor(anchor);
679
+ }
255
680
  selectionRectsForPage(pageNumber) {
256
681
  return this.selectionRects().filter((rect) => rect.pageNumber === pageNumber);
257
682
  }
258
- isPageImageFresh(page) {
259
- return !!page.url && Math.abs((page.renderedScale ?? 0) - page.scale) < 0.0001;
260
- }
261
683
  startTextSelection(event, page) {
262
684
  if (event.button !== 0 || page.textGlyphs.length === 0) {
263
685
  return;
@@ -295,17 +717,148 @@ class PdfViewer {
295
717
  surface.releasePointerCapture?.(event.pointerId);
296
718
  }
297
719
  }
720
+ loadAnnotationsDataSource(dataSource, context, token) {
721
+ if (!dataSource) {
722
+ this.setAnnotationItems([], token);
723
+ return () => { };
724
+ }
725
+ try {
726
+ if (Array.isArray(dataSource)) {
727
+ this.setAnnotationItems(dataSource, token);
728
+ return () => { };
729
+ }
730
+ if (this.isServerAnnotationDataSource(dataSource)) {
731
+ let isActive = true;
732
+ dataSource.getAnnotations({
733
+ ...context,
734
+ successCallback: (annotations) => {
735
+ if (isActive) {
736
+ this.setAnnotationItems(annotations, token);
737
+ }
738
+ },
739
+ failCallback: () => {
740
+ if (isActive) {
741
+ this.setAnnotationItems([], token);
742
+ }
743
+ },
744
+ });
745
+ return () => {
746
+ isActive = false;
747
+ };
748
+ }
749
+ const result = typeof dataSource === 'function' ? dataSource(context) : dataSource;
750
+ return this.applyAnnotationDataSourceResult(result, token);
751
+ }
752
+ catch {
753
+ this.setAnnotationItems([], token);
754
+ return () => { };
755
+ }
756
+ }
757
+ applyAnnotationDataSourceResult(result, token) {
758
+ if (isObservable(result)) {
759
+ const subscription = result.subscribe({
760
+ next: (annotations) => this.setAnnotationItems(annotations, token),
761
+ error: () => this.setAnnotationItems([], token),
762
+ });
763
+ return () => subscription.unsubscribe();
764
+ }
765
+ if (this.isPromiseLike(result)) {
766
+ let isActive = true;
767
+ result
768
+ .then((annotations) => {
769
+ if (isActive) {
770
+ this.setAnnotationItems(annotations, token);
771
+ }
772
+ })
773
+ .catch(() => {
774
+ if (isActive) {
775
+ this.setAnnotationItems([], token);
776
+ }
777
+ });
778
+ return () => {
779
+ isActive = false;
780
+ };
781
+ }
782
+ this.setAnnotationItems(result, token);
783
+ return () => { };
784
+ }
785
+ setAnnotationItems(annotations, token) {
786
+ if (token === this.annotationDataSourceToken) {
787
+ this.annotationItems.set(annotations ?? []);
788
+ }
789
+ }
790
+ isServerAnnotationDataSource(dataSource) {
791
+ return typeof dataSource === 'object'
792
+ && dataSource !== null
793
+ && 'getAnnotations' in dataSource
794
+ && typeof dataSource.getAnnotations === 'function';
795
+ }
796
+ isPromiseLike(value) {
797
+ return typeof value === 'object'
798
+ && value !== null
799
+ && 'then' in value
800
+ && typeof value.then === 'function';
801
+ }
298
802
  cancelTextSelection() {
299
803
  this.selectionStart = null;
300
804
  }
805
+ async searchPdf(query, options) {
806
+ const token = ++this.searchToken;
807
+ const keyword = query.trim();
808
+ if (!keyword || !this.engine || !this.pdfDocument) {
809
+ this.pdfSearchResults.set([]);
810
+ return;
811
+ }
812
+ const flags = [];
813
+ if (options.caseSensitive) {
814
+ flags.push(MatchFlag.MatchCase);
815
+ }
816
+ if (options.wholeWord) {
817
+ flags.push(MatchFlag.MatchWholeWord);
818
+ }
819
+ try {
820
+ const searchResult = await this.engine.searchAllPages(this.pdfDocument, keyword, { flags }).toPromise();
821
+ if (token !== this.searchToken) {
822
+ return;
823
+ }
824
+ this.pdfSearchResults.set(searchResult.results.map((result, index) => this.toSearchResultView(result, index)));
825
+ }
826
+ catch (error) {
827
+ if (token === this.searchToken) {
828
+ this.pdfSearchResults.set([]);
829
+ this.error.emit(error);
830
+ }
831
+ }
832
+ }
833
+ toSearchResultView(result, index) {
834
+ const context = result.context;
835
+ const excerpt = [
836
+ context.truncatedLeft ? '...' : '',
837
+ context.before,
838
+ context.match,
839
+ context.after,
840
+ context.truncatedRight ? '...' : '',
841
+ ]
842
+ .filter((part) => part.length > 0)
843
+ .join(' ')
844
+ .replace(/\s+/g, ' ')
845
+ .trim();
846
+ return {
847
+ id: `${result.pageIndex}-${result.charIndex}-${result.charCount}-${index}`,
848
+ pageNumber: result.pageIndex + 1,
849
+ excerpt,
850
+ };
851
+ }
301
852
  async loadDocument(source, wasmUrl) {
302
853
  const token = ++this.loadToken;
303
854
  this.renderToken++;
304
855
  this.isLoading.set(true);
305
856
  this.errorState.set(null);
306
857
  this.pageCount.set(0);
858
+ this.searchToken++;
307
859
  this.selectionStart = null;
308
860
  this.selectionRects.set([]);
861
+ this.pdfSearchResults.set([]);
309
862
  this.revokeRenderedPages();
310
863
  this.revokeThumbnailPages();
311
864
  await this.closeDocument();
@@ -337,6 +890,10 @@ class PdfViewer {
337
890
  this.loaded.emit({ pageCount: pdfDocument.pageCount });
338
891
  this.initializePageShells(pdfDocument, this.zoom(), this.renderAll(), this.activePage());
339
892
  this.schedulePageObserverRefresh();
893
+ await this.searchPdf(this.activeSearchQuery() || this.searchQuery(), {
894
+ caseSensitive: false,
895
+ wholeWord: false,
896
+ });
340
897
  await this.renderVisiblePages(token, ++this.renderToken, {
341
898
  zoom: this.zoom(),
342
899
  activePage: this.activePage(),
@@ -403,6 +960,21 @@ class PdfViewer {
403
960
  this.cancelVisiblePageRenderFrame();
404
961
  const token = this.loadToken;
405
962
  const renderToken = ++this.renderToken;
963
+ const renderDelay = this.getQualityRenderDelay();
964
+ if (renderDelay > 0) {
965
+ this.visiblePageRenderTimeout = targetWindow.setTimeout(() => {
966
+ this.visiblePageRenderTimeout = null;
967
+ this.queueVisiblePageRenderFrame(token, renderToken, options);
968
+ }, renderDelay);
969
+ return;
970
+ }
971
+ this.queueVisiblePageRenderFrame(token, renderToken, options);
972
+ }
973
+ queueVisiblePageRenderFrame(token, renderToken, options) {
974
+ const targetWindow = this.document.defaultView;
975
+ if (!targetWindow) {
976
+ return;
977
+ }
406
978
  this.visiblePageRenderFrame = targetWindow.requestAnimationFrame(() => {
407
979
  this.visiblePageRenderFrame = null;
408
980
  void this.renderVisiblePages(token, renderToken, options);
@@ -435,11 +1007,12 @@ class PdfViewer {
435
1007
  if (currentPage.url && Math.abs((currentPage.renderedScale ?? 0) - renderScale) < 0.0001) {
436
1008
  continue;
437
1009
  }
438
- this.patchRenderedPage(pageNumber, { isRendering: true });
1010
+ this.patchRenderedPage(pageNumber, { isRendering: !currentPage.url });
439
1011
  const page = pdfDocument.pages[pageNumber - 1];
1012
+ const rasterOptions = this.getPageRasterRenderOptions(page, renderScale);
440
1013
  const blob = await this.engine.renderPage(pdfDocument, page, {
441
- scaleFactor: renderScale,
442
- dpr: this.getDevicePixelRatio(),
1014
+ scaleFactor: rasterOptions.scaleFactor,
1015
+ dpr: rasterOptions.dpr,
443
1016
  withAnnotations: options.withAnnotations,
444
1017
  withForms: options.withForms,
445
1018
  }).toPromise();
@@ -471,6 +1044,7 @@ class PdfViewer {
471
1044
  setZoom(scale) {
472
1045
  const nextZoom = this.sanitizeScale(scale);
473
1046
  if (this.zoom() !== nextZoom) {
1047
+ this.lastZoomChangeTime = this.getCurrentTime();
474
1048
  this.zoom.set(nextZoom);
475
1049
  }
476
1050
  this.applyInstantZoom(nextZoom);
@@ -492,9 +1066,7 @@ class PdfViewer {
492
1066
  const displaySize = this.getPageDisplaySize(pdfPage, nextScale);
493
1067
  return {
494
1068
  ...page,
495
- url: null,
496
1069
  scale: nextScale,
497
- renderedScale: null,
498
1070
  width: displaySize.width,
499
1071
  height: displaySize.height,
500
1072
  isRendering: false,
@@ -504,11 +1076,6 @@ class PdfViewer {
504
1076
  if (!hasChanges) {
505
1077
  return;
506
1078
  }
507
- for (const page of pages) {
508
- if (page.url) {
509
- this.revokeObjectUrl(page.url);
510
- }
511
- }
512
1079
  this.selectionStart = null;
513
1080
  this.selectionRects.set([]);
514
1081
  this.renderedPages.set(nextPages);
@@ -805,6 +1372,23 @@ class PdfViewer {
805
1372
  }
806
1373
  URL.revokeObjectURL(url);
807
1374
  }
1375
+ getSourceName(source) {
1376
+ if (typeof File !== 'undefined' && source instanceof File && source.name) {
1377
+ return source.name;
1378
+ }
1379
+ if (typeof source !== 'string' || source.trim().length === 0) {
1380
+ return 'Document.pdf';
1381
+ }
1382
+ try {
1383
+ const url = new URL(source, this.document.baseURI);
1384
+ const pathName = url.pathname.split('/').filter(Boolean).pop();
1385
+ return pathName ? decodeURIComponent(pathName) : 'Document.pdf';
1386
+ }
1387
+ catch {
1388
+ const pathName = source.split('?')[0]?.split('#')[0]?.split('/').filter(Boolean).pop();
1389
+ return pathName ? decodeURIComponent(pathName) : 'Document.pdf';
1390
+ }
1391
+ }
808
1392
  sanitizePage(pageNumber) {
809
1393
  return this.clamp(Math.trunc(Number.isFinite(pageNumber) ? pageNumber : 1), 1, Math.max(this.pageCount(), 1));
810
1394
  }
@@ -815,15 +1399,43 @@ class PdfViewer {
815
1399
  roundZoom(value) {
816
1400
  return this.sanitizeScale(Math.round(this.sanitizeScale(value) * 100) / 100);
817
1401
  }
1402
+ floorFitZoom(value) {
1403
+ return this.sanitizeScale(Math.floor(this.sanitizeScale(value) * 100) / 100);
1404
+ }
818
1405
  clamp(value, min, max) {
819
1406
  return Math.min(Math.max(value, min), max);
820
1407
  }
821
1408
  roundCssPixel(value) {
822
1409
  return Math.round(value * 100) / 100;
823
1410
  }
1411
+ parseCssPixel(value) {
1412
+ const parsed = Number.parseFloat(value);
1413
+ return Number.isFinite(parsed) ? parsed : 0;
1414
+ }
824
1415
  getDevicePixelRatio() {
825
1416
  return this.document.defaultView?.devicePixelRatio || 1;
826
1417
  }
1418
+ getPageRasterRenderOptions(page, scale) {
1419
+ const devicePixelRatio = Math.max(1, this.getDevicePixelRatio());
1420
+ const pageSize = this.getPageBaseSize(page);
1421
+ const maxRenderPixels = this.sanitizePositiveNumber(this.maxRenderPixels(), 128_000_000);
1422
+ const maxRenderDimension = this.sanitizePositiveNumber(this.maxRenderDimension(), 12_000);
1423
+ const targetEffectiveScale = scale * devicePixelRatio;
1424
+ const dimensionEffectiveScale = maxRenderDimension / Math.max(pageSize.width, pageSize.height);
1425
+ const pixelEffectiveScale = Math.sqrt(maxRenderPixels / (pageSize.width * pageSize.height));
1426
+ const effectiveScale = this.clamp(Math.min(targetEffectiveScale, dimensionEffectiveScale, pixelEffectiveScale), 0.05, targetEffectiveScale);
1427
+ const dprAtLayoutScale = effectiveScale / scale;
1428
+ if (dprAtLayoutScale >= 1) {
1429
+ return {
1430
+ scaleFactor: scale,
1431
+ dpr: this.clamp(dprAtLayoutScale, 1, devicePixelRatio),
1432
+ };
1433
+ }
1434
+ return {
1435
+ scaleFactor: effectiveScale,
1436
+ dpr: 1,
1437
+ };
1438
+ }
827
1439
  createDocumentId() {
828
1440
  return `ngs-pdf-${Date.now()}-${Math.random().toString(36).slice(2)}`;
829
1441
  }
@@ -849,12 +1461,82 @@ class PdfViewer {
849
1461
  this.pageObserverFrame = null;
850
1462
  }
851
1463
  cancelVisiblePageRenderFrame() {
1464
+ if (this.visiblePageRenderTimeout !== null) {
1465
+ this.document.defaultView?.clearTimeout(this.visiblePageRenderTimeout);
1466
+ this.visiblePageRenderTimeout = null;
1467
+ }
852
1468
  if (this.visiblePageRenderFrame === null) {
853
1469
  return;
854
1470
  }
855
1471
  this.document.defaultView?.cancelAnimationFrame(this.visiblePageRenderFrame);
856
1472
  this.visiblePageRenderFrame = null;
857
1473
  }
1474
+ getCurrentTime() {
1475
+ return this.document.defaultView?.performance?.now() ?? Date.now();
1476
+ }
1477
+ getQualityRenderDelay() {
1478
+ const elapsed = this.getCurrentTime() - this.lastZoomChangeTime;
1479
+ if (elapsed >= this.qualityRenderZoomIdleDelay) {
1480
+ return 0;
1481
+ }
1482
+ return Math.max(0, this.qualityRenderZoomIdleDelay - elapsed);
1483
+ }
1484
+ getZoomAnchor(container, event) {
1485
+ if (!Number.isFinite(event.clientX) || !Number.isFinite(event.clientY)) {
1486
+ return null;
1487
+ }
1488
+ const targetDocument = container.ownerDocument;
1489
+ const targetElement = targetDocument.elementFromPoint(event.clientX, event.clientY);
1490
+ const pageElement = targetElement?.closest('[data-ngs-pdf-page]');
1491
+ if (!pageElement || !container.contains(pageElement)) {
1492
+ return null;
1493
+ }
1494
+ const containerRect = container.getBoundingClientRect();
1495
+ const pageRect = pageElement.getBoundingClientRect();
1496
+ const pageNumber = Number(pageElement.dataset['ngsPdfPage']);
1497
+ if (!Number.isFinite(pageNumber) || pageRect.width <= 0 || pageRect.height <= 0) {
1498
+ return null;
1499
+ }
1500
+ return {
1501
+ container,
1502
+ pageNumber,
1503
+ relativeX: this.clamp((event.clientX - pageRect.left) / pageRect.width, 0, 1),
1504
+ relativeY: this.clamp((event.clientY - pageRect.top) / pageRect.height, 0, 1),
1505
+ viewportX: event.clientX - containerRect.left,
1506
+ viewportY: event.clientY - containerRect.top,
1507
+ };
1508
+ }
1509
+ restoreZoomAnchor(anchor) {
1510
+ if (!anchor) {
1511
+ return;
1512
+ }
1513
+ const targetWindow = this.document.defaultView;
1514
+ const restore = () => {
1515
+ const pageElement = anchor.container.querySelector(`[data-ngs-pdf-page="${anchor.pageNumber}"]`);
1516
+ if (!pageElement) {
1517
+ return;
1518
+ }
1519
+ const containerRect = anchor.container.getBoundingClientRect();
1520
+ const pageRect = pageElement.getBoundingClientRect();
1521
+ const targetLeft = anchor.container.scrollLeft +
1522
+ pageRect.left -
1523
+ containerRect.left +
1524
+ pageRect.width * anchor.relativeX -
1525
+ anchor.viewportX;
1526
+ const targetTop = anchor.container.scrollTop +
1527
+ pageRect.top -
1528
+ containerRect.top +
1529
+ pageRect.height * anchor.relativeY -
1530
+ anchor.viewportY;
1531
+ anchor.container.scrollLeft = this.clamp(targetLeft, 0, Math.max(0, anchor.container.scrollWidth - anchor.container.clientWidth));
1532
+ anchor.container.scrollTop = this.clamp(targetTop, 0, Math.max(0, anchor.container.scrollHeight - anchor.container.clientHeight));
1533
+ };
1534
+ if (!targetWindow) {
1535
+ restore();
1536
+ return;
1537
+ }
1538
+ targetWindow.requestAnimationFrame(restore);
1539
+ }
858
1540
  disconnectPageObserver() {
859
1541
  this.cancelPageObserverFrame();
860
1542
  this.pageIntersectionObserver?.disconnect();
@@ -924,6 +1606,32 @@ class PdfViewer {
924
1606
  }
925
1607
  return true;
926
1608
  }
1609
+ getFitScale(mode) {
1610
+ const container = this.viewerBody()?.nativeElement;
1611
+ const page = this.pdfDocument?.pages[this.clamp(this.activePage(), 1, Math.max(this.pageCount(), 1)) - 1];
1612
+ if (!container || !page) {
1613
+ return null;
1614
+ }
1615
+ const pageSize = this.getPageBaseSize(page);
1616
+ const pagesElement = container.querySelector('.pdf-viewer-pages');
1617
+ const computedStyle = pagesElement && this.document.defaultView
1618
+ ? this.document.defaultView.getComputedStyle(pagesElement)
1619
+ : null;
1620
+ const horizontalPadding = computedStyle
1621
+ ? this.parseCssPixel(computedStyle.paddingLeft) + this.parseCssPixel(computedStyle.paddingRight)
1622
+ : 0;
1623
+ const verticalPadding = computedStyle
1624
+ ? this.parseCssPixel(computedStyle.paddingTop) + this.parseCssPixel(computedStyle.paddingBottom)
1625
+ : 0;
1626
+ const fitAllowance = 1;
1627
+ const availableWidth = Math.max(1, container.clientWidth - horizontalPadding - fitAllowance);
1628
+ const availableHeight = Math.max(1, container.clientHeight - verticalPadding - fitAllowance);
1629
+ const widthScale = availableWidth / pageSize.width;
1630
+ if (mode === 'fit-width') {
1631
+ return this.floorFitZoom(widthScale);
1632
+ }
1633
+ return this.floorFitZoom(Math.min(widthScale, availableHeight / pageSize.height));
1634
+ }
927
1635
  getScaleBounds() {
928
1636
  const min = this.sanitizePositiveNumber(this.minScale(), 0.2);
929
1637
  const max = this.sanitizePositiveNumber(this.maxScale(), 60);
@@ -936,12 +1644,19 @@ class PdfViewer {
936
1644
  return Number.isFinite(value) && value > 0 ? value : fallback;
937
1645
  }
938
1646
  getPageDisplaySize(page, scale) {
1647
+ const pageSize = this.getPageBaseSize(page);
1648
+ return {
1649
+ width: Math.max(1, Math.round(pageSize.width * scale)),
1650
+ height: Math.max(1, Math.round(pageSize.height * scale)),
1651
+ };
1652
+ }
1653
+ getPageBaseSize(page) {
939
1654
  const isRotatedSideways = page.rotation === 1 || page.rotation === 3;
940
1655
  const width = isRotatedSideways ? page.size.height : page.size.width;
941
1656
  const height = isRotatedSideways ? page.size.width : page.size.height;
942
1657
  return {
943
- width: Math.max(1, Math.round(width * scale)),
944
- height: Math.max(1, Math.round(height * scale)),
1658
+ width: Math.max(1, width),
1659
+ height: Math.max(1, height),
945
1660
  };
946
1661
  }
947
1662
  scrollToPage(pageNumber) {
@@ -958,6 +1673,7 @@ class PdfViewer {
958
1673
  this.startProgrammaticScrollLock(pageNumber, scrollDistance);
959
1674
  container.scrollTo({
960
1675
  top: nextScrollTop,
1676
+ left: this.getPageScrollLeft(container, target),
961
1677
  behavior: 'smooth',
962
1678
  });
963
1679
  }
@@ -1046,6 +1762,12 @@ class PdfViewer {
1046
1762
  getPageScrollTop(container, target) {
1047
1763
  return this.getElementScrollTop(container, target, 'start');
1048
1764
  }
1765
+ getPageScrollLeft(container, target) {
1766
+ const containerRect = container.getBoundingClientRect();
1767
+ const targetRect = target.getBoundingClientRect();
1768
+ const targetLeft = container.scrollLeft + targetRect.left - containerRect.left;
1769
+ return this.clamp(targetLeft, 0, Math.max(0, container.scrollWidth - container.clientWidth));
1770
+ }
1049
1771
  getElementScrollTop(container, target, align) {
1050
1772
  const containerRect = container.getBoundingClientRect();
1051
1773
  const targetRect = target.getBoundingClientRect();
@@ -1108,13 +1830,14 @@ class PdfViewer {
1108
1830
  this.programmaticScrollTargetPage = null;
1109
1831
  }
1110
1832
  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 });
1833
+ 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 />\n </ngs-toolbar-item>\n <ngs-toolbar-title class=\"min-w-0 flex-1 truncate text-sm! 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-5\">\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-16 cursor-pointer border-0 bg-transparent p-0 text-center text-sm font-bold 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 />\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-24 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 (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 (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 group-[.is-active]:border-border group-[.is-active]:bg-primary-container group-[.is-active]:shadow-xs group-[.is-active]:ring-2 group-[.is-active]:ring-primary-container\" 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 class=\"pdf-viewer-pages grid min-h-full min-w-full w-max content-start grid-flow-row box-border justify-items-center gap-6 p-6\">\n @for (pdfPage of renderedPages(); 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 </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;display:block;height:var(--ngs-pdf-viewer-height);min-height:var(--ngs-pdf-viewer-min-height);overflow:hidden}: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
1834
  }
1113
1835
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PdfViewer, decorators: [{
1114
1836
  type: Component,
1115
- args: [{ selector: 'ngs-pdf-viewer', exportAs: 'ngsPdfViewer', standalone: true, imports: [
1837
+ args: [{ selector: 'ngs-pdf-viewer', exportAs: 'ngsPdfViewer', imports: [
1116
1838
  BlockLoader,
1117
1839
  Button,
1840
+ Divider,
1118
1841
  Icon,
1119
1842
  ImagePlaceholder,
1120
1843
  Menu,
@@ -1123,21 +1846,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
1123
1846
  MenuItem,
1124
1847
  MenuTrigger,
1125
1848
  Panel,
1849
+ PanelAside,
1126
1850
  PanelContent,
1127
1851
  PanelHeader,
1128
1852
  PanelSidebar,
1853
+ Toolbar,
1854
+ ToolbarItem,
1855
+ ToolbarSpacer,
1856
+ ToolbarTitle,
1857
+ PdfViewerAnnotations,
1858
+ PdfViewerSearch,
1129
1859
  ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1130
1860
  class: 'ngs-pdf-viewer not-prose',
1131
1861
  '[class.is-loading]': 'isLoading()',
1132
1862
  '[class.has-toolbar]': 'showToolbar()',
1133
1863
  '[class.has-page-list]': 'isPageListVisible()',
1134
1864
  '[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"] }] } });
1865
+ }, 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 />\n </ngs-toolbar-item>\n <ngs-toolbar-title class=\"min-w-0 flex-1 truncate text-sm! 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-5\">\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-16 cursor-pointer border-0 bg-transparent p-0 text-center text-sm font-bold 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 />\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-24 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 (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 (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 group-[.is-active]:border-border group-[.is-active]:bg-primary-container group-[.is-active]:shadow-xs group-[.is-active]:ring-2 group-[.is-active]:ring-primary-container\" 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 class=\"pdf-viewer-pages grid min-h-full min-w-full w-max content-start grid-flow-row box-border justify-items-center gap-6 p-6\">\n @for (pdfPage of renderedPages(); 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 </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;display:block;height:var(--ngs-pdf-viewer-height);min-height:var(--ngs-pdf-viewer-min-height);overflow:hidden}:host .pdf-viewer-page__selection-layer{forced-color-adjust:none}\n"] }]
1866
+ }], 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
1867
 
1138
1868
  /**
1139
1869
  * Generated bundle index. Do not edit.
1140
1870
  */
1141
1871
 
1142
- export { PdfViewer, PdfViewerEngineService };
1872
+ export { PdfViewer, PdfViewerAnnotationDef, PdfViewerAnnotations, PdfViewerEngineService, PdfViewerSearch };
1143
1873
  //# sourceMappingURL=ngstarter-ui-components-pdf-viewer.mjs.map