@embedpdf/angular 3.0.0-next.14 → 3.0.0-next.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +309 -0
- package/dist/README.md +309 -0
- package/dist/actions/index.d.ts +34 -16
- package/dist/anchored/index.d.ts +67 -0
- package/dist/annotation/index.d.ts +602 -0
- package/dist/attachment/index.d.ts +29 -0
- package/dist/commands/index.d.ts +109 -0
- package/dist/fesm2022/embedpdf-angular-actions.mjs +117 -36
- package/dist/fesm2022/embedpdf-angular-actions.mjs.map +1 -1
- package/dist/fesm2022/embedpdf-angular-anchored.mjs +148 -0
- package/dist/fesm2022/embedpdf-angular-anchored.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-annotation.mjs +1933 -0
- package/dist/fesm2022/embedpdf-angular-annotation.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-attachment.mjs +67 -0
- package/dist/fesm2022/embedpdf-angular-attachment.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-commands.mjs +235 -0
- package/dist/fesm2022/embedpdf-angular-commands.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-form.mjs +1074 -0
- package/dist/fesm2022/embedpdf-angular-form.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-history.mjs +54 -0
- package/dist/fesm2022/embedpdf-angular-history.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-i18n.mjs +100 -0
- package/dist/fesm2022/embedpdf-angular-i18n.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-interaction.mjs +157 -0
- package/dist/fesm2022/embedpdf-angular-interaction.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-link.mjs +306 -0
- package/dist/fesm2022/embedpdf-angular-link.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-measurement.mjs +106 -0
- package/dist/fesm2022/embedpdf-angular-measurement.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-metadata.mjs +77 -0
- package/dist/fesm2022/embedpdf-angular-metadata.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-outline.mjs +76 -0
- package/dist/fesm2022/embedpdf-angular-outline.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-page-edit.mjs +68 -0
- package/dist/fesm2022/embedpdf-angular-page-edit.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-page-view.mjs +297 -0
- package/dist/fesm2022/embedpdf-angular-page-view.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-redaction.mjs +93 -0
- package/dist/fesm2022/embedpdf-angular-redaction.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-render.mjs +331 -69
- package/dist/fesm2022/embedpdf-angular-render.mjs.map +1 -1
- package/dist/fesm2022/embedpdf-angular-runtime.mjs +1151 -390
- package/dist/fesm2022/embedpdf-angular-runtime.mjs.map +1 -1
- package/dist/fesm2022/embedpdf-angular-search.mjs +251 -0
- package/dist/fesm2022/embedpdf-angular-search.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-selection.mjs +477 -0
- package/dist/fesm2022/embedpdf-angular-selection.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-shell.mjs +177 -0
- package/dist/fesm2022/embedpdf-angular-shell.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-signature.mjs +120 -0
- package/dist/fesm2022/embedpdf-angular-signature.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-stage.mjs +688 -288
- package/dist/fesm2022/embedpdf-angular-stage.mjs.map +1 -1
- package/dist/fesm2022/embedpdf-angular-stamp.mjs +169 -0
- package/dist/fesm2022/embedpdf-angular-stamp.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-testing.mjs +89 -0
- package/dist/fesm2022/embedpdf-angular-testing.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-toolbar.mjs +1123 -0
- package/dist/fesm2022/embedpdf-angular-toolbar.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular-view-manager.mjs +69 -0
- package/dist/fesm2022/embedpdf-angular-view-manager.mjs.map +1 -0
- package/dist/fesm2022/embedpdf-angular.mjs +25 -3
- package/dist/fesm2022/embedpdf-angular.mjs.map +1 -1
- package/dist/form/index.d.ts +79 -0
- package/dist/history/index.d.ts +28 -0
- package/dist/i18n/index.d.ts +52 -0
- package/dist/index.d.ts +22 -0
- package/dist/interaction/index.d.ts +58 -0
- package/dist/link/index.d.ts +77 -0
- package/dist/measurement/index.d.ts +46 -0
- package/dist/metadata/index.d.ts +51 -0
- package/dist/outline/index.d.ts +32 -0
- package/dist/page-edit/index.d.ts +27 -0
- package/dist/page-view/index.d.ts +94 -0
- package/dist/redaction/index.d.ts +38 -0
- package/dist/render/index.d.ts +45 -6
- package/dist/runtime/index.d.ts +579 -199
- package/dist/search/index.d.ts +71 -0
- package/dist/selection/index.d.ts +141 -0
- package/dist/shell/index.d.ts +88 -0
- package/dist/signature/index.d.ts +45 -0
- package/dist/stage/index.d.ts +242 -141
- package/dist/stamp/index.d.ts +58 -0
- package/dist/testing/index.d.ts +53 -0
- package/dist/toolbar/index.d.ts +257 -0
- package/dist/view-manager/index.d.ts +23 -0
- package/package.json +145 -14
|
@@ -1,31 +1,170 @@
|
|
|
1
|
-
import { StageToken,
|
|
1
|
+
import { StageToken, stageState, DEFAULT_SETTINGS, stagePlugin, createScrollHandler } from '@embedpdf/plugin-stage';
|
|
2
2
|
export * from '@embedpdf/plugin-stage';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { inject, TemplateRef, Directive, input,
|
|
4
|
+
import { computed, Injectable, inject, TemplateRef, Directive, input, viewChild, Injector, ChangeDetectionStrategy, Component, signal, output, contentChild, PLATFORM_ID, effect, untracked, ElementRef, forwardRef, DestroyRef } from '@angular/core';
|
|
5
|
+
import { injectKernelHost, CapabilityBinding, injectDocumentScope, createPageContext, EPDF_PAGE, EPDF_DOCUMENT_SCOPE, EPDF_SCOPED_SERVICES, outsideStageError } from '@embedpdf/angular/runtime';
|
|
5
6
|
import { NgTemplateOutlet, isPlatformBrowser } from '@angular/common';
|
|
6
|
-
import {
|
|
7
|
-
import { InteractionToken } from '@embedpdf/plugin-interaction/contract';
|
|
8
|
-
import { createStageSurface } from '@embedpdf/web';
|
|
7
|
+
import { toPageRef, shallowEqual } from '@embedpdf/core';
|
|
8
|
+
import { InteractionToken } from '@embedpdf/plugin-interaction/contract/host';
|
|
9
|
+
import { pageSurfaceLayout, paint, stagePageDemand, stageViewProjector, createStageSurface, scrollbarLayout, paintDefault, createScrollbarPresses } from '@embedpdf/web';
|
|
10
|
+
import { EPDF_PROJECTOR } from '@embedpdf/angular/anchored';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
13
|
+
* `EpdfStageRef`: a Stage's API, for code that injects it. It has every method of the Stage
|
|
14
|
+
* page's Methods table, every value of its State table as a signal (`zoomLevel()`,
|
|
15
|
+
* `currentPageIndex()`, `pageCount()`), its settings as `settings()`, and its events as streams
|
|
16
|
+
* (`pageChanged$`).
|
|
17
|
+
*
|
|
18
|
+
* Inside `<epdf-stage>`, `inject(EpdfStageRef)` is that Stage: the component extends this class
|
|
19
|
+
* and provides itself under it, so its `[token]` view and its `[document]` are what you drive.
|
|
20
|
+
* Anywhere else under a viewer it drives the main view (`StageToken`) of the document in scope
|
|
21
|
+
* (`[epdfDocumentScope]`), else of the active document, as `inject(EpdfSearch)` does. Around a
|
|
22
|
+
* Stage, its template reference (`#stage="epdfStage"`) or `viewChild(EpdfStage)` reaches it.
|
|
23
|
+
*
|
|
24
|
+
* Without a document its signals read their empty values, and a method called then refuses
|
|
25
|
+
* with `not-ready`. Each method looks the view up when it's called, so it acts on the document
|
|
26
|
+
* shown at that moment.
|
|
27
|
+
*/
|
|
28
|
+
const NO_SCROLL = Object.freeze({
|
|
29
|
+
scrollLeft: 0,
|
|
30
|
+
scrollTop: 0,
|
|
31
|
+
scrollWidth: 0,
|
|
32
|
+
scrollHeight: 0,
|
|
33
|
+
clientWidth: 0,
|
|
34
|
+
clientHeight: 0,
|
|
35
|
+
scrollableX: false,
|
|
36
|
+
scrollableY: false,
|
|
37
|
+
});
|
|
38
|
+
/** The same runtime token, typed with the wider capability the Stage itself drives. */
|
|
39
|
+
const asHost = (token) => token;
|
|
40
|
+
class EpdfStageRef {
|
|
41
|
+
/** The viewer the view is in. */
|
|
42
|
+
host = injectKernelHost(this.usedAs());
|
|
43
|
+
/** The view, for the document in scope: inside `<epdf-stage>`, the Stage's own. */
|
|
44
|
+
binding = new CapabilityBinding(this.host, () => asHost(this.view()), injectDocumentScope());
|
|
45
|
+
/** The view it drives: the main one. `<epdf-stage>` drives the one its `[token]` names. */
|
|
46
|
+
view() {
|
|
47
|
+
return StageToken;
|
|
48
|
+
}
|
|
49
|
+
/** What an error says was used without a viewer. */
|
|
50
|
+
usedAs() {
|
|
51
|
+
return 'inject(EpdfStageRef)';
|
|
52
|
+
}
|
|
53
|
+
// ── state: the Stage page's State table, one signal per value ─────────────────
|
|
54
|
+
state = this.binding.select((stage) => stageState.read(stage), stageState.empty);
|
|
55
|
+
/** The zoom as a number: `1` is 100%. */
|
|
56
|
+
zoomLevel = computed(() => this.state().zoomLevel, ...(ngDevMode ? [{ debugName: "zoomLevel" }] : []));
|
|
57
|
+
/** The fit mode, such as `'fit-width'`, or `'custom'` after a pinch or an exact level. */
|
|
58
|
+
zoomMode = computed(() => this.state().zoomMode, ...(ngDevMode ? [{ debugName: "zoomMode" }] : []));
|
|
59
|
+
/** The current page's place in the document, from 0. */
|
|
60
|
+
currentPageIndex = computed(() => this.state().currentPageIndex, ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : []));
|
|
61
|
+
/** The current page's ref, or null before the document has pages. */
|
|
62
|
+
currentPage = computed(() => this.state().currentPage, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
|
|
63
|
+
/** How many pages the document has. */
|
|
64
|
+
pageCount = computed(() => this.state().pageCount, ...(ngDevMode ? [{ debugName: "pageCount" }] : []));
|
|
65
|
+
/** How the view is turned: `0`, `90`, `180` or `270`. */
|
|
66
|
+
viewRotation = computed(() => this.state().viewRotation, ...(ngDevMode ? [{ debugName: "viewRotation" }] : []));
|
|
67
|
+
/** The names of the responsive rules that apply now, in order. */
|
|
68
|
+
activeRules = computed(() => this.state().activeRules, ...(ngDevMode ? [{ debugName: "activeRules" }] : []));
|
|
69
|
+
/** The view's settings; the defaults without a document. Change them with `updateSettings()`. */
|
|
70
|
+
settings = this.binding.select((stage) => stage.getSettings(), DEFAULT_SETTINGS);
|
|
71
|
+
/** The scroll position as a scrolling element has it, in screen pixels; all zero without a document. */
|
|
72
|
+
scrollMetrics = this.binding.select((stage) => stage.getScrollMetrics(), NO_SCROLL, Object.is);
|
|
73
|
+
// ── events, as streams ───────────────────────────────────────────────────────
|
|
74
|
+
/** Another page became the current one: `page`, `pageIndex`, `previousPageIndex`. */
|
|
75
|
+
pageChanged$ = this.binding.stream((stage) => stage.onPageChanged);
|
|
76
|
+
/** The zoom level changed: `level`, `previousLevel`, `mode`. */
|
|
77
|
+
zoomChanged$ = this.binding.stream((stage) => stage.onZoomChanged);
|
|
78
|
+
/** The view moved, on every frame of a scroll or zoom: `camera`. */
|
|
79
|
+
cameraChanged$ = this.binding.stream((stage) => stage.onCameraChanged);
|
|
80
|
+
/** Scrolling, a fling or an animated move came to rest: `camera`. */
|
|
81
|
+
motionEnded$ = this.binding.stream((stage) => stage.onMotionEnded);
|
|
82
|
+
/** The Stage itself changed size: `size`. */
|
|
83
|
+
viewportChanged$ = this.binding.stream((stage) => stage.onViewportChanged);
|
|
84
|
+
/** A setting changed, by a call or a responsive rule: `settings`, `changed`. */
|
|
85
|
+
settingsChanged$ = this.binding.stream((stage) => stage.onSettingsChanged);
|
|
86
|
+
// ── methods: the Stage page's Methods table ──────────────────────────────────
|
|
87
|
+
getCamera = this.binding.method('getCamera');
|
|
88
|
+
setCamera = this.binding.method('setCamera');
|
|
89
|
+
getViewportSize = this.binding.method('getViewportSize');
|
|
90
|
+
getZoomLevel = this.binding.method('getZoomLevel');
|
|
91
|
+
getZoomMode = this.binding.method('getZoomMode');
|
|
92
|
+
getViewRotation = this.binding.method('getViewRotation');
|
|
93
|
+
getCurrentPage = this.binding.method('getCurrentPage');
|
|
94
|
+
getCurrentPageIndex = this.binding.method('getCurrentPageIndex');
|
|
95
|
+
getPageCount = this.binding.method('getPageCount');
|
|
96
|
+
listCurrentItemPages = this.binding.method('listCurrentItemPages');
|
|
97
|
+
listVisiblePages = this.binding.method('listVisiblePages');
|
|
98
|
+
isPageVisible = this.binding.method('isPageVisible');
|
|
99
|
+
getViewpoint = this.binding.method('getViewpoint');
|
|
100
|
+
getViewState = this.binding.method('getViewState');
|
|
101
|
+
applyViewState = this.binding.method('applyViewState');
|
|
102
|
+
isMoving = this.binding.method('isMoving');
|
|
103
|
+
stopMotion = this.binding.method('stopMotion');
|
|
104
|
+
zoomTo = this.binding.method('zoomTo');
|
|
105
|
+
zoomBy = this.binding.method('zoomBy');
|
|
106
|
+
zoomIn = this.binding.method('zoomIn');
|
|
107
|
+
zoomOut = this.binding.method('zoomOut');
|
|
108
|
+
fitWidth = this.binding.method('fitWidth');
|
|
109
|
+
fitPage = this.binding.method('fitPage');
|
|
110
|
+
fitAll = this.binding.method('fitAll');
|
|
111
|
+
fitAutomatic = this.binding.method('fitAutomatic');
|
|
112
|
+
goToPage = this.binding.method('goToPage');
|
|
113
|
+
goToDestination = this.binding.method('goToDestination');
|
|
114
|
+
goToFirstPage = this.binding.method('goToFirstPage');
|
|
115
|
+
goToLastPage = this.binding.method('goToLastPage');
|
|
116
|
+
nextPage = this.binding.method('nextPage');
|
|
117
|
+
previousPage = this.binding.method('previousPage');
|
|
118
|
+
canGoNext = this.binding.method('canGoNext');
|
|
119
|
+
canGoPrevious = this.binding.method('canGoPrevious');
|
|
120
|
+
reveal = this.binding.method('reveal');
|
|
121
|
+
scrollTo = this.binding.method('scrollTo');
|
|
122
|
+
scrollBy = this.binding.method('scrollBy');
|
|
123
|
+
panBy = this.binding.method('panBy');
|
|
124
|
+
resetView = this.binding.method('resetView');
|
|
125
|
+
setViewRotation = this.binding.method('setViewRotation');
|
|
126
|
+
rotateViewBy = this.binding.method('rotateViewBy');
|
|
127
|
+
getSettings = this.binding.method('getSettings');
|
|
128
|
+
updateSettings = this.binding.method('updateSettings');
|
|
129
|
+
resetSettings = this.binding.method('resetSettings');
|
|
130
|
+
setResponsiveRules = this.binding.method('setResponsiveRules');
|
|
131
|
+
listActiveRules = this.binding.method('listActiveRules');
|
|
132
|
+
matchesRule = this.binding.method('matchesRule');
|
|
133
|
+
getPageAt = this.binding.method('getPageAt');
|
|
134
|
+
viewportToPage = this.binding.method('viewportToPage');
|
|
135
|
+
pageToViewport = this.binding.method('pageToViewport');
|
|
136
|
+
pageRectToViewport = this.binding.method('pageRectToViewport');
|
|
137
|
+
getPageFrame = this.binding.method('getPageFrame');
|
|
138
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfStageRef, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
139
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfStageRef, providedIn: 'root' });
|
|
140
|
+
}
|
|
141
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfStageRef, decorators: [{
|
|
142
|
+
type: Injectable,
|
|
143
|
+
args: [{ providedIn: 'root' }]
|
|
144
|
+
}] });
|
|
145
|
+
|
|
146
|
+
function withStage(options) {
|
|
147
|
+
return { plugins: [stagePlugin(options)], services: [EpdfStageRef] };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The templates a Stage draws each page with. React's page function becomes a marker directive
|
|
152
|
+
* on an `<ng-template>`, typed so `let-page` is the page context:
|
|
14
153
|
*
|
|
15
154
|
* <epdf-stage>
|
|
16
|
-
* <ng-template epdfPage>
|
|
17
|
-
* <ng-template epdfPageChrome>
|
|
155
|
+
* <ng-template epdfPage> on the page: turns with it (layers)
|
|
156
|
+
* <ng-template epdfPageChrome> around the page: never turns (labels, buttons)
|
|
18
157
|
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
158
|
+
* `let-page` is the same context a component inside the template gets from `injectPage()`.
|
|
159
|
+
*/
|
|
160
|
+
/**
|
|
161
|
+
* What each visible page draws: the render layer, text selection, annotations, your markers.
|
|
162
|
+
* It sits in the page's content box and turns with the page, so positions in it are page
|
|
163
|
+
* coordinates.
|
|
22
164
|
*/
|
|
23
|
-
/** PAGE-SPACE content for each visible page (RenderLayer, annotations,
|
|
24
|
-
* markers). Rendered inside the page's content frame, so it ROTATES with the
|
|
25
|
-
* page's display rotation — coordinates are plain PDF points. */
|
|
26
165
|
class EpdfPageTemplate {
|
|
27
166
|
template = inject(TemplateRef);
|
|
28
|
-
static ngTemplateContextGuard(
|
|
167
|
+
static ngTemplateContextGuard(_directive, context) {
|
|
29
168
|
return true;
|
|
30
169
|
}
|
|
31
170
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfPageTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
@@ -33,15 +172,16 @@ class EpdfPageTemplate {
|
|
|
33
172
|
}
|
|
34
173
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfPageTemplate, decorators: [{
|
|
35
174
|
type: Directive,
|
|
36
|
-
args: [{ selector: 'ng-template[epdfPage]'
|
|
175
|
+
args: [{ selector: 'ng-template[epdfPage]' }]
|
|
37
176
|
}] });
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
177
|
+
/**
|
|
178
|
+
* What goes around each visible page: a page number, a border, a row of buttons. It fills the
|
|
179
|
+
* page's box with the bands `pageFrame` reserves, and never turns, so a label in the bottom
|
|
180
|
+
* band is `bottom: 0` with `[style.height.px]="page.frame().bottom"`.
|
|
181
|
+
*/
|
|
42
182
|
class EpdfPageChrome {
|
|
43
183
|
template = inject(TemplateRef);
|
|
44
|
-
static ngTemplateContextGuard(
|
|
184
|
+
static ngTemplateContextGuard(_directive, context) {
|
|
45
185
|
return true;
|
|
46
186
|
}
|
|
47
187
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfPageChrome, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
@@ -49,96 +189,103 @@ class EpdfPageChrome {
|
|
|
49
189
|
}
|
|
50
190
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfPageChrome, decorators: [{
|
|
51
191
|
type: Directive,
|
|
52
|
-
args: [{ selector: 'ng-template[epdfPageChrome]'
|
|
192
|
+
args: [{ selector: 'ng-template[epdfPageChrome]' }]
|
|
53
193
|
}] });
|
|
54
194
|
|
|
55
195
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
196
|
+
* One page on a Stage: its box, its shadow, its turned content, and the page and chrome
|
|
197
|
+
* templates drawn into them. The Stage tracks surfaces by the page's object number, so a
|
|
198
|
+
* surface (and every layer in it) lives while its page is on screen, through camera moves and
|
|
199
|
+
* page reorders.
|
|
58
200
|
*
|
|
59
|
-
*
|
|
60
|
-
* `
|
|
61
|
-
*
|
|
62
|
-
* injector or context object identity would make `NgTemplateOutlet` recreate
|
|
63
|
-
* the embedded views — destroying layers at 120Hz. Don't.
|
|
201
|
+
* The page context and the injector that provides it are made once per surface; camera frames
|
|
202
|
+
* arrive as new `visiblePage` input values and change only the signals inside the context. A
|
|
203
|
+
* new context or injector would make the template outlet recreate every layer on every frame.
|
|
64
204
|
*
|
|
65
|
-
*
|
|
66
|
-
* reserved chrome bands; the shadow is axis-aligned and stays
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
205
|
+
* The geometry is every framework's (`pageSurfaceLayout` from `@embedpdf/web`): the outer box
|
|
206
|
+
* is the page's footprint plus the reserved chrome bands; the shadow is axis-aligned and stays
|
|
207
|
+
* put when the page turns; only the content box turns, and at rotation 0 it carries no
|
|
208
|
+
* transform, so it snaps to pixels like the shadow behind it. Every number comes from the page
|
|
209
|
+
* transform.
|
|
70
210
|
*/
|
|
71
211
|
class EpdfPageSurface {
|
|
72
|
-
|
|
212
|
+
visiblePage = input.required(...(ngDevMode ? [{ debugName: "visiblePage" }] : []));
|
|
213
|
+
/** The bands reserved around the page, in screen pixels. */
|
|
73
214
|
frame = input.required(...(ngDevMode ? [{ debugName: "frame" }] : []));
|
|
74
215
|
documentId = input.required(...(ngDevMode ? [{ debugName: "documentId" }] : []));
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
216
|
+
/** The Stage's view; the tile demand reads what's on screen from it when asked. */
|
|
217
|
+
stage = input.required(...(ngDevMode ? [{ debugName: "stage" }] : []));
|
|
218
|
+
/** The viewer's `page` settings: the background and the shadow. */
|
|
219
|
+
look = input.required(...(ngDevMode ? [{ debugName: "look" }] : []));
|
|
220
|
+
pageTemplate = input.required(...(ngDevMode ? [{ debugName: "pageTemplate" }] : []));
|
|
221
|
+
chromeTemplate = input(null, ...(ngDevMode ? [{ debugName: "chromeTemplate" }] : []));
|
|
222
|
+
transform = computed(() => this.visiblePage().transform, ...(ngDevMode ? [{ debugName: "transform" }] : []));
|
|
223
|
+
// The outer box, the shadow and the turned content box, from `@embedpdf/web`'s layout, shared
|
|
224
|
+
// by every framework; screenX/screenY are the footprint's device-snapped corner.
|
|
225
|
+
layout = computed(() => {
|
|
226
|
+
const visiblePage = this.visiblePage();
|
|
227
|
+
return pageSurfaceLayout(this.transform(), this.frame(), {
|
|
228
|
+
x: visiblePage.screenX,
|
|
229
|
+
y: visiblePage.screenY,
|
|
230
|
+
});
|
|
231
|
+
}, ...(ngDevMode ? [{ debugName: "layout" }] : []));
|
|
232
|
+
// The settings, unless CSS sets `--epdf-page-shadow` or `--epdf-page-background`.
|
|
233
|
+
shadow = computed(() => paint('page-shadow', this.look().shadow), ...(ngDevMode ? [{ debugName: "shadow" }] : []));
|
|
234
|
+
background = computed(() => paint('page-background', this.look().background), ...(ngDevMode ? [{ debugName: "background" }] : []));
|
|
235
|
+
content = viewChild.required('content');
|
|
236
|
+
pageRef = null;
|
|
237
|
+
/** The page context, one for the surface's lifetime. */
|
|
94
238
|
page = createPageContext({
|
|
95
239
|
documentId: () => this.documentId(),
|
|
96
|
-
|
|
97
|
-
|
|
240
|
+
// The Stage builds a new ref each frame; the surface keeps the first, so layers can key
|
|
241
|
+
// their work on it.
|
|
242
|
+
ref: () => (this.pageRef ??= toPageRef(this.visiblePage().ref.objectNumber)),
|
|
243
|
+
view: () => this.stage().getLensId(),
|
|
244
|
+
pageIndex: computed(() => this.visiblePage().pageIndex),
|
|
98
245
|
frame: this.frame,
|
|
99
|
-
transform: this.
|
|
100
|
-
getRect: () => this.
|
|
246
|
+
transform: this.transform,
|
|
247
|
+
getRect: () => this.content().nativeElement.getBoundingClientRect(),
|
|
248
|
+
// Read live from the Stage: off screen, the page wants nothing (an empty rect).
|
|
249
|
+
getViewDemand: () => stagePageDemand(this.stage(), this.visiblePage().ref.objectNumber, this.transform().deviceWidth),
|
|
101
250
|
});
|
|
102
|
-
/**
|
|
103
|
-
* this surface's node injector so layers also reach the kernel host. */
|
|
251
|
+
/** One injector for the surface's lifetime, under this surface, so layers reach the viewer too. */
|
|
104
252
|
pageInjector = Injector.create({
|
|
105
253
|
providers: [{ provide: EPDF_PAGE, useValue: this.page }],
|
|
106
254
|
parent: inject(Injector),
|
|
107
255
|
});
|
|
108
256
|
templateContext = { $implicit: this.page };
|
|
109
257
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfPageSurface, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
110
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: EpdfPageSurface, isStandalone: true, selector: "epdf-page-surface", inputs: {
|
|
111
|
-
<!--
|
|
112
|
-
|
|
113
|
-
stays put under rotation. -->
|
|
258
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: EpdfPageSurface, isStandalone: true, selector: "epdf-page-surface", inputs: { visiblePage: { classPropertyName: "visiblePage", publicName: "visiblePage", isSignal: true, isRequired: true, transformFunction: null }, frame: { classPropertyName: "frame", publicName: "frame", isSignal: true, isRequired: true, transformFunction: null }, documentId: { classPropertyName: "documentId", publicName: "documentId", isSignal: true, isRequired: true, transformFunction: null }, stage: { classPropertyName: "stage", publicName: "stage", isSignal: true, isRequired: true, transformFunction: null }, look: { classPropertyName: "look", publicName: "look", isSignal: true, isRequired: true, transformFunction: null }, pageTemplate: { classPropertyName: "pageTemplate", publicName: "pageTemplate", isSignal: true, isRequired: true, transformFunction: null }, chromeTemplate: { classPropertyName: "chromeTemplate", publicName: "chromeTemplate", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.left.px": "layout().outer.left", "style.top.px": "layout().outer.top", "style.width.px": "layout().outer.width", "style.height.px": "layout().outer.height" }, styleAttribute: "position: absolute; display: block;" }, viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
259
|
+
<!-- The shadow only: axis-aligned at the content box, with no fill that could show behind
|
|
260
|
+
the picture, and it stays put when the page turns. -->
|
|
114
261
|
<div
|
|
115
|
-
style="position: absolute
|
|
116
|
-
[style.left.px]="
|
|
117
|
-
[style.top.px]="
|
|
118
|
-
[style.width.px]="
|
|
119
|
-
[style.height.px]="
|
|
262
|
+
style="position: absolute"
|
|
263
|
+
[style.left.px]="layout().shadow.left"
|
|
264
|
+
[style.top.px]="layout().shadow.top"
|
|
265
|
+
[style.width.px]="layout().shadow.width"
|
|
266
|
+
[style.height.px]="layout().shadow.height"
|
|
267
|
+
[style.box-shadow]="shadow()"
|
|
120
268
|
></div>
|
|
121
|
-
<!--
|
|
122
|
-
|
|
123
|
-
suppressed on the whole page subtree. -->
|
|
269
|
+
<!-- The page: its background and content as one box, the only thing that turns. The layers
|
|
270
|
+
draw their own selection, so the browser's is off for the whole page. -->
|
|
124
271
|
<div
|
|
125
272
|
#content
|
|
126
|
-
style="position: absolute;
|
|
127
|
-
[style.left.px]="
|
|
128
|
-
[style.top.px]="
|
|
129
|
-
[style.width.px]="
|
|
130
|
-
[style.height.px]="
|
|
131
|
-
[style.
|
|
273
|
+
style="position: absolute; user-select: none; -webkit-user-select: none"
|
|
274
|
+
[style.left.px]="layout().content.left"
|
|
275
|
+
[style.top.px]="layout().content.top"
|
|
276
|
+
[style.width.px]="layout().content.width"
|
|
277
|
+
[style.height.px]="layout().content.height"
|
|
278
|
+
[style.background]="background()"
|
|
279
|
+
[style.transform]="layout().turn"
|
|
132
280
|
>
|
|
133
281
|
<ng-container
|
|
134
|
-
[ngTemplateOutlet]="
|
|
282
|
+
[ngTemplateOutlet]="pageTemplate()"
|
|
135
283
|
[ngTemplateOutletContext]="templateContext"
|
|
136
284
|
[ngTemplateOutletInjector]="pageInjector"
|
|
137
285
|
/>
|
|
138
286
|
</div>
|
|
139
|
-
<!--
|
|
140
|
-
|
|
141
|
-
@if (chromeTpl(); as chrome) {
|
|
287
|
+
<!-- Around the page: fills the outer box, never turns. -->
|
|
288
|
+
@if (chromeTemplate(); as chrome) {
|
|
142
289
|
<ng-container
|
|
143
290
|
[ngTemplateOutlet]="chrome"
|
|
144
291
|
[ngTemplateOutletContext]="templateContext"
|
|
@@ -151,48 +298,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImpor
|
|
|
151
298
|
type: Component,
|
|
152
299
|
args: [{
|
|
153
300
|
selector: 'epdf-page-surface',
|
|
154
|
-
standalone: true,
|
|
155
301
|
imports: [NgTemplateOutlet],
|
|
156
302
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
157
303
|
host: {
|
|
158
304
|
style: 'position: absolute; display: block;',
|
|
159
|
-
'[style.left.px]': '
|
|
160
|
-
'[style.top.px]': '
|
|
161
|
-
'[style.width.px]': '
|
|
162
|
-
'[style.height.px]': '
|
|
305
|
+
'[style.left.px]': 'layout().outer.left',
|
|
306
|
+
'[style.top.px]': 'layout().outer.top',
|
|
307
|
+
'[style.width.px]': 'layout().outer.width',
|
|
308
|
+
'[style.height.px]': 'layout().outer.height',
|
|
163
309
|
},
|
|
164
310
|
template: `
|
|
165
|
-
<!--
|
|
166
|
-
|
|
167
|
-
stays put under rotation. -->
|
|
311
|
+
<!-- The shadow only: axis-aligned at the content box, with no fill that could show behind
|
|
312
|
+
the picture, and it stays put when the page turns. -->
|
|
168
313
|
<div
|
|
169
|
-
style="position: absolute
|
|
170
|
-
[style.left.px]="
|
|
171
|
-
[style.top.px]="
|
|
172
|
-
[style.width.px]="
|
|
173
|
-
[style.height.px]="
|
|
314
|
+
style="position: absolute"
|
|
315
|
+
[style.left.px]="layout().shadow.left"
|
|
316
|
+
[style.top.px]="layout().shadow.top"
|
|
317
|
+
[style.width.px]="layout().shadow.width"
|
|
318
|
+
[style.height.px]="layout().shadow.height"
|
|
319
|
+
[style.box-shadow]="shadow()"
|
|
174
320
|
></div>
|
|
175
|
-
<!--
|
|
176
|
-
|
|
177
|
-
suppressed on the whole page subtree. -->
|
|
321
|
+
<!-- The page: its background and content as one box, the only thing that turns. The layers
|
|
322
|
+
draw their own selection, so the browser's is off for the whole page. -->
|
|
178
323
|
<div
|
|
179
324
|
#content
|
|
180
|
-
style="position: absolute;
|
|
181
|
-
[style.left.px]="
|
|
182
|
-
[style.top.px]="
|
|
183
|
-
[style.width.px]="
|
|
184
|
-
[style.height.px]="
|
|
185
|
-
[style.
|
|
325
|
+
style="position: absolute; user-select: none; -webkit-user-select: none"
|
|
326
|
+
[style.left.px]="layout().content.left"
|
|
327
|
+
[style.top.px]="layout().content.top"
|
|
328
|
+
[style.width.px]="layout().content.width"
|
|
329
|
+
[style.height.px]="layout().content.height"
|
|
330
|
+
[style.background]="background()"
|
|
331
|
+
[style.transform]="layout().turn"
|
|
186
332
|
>
|
|
187
333
|
<ng-container
|
|
188
|
-
[ngTemplateOutlet]="
|
|
334
|
+
[ngTemplateOutlet]="pageTemplate()"
|
|
189
335
|
[ngTemplateOutletContext]="templateContext"
|
|
190
336
|
[ngTemplateOutletInjector]="pageInjector"
|
|
191
337
|
/>
|
|
192
338
|
</div>
|
|
193
|
-
<!--
|
|
194
|
-
|
|
195
|
-
@if (chromeTpl(); as chrome) {
|
|
339
|
+
<!-- Around the page: fills the outer box, never turns. -->
|
|
340
|
+
@if (chromeTemplate(); as chrome) {
|
|
196
341
|
<ng-container
|
|
197
342
|
[ngTemplateOutlet]="chrome"
|
|
198
343
|
[ngTemplateOutletContext]="templateContext"
|
|
@@ -204,239 +349,494 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImpor
|
|
|
204
349
|
}] });
|
|
205
350
|
|
|
206
351
|
/**
|
|
207
|
-
* `<epdf-stage
|
|
208
|
-
*
|
|
352
|
+
* `<epdf-stage>`: shows the document's pages. It lays them out, keeps only the visible ones
|
|
353
|
+
* mounted, handles scrolling, zoom and page navigation by mouse, keyboard and touch, and draws
|
|
354
|
+
* each page with your `<ng-template epdfPage>`.
|
|
209
355
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
356
|
+
* The component is the Stage's API, `EpdfStageRef`'s (`stage-ref.ts`): every method of the
|
|
357
|
+
* Stage page's Methods table, every value of its State table as a signal, its settings as
|
|
358
|
+
* `settings()`, and its events as streams (`pageChanged$`). From around it, a template reference
|
|
359
|
+
* (`<epdf-stage #stage="epdfStage">`, then `stage.zoomIn()` and `stage.zoomLevel()`) or
|
|
360
|
+
* `viewChild(EpdfStage)` reaches it; from inside it, `inject(EpdfStageRef)`. `[(page)]` and
|
|
361
|
+
* `[(zoom)]` bind the current page index and the zoom level.
|
|
215
362
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
363
|
+
* `[document]` opens a document for this Stage and follows the value: a new source opens, the
|
|
364
|
+
* old document closes. `[token]` makes it a second view, registered with `withStage({ token })`.
|
|
365
|
+
*
|
|
366
|
+
* Without a document it shows no pages and its signals read their empty values; a method
|
|
367
|
+
* called then refuses with `not-ready`.
|
|
368
|
+
*/
|
|
369
|
+
const NO_PAGES = Object.freeze([]);
|
|
370
|
+
const NO_FRAME = Object.freeze({ top: 0, right: 0, bottom: 0, left: 0 });
|
|
371
|
+
const frameEqual = (left, right) => left.top === right.top &&
|
|
372
|
+
left.right === right.right &&
|
|
373
|
+
left.bottom === right.bottom &&
|
|
374
|
+
left.left === right.left;
|
|
375
|
+
/**
|
|
376
|
+
* The document a Stage shows: the one its `[document]` opened, else its scope's. The Stage and
|
|
377
|
+
* what's inside it read it, so it's made before the Stage itself.
|
|
220
378
|
*/
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
379
|
+
class StageDocument {
|
|
380
|
+
parent = inject(EPDF_DOCUMENT_SCOPE, { optional: true, skipSelf: true });
|
|
381
|
+
/** The document `[document]` opened, once it has its id. */
|
|
382
|
+
opened = signal(null, ...(ngDevMode ? [{ debugName: "opened" }] : []));
|
|
383
|
+
id = computed(() => this.opened() ?? this.parent?.id() ?? null, ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
384
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: StageDocument, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
385
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: StageDocument });
|
|
386
|
+
}
|
|
387
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: StageDocument, decorators: [{
|
|
388
|
+
type: Injectable
|
|
389
|
+
}] });
|
|
390
|
+
class EpdfStage extends EpdfStageRef {
|
|
391
|
+
/** The view to drive: a token registered with `withStage({ token })`; the main view without one. */
|
|
392
|
+
token = input(undefined, ...(ngDevMode ? [{ debugName: "token" }] : []));
|
|
393
|
+
/** A document to open for this Stage. A new value opens it and closes the one before. */
|
|
394
|
+
document = input(undefined, ...(ngDevMode ? [{ debugName: "document" }] : []));
|
|
395
|
+
/** The current page's index, from 0: `[(page)]`. Setting it goes to that page. */
|
|
396
|
+
page = input(undefined, ...(ngDevMode ? [{ debugName: "page" }] : []));
|
|
397
|
+
/** Another page became the current one: its index. The output half of `[(page)]`. */
|
|
398
|
+
pageChange = output();
|
|
399
|
+
/** The zoom level, `1` for 100%: `[(zoom)]`. Setting it zooms there. */
|
|
400
|
+
zoom = input(undefined, ...(ngDevMode ? [{ debugName: "zoom" }] : []));
|
|
401
|
+
/** The zoom level changed. The output half of `[(zoom)]`. */
|
|
402
|
+
zoomChange = output();
|
|
403
|
+
ownDocument = inject(StageDocument);
|
|
404
|
+
/** The document this Stage shows, for what's inside it: its own `[document]`, else its scope's. */
|
|
405
|
+
documentScope = this.ownDocument;
|
|
406
|
+
/** The view's capability, or null without a document. */
|
|
407
|
+
hostCapability = this.binding.capability;
|
|
408
|
+
view() {
|
|
409
|
+
return this.token() ?? StageToken;
|
|
410
|
+
}
|
|
411
|
+
usedAs() {
|
|
412
|
+
return '<epdf-stage>';
|
|
413
|
+
}
|
|
414
|
+
// ── what the template draws ──────────────────────────────────────────────────
|
|
415
|
+
/** The pages on screen, each with its place and transform: a new list every camera frame. */
|
|
416
|
+
pages = this.binding.select((stage) => stage.listVisiblePages(), NO_PAGES, Object.is);
|
|
417
|
+
frame = this.binding.select((stage) => stage.getSettings().pageFrame, NO_FRAME, frameEqual);
|
|
418
|
+
documentId = this.host.read((kernel) => this.documentScope.id() ?? kernel.documents.getActiveId(), () => null);
|
|
419
|
+
/** The viewer's `page` settings: each page's background and shadow. */
|
|
420
|
+
look = computed(() => this.host.viewerSettings().page, ...(ngDevMode ? [{ debugName: "look", equal: shallowEqual }] : [{
|
|
421
|
+
equal: shallowEqual,
|
|
422
|
+
}]));
|
|
423
|
+
pageTemplateDirective = contentChild(EpdfPageTemplate, ...(ngDevMode ? [{ debugName: "pageTemplateDirective" }] : []));
|
|
424
|
+
chromeTemplateDirective = contentChild(EpdfPageChrome, ...(ngDevMode ? [{ debugName: "chromeTemplateDirective" }] : []));
|
|
425
|
+
// Typed, so the built declarations name `EpdfPageTemplateContext` from this file instead of
|
|
426
|
+
// importing this entry point by its own package name.
|
|
427
|
+
pageTemplate = computed(() => this.pageTemplateDirective()?.template ?? null, ...(ngDevMode ? [{ debugName: "pageTemplate" }] : []));
|
|
428
|
+
chromeTemplate = computed(() => this.chromeTemplateDirective()?.template ?? null, ...(ngDevMode ? [{ debugName: "chromeTemplate" }] : []));
|
|
429
|
+
// ── input ────────────────────────────────────────────────────────────────────
|
|
430
|
+
// How this view takes pointer input is its own settings: a change binds the surface again.
|
|
431
|
+
interaction = this.binding.select((stage) => stage.getSettings().interaction, true);
|
|
432
|
+
panFallback = this.binding.select((stage) => stage.getSettings().panFallback, true);
|
|
433
|
+
zoomGestures = this.binding.select((stage) => stage.getSettings().zoomGestures, true);
|
|
434
|
+
/** The interaction hub of this Stage's document: the active tool takes the pointer. */
|
|
435
|
+
hub = new CapabilityBinding(this.host, () => InteractionToken, this.documentScope.id).capability;
|
|
436
|
+
usesHub = computed(() => this.interaction() && this.hub() !== null, ...(ngDevMode ? [{ debugName: "usesHub" }] : []));
|
|
437
|
+
hubCursor = this.host.read(() => this.hub()?.getCursor() ?? 'default', () => 'default');
|
|
438
|
+
/** The hub's cursor (text, grab, a tool's own), while the hub takes this Stage's pointer. */
|
|
439
|
+
cursor = computed(() => (this.usesHub() ? this.hubCursor() : null), ...(ngDevMode ? [{ debugName: "cursor" }] : []));
|
|
440
|
+
// ── anchored UI ──────────────────────────────────────────────────────────────
|
|
225
441
|
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* Pan is then the `pan` tool's job and dragging in `pointer` mode selects
|
|
230
|
-
* text (incl. across pages).
|
|
231
|
-
*
|
|
232
|
-
* Default TRUE: registering `interactionPlugin()` is the one opt-in — tools
|
|
233
|
-
* just work; without the hub this is inert and the stage falls back to
|
|
234
|
-
* built-in drag-to-pan, so a hub-less setup costs nothing. Set `false` on
|
|
235
|
-
* SECONDARY lenses (a thumbnail rail) that should stay click-to-navigate
|
|
236
|
-
* instead of feeding the document's tools.
|
|
442
|
+
* How anchored UI (`<epdf-anchored>`) inside the Stage follows the camera: a projection
|
|
443
|
+
* through the view, and the visible pages as its revision, so the pages and the UI on them
|
|
444
|
+
* update in the same change detection pass and never a frame apart.
|
|
237
445
|
*/
|
|
238
|
-
|
|
446
|
+
projection = {
|
|
447
|
+
// `@embedpdf/web`'s Stage projection, shared by every framework: page boxes to the Stage's
|
|
448
|
+
// own coordinates.
|
|
449
|
+
projector: computed(() => {
|
|
450
|
+
const stage = this.hostCapability();
|
|
451
|
+
return stage ? stageViewProjector(() => stage) : null;
|
|
452
|
+
}),
|
|
453
|
+
revision: this.pages,
|
|
454
|
+
shownPages: computed(() => {
|
|
455
|
+
const shown = this.pages().map((visible) => visible.ref.objectNumber);
|
|
456
|
+
return new Set(shown);
|
|
457
|
+
},
|
|
458
|
+
// The same set until a page comes on screen or leaves it.
|
|
459
|
+
{
|
|
460
|
+
equal: (left, right) => left.size === right.size && [...left].every((objectNumber) => right.has(objectNumber)),
|
|
461
|
+
}),
|
|
462
|
+
};
|
|
463
|
+
constructor() {
|
|
464
|
+
super();
|
|
465
|
+
this.followDocumentInput();
|
|
466
|
+
this.followPageAndZoom();
|
|
467
|
+
if (isPlatformBrowser(inject(PLATFORM_ID)))
|
|
468
|
+
this.bindSurface();
|
|
469
|
+
}
|
|
470
|
+
/** `[document]`: open it for this Stage once the viewer runs; close it when the value changes. */
|
|
471
|
+
followDocumentInput() {
|
|
472
|
+
effect((onCleanup) => {
|
|
473
|
+
const source = this.document();
|
|
474
|
+
const kernel = this.host.kernel();
|
|
475
|
+
if (!source || !kernel || this.host.status() !== 'ready')
|
|
476
|
+
return;
|
|
477
|
+
untracked(() => {
|
|
478
|
+
const cancel = new AbortController();
|
|
479
|
+
const opening = kernel.documents.open(source, { signal: cancel.signal });
|
|
480
|
+
// The new tab is the active one at once; its id is final when the open resolves.
|
|
481
|
+
let id = kernel.documents.getActiveId();
|
|
482
|
+
opening.then(({ document }) => {
|
|
483
|
+
id = document.id;
|
|
484
|
+
if (!cancel.signal.aborted)
|
|
485
|
+
this.ownDocument.opened.set(document.id);
|
|
486
|
+
}, () => {
|
|
487
|
+
// Its tab shows why it couldn't be opened.
|
|
488
|
+
});
|
|
489
|
+
onCleanup(() => {
|
|
490
|
+
cancel.abort();
|
|
491
|
+
this.ownDocument.opened.set(null);
|
|
492
|
+
if (id && kernel.documents.has(id))
|
|
493
|
+
void kernel.documents.close(id).catch(() => { });
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
}
|
|
239
498
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
499
|
+
* `[(page)]` and `[(zoom)]`. A value from the parent moves the view there; a move of the view
|
|
500
|
+
* (scroll, pinch, a button) goes out as `pageChange` and `zoomChange`. When the Stage changes
|
|
501
|
+
* documents (another tab), the new document keeps its own place and reports it.
|
|
243
502
|
*/
|
|
244
|
-
|
|
503
|
+
followPageAndZoom() {
|
|
504
|
+
let previous = null;
|
|
505
|
+
effect(() => {
|
|
506
|
+
const stage = this.hostCapability();
|
|
507
|
+
const page = this.page();
|
|
508
|
+
const zoom = this.zoom();
|
|
509
|
+
untracked(() => {
|
|
510
|
+
const switched = previous !== null && stage !== null && stage !== previous;
|
|
511
|
+
previous = stage;
|
|
512
|
+
if (!stage)
|
|
513
|
+
return;
|
|
514
|
+
if (switched) {
|
|
515
|
+
if (page !== undefined && page !== stage.getCurrentPageIndex()) {
|
|
516
|
+
this.pageChange.emit(stage.getCurrentPageIndex());
|
|
517
|
+
}
|
|
518
|
+
if (zoom !== undefined && zoom !== stage.getZoomLevel()) {
|
|
519
|
+
this.zoomChange.emit(stage.getZoomLevel());
|
|
520
|
+
}
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (page !== undefined && page !== stage.getCurrentPageIndex())
|
|
524
|
+
stage.goToPage(page);
|
|
525
|
+
if (zoom !== undefined && zoom !== stage.getZoomLevel())
|
|
526
|
+
stage.zoomTo(zoom);
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
effect((onCleanup) => {
|
|
530
|
+
const stage = this.hostCapability();
|
|
531
|
+
if (!stage)
|
|
532
|
+
return;
|
|
533
|
+
const offPage = stage.onPageChanged(({ pageIndex }) => this.pageChange.emit(pageIndex));
|
|
534
|
+
const offZoom = stage.onZoomChanged(({ level }) => this.zoomChange.emit(level));
|
|
535
|
+
onCleanup(() => {
|
|
536
|
+
offPage();
|
|
537
|
+
offZoom();
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
}
|
|
245
541
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
542
|
+
* The browser side: the viewport size and pixel ratio, wheel and touch gestures, and the
|
|
543
|
+
* pointer going to the interaction hub. All of it is `@embedpdf/web`'s, shared by every
|
|
544
|
+
* framework, so the Stage feels the same everywhere; this binds it again when the view, the
|
|
545
|
+
* hub or the input settings change.
|
|
250
546
|
*/
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
token = input(StageToken, ...(ngDevMode ? [{ debugName: "token" }] : []));
|
|
254
|
-
host = injectKernelHost();
|
|
255
|
-
stage = injectOptionalCapabilityFor(() => this.token());
|
|
256
|
-
ix = injectOptionalCapability(InteractionToken);
|
|
257
|
-
useHub = computed(() => this.interaction() && this.ix() !== null, ...(ngDevMode ? [{ debugName: "useHub" }] : []));
|
|
258
|
-
// The hub's resolved cursor (text/grab/…), applied to the viewport when driving.
|
|
259
|
-
hubCursor = this.host.value(() => this.ix()?.cursor() ?? 'default');
|
|
260
|
-
cursor = computed(() => (this.useHub() ? this.hubCursor() : null), ...(ngDevMode ? [{ debugName: "cursor" }] : []));
|
|
261
|
-
documentId = injectDocumentId();
|
|
262
|
-
// visiblePages already folds in the camera (each page carries its
|
|
263
|
-
// device-snapped screenX/screenY + transform), so panning re-emits the list —
|
|
264
|
-
// no separate camera subscription needed for positioning. The capability
|
|
265
|
-
// memoizes, so Object.is equality suffices.
|
|
266
|
-
pages = injectOptionalSelectorFor(() => this.token(), (c) => c.visiblePages(), EMPTY_PAGES);
|
|
267
|
-
// Reserved chrome bands (screen px), uniform across pages — the frame the
|
|
268
|
-
// outer box reserves and the chrome template paints into.
|
|
269
|
-
frame = injectOptionalSelectorFor(() => this.token(), (c) => c.pageFrame(), NO_FRAME, frameEqual);
|
|
270
|
-
pageTemplateDir = contentChild(EpdfPageTemplate, ...(ngDevMode ? [{ debugName: "pageTemplateDir" }] : []));
|
|
271
|
-
chromeTemplateDir = contentChild(EpdfPageChrome, ...(ngDevMode ? [{ debugName: "chromeTemplateDir" }] : []));
|
|
272
|
-
pageTemplate = computed(() => this.pageTemplateDir()?.template ?? null, ...(ngDevMode ? [{ debugName: "pageTemplate" }] : []));
|
|
273
|
-
chromeTemplate = computed(() => this.chromeTemplateDir()?.template ?? null, ...(ngDevMode ? [{ debugName: "chromeTemplate" }] : []));
|
|
274
|
-
constructor() {
|
|
275
|
-
if (!isPlatformBrowser(inject(PLATFORM_ID)))
|
|
276
|
-
return;
|
|
277
|
-
const el = inject(ElementRef).nativeElement;
|
|
278
|
-
const zone = inject(NgZone);
|
|
279
|
-
// Rebinds when the stage capability (document open/close/switch, token
|
|
280
|
-
// change), hub, or gesture flags change — the Angular spelling of the React
|
|
281
|
-
// Stage effect and its dependency array.
|
|
547
|
+
bindSurface() {
|
|
548
|
+
const element = inject(ElementRef).nativeElement;
|
|
282
549
|
effect((onCleanup) => {
|
|
283
|
-
const stage = this.
|
|
284
|
-
const
|
|
285
|
-
const
|
|
550
|
+
const stage = this.hostCapability();
|
|
551
|
+
const hub = this.hub();
|
|
552
|
+
const usesHub = this.usesHub();
|
|
286
553
|
const panFallback = this.panFallback();
|
|
287
554
|
const zoomGestures = this.zoomGestures();
|
|
288
555
|
if (!stage)
|
|
289
|
-
return;
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
cleanups.push(ix.registerHandler(createScrollHandler(stage, ix, { panFallback }), {
|
|
306
|
-
source: stage.lensId(),
|
|
307
|
-
}));
|
|
308
|
-
}
|
|
556
|
+
return;
|
|
557
|
+
const detachSurface = createStageSurface(element, stage, {
|
|
558
|
+
hub: usesHub ? hub : null,
|
|
559
|
+
source: stage.getLensId(),
|
|
560
|
+
zoomGestures,
|
|
561
|
+
});
|
|
562
|
+
// The pointer and this view's drag-to-scroll go to the hub together, for this view only,
|
|
563
|
+
// so two Stages on one document never scroll each other.
|
|
564
|
+
const offScroll = usesHub && hub
|
|
565
|
+
? hub.registerHandler(createScrollHandler(stage, hub, { panFallback }), {
|
|
566
|
+
source: stage.getLensId(),
|
|
567
|
+
})
|
|
568
|
+
: null;
|
|
569
|
+
onCleanup(() => {
|
|
570
|
+
offScroll?.();
|
|
571
|
+
detachSurface();
|
|
309
572
|
});
|
|
310
|
-
onCleanup(() => cleanups.forEach((fn) => fn()));
|
|
311
573
|
});
|
|
312
574
|
}
|
|
313
575
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfStage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
314
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: EpdfStage, isStandalone: true, selector: "epdf-stage", inputs: {
|
|
315
|
-
|
|
316
|
-
|
|
576
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: EpdfStage, isStandalone: true, selector: "epdf-stage", inputs: { token: { classPropertyName: "token", publicName: "token", isSignal: true, isRequired: false, transformFunction: null }, document: { classPropertyName: "document", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", zoomChange: "zoomChange" }, host: { properties: { "style.cursor": "cursor()" }, styleAttribute: "position: relative; display: block; overflow: hidden; touch-action: none;" }, providers: [
|
|
577
|
+
// The services inside act on this Stage's document. After them, so it wins over the
|
|
578
|
+
// `EpdfStageRef` among them: what's inside injects this Stage as `EpdfStageRef`, talks to
|
|
579
|
+
// its document, and projects anchored UI through its camera.
|
|
580
|
+
EPDF_SCOPED_SERVICES,
|
|
581
|
+
StageDocument,
|
|
582
|
+
{ provide: EPDF_DOCUMENT_SCOPE, useExisting: StageDocument },
|
|
583
|
+
{ provide: EpdfStageRef, useExisting: forwardRef(() => EpdfStage) },
|
|
584
|
+
{ provide: EPDF_PROJECTOR, useFactory: () => inject(EpdfStage).projection },
|
|
585
|
+
], queries: [{ propertyName: "pageTemplateDirective", first: true, predicate: EpdfPageTemplate, descendants: true, isSignal: true }, { propertyName: "chromeTemplateDirective", first: true, predicate: EpdfPageChrome, descendants: true, isSignal: true }], exportAs: ["epdfStage"], usesInheritance: true, ngImport: i0, template: `
|
|
586
|
+
@if (pageTemplate(); as template) {
|
|
587
|
+
@for (visiblePage of pages(); track visiblePage.ref.objectNumber) {
|
|
317
588
|
<epdf-page-surface
|
|
318
|
-
[
|
|
589
|
+
[visiblePage]="visiblePage"
|
|
319
590
|
[frame]="frame()"
|
|
320
591
|
[documentId]="documentId() ?? ''"
|
|
321
|
-
[
|
|
322
|
-
[
|
|
592
|
+
[stage]="hostCapability()!"
|
|
593
|
+
[look]="look()"
|
|
594
|
+
[pageTemplate]="template"
|
|
595
|
+
[chromeTemplate]="chromeTemplate()"
|
|
323
596
|
/>
|
|
324
597
|
}
|
|
325
598
|
}
|
|
326
|
-
<!--
|
|
327
|
-
renders above the pages (menus, controls, scrollbars). -->
|
|
599
|
+
<!-- What you put inside <epdf-stage> sits over the pages, in the Stage's own box. -->
|
|
328
600
|
<ng-content />
|
|
329
|
-
`, isInline: true, dependencies: [{ kind: "component", type: EpdfPageSurface, selector: "epdf-page-surface", inputs: ["
|
|
601
|
+
`, isInline: true, dependencies: [{ kind: "component", type: EpdfPageSurface, selector: "epdf-page-surface", inputs: ["visiblePage", "frame", "documentId", "stage", "look", "pageTemplate", "chromeTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
330
602
|
}
|
|
331
603
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfStage, decorators: [{
|
|
332
604
|
type: Component,
|
|
333
605
|
args: [{
|
|
334
606
|
selector: 'epdf-stage',
|
|
335
|
-
|
|
607
|
+
exportAs: 'epdfStage',
|
|
336
608
|
imports: [EpdfPageSurface],
|
|
337
609
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
610
|
+
providers: [
|
|
611
|
+
// The services inside act on this Stage's document. After them, so it wins over the
|
|
612
|
+
// `EpdfStageRef` among them: what's inside injects this Stage as `EpdfStageRef`, talks to
|
|
613
|
+
// its document, and projects anchored UI through its camera.
|
|
614
|
+
EPDF_SCOPED_SERVICES,
|
|
615
|
+
StageDocument,
|
|
616
|
+
{ provide: EPDF_DOCUMENT_SCOPE, useExisting: StageDocument },
|
|
617
|
+
{ provide: EpdfStageRef, useExisting: forwardRef(() => EpdfStage) },
|
|
618
|
+
{ provide: EPDF_PROJECTOR, useFactory: () => inject(EpdfStage).projection },
|
|
619
|
+
],
|
|
338
620
|
host: {
|
|
339
|
-
style: 'position: relative; overflow: hidden; touch-action: none;
|
|
621
|
+
style: 'position: relative; display: block; overflow: hidden; touch-action: none;',
|
|
340
622
|
'[style.cursor]': 'cursor()',
|
|
341
623
|
},
|
|
342
624
|
template: `
|
|
343
|
-
@if (pageTemplate(); as
|
|
344
|
-
@for (
|
|
625
|
+
@if (pageTemplate(); as template) {
|
|
626
|
+
@for (visiblePage of pages(); track visiblePage.ref.objectNumber) {
|
|
345
627
|
<epdf-page-surface
|
|
346
|
-
[
|
|
628
|
+
[visiblePage]="visiblePage"
|
|
347
629
|
[frame]="frame()"
|
|
348
630
|
[documentId]="documentId() ?? ''"
|
|
349
|
-
[
|
|
350
|
-
[
|
|
631
|
+
[stage]="hostCapability()!"
|
|
632
|
+
[look]="look()"
|
|
633
|
+
[pageTemplate]="template"
|
|
634
|
+
[chromeTemplate]="chromeTemplate()"
|
|
351
635
|
/>
|
|
352
636
|
}
|
|
353
637
|
}
|
|
354
|
-
<!--
|
|
355
|
-
renders above the pages (menus, controls, scrollbars). -->
|
|
638
|
+
<!-- What you put inside <epdf-stage> sits over the pages, in the Stage's own box. -->
|
|
356
639
|
<ng-content />
|
|
357
640
|
`,
|
|
358
641
|
}]
|
|
359
642
|
}], ctorParameters: () => [] });
|
|
360
643
|
|
|
361
|
-
/** Late-binding method passthrough: exact capability signature, current doc. */
|
|
362
|
-
const lazy = (cap, key) => ((...args) => cap()[key](...args));
|
|
363
|
-
function injectStage(token = StageToken) {
|
|
364
|
-
return injectCapability(token);
|
|
365
|
-
}
|
|
366
|
-
function injectZoom(token = StageToken) {
|
|
367
|
-
const s = injectCapability(token);
|
|
368
|
-
return {
|
|
369
|
-
zoom: injectSelector(token, (c) => c.zoomLevel()),
|
|
370
|
-
/** Active zoom intent: 'automatic' | 'fit-page' | 'fit-width' | 'fit-all' | 'custom'. */
|
|
371
|
-
mode: injectSelector(token, (c) => c.zoomMode()),
|
|
372
|
-
zoomIn: lazy(s, 'zoomIn'),
|
|
373
|
-
zoomOut: lazy(s, 'zoomOut'),
|
|
374
|
-
fitWidth: lazy(s, 'fitWidth'),
|
|
375
|
-
fitPage: lazy(s, 'fitPage'),
|
|
376
|
-
fitAll: lazy(s, 'fitAll'),
|
|
377
|
-
automatic: lazy(s, 'automatic'),
|
|
378
|
-
zoomTo: lazy(s, 'zoomTo'),
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
function injectPages(token = StageToken) {
|
|
382
|
-
const s = injectCapability(token);
|
|
383
|
-
return {
|
|
384
|
-
currentPage: injectSelector(token, (c) => c.currentPage()),
|
|
385
|
-
pageCount: injectSelector(token, (c) => c.pageCount()),
|
|
386
|
-
goToPage: lazy(s, 'goToPage'),
|
|
387
|
-
next: lazy(s, 'next'),
|
|
388
|
-
prev: lazy(s, 'prev'),
|
|
389
|
-
reveal: lazy(s, 'reveal'),
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
function injectLayout(token = StageToken) {
|
|
393
|
-
const s = injectCapability(token);
|
|
394
|
-
return {
|
|
395
|
-
flow: injectSelector(token, (c) => c.flow()),
|
|
396
|
-
layout: injectSelector(token, (c) => c.layout()),
|
|
397
|
-
spread: injectSelector(token, (c) => c.spread()),
|
|
398
|
-
sizing: injectSelector(token, (c) => c.sizing()),
|
|
399
|
-
bounded: injectSelector(token, (c) => c.bounded()),
|
|
400
|
-
setFlow: lazy(s, 'setFlow'),
|
|
401
|
-
setLayout: lazy(s, 'setLayout'),
|
|
402
|
-
setSpread: lazy(s, 'setSpread'),
|
|
403
|
-
setSizing: lazy(s, 'setSizing'),
|
|
404
|
-
setBounded: lazy(s, 'setBounded'),
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
/** The document's page list (with PDF labels) + the current item's pages — the
|
|
408
|
-
* data for page thumbnails / worksheet-style page tabs. */
|
|
409
|
-
function injectPageList(token = StageToken) {
|
|
410
|
-
return {
|
|
411
|
-
pages: injectSelector(token, (c) => c.pages(), (a, b) => a.length === b.length && a.every((p, i) => p.pon === b[i].pon && p.label === b[i].label)),
|
|
412
|
-
currentItemPages: injectSelector(token, (c) => c.currentItemPages(), shallowArray),
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
644
|
/**
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
645
|
+
* `<epdf-scrollbar>`: a scrollbar for the Stage it's in, that you style yourself. Put it inside
|
|
646
|
+
* `<epdf-stage>`; it reads the Stage's `scrollMetrics()` (the numbers a scrolling element has)
|
|
647
|
+
* and scrolls through its calls.
|
|
648
|
+
*
|
|
649
|
+
* It behaves like a native bar: drag the thumb (the point you grabbed stays under the pointer),
|
|
650
|
+
* press the track to page toward the pointer (repeating while held, stopping at the pointer),
|
|
651
|
+
* a minimum thumb length on long documents, and macOS-style hiding when nothing moves. On an
|
|
652
|
+
* unbounded Stage the metrics already cover the content and the view together, so the bar
|
|
653
|
+
* shrinks as you pan away; the mapping is frozen while the thumb is dragged, so the thumb never
|
|
654
|
+
* chases itself.
|
|
655
|
+
*
|
|
656
|
+
* The element is the track; style it with your own class and these attributes:
|
|
657
|
+
* [data-embedpdf-scrollbar][data-axis="y"][data-state="visible|hidden"]
|
|
658
|
+
* [data-embedpdf-scrollbar][data-dragging]
|
|
659
|
+
* [data-embedpdf-scrollbar-thumb] (or `thumbClass`)
|
|
660
|
+
* Until you style the thumb, its color is `--epdf-scrollbar-thumb`.
|
|
419
661
|
*/
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
662
|
+
class EpdfScrollbar {
|
|
663
|
+
/** `'y'` for a vertical bar, `'x'` for a horizontal one. */
|
|
664
|
+
axis = input.required(...(ngDevMode ? [{ debugName: "axis" }] : []));
|
|
665
|
+
/**
|
|
666
|
+
* Hide this many milliseconds after the view stops moving (hover and dragging keep it shown;
|
|
667
|
+
* a hidden bar ignores the pointer, like macOS), or `false` to always show it.
|
|
668
|
+
*/
|
|
669
|
+
autoHide = input(1200, ...(ngDevMode ? [{ debugName: "autoHide", transform: (value) => (value === false || value === 'false' ? false : Number(value)) }] : [{
|
|
670
|
+
transform: (value) => (value === false || value === 'false' ? false : Number(value)),
|
|
671
|
+
}]));
|
|
672
|
+
/** The shortest thumb, in pixels, so it never vanishes on a long document. */
|
|
673
|
+
minThumbSize = input(24, ...(ngDevMode ? [{ debugName: "minThumbSize" }] : []));
|
|
674
|
+
/**
|
|
675
|
+
* Pressing the track: `'page'` steps 90% of a view toward the pointer and repeats while held;
|
|
676
|
+
* `'jump'` puts the thumb under the pointer and goes on as a drag.
|
|
677
|
+
*/
|
|
678
|
+
trackPress = input('page', ...(ngDevMode ? [{ debugName: "trackPress" }] : []));
|
|
679
|
+
/** The thumb's class. */
|
|
680
|
+
thumbClass = input(null, ...(ngDevMode ? [{ debugName: "thumbClass" }] : []));
|
|
681
|
+
stage;
|
|
682
|
+
element = inject(ElementRef).nativeElement;
|
|
683
|
+
trackPx = signal(0, ...(ngDevMode ? [{ debugName: "trackPx" }] : []));
|
|
684
|
+
dragging = signal(false, ...(ngDevMode ? [{ debugName: "dragging" }] : []));
|
|
685
|
+
hovered = signal(false, ...(ngDevMode ? [{ debugName: "hovered" }] : []));
|
|
686
|
+
/** The view moved a moment ago. */
|
|
687
|
+
active = signal(true, ...(ngDevMode ? [{ debugName: "active" }] : []));
|
|
688
|
+
vertical = computed(() => this.axis() === 'y', ...(ngDevMode ? [{ debugName: "vertical" }] : []));
|
|
689
|
+
scrollable = computed(() => {
|
|
690
|
+
const metrics = this.stage.scrollMetrics();
|
|
691
|
+
return this.vertical() ? metrics.scrollableY : metrics.scrollableX;
|
|
692
|
+
}, ...(ngDevMode ? [{ debugName: "scrollable" }] : []));
|
|
693
|
+
/** The thumb along the track: one formula for drawing, dragging and paging, in every framework. */
|
|
694
|
+
layout = computed(() => scrollbarLayout(this.stage.scrollMetrics(), this.vertical(), this.trackPx(), this.minThumbSize()), ...(ngDevMode ? [{ debugName: "layout" }] : []));
|
|
695
|
+
shown = computed(() => this.autoHide() === false ||
|
|
696
|
+
this.autoHide() === 0 ||
|
|
697
|
+
this.active() ||
|
|
698
|
+
this.hovered() ||
|
|
699
|
+
this.dragging(), ...(ngDevMode ? [{ debugName: "shown" }] : []));
|
|
700
|
+
thumbColor = paintDefault('scrollbar-thumb');
|
|
701
|
+
round = Math.round;
|
|
702
|
+
/** Thumb drags, track paging and jumps: `@embedpdf/web`'s, shared by every framework. */
|
|
703
|
+
presses = createScrollbarPresses((dragging) => this.dragging.set(dragging));
|
|
704
|
+
constructor() {
|
|
705
|
+
const stage = inject(EpdfStage, { optional: true });
|
|
706
|
+
if (!stage)
|
|
707
|
+
throw outsideStageError('<epdf-scrollbar>');
|
|
708
|
+
this.stage = stage;
|
|
709
|
+
// Any move of the view shows the bar again and starts the countdown to hiding it.
|
|
710
|
+
effect((onCleanup) => {
|
|
711
|
+
this.stage.scrollMetrics();
|
|
712
|
+
const hideAfter = this.autoHide();
|
|
713
|
+
if (hideAfter === false || hideAfter === 0)
|
|
714
|
+
return;
|
|
715
|
+
this.active.set(true);
|
|
716
|
+
const timer = setTimeout(() => this.active.set(false), hideAfter);
|
|
717
|
+
onCleanup(() => clearTimeout(timer));
|
|
718
|
+
});
|
|
719
|
+
// The track's length, which the thumb maps onto; again whenever the bar appears or resizes.
|
|
720
|
+
effect((onCleanup) => {
|
|
721
|
+
const vertical = this.vertical();
|
|
722
|
+
if (!this.scrollable() || typeof ResizeObserver === 'undefined')
|
|
723
|
+
return;
|
|
724
|
+
const measure = () => this.trackPx.set(vertical ? this.element.clientHeight : this.element.clientWidth);
|
|
725
|
+
const observer = new ResizeObserver(measure);
|
|
726
|
+
observer.observe(this.element);
|
|
727
|
+
measure();
|
|
728
|
+
onCleanup(() => observer.disconnect());
|
|
729
|
+
});
|
|
730
|
+
inject(DestroyRef).onDestroy(() => this.endDrag());
|
|
731
|
+
}
|
|
732
|
+
/** The bar as it is now, for `@embedpdf/web`'s press handling. */
|
|
733
|
+
track() {
|
|
734
|
+
const vertical = this.vertical();
|
|
735
|
+
return {
|
|
736
|
+
stage: this.stage,
|
|
737
|
+
vertical,
|
|
738
|
+
element: this.element,
|
|
739
|
+
layout: this.layout(),
|
|
740
|
+
// Fresh geometry for paging steps, from the metrics as they are now.
|
|
741
|
+
liveLayout: () => scrollbarLayout(this.stage.scrollMetrics(), vertical, this.trackPx(), this.minThumbSize()),
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
onThumbDown(event) {
|
|
745
|
+
this.presses.pressThumb(event, this.track());
|
|
746
|
+
}
|
|
747
|
+
onTrackDown(event) {
|
|
748
|
+
this.presses.pressTrack(event, this.track(), this.trackPress());
|
|
749
|
+
}
|
|
750
|
+
/** Pointer capture sends the moves here, for thumb drags, jump drags and paging alike. */
|
|
751
|
+
onMove(event) {
|
|
752
|
+
this.presses.move(event, this.track());
|
|
753
|
+
}
|
|
754
|
+
endDrag() {
|
|
755
|
+
this.presses.release();
|
|
756
|
+
}
|
|
757
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfScrollbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
758
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.2.4", type: EpdfScrollbar, isStandalone: true, selector: "epdf-scrollbar", inputs: { axis: { classPropertyName: "axis", publicName: "axis", isSignal: true, isRequired: true, transformFunction: null }, autoHide: { classPropertyName: "autoHide", publicName: "autoHide", isSignal: true, isRequired: false, transformFunction: null }, minThumbSize: { classPropertyName: "minThumbSize", publicName: "minThumbSize", isSignal: true, isRequired: false, transformFunction: null }, trackPress: { classPropertyName: "trackPress", publicName: "trackPress", isSignal: true, isRequired: false, transformFunction: null }, thumbClass: { classPropertyName: "thumbClass", publicName: "thumbClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "scrollbar", "data-embedpdf-scrollbar": "" }, listeners: { "pointerdown": "onTrackDown($event)", "pointermove": "onMove($event)", "pointerup": "endDrag()", "pointercancel": "endDrag()", "pointerenter": "hovered.set(true)", "pointerleave": "hovered.set(false)" }, properties: { "attr.aria-orientation": "vertical() ? 'vertical' : 'horizontal'", "attr.aria-valuemin": "0", "attr.aria-valuemax": "round(layout().maxOffset)", "attr.aria-valuenow": "round(layout().offset)", "attr.data-axis": "axis()", "attr.data-state": "shown() ? 'visible' : 'hidden'", "attr.data-dragging": "dragging() ? '' : null", "style.display": "scrollable() ? 'block' : 'none'", "style.top.px": "vertical() ? 0 : null", "style.left.px": "vertical() ? null : 0", "style.right.px": "0", "style.bottom.px": "0", "style.width.px": "vertical() ? 12 : null", "style.height.px": "vertical() ? null : 12", "style.opacity": "shown() ? 1 : 0", "style.pointer-events": "shown() ? 'auto' : 'none'" }, styleAttribute: "position: absolute; touch-action: none; user-select: none; transition: opacity 200ms" }, ngImport: i0, template: `
|
|
759
|
+
<div
|
|
760
|
+
data-embedpdf-scrollbar-thumb=""
|
|
761
|
+
[class]="thumbClass()"
|
|
762
|
+
style="position: absolute; border-radius: 6px"
|
|
763
|
+
[style.background]="thumbColor"
|
|
764
|
+
[style.left.px]="vertical() ? 2 : layout().thumbPosition"
|
|
765
|
+
[style.right.px]="vertical() ? 2 : null"
|
|
766
|
+
[style.top.px]="vertical() ? layout().thumbPosition : 2"
|
|
767
|
+
[style.bottom.px]="vertical() ? null : 2"
|
|
768
|
+
[style.width.px]="vertical() ? null : layout().thumbLength"
|
|
769
|
+
[style.height.px]="vertical() ? layout().thumbLength : null"
|
|
770
|
+
(pointerdown)="onThumbDown($event)"
|
|
771
|
+
></div>
|
|
772
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
429
773
|
}
|
|
774
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: EpdfScrollbar, decorators: [{
|
|
775
|
+
type: Component,
|
|
776
|
+
args: [{
|
|
777
|
+
selector: 'epdf-scrollbar',
|
|
778
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
779
|
+
host: {
|
|
780
|
+
role: 'scrollbar',
|
|
781
|
+
'data-embedpdf-scrollbar': '',
|
|
782
|
+
style: 'position: absolute; touch-action: none; user-select: none; transition: opacity 200ms',
|
|
783
|
+
'[attr.aria-orientation]': "vertical() ? 'vertical' : 'horizontal'",
|
|
784
|
+
'[attr.aria-valuemin]': '0',
|
|
785
|
+
'[attr.aria-valuemax]': 'round(layout().maxOffset)',
|
|
786
|
+
'[attr.aria-valuenow]': 'round(layout().offset)',
|
|
787
|
+
'[attr.data-axis]': 'axis()',
|
|
788
|
+
'[attr.data-state]': "shown() ? 'visible' : 'hidden'",
|
|
789
|
+
'[attr.data-dragging]': "dragging() ? '' : null",
|
|
790
|
+
// Pinned to the right (y) or bottom (x) edge of the Stage; nothing to scroll, no bar.
|
|
791
|
+
'[style.display]': "scrollable() ? 'block' : 'none'",
|
|
792
|
+
'[style.top.px]': 'vertical() ? 0 : null',
|
|
793
|
+
'[style.left.px]': 'vertical() ? null : 0',
|
|
794
|
+
'[style.right.px]': '0',
|
|
795
|
+
'[style.bottom.px]': '0',
|
|
796
|
+
'[style.width.px]': 'vertical() ? 12 : null',
|
|
797
|
+
'[style.height.px]': 'vertical() ? null : 12',
|
|
798
|
+
'[style.opacity]': 'shown() ? 1 : 0',
|
|
799
|
+
// A hidden bar must not take the clicks meant for the pages under it.
|
|
800
|
+
'[style.pointer-events]': "shown() ? 'auto' : 'none'",
|
|
801
|
+
'(pointerdown)': 'onTrackDown($event)',
|
|
802
|
+
'(pointermove)': 'onMove($event)',
|
|
803
|
+
'(pointerup)': 'endDrag()',
|
|
804
|
+
'(pointercancel)': 'endDrag()',
|
|
805
|
+
'(pointerenter)': 'hovered.set(true)',
|
|
806
|
+
'(pointerleave)': 'hovered.set(false)',
|
|
807
|
+
},
|
|
808
|
+
template: `
|
|
809
|
+
<div
|
|
810
|
+
data-embedpdf-scrollbar-thumb=""
|
|
811
|
+
[class]="thumbClass()"
|
|
812
|
+
style="position: absolute; border-radius: 6px"
|
|
813
|
+
[style.background]="thumbColor"
|
|
814
|
+
[style.left.px]="vertical() ? 2 : layout().thumbPosition"
|
|
815
|
+
[style.right.px]="vertical() ? 2 : null"
|
|
816
|
+
[style.top.px]="vertical() ? layout().thumbPosition : 2"
|
|
817
|
+
[style.bottom.px]="vertical() ? null : 2"
|
|
818
|
+
[style.width.px]="vertical() ? null : layout().thumbLength"
|
|
819
|
+
[style.height.px]="vertical() ? layout().thumbLength : null"
|
|
820
|
+
(pointerdown)="onThumbDown($event)"
|
|
821
|
+
></div>
|
|
822
|
+
`,
|
|
823
|
+
}]
|
|
824
|
+
}], ctorParameters: () => [] });
|
|
430
825
|
|
|
431
826
|
/**
|
|
432
|
-
* @embedpdf/angular/stage
|
|
433
|
-
*
|
|
827
|
+
* @embedpdf/angular/stage: `<epdf-stage>` and what goes with it.
|
|
828
|
+
*
|
|
829
|
+
* withStage(options) the plugin, for provideEmbedPdf()
|
|
830
|
+
* <epdf-stage #stage="epdfStage"> the pages, and the Stage's API
|
|
831
|
+
* inject(EpdfStageRef) the Stage's API, from inside a Stage or anywhere
|
|
832
|
+
* <ng-template epdfPage>, epdfPageChrome what each page draws, and around it
|
|
833
|
+
* <epdf-scrollbar> a scrollbar you style yourself
|
|
434
834
|
*/
|
|
435
|
-
//
|
|
835
|
+
// The plugin's types and helpers, so app code has one import for the feature.
|
|
436
836
|
|
|
437
837
|
/**
|
|
438
838
|
* Generated bundle index. Do not edit.
|
|
439
839
|
*/
|
|
440
840
|
|
|
441
|
-
export { EpdfPageChrome,
|
|
841
|
+
export { EpdfPageChrome, EpdfPageTemplate, EpdfScrollbar, EpdfStage, EpdfStageRef, withStage };
|
|
442
842
|
//# sourceMappingURL=embedpdf-angular-stage.mjs.map
|