@embedpdf/angular 3.0.0-next.13 → 3.0.0-next.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +309 -0
  2. package/dist/README.md +309 -0
  3. package/dist/actions/index.d.ts +34 -16
  4. package/dist/anchored/index.d.ts +67 -0
  5. package/dist/annotation/index.d.ts +599 -0
  6. package/dist/commands/index.d.ts +109 -0
  7. package/dist/fesm2022/embedpdf-angular-actions.mjs +117 -36
  8. package/dist/fesm2022/embedpdf-angular-actions.mjs.map +1 -1
  9. package/dist/fesm2022/embedpdf-angular-anchored.mjs +148 -0
  10. package/dist/fesm2022/embedpdf-angular-anchored.mjs.map +1 -0
  11. package/dist/fesm2022/embedpdf-angular-annotation.mjs +1960 -0
  12. package/dist/fesm2022/embedpdf-angular-annotation.mjs.map +1 -0
  13. package/dist/fesm2022/embedpdf-angular-commands.mjs +235 -0
  14. package/dist/fesm2022/embedpdf-angular-commands.mjs.map +1 -0
  15. package/dist/fesm2022/embedpdf-angular-form.mjs +1074 -0
  16. package/dist/fesm2022/embedpdf-angular-form.mjs.map +1 -0
  17. package/dist/fesm2022/embedpdf-angular-history.mjs +54 -0
  18. package/dist/fesm2022/embedpdf-angular-history.mjs.map +1 -0
  19. package/dist/fesm2022/embedpdf-angular-i18n.mjs +100 -0
  20. package/dist/fesm2022/embedpdf-angular-i18n.mjs.map +1 -0
  21. package/dist/fesm2022/embedpdf-angular-interaction.mjs +157 -0
  22. package/dist/fesm2022/embedpdf-angular-interaction.mjs.map +1 -0
  23. package/dist/fesm2022/embedpdf-angular-link.mjs +306 -0
  24. package/dist/fesm2022/embedpdf-angular-link.mjs.map +1 -0
  25. package/dist/fesm2022/embedpdf-angular-measurement.mjs +106 -0
  26. package/dist/fesm2022/embedpdf-angular-measurement.mjs.map +1 -0
  27. package/dist/fesm2022/embedpdf-angular-metadata.mjs +77 -0
  28. package/dist/fesm2022/embedpdf-angular-metadata.mjs.map +1 -0
  29. package/dist/fesm2022/embedpdf-angular-page-edit.mjs +65 -0
  30. package/dist/fesm2022/embedpdf-angular-page-edit.mjs.map +1 -0
  31. package/dist/fesm2022/embedpdf-angular-page-view.mjs +297 -0
  32. package/dist/fesm2022/embedpdf-angular-page-view.mjs.map +1 -0
  33. package/dist/fesm2022/embedpdf-angular-redaction.mjs +93 -0
  34. package/dist/fesm2022/embedpdf-angular-redaction.mjs.map +1 -0
  35. package/dist/fesm2022/embedpdf-angular-render.mjs +331 -69
  36. package/dist/fesm2022/embedpdf-angular-render.mjs.map +1 -1
  37. package/dist/fesm2022/embedpdf-angular-runtime.mjs +1147 -390
  38. package/dist/fesm2022/embedpdf-angular-runtime.mjs.map +1 -1
  39. package/dist/fesm2022/embedpdf-angular-search.mjs +251 -0
  40. package/dist/fesm2022/embedpdf-angular-search.mjs.map +1 -0
  41. package/dist/fesm2022/embedpdf-angular-selection.mjs +477 -0
  42. package/dist/fesm2022/embedpdf-angular-selection.mjs.map +1 -0
  43. package/dist/fesm2022/embedpdf-angular-shell.mjs +177 -0
  44. package/dist/fesm2022/embedpdf-angular-shell.mjs.map +1 -0
  45. package/dist/fesm2022/embedpdf-angular-signature.mjs +120 -0
  46. package/dist/fesm2022/embedpdf-angular-signature.mjs.map +1 -0
  47. package/dist/fesm2022/embedpdf-angular-stage.mjs +688 -288
  48. package/dist/fesm2022/embedpdf-angular-stage.mjs.map +1 -1
  49. package/dist/fesm2022/embedpdf-angular-stamp.mjs +169 -0
  50. package/dist/fesm2022/embedpdf-angular-stamp.mjs.map +1 -0
  51. package/dist/fesm2022/embedpdf-angular-testing.mjs +89 -0
  52. package/dist/fesm2022/embedpdf-angular-testing.mjs.map +1 -0
  53. package/dist/fesm2022/embedpdf-angular-toolbar.mjs +1123 -0
  54. package/dist/fesm2022/embedpdf-angular-toolbar.mjs.map +1 -0
  55. package/dist/fesm2022/embedpdf-angular-view-manager.mjs +69 -0
  56. package/dist/fesm2022/embedpdf-angular-view-manager.mjs.map +1 -0
  57. package/dist/fesm2022/embedpdf-angular.mjs +23 -3
  58. package/dist/fesm2022/embedpdf-angular.mjs.map +1 -1
  59. package/dist/form/index.d.ts +79 -0
  60. package/dist/history/index.d.ts +28 -0
  61. package/dist/i18n/index.d.ts +52 -0
  62. package/dist/index.d.ts +20 -0
  63. package/dist/interaction/index.d.ts +58 -0
  64. package/dist/link/index.d.ts +77 -0
  65. package/dist/measurement/index.d.ts +46 -0
  66. package/dist/metadata/index.d.ts +51 -0
  67. package/dist/page-edit/index.d.ts +22 -0
  68. package/dist/page-view/index.d.ts +94 -0
  69. package/dist/redaction/index.d.ts +38 -0
  70. package/dist/render/index.d.ts +45 -6
  71. package/dist/runtime/index.d.ts +579 -199
  72. package/dist/search/index.d.ts +71 -0
  73. package/dist/selection/index.d.ts +141 -0
  74. package/dist/shell/index.d.ts +88 -0
  75. package/dist/signature/index.d.ts +45 -0
  76. package/dist/stage/index.d.ts +242 -141
  77. package/dist/stamp/index.d.ts +58 -0
  78. package/dist/testing/index.d.ts +53 -0
  79. package/dist/toolbar/index.d.ts +257 -0
  80. package/dist/view-manager/index.d.ts +23 -0
  81. package/package.json +135 -14
@@ -1,31 +1,170 @@
1
- import { StageToken, createScrollHandler, settingsEqual } from '@embedpdf/plugin-stage';
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, computed, viewChild, Injector, ChangeDetectionStrategy, Component, contentChild, PLATFORM_ID, ElementRef, NgZone, effect } from '@angular/core';
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 { createPageContext, EPDF_PAGE, injectKernelHost, injectOptionalCapabilityFor, injectOptionalCapability, injectDocumentId, injectOptionalSelectorFor, injectCapability, injectSelector, shallowArray } from '@embedpdf/angular/runtime';
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
- * The render-prop translation: React's `children: (page) => ReactNode` becomes
12
- * marker directives on `<ng-template>`, typed so `let-page` infers
13
- * `EpdfPageContext` in the template.
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> page-space content (rotates)
17
- * <ng-template epdfPageChrome> box-space chrome (never rotates)
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
- * The `$implicit` context is the SAME stable object the per-page injector
20
- * provides as `EPDF_PAGE` — `let-page` is sugar for inline chrome; components
21
- * inside the template just call `injectPage()`.
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(_dir, ctx) {
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]', standalone: true }]
175
+ args: [{ selector: 'ng-template[epdfPage]' }]
37
176
  }] });
38
- /** BOX-SPACE chrome for each visible page (page-number label, selection
39
- * border, per-page buttons). Rendered into the OUTER box (content + reserved
40
- * `pageFrame`), so it does NOT rotate and the reserved bands are plain
41
- * regions (`bottom: 0; height: page.frame().bottom`). */
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(_dir, ctx) {
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]', standalone: true }]
192
+ args: [{ selector: 'ng-template[epdfPageChrome]' }]
53
193
  }] });
54
194
 
55
195
  /**
56
- * INTERNAL page shell — one per visible page, tracked by `pon` so the instance
57
- * (and everything below) survives camera motion and page reorders.
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
- * THE STABILITY INVARIANT (see page-context.ts): `page` (the context) and
60
- * `pageInjector` are created ONCE per surface. Camera frames arrive as new
61
- * `vp` input values; only the signals inside the context change. Changing the
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
- * Geometry mirrors React's PageSurface: the outer box = content footprint +
66
- * reserved chrome bands; the shadow is axis-aligned and stays put under
67
- * rotation; the content wrapper is the ONLY thing rotation turns, and carries
68
- * no transform at rotation 0 so it pixel-snaps like the shadow behind it.
69
- * All numbers come from the transform — never re-derive `* zoom` / `* dpr`.
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
- vp = input.required(...(ngDevMode ? [{ debugName: "vp" }] : []));
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
- pageTpl = input.required(...(ngDevMode ? [{ debugName: "pageTpl" }] : []));
76
- chromeTpl = input(null, ...(ngDevMode ? [{ debugName: "chromeTpl" }] : []));
77
- t = computed(() => this.vp().transform, ...(ngDevMode ? [{ debugName: "t" }] : []));
78
- // Outer box = display footprint + reserved chrome bands; screenX/screenY are
79
- // the device-snapped footprint top-left, so the box sits one frame further out.
80
- left = computed(() => this.vp().screenX - this.frame().left, ...(ngDevMode ? [{ debugName: "left" }] : []));
81
- top = computed(() => this.vp().screenY - this.frame().top, ...(ngDevMode ? [{ debugName: "top" }] : []));
82
- outerWidth = computed(() => this.t().viewWidth + this.frame().left + this.frame().right, ...(ngDevMode ? [{ debugName: "outerWidth" }] : []));
83
- outerHeight = computed(() => this.t().viewHeight + this.frame().top + this.frame().bottom, ...(ngDevMode ? [{ debugName: "outerHeight" }] : []));
84
- // Center the (possibly rotated) content box on the display box and rotate
85
- // about its center — NO translate(), so rotation 0 carries no transform.
86
- contentLeft = computed(() => this.frame().left + (this.t().viewWidth - this.t().contentWidth) / 2, ...(ngDevMode ? [{ debugName: "contentLeft" }] : []));
87
- contentTop = computed(() => this.frame().top + (this.t().viewHeight - this.t().contentHeight) / 2, ...(ngDevMode ? [{ debugName: "contentTop" }] : []));
88
- rotate = computed(() => {
89
- const rotation = this.vp().rotation;
90
- return rotation ? `rotate(${rotation}deg)` : null;
91
- }, ...(ngDevMode ? [{ debugName: "rotate" }] : []));
92
- contentEl = viewChild.required('content');
93
- /** ONE stable context per surface — volatile parts are signals inside it. */
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
- pon: () => this.vp().pon,
97
- pageIndex: computed(() => this.vp().pageIndex),
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.t,
100
- getRect: () => this.contentEl().nativeElement.getBoundingClientRect(),
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
- /** ONE stable injector per surface (see the invariant above), parented to
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: { vp: { classPropertyName: "vp", publicName: "vp", 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 }, pageTpl: { classPropertyName: "pageTpl", publicName: "pageTpl", isSignal: true, isRequired: true, transformFunction: null }, chromeTpl: { classPropertyName: "chromeTpl", publicName: "chromeTpl", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.left.px": "left()", "style.top.px": "top()", "style.width.px": "outerWidth()", "style.height.px": "outerHeight()" }, styleAttribute: "position: absolute; display: block;" }, viewQueries: [{ propertyName: "contentEl", first: true, predicate: ["content"], descendants: true, isSignal: true }], ngImport: i0, template: `
111
- <!-- drop shadow ONLY — axis-aligned at the content box (inset by the frame),
112
- transparent fill so it can never peek out behind the bitmap, and it
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; box-shadow: var(--epdf-page-shadow, 0 6px 18px rgba(0, 0, 0, 0.18));"
116
- [style.left.px]="frame().left"
117
- [style.top.px]="frame().top"
118
- [style.width.px]="t().viewWidth"
119
- [style.height.px]="t().viewHeight"
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
- <!-- the page: white backing + content as ONE box — the only thing rotation
122
- turns. We render our own selection highlights, so native selection is
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; background: #fff; user-select: none; -webkit-user-select: none;"
127
- [style.left.px]="contentLeft()"
128
- [style.top.px]="contentTop()"
129
- [style.width.px]="t().contentWidth"
130
- [style.height.px]="t().contentHeight"
131
- [style.transform]="rotate()"
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]="pageTpl()"
282
+ [ngTemplateOutlet]="pageTemplate()"
135
283
  [ngTemplateOutletContext]="templateContext"
136
284
  [ngTemplateOutletInjector]="pageInjector"
137
285
  />
138
286
  </div>
139
- <!-- box-space chrome — fills the outer box, NEVER rotates. Bands are plain
140
- regions: a label is bottom: 0; height: frame().bottom. -->
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]': 'left()',
160
- '[style.top.px]': 'top()',
161
- '[style.width.px]': 'outerWidth()',
162
- '[style.height.px]': 'outerHeight()',
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
- <!-- drop shadow ONLY — axis-aligned at the content box (inset by the frame),
166
- transparent fill so it can never peek out behind the bitmap, and it
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; box-shadow: var(--epdf-page-shadow, 0 6px 18px rgba(0, 0, 0, 0.18));"
170
- [style.left.px]="frame().left"
171
- [style.top.px]="frame().top"
172
- [style.width.px]="t().viewWidth"
173
- [style.height.px]="t().viewHeight"
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
- <!-- the page: white backing + content as ONE box — the only thing rotation
176
- turns. We render our own selection highlights, so native selection is
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; background: #fff; user-select: none; -webkit-user-select: none;"
181
- [style.left.px]="contentLeft()"
182
- [style.top.px]="contentTop()"
183
- [style.width.px]="t().contentWidth"
184
- [style.height.px]="t().contentHeight"
185
- [style.transform]="rotate()"
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]="pageTpl()"
334
+ [ngTemplateOutlet]="pageTemplate()"
189
335
  [ngTemplateOutletContext]="templateContext"
190
336
  [ngTemplateOutletInjector]="pageInjector"
191
337
  />
192
338
  </div>
193
- <!-- box-space chrome — fills the outer box, NEVER rotates. Bands are plain
194
- regions: a label is bottom: 0; height: frame().bottom. -->
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>` — virtualizes and positions page surfaces by the camera, and
208
- * stamps each one with YOUR `<ng-template epdfPage>` — you bring the layers.
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
- * NULL-SAFE BY DESIGN (a deliberate deviation from React's strict Stage):
211
- * Angular instantiates projected content eagerly, so a stage constructed
212
- * before any document exists must not crash — it renders zero pages and its
213
- * listeners no-op until the document arrives. Teach the document gate anyway:
214
- * document CHROME (toolbars, panels) still belongs behind `*epdfDocumentGate`.
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
- * ZONE RULE: every DOM listener here attaches OUTSIDE Angular's zone — a
217
- * 120Hz pointermove stream must never trigger app-wide change detection.
218
- * State flows kernel → tick signal → computeds → bindings, so the adapter is
219
- * zone-agnostic (zoneless recommended, zone.js tolerated).
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
- const EMPTY_PAGES = [];
222
- const NO_FRAME = { top: 0, right: 0, bottom: 0, left: 0 };
223
- const frameEqual = (a, b) => a.top === b.top && a.right === b.right && a.bottom === b.bottom && a.left === b.left;
224
- class EpdfStage {
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
- * Route this Stage's pointer events to the interaction hub (page-resolved via
227
- * `pageAt`) — AND register this lens's tool-gated pan-scroll handler with it
228
- * (lens-scoped, so multiple stages on one document never pan each other).
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
- interaction = input(true, ...(ngDevMode ? [{ debugName: "interaction" }] : []));
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
- * With `interaction`: let drags over page GAPS pan regardless of the active
241
- * tool (and show a grab cursor there) — the gutter always pans; there is
242
- * nothing to draw/select outside a page. Default true.
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
- panFallback = input(true, ...(ngDevMode ? [{ debugName: "panFallback" }] : []));
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
- * Ambient ZOOM gestures on this stage: ctrl/cmd+wheel and trackpad pinch
247
- * (Safari gesture events included). Default true. Turn OFF for follower
248
- * lenses with a fixed magnification (a thumbnail rail should scroll under
249
- * cmd+wheel, not zoom); pinches are still swallowed either way.
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
- zoomGestures = input(true, ...(ngDevMode ? [{ debugName: "zoomGestures" }] : []));
252
- /** The stage lens to drive (default: the main StageToken). */
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.stage();
284
- const ix = this.ix();
285
- const useHub = this.interaction() && !!ix;
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; // no document yet — nothing to drive
290
- const cleanups = [];
291
- zone.runOutsideAngular(() => {
292
- // The WHOLE browser binding — viewport/DPR reporting, sample
293
- // normalization, gesture controller — is the shared @embedpdf/web
294
- // surface, so every framework adapter has one feel. This component
295
- // keeps only Angular glue.
296
- cleanups.push(createStageSurface(el, stage, {
297
- hub: useHub ? ix : null,
298
- source: stage.lensId(),
299
- zoomGestures,
300
- }));
301
- // Interaction opt-in lives WITH the sample source: the same knob that
302
- // forwards this lens's samples also registers its pan-scroll handler,
303
- // lens-scoped — two stages on one document can never pan each other.
304
- if (useHub && ix) {
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: { interaction: { classPropertyName: "interaction", publicName: "interaction", isSignal: true, isRequired: false, transformFunction: null }, panFallback: { classPropertyName: "panFallback", publicName: "panFallback", isSignal: true, isRequired: false, transformFunction: null }, zoomGestures: { classPropertyName: "zoomGestures", publicName: "zoomGestures", isSignal: true, isRequired: false, transformFunction: null }, token: { classPropertyName: "token", publicName: "token", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.cursor": "cursor()" }, styleAttribute: "position: relative; overflow: hidden; touch-action: none; display: block;" }, queries: [{ propertyName: "pageTemplateDir", first: true, predicate: EpdfPageTemplate, descendants: true, isSignal: true }, { propertyName: "chromeTemplateDir", first: true, predicate: EpdfPageChrome, descendants: true, isSignal: true }], ngImport: i0, template: `
315
- @if (pageTemplate(); as tpl) {
316
- @for (p of pages(); track p.pon) {
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
- [vp]="p"
589
+ [visiblePage]="visiblePage"
319
590
  [frame]="frame()"
320
591
  [documentId]="documentId() ?? ''"
321
- [pageTpl]="tpl"
322
- [chromeTpl]="chromeTemplate()"
592
+ [stage]="hostCapability()!"
593
+ [look]="look()"
594
+ [pageTemplate]="template"
595
+ [chromeTemplate]="chromeTemplate()"
323
596
  />
324
597
  }
325
598
  }
326
- <!-- viewport-space overlay UI: anything projected into <epdf-stage>
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: ["vp", "frame", "documentId", "pageTpl", "chromeTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
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
- standalone: true,
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; display: block;',
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 tpl) {
344
- @for (p of pages(); track p.pon) {
625
+ @if (pageTemplate(); as template) {
626
+ @for (visiblePage of pages(); track visiblePage.ref.objectNumber) {
345
627
  <epdf-page-surface
346
- [vp]="p"
628
+ [visiblePage]="visiblePage"
347
629
  [frame]="frame()"
348
630
  [documentId]="documentId() ?? ''"
349
- [pageTpl]="tpl"
350
- [chromeTpl]="chromeTemplate()"
631
+ [stage]="hostCapability()!"
632
+ [look]="look()"
633
+ [pageTemplate]="template"
634
+ [chromeTemplate]="chromeTemplate()"
351
635
  />
352
636
  }
353
637
  }
354
- <!-- viewport-space overlay UI: anything projected into <epdf-stage>
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
- * All Stage settings + the batch `update`. The seam for "presets are a customer
417
- * concern": keep your own `Partial<StageSettings>` objects and apply them with
418
- * `update(preset)` (one anchor-preserving change).
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
- function injectStageSettings(token = StageToken) {
421
- const s = injectCapability(token);
422
- return {
423
- // settingsEqual derives from the plugin's settings registry — a new setting
424
- // is covered automatically, without this package spelling out the shape.
425
- settings: injectSelector(token, (c) => c.settings(), settingsEqual),
426
- update: lazy(s, 'update'),
427
- reset: lazy(s, 'resetView'),
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 — the viewport trunk: `<epdf-stage>` + page
433
- * templates + the stage facade inject functions.
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
- // One-line-per-feature: registration travels with the UI.
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, EpdfPageSurface, EpdfPageTemplate, EpdfStage, injectLayout, injectPageList, injectPages, injectStage, injectStageSettings, injectZoom };
841
+ export { EpdfPageChrome, EpdfPageTemplate, EpdfScrollbar, EpdfStage, EpdfStageRef, withStage };
442
842
  //# sourceMappingURL=embedpdf-angular-stage.mjs.map