@embedpdf/plugin-stage 3.0.0-next.1 → 3.0.0-next.11
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/dist/contract-BU33R4oL.cjs +13 -0
- package/dist/contract-BU33R4oL.cjs.map +1 -0
- package/dist/contract-CPSX8ANN.js +8 -0
- package/dist/contract-CPSX8ANN.js.map +1 -0
- package/dist/contract.cjs +10 -0
- package/dist/contract.d.cts +3 -0
- package/dist/contract.d.ts +3 -0
- package/dist/contract.js +2 -0
- package/dist/destination.cjs +147 -0
- package/dist/destination.cjs.map +1 -0
- package/dist/destination.d.cts +19 -0
- package/dist/destination.d.ts +19 -0
- package/dist/destination.js +146 -0
- package/dist/destination.js.map +1 -0
- package/dist/index.cjs +706 -245
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -556
- package/dist/index.d.ts +57 -556
- package/dist/index.js +703 -246
- package/dist/index.js.map +1 -1
- package/dist/types-CdIFKQnj.d.ts +656 -0
- package/dist/types-qdO_v-V-.d.cts +656 -0
- package/package.json +16 -6
package/dist/index.cjs
CHANGED
|
@@ -21,11 +21,158 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
+
const require_destination = require("./destination.cjs");
|
|
25
|
+
const require_contract = require("./contract-BU33R4oL.cjs");
|
|
24
26
|
let _embedpdf_core = require("@embedpdf/core");
|
|
25
|
-
let
|
|
27
|
+
let _embedpdf_plugin_actions_contract = require("@embedpdf/plugin-actions/contract");
|
|
28
|
+
let _embedpdf_plugin_actions_contract_host = require("@embedpdf/plugin-actions/contract/host");
|
|
26
29
|
let _embedpdf_core_stage = require("@embedpdf/core-stage");
|
|
27
30
|
_embedpdf_core_stage = __toESM(_embedpdf_core_stage, 1);
|
|
28
31
|
let _embedpdf_core_geometry = require("@embedpdf/core-geometry");
|
|
32
|
+
//#region src/motion.ts
|
|
33
|
+
/**
|
|
34
|
+
* The camera's MOTION vocabulary — every formula, pure.
|
|
35
|
+
*
|
|
36
|
+
* The capability owns the impure drivers (scheduler frames, dispatch, gesture
|
|
37
|
+
* state); the LAWS those drivers integrate live here, parameterized by dt/t
|
|
38
|
+
* with no state, no scheduler, no dispatch — unit-testable in isolation, and
|
|
39
|
+
* with zero plugin dependencies, liftable into stage-core if the native port
|
|
40
|
+
* ever wants them.
|
|
41
|
+
*
|
|
42
|
+
* Two families:
|
|
43
|
+
* • ballistic — one per-axis law for every free motion: `glideStep` (the
|
|
44
|
+
* touch fling, decaying on UIScrollView's curve) and `springStep` (a
|
|
45
|
+
* critically-damped return to the clamp). A glide that reaches a content
|
|
46
|
+
* edge converts to a spring with its velocity carried in, so the bounce
|
|
47
|
+
* grows out of physics rather than a scripted overshoot.
|
|
48
|
+
* • resistance — the iOS rubber-band curve `rubberOut` and its exact
|
|
49
|
+
* inverse `rubberIn`: dragging `d` px past an edge displays an
|
|
50
|
+
* asymptotically-bounded stretch, and re-entering a gesture over a
|
|
51
|
+
* stretched camera (the mid-bounce catch) reconstructs the
|
|
52
|
+
* finger-integrated position the curve came from.
|
|
53
|
+
* Plus the tween interpolators: `easeOutCubic` for coordinate tweens and
|
|
54
|
+
* `zoomLerp` — GEOMETRIC zoom interpolation (zoom is multiplicative; equal
|
|
55
|
+
* ratios per unit time, not equal deltas — the linear lerp's visible "dip").
|
|
56
|
+
*/
|
|
57
|
+
/** Resistance coefficient of the rubber-band curve (the iOS feel). */
|
|
58
|
+
const RUBBER = .55;
|
|
59
|
+
/** Glide decay per ms — UIScrollView's normal deceleration rate. */
|
|
60
|
+
const FLING_DECAY = .998;
|
|
61
|
+
/** rad/ms — critically damped, ~400ms to settle. */
|
|
62
|
+
const SPRING_OMEGA = .015;
|
|
63
|
+
/**
|
|
64
|
+
* Overshoot distance → displayed stretch: asymptotic to the viewport
|
|
65
|
+
* dimension `dim`, so the content never leaves the screen however far the
|
|
66
|
+
* finger travels. `rubberOut(0) === 0` — inside the bounds the curve is
|
|
67
|
+
* invisible.
|
|
68
|
+
*/
|
|
69
|
+
const rubberOut = (d, dim) => (1 - 1 / (d * RUBBER / dim + 1)) * dim;
|
|
70
|
+
/** Exact inverse of `rubberOut` (defined for `out < dim`). */
|
|
71
|
+
const rubberIn = (out, dim) => out * dim / (RUBBER * (dim - out));
|
|
72
|
+
/**
|
|
73
|
+
* One frame of free glide: exponential velocity decay, position advanced by
|
|
74
|
+
* the midpoint integral of the decay over `dt` (exact for the exponential —
|
|
75
|
+
* frame-rate independent).
|
|
76
|
+
*/
|
|
77
|
+
const glideStep = (p, v, dt) => {
|
|
78
|
+
const k = Math.pow(FLING_DECAY, dt);
|
|
79
|
+
const np = p + v * (dt * (1 + k) / 2);
|
|
80
|
+
const nv = v * k;
|
|
81
|
+
return Math.abs(nv) < .02 ? {
|
|
82
|
+
p: np,
|
|
83
|
+
v: 0,
|
|
84
|
+
done: true
|
|
85
|
+
} : {
|
|
86
|
+
p: np,
|
|
87
|
+
v: nv,
|
|
88
|
+
done: false
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* One frame of critically-damped spring toward `edge` — semi-implicit Euler
|
|
93
|
+
* on x'' = −ω²(x−e) − 2ωx'. Lands exactly ON the edge (position snapped,
|
|
94
|
+
* velocity zeroed) once inside half a pixel at negligible speed.
|
|
95
|
+
*/
|
|
96
|
+
const springStep = (p, v, edge, dt) => {
|
|
97
|
+
const nv = v + (-.015 * SPRING_OMEGA * (p - edge) - 2 * SPRING_OMEGA * v) * dt;
|
|
98
|
+
const np = p + nv * dt;
|
|
99
|
+
if (Math.abs(np - edge) < .5 && Math.abs(nv) < .01) return {
|
|
100
|
+
p: edge,
|
|
101
|
+
v: 0,
|
|
102
|
+
done: true
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
p: np,
|
|
106
|
+
v: nv,
|
|
107
|
+
done: false
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
|
|
111
|
+
/**
|
|
112
|
+
* Geometric zoom interpolation: `z0·(z1/z0)^k`. Zoom is multiplicative, so a
|
|
113
|
+
* constant-RATE zoom moves equal ratios per unit time; interpolating the
|
|
114
|
+
* level linearly instead makes the start rush and the end crawl (and, under
|
|
115
|
+
* a focal anchor, swings the anchored point along a curved path).
|
|
116
|
+
*/
|
|
117
|
+
const zoomLerp = (z0, z1, k) => z0 * Math.pow(z1 / z0, k);
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/responsive.ts
|
|
120
|
+
/**
|
|
121
|
+
* Container queries for the settings bag — the pure half. The capability owns
|
|
122
|
+
* the driver (when to re-resolve, how to react); the LAWS live here: what a
|
|
123
|
+
* box is, when a query matches, and what the effective settings are. No state,
|
|
124
|
+
* no dispatch — property-testable in isolation (the `wheel.ts` convention).
|
|
125
|
+
*
|
|
126
|
+
* The query vocabulary is deliberately just the box: a headless, DOM-free
|
|
127
|
+
* stage knows exactly one environmental fact — the viewport it was told.
|
|
128
|
+
* Anything else (pointer coarseness, platform, app state) is either per-event
|
|
129
|
+
* (`PointerSample.pointerType`) or the app's own business via `update()`.
|
|
130
|
+
*/
|
|
131
|
+
/** The reported viewport as a queryable box. A square box is 'portrait' (CSS). */
|
|
132
|
+
const boxOf = (vp) => ({
|
|
133
|
+
width: vp.width,
|
|
134
|
+
height: vp.height,
|
|
135
|
+
orientation: vp.height >= vp.width ? "portrait" : "landscape"
|
|
136
|
+
});
|
|
137
|
+
/** All bounds inclusive, all fields optional, conditions AND-ed (CSS ranges). */
|
|
138
|
+
const matchesQuery = (q, box) => (q.minWidth === void 0 || box.width >= q.minWidth) && (q.maxWidth === void 0 || box.width <= q.maxWidth) && (q.minHeight === void 0 || box.height >= q.minHeight) && (q.maxHeight === void 0 || box.height <= q.maxHeight) && (q.orientation === void 0 || box.orientation === q.orientation);
|
|
139
|
+
const ruleMatches = (rule, box) => typeof rule.when === "function" ? !!rule.when(box) : matchesQuery(rule.when, box);
|
|
140
|
+
/** Merge a patch over settings, skipping undefined values (Partial semantics). */
|
|
141
|
+
const mergeSettings = (into, patch) => {
|
|
142
|
+
const out = { ...into };
|
|
143
|
+
let key;
|
|
144
|
+
for (key in patch) {
|
|
145
|
+
const value = patch[key];
|
|
146
|
+
if (value !== void 0) Object.assign(out, { [key]: value });
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Setting-value equality: primitives by identity, the flat objects the
|
|
152
|
+
* settings vocabulary uses (`{ px }`, `{ x, y }`, page frames, zoom specs) by
|
|
153
|
+
* one level of own-key comparison. Settings values are never nested deeper.
|
|
154
|
+
*/
|
|
155
|
+
const eqSetting = (a, b) => {
|
|
156
|
+
if (a === b) return true;
|
|
157
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
158
|
+
const ka = Object.keys(a);
|
|
159
|
+
const kb = Object.keys(b);
|
|
160
|
+
return ka.length === kb.length && ka.every((k) => a[k] === b[k]);
|
|
161
|
+
};
|
|
162
|
+
const resolveResponsive = (base, rules, box) => {
|
|
163
|
+
let effective = base;
|
|
164
|
+
const active = [];
|
|
165
|
+
for (const rule of rules) {
|
|
166
|
+
if (!ruleMatches(rule, box)) continue;
|
|
167
|
+
if (rule.name !== void 0) active.push(rule.name);
|
|
168
|
+
if (rule.settings) effective = mergeSettings(effective, rule.settings);
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
effective,
|
|
172
|
+
active
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
//#endregion
|
|
29
176
|
//#region src/settings.ts
|
|
30
177
|
/**
|
|
31
178
|
* Out-of-the-box defaults — a sensible document-reading feel. They are JUST
|
|
@@ -71,6 +218,18 @@ const DEFAULT_SETTINGS = {
|
|
|
71
218
|
scrollBehavior: "smooth",
|
|
72
219
|
viewUnitsPerPoint: 96 / 72
|
|
73
220
|
};
|
|
221
|
+
/**
|
|
222
|
+
* Out-of-the-box responsive rules — the platform feel with zero configuration:
|
|
223
|
+
* a compact container gets the thin gutter phones use (space, not device — an
|
|
224
|
+
* embedded 500px pane on a desktop is compact too). Override the whole list
|
|
225
|
+
* with `stagePlugin({ responsive: [...] })`; `responsive: []` opts out. The
|
|
226
|
+
* 'compact' name is queryable app-side via `stage.matches('compact')`.
|
|
227
|
+
*/
|
|
228
|
+
const DEFAULT_RESPONSIVE = [{
|
|
229
|
+
name: "compact",
|
|
230
|
+
when: { maxWidth: 600 },
|
|
231
|
+
settings: { padding: 4 }
|
|
232
|
+
}];
|
|
74
233
|
const SETTINGS_EFFECT = {
|
|
75
234
|
flow: "reflow",
|
|
76
235
|
layout: "scene",
|
|
@@ -109,8 +268,9 @@ const settingsEqual = (a, b) => SETTING_KEYS.every((k) => valueEq(a[k], b[k]));
|
|
|
109
268
|
* Layering, stated honestly:
|
|
110
269
|
* • stage-core — PURE spatial math (no DOM, no time). Ports to Rust later.
|
|
111
270
|
* • this capability — the IMPURE platform shell. It dispatches, caches, and owns
|
|
112
|
-
* the camera tween. It is NOT pure;
|
|
113
|
-
*
|
|
271
|
+
* the camera tween. It is NOT pure; its one host dependency (frame timing)
|
|
272
|
+
* enters through the Scheduler seam — injectable, defaulting to the host's
|
|
273
|
+
* own frame clock when one exists (see the seam below).
|
|
114
274
|
*
|
|
115
275
|
* The model: EVERY camera move is defined by what it holds fixed.
|
|
116
276
|
* • gestures (pan/pinch/wheel) — the content under the pointer. Physics, no setting.
|
|
@@ -396,12 +556,100 @@ function createStageCapability(ctx, config = {}) {
|
|
|
396
556
|
fitAlign: ctx.getState().fitAlign,
|
|
397
557
|
direction: ctx.getState().direction
|
|
398
558
|
});
|
|
559
|
+
let placementStarted = false;
|
|
560
|
+
let gestureDepth = 0;
|
|
561
|
+
let gestureZoomed = false;
|
|
562
|
+
let gestureElastic = false;
|
|
563
|
+
let gestureRaw = null;
|
|
564
|
+
const REST_MS = 150;
|
|
565
|
+
let restRaf = 0;
|
|
566
|
+
const armRest = () => {
|
|
567
|
+
if (!canAnimate || !placementStarted) return;
|
|
568
|
+
if (ctx.getState().cameraResting) ctx.dispatch({
|
|
569
|
+
type: "CAMERA_REST",
|
|
570
|
+
resting: false
|
|
571
|
+
});
|
|
572
|
+
if (restRaf) scheduler.caf(restRaf);
|
|
573
|
+
let t0 = 0;
|
|
574
|
+
const tick = (ts) => {
|
|
575
|
+
restRaf = 0;
|
|
576
|
+
if (!t0) t0 = ts;
|
|
577
|
+
if (ts - t0 >= REST_MS) {
|
|
578
|
+
ctx.dispatch({
|
|
579
|
+
type: "CAMERA_REST",
|
|
580
|
+
resting: true
|
|
581
|
+
});
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
restRaf = scheduler.raf(tick);
|
|
585
|
+
};
|
|
586
|
+
restRaf = scheduler.raf(tick);
|
|
587
|
+
};
|
|
399
588
|
const setCam = (next, bounds = stayBounds()) => {
|
|
589
|
+
const clamped = _embedpdf_core_stage.clampCamera(next, bounds, vp(), constraint());
|
|
590
|
+
if (clamped.zoom !== cam().zoom) if (gestureDepth > 0) {
|
|
591
|
+
gestureZoomed = true;
|
|
592
|
+
if (ctx.getState().cameraResting) ctx.dispatch({
|
|
593
|
+
type: "CAMERA_REST",
|
|
594
|
+
resting: false
|
|
595
|
+
});
|
|
596
|
+
if (restRaf) {
|
|
597
|
+
scheduler.caf(restRaf);
|
|
598
|
+
restRaf = 0;
|
|
599
|
+
}
|
|
600
|
+
} else armRest();
|
|
400
601
|
ctx.dispatch({
|
|
401
602
|
type: "CAMERA",
|
|
402
|
-
camera:
|
|
603
|
+
camera: clamped
|
|
403
604
|
});
|
|
404
605
|
};
|
|
606
|
+
const axisTravels = (origin, content, view, zoom) => !_embedpdf_core_stage.travelRange(origin, content, view, zoom, constraint().padding).fits;
|
|
607
|
+
/** Unclamped finger-integrated camera → the DISPLAYED camera: clamp, then
|
|
608
|
+
* re-apply the overshoot through the resistance curve, per travelling axis.
|
|
609
|
+
* Inside the bounds this is exactly the clamp (rubber of zero is zero);
|
|
610
|
+
* on a fitting axis it is exactly the clamp ALWAYS. */
|
|
611
|
+
const rubberize = (raw) => {
|
|
612
|
+
const clamped = _embedpdf_core_stage.clampCamera(raw, stayBounds(), vp(), constraint());
|
|
613
|
+
const v = vp();
|
|
614
|
+
const b = stayBounds();
|
|
615
|
+
const axis = (rawA, clampedA, dim, travels) => {
|
|
616
|
+
if (!travels) return clampedA;
|
|
617
|
+
const dWorld = rawA - clampedA;
|
|
618
|
+
if (dWorld === 0) return clampedA;
|
|
619
|
+
const out = rubberOut(Math.abs(dWorld) * raw.zoom, Math.max(1, dim));
|
|
620
|
+
return clampedA + Math.sign(dWorld) * out / raw.zoom;
|
|
621
|
+
};
|
|
622
|
+
return {
|
|
623
|
+
zoom: raw.zoom,
|
|
624
|
+
x: axis(raw.x, clamped.x, v.width, axisTravels(b.x, b.width, v.width, raw.zoom)),
|
|
625
|
+
y: axis(raw.y, clamped.y, v.height, axisTravels(b.y, b.height, v.height, raw.zoom))
|
|
626
|
+
};
|
|
627
|
+
};
|
|
628
|
+
/** Displayed camera → the raw position `rubberize` would have produced it
|
|
629
|
+
* from. Overshoot is capped just under the asymptote so the inverse stays
|
|
630
|
+
* finite whatever state a catch finds the camera in. */
|
|
631
|
+
const unrubberize = (displayed) => {
|
|
632
|
+
const clamped = _embedpdf_core_stage.clampCamera(displayed, stayBounds(), vp(), constraint());
|
|
633
|
+
const v = vp();
|
|
634
|
+
const b = stayBounds();
|
|
635
|
+
const axis = (dispA, clampedA, dim, travels) => {
|
|
636
|
+
if (!travels) return clampedA;
|
|
637
|
+
const dWorld = dispA - clampedA;
|
|
638
|
+
if (dWorld === 0) return clampedA;
|
|
639
|
+
const d = Math.max(1, dim);
|
|
640
|
+
const out = Math.min(Math.abs(dWorld) * displayed.zoom, d - 1);
|
|
641
|
+
return clampedA + Math.sign(dWorld) * rubberIn(out, d) / displayed.zoom;
|
|
642
|
+
};
|
|
643
|
+
return {
|
|
644
|
+
zoom: displayed.zoom,
|
|
645
|
+
x: axis(displayed.x, clamped.x, v.width, axisTravels(b.x, b.width, v.width, displayed.zoom)),
|
|
646
|
+
y: axis(displayed.y, clamped.y, v.height, axisTravels(b.y, b.height, v.height, displayed.zoom))
|
|
647
|
+
};
|
|
648
|
+
};
|
|
649
|
+
const setCamRaw = (c) => ctx.dispatch({
|
|
650
|
+
type: "CAMERA",
|
|
651
|
+
camera: c
|
|
652
|
+
});
|
|
405
653
|
/**
|
|
406
654
|
* Cursor reconciliation, one direction per interaction:
|
|
407
655
|
* navigation → cursor is INTENT, set explicitly; the camera honors it as far
|
|
@@ -435,7 +683,6 @@ function createStageCapability(ctx, config = {}) {
|
|
|
435
683
|
}
|
|
436
684
|
cancelAnim();
|
|
437
685
|
const from = cam();
|
|
438
|
-
const ease = (t) => 1 - Math.pow(1 - t, 3);
|
|
439
686
|
let started = false;
|
|
440
687
|
let t0 = 0;
|
|
441
688
|
const tick = (now) => {
|
|
@@ -443,7 +690,7 @@ function createStageCapability(ctx, config = {}) {
|
|
|
443
690
|
started = true;
|
|
444
691
|
t0 = now;
|
|
445
692
|
}
|
|
446
|
-
const k =
|
|
693
|
+
const k = easeOutCubic(Math.min(1, (now - t0) / ms));
|
|
447
694
|
setCam({
|
|
448
695
|
x: lerp(from.x, target.x, k),
|
|
449
696
|
y: lerp(from.y, target.y, k),
|
|
@@ -457,6 +704,116 @@ function createStageCapability(ctx, config = {}) {
|
|
|
457
704
|
};
|
|
458
705
|
raf = scheduler.raf(tick);
|
|
459
706
|
};
|
|
707
|
+
/**
|
|
708
|
+
* Zoom-anchored tween — animates THE INVARIANT, not the coordinates. A
|
|
709
|
+
* camera move is defined by what it holds fixed; for an anchored zoom that
|
|
710
|
+
* is "the focal page-point stays at `pt` while scale changes". Lerping
|
|
711
|
+
* x/y/zoom independently (the plain tween) breaks that mid-flight: holding
|
|
712
|
+
* a screen point requires `x(t) = focal.x − pt.x / zoom(t)` — hyperbolic in
|
|
713
|
+
* the zoom — so linear coordinates swing the tapped point along a curved
|
|
714
|
+
* path (the visible "dip"). Here the zoom interpolates GEOMETRICALLY
|
|
715
|
+
* (constant rate — zoom is multiplicative) and each frame's camera derives
|
|
716
|
+
* from the anchor at that zoom, then clamps: the focal point is stationary
|
|
717
|
+
* by construction, and a zoom-out rails into its fit progressively as the
|
|
718
|
+
* shrinking travel range lets the clamp take over.
|
|
719
|
+
*/
|
|
720
|
+
const animateZoomAnchored = (focal, pt, targetZoom, ms = 240, then) => {
|
|
721
|
+
if (!canAnimate) {
|
|
722
|
+
const sc = buildScene();
|
|
723
|
+
if (sc.itemCount) setCam(_embedpdf_core_stage.cameraForAnchorAtScreen(focal, sc, pt, targetZoom));
|
|
724
|
+
then?.();
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
cancelAnim();
|
|
728
|
+
const z0 = cam().zoom;
|
|
729
|
+
let started = false;
|
|
730
|
+
let t0 = 0;
|
|
731
|
+
const tick = (now) => {
|
|
732
|
+
if (!started) {
|
|
733
|
+
started = true;
|
|
734
|
+
t0 = now;
|
|
735
|
+
}
|
|
736
|
+
const k = easeOutCubic(Math.min(1, (now - t0) / ms));
|
|
737
|
+
const z = zoomLerp(z0, targetZoom, k);
|
|
738
|
+
const sc = buildScene();
|
|
739
|
+
if (sc.itemCount) setCam(_embedpdf_core_stage.cameraForAnchorAtScreen(focal, sc, pt, z));
|
|
740
|
+
if (k < 1) raf = scheduler.raf(tick);
|
|
741
|
+
else {
|
|
742
|
+
raf = 0;
|
|
743
|
+
then?.();
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
raf = scheduler.raf(tick);
|
|
747
|
+
};
|
|
748
|
+
const startCameraPhysics = (vxFinger, vyFinger) => {
|
|
749
|
+
if (!canAnimate) {
|
|
750
|
+
setCam(cam());
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
cancelAnim();
|
|
754
|
+
const zoom = cam().zoom;
|
|
755
|
+
const c0 = cam();
|
|
756
|
+
const cl0 = _embedpdf_core_stage.clampCamera(c0, stayBounds(), vp(), constraint());
|
|
757
|
+
const b0 = stayBounds();
|
|
758
|
+
const v0 = vp();
|
|
759
|
+
const mk = (camA, clampedA, fingerV) => ({
|
|
760
|
+
p: camA * zoom,
|
|
761
|
+
v: -fingerV / 1e3,
|
|
762
|
+
springing: camA !== clampedA,
|
|
763
|
+
done: false
|
|
764
|
+
});
|
|
765
|
+
const ax = mk(c0.x, cl0.x, axisTravels(b0.x, b0.width, v0.width, zoom) ? vxFinger : 0);
|
|
766
|
+
const ay = mk(c0.y, cl0.y, axisTravels(b0.y, b0.height, v0.height, zoom) ? vyFinger : 0);
|
|
767
|
+
if (!ax.springing && !ay.springing && Math.hypot(ax.v, ay.v) < .02) return;
|
|
768
|
+
let started = false;
|
|
769
|
+
let last = 0;
|
|
770
|
+
const tick = (now) => {
|
|
771
|
+
raf = 0;
|
|
772
|
+
const dt = started ? Math.min(64, Math.max(1, now - last)) : 16;
|
|
773
|
+
started = true;
|
|
774
|
+
last = now;
|
|
775
|
+
const cl = _embedpdf_core_stage.clampCamera({
|
|
776
|
+
x: ax.p / zoom,
|
|
777
|
+
y: ay.p / zoom,
|
|
778
|
+
zoom
|
|
779
|
+
}, stayBounds(), vp(), constraint());
|
|
780
|
+
const step = (a, edgeWorld) => {
|
|
781
|
+
if (a.done) return;
|
|
782
|
+
const r = a.springing ? springStep(a.p, a.v, edgeWorld * zoom, dt) : glideStep(a.p, a.v, dt);
|
|
783
|
+
a.p = r.p;
|
|
784
|
+
a.v = r.v;
|
|
785
|
+
a.done = r.done;
|
|
786
|
+
};
|
|
787
|
+
step(ax, cl.x);
|
|
788
|
+
step(ay, cl.y);
|
|
789
|
+
const ncl = _embedpdf_core_stage.clampCamera({
|
|
790
|
+
x: ax.p / zoom,
|
|
791
|
+
y: ay.p / zoom,
|
|
792
|
+
zoom
|
|
793
|
+
}, stayBounds(), vp(), constraint());
|
|
794
|
+
const convert = (a, clampedA) => {
|
|
795
|
+
if (!a.springing && Math.abs(clampedA * zoom - a.p) > 1e-6) {
|
|
796
|
+
a.p = clampedA * zoom;
|
|
797
|
+
a.springing = true;
|
|
798
|
+
a.done = false;
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
convert(ax, ncl.x);
|
|
802
|
+
convert(ay, ncl.y);
|
|
803
|
+
setCamRaw({
|
|
804
|
+
x: ax.p / zoom,
|
|
805
|
+
y: ay.p / zoom,
|
|
806
|
+
zoom
|
|
807
|
+
});
|
|
808
|
+
syncCursorFromCamera();
|
|
809
|
+
if (ax.done && ay.done) {
|
|
810
|
+
setCam(cam());
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
raf = scheduler.raf(tick);
|
|
814
|
+
};
|
|
815
|
+
raf = scheduler.raf(tick);
|
|
816
|
+
};
|
|
460
817
|
const anchorAt = (at) => {
|
|
461
818
|
const sc = buildScene();
|
|
462
819
|
return sc.itemCount ? _embedpdf_core_stage.anchorFromCamera(cam(), sc, vp(), at) : {
|
|
@@ -501,7 +858,16 @@ function createStageCapability(ctx, config = {}) {
|
|
|
501
858
|
if (isFitAll()) goToTarget(ctx.getState().cursor, { behavior: "instant" });
|
|
502
859
|
else applyAnchor(anchor, align);
|
|
503
860
|
};
|
|
861
|
+
/** Tag what drives the NEXT camera/cursor change — dispatched on flips
|
|
862
|
+
* only, read by the page-state feed (see StageState.motionCause). */
|
|
863
|
+
const markCause = (cause) => {
|
|
864
|
+
if (ctx.getState().motionCause !== cause) ctx.dispatch({
|
|
865
|
+
type: "MOTION_CAUSE",
|
|
866
|
+
cause
|
|
867
|
+
});
|
|
868
|
+
};
|
|
504
869
|
const goToTarget = (pageIndex, opts) => {
|
|
870
|
+
markCause("programmatic");
|
|
505
871
|
cancelAnim();
|
|
506
872
|
const doc = ctx.document();
|
|
507
873
|
if (!doc || doc.pageCount === 0) return;
|
|
@@ -584,8 +950,11 @@ function createStageCapability(ctx, config = {}) {
|
|
|
584
950
|
} : unrotatedPoints(box);
|
|
585
951
|
const c = cam();
|
|
586
952
|
const ratio = dpr();
|
|
587
|
-
const
|
|
588
|
-
const
|
|
953
|
+
const rawX = (box.x - c.x) * c.zoom;
|
|
954
|
+
const rawY = (box.y - c.y) * c.zoom;
|
|
955
|
+
const resting = ctx.getState().cameraResting;
|
|
956
|
+
const screenX = resting ? (0, _embedpdf_core_geometry.snapToDevice)(rawX, ratio) : rawX;
|
|
957
|
+
const screenY = resting ? (0, _embedpdf_core_geometry.snapToDevice)(rawY, ratio) : rawY;
|
|
589
958
|
const transform = (0, _embedpdf_core_geometry.pageTransform)({
|
|
590
959
|
pageSize,
|
|
591
960
|
rotation: box.rotation,
|
|
@@ -605,7 +974,7 @@ function createStageCapability(ctx, config = {}) {
|
|
|
605
974
|
width: transform.viewWidth,
|
|
606
975
|
height: transform.viewHeight
|
|
607
976
|
});
|
|
608
|
-
const visibleRect = onScreen.width > 0 && onScreen.height > 0 ? transform.
|
|
977
|
+
const visibleRect = onScreen.width > 0 && onScreen.height > 0 ? transform.viewToContentRect(onScreen) : {
|
|
609
978
|
x: 0,
|
|
610
979
|
y: 0,
|
|
611
980
|
width: 0,
|
|
@@ -620,8 +989,19 @@ function createStageCapability(ctx, config = {}) {
|
|
|
620
989
|
visibleRect
|
|
621
990
|
};
|
|
622
991
|
};
|
|
992
|
+
const EMPTY_SCROLL_METRICS = {
|
|
993
|
+
scrollLeft: 0,
|
|
994
|
+
scrollTop: 0,
|
|
995
|
+
scrollWidth: 0,
|
|
996
|
+
scrollHeight: 0,
|
|
997
|
+
clientWidth: 0,
|
|
998
|
+
clientHeight: 0,
|
|
999
|
+
scrollableX: false,
|
|
1000
|
+
scrollableY: false
|
|
1001
|
+
};
|
|
623
1002
|
let scrollMemo = null;
|
|
624
1003
|
const scrollMetricsNow = () => {
|
|
1004
|
+
if (!ctx.getState().placed) return EMPTY_SCROLL_METRICS;
|
|
625
1005
|
const sc = buildScene();
|
|
626
1006
|
const m = _embedpdf_core_stage.scrollMetrics(cam(), sc.itemCount ? stayBounds() : {
|
|
627
1007
|
x: 0,
|
|
@@ -634,14 +1014,16 @@ function createStageCapability(ctx, config = {}) {
|
|
|
634
1014
|
scrollMemo = m;
|
|
635
1015
|
return m;
|
|
636
1016
|
};
|
|
1017
|
+
const EMPTY_VISIBLE_PAGES = [];
|
|
637
1018
|
let visSig = "";
|
|
638
1019
|
let vis = [];
|
|
639
1020
|
const visiblePages = () => {
|
|
1021
|
+
if (!ctx.getState().placed) return EMPTY_VISIBLE_PAGES;
|
|
640
1022
|
const c = cam();
|
|
641
1023
|
const v = vp();
|
|
642
1024
|
const sc = buildScene();
|
|
643
1025
|
const items = paged() ? sc.items.slice(0, 1) : sc.query(_embedpdf_core_stage.cameraWorldRect(c, v));
|
|
644
|
-
const sig = `${sceneCache.key}|${ctx.getState().flow}|${c.x},${c.y},${c.zoom}|${v.width}x${v.height}|${dpr()}`;
|
|
1026
|
+
const sig = `${sceneCache.key}|${ctx.getState().flow}|${c.x},${c.y},${c.zoom}|${v.width}x${v.height}|${dpr()}|r${ctx.getState().cameraResting ? 1 : 0}`;
|
|
645
1027
|
if (sig === visSig) return vis;
|
|
646
1028
|
visSig = sig;
|
|
647
1029
|
vis = items.flatMap((it) => it.pages).map(withTransform);
|
|
@@ -653,8 +1035,73 @@ function createStageCapability(ctx, config = {}) {
|
|
|
653
1035
|
return out;
|
|
654
1036
|
};
|
|
655
1037
|
const snapshotSettings = () => pickSettings(ctx.getState());
|
|
1038
|
+
/**
|
|
1039
|
+
* Settings change, reacted per the registry — the strongest effect among the
|
|
1040
|
+
* touched settings wins: 'reflow' ⊃ 'scene'/'refit' ⊃ 'reclamp' ⊃ 'none'.
|
|
1041
|
+
* The ONE reactive applier: the public `update()` and the responsive driver
|
|
1042
|
+
* both land here, so a breakpoint crossing gets exactly the invariant a
|
|
1043
|
+
* hand-written update would.
|
|
1044
|
+
*/
|
|
1045
|
+
const applySettings = (patch) => {
|
|
1046
|
+
cancelAnim();
|
|
1047
|
+
const touched = (effect) => SETTING_KEYS.some((k) => patch[k] !== void 0 && SETTINGS_EFFECT[k] === effect);
|
|
1048
|
+
const align = touched("refit") && !touched("scene") && !touched("reflow") ? ctx.getState().zoomAlign : ctx.getState().anchorAlign;
|
|
1049
|
+
const anchor = anchorAt(alignPoint(align, vp()));
|
|
1050
|
+
ctx.dispatch({
|
|
1051
|
+
type: "PATCH",
|
|
1052
|
+
patch
|
|
1053
|
+
});
|
|
1054
|
+
if (touched("scene")) sceneCache = null;
|
|
1055
|
+
if (touched("reflow")) goToTarget(ctx.getState().cursor, { behavior: "instant" });
|
|
1056
|
+
else if (touched("scene") || touched("refit")) reapply(anchor, align);
|
|
1057
|
+
else if (touched("reclamp")) setCam(cam());
|
|
1058
|
+
};
|
|
1059
|
+
let base = snapshotSettings();
|
|
1060
|
+
let rules = config.responsive ?? DEFAULT_RESPONSIVE;
|
|
1061
|
+
let lastEffective = base;
|
|
1062
|
+
const publishActive = (active) => {
|
|
1063
|
+
const prev = ctx.getState().activeRules;
|
|
1064
|
+
if (prev.length !== active.length || active.some((n, i) => n !== prev[i])) ctx.dispatch({
|
|
1065
|
+
type: "RESPONSIVE",
|
|
1066
|
+
active
|
|
1067
|
+
});
|
|
1068
|
+
};
|
|
1069
|
+
/**
|
|
1070
|
+
* Re-resolve and apply what CHANGED since the last resolution. `react: true`
|
|
1071
|
+
* routes the patch through `applySettings` (full anchor-preserving update);
|
|
1072
|
+
* `react: false` (inside `setViewport`, whose caller runs its own reframe)
|
|
1073
|
+
* just lands the patch + cache invalidation and reports the strongest
|
|
1074
|
+
* effect so the caller can escalate a rule-driven flow flip.
|
|
1075
|
+
*/
|
|
1076
|
+
const syncResponsive = (react) => {
|
|
1077
|
+
const { effective, active } = resolveResponsive(base, rules, boxOf(vp()));
|
|
1078
|
+
const patch = {};
|
|
1079
|
+
for (const k of SETTING_KEYS) if (!eqSetting(effective[k], lastEffective[k])) Object.assign(patch, { [k]: effective[k] });
|
|
1080
|
+
lastEffective = effective;
|
|
1081
|
+
publishActive(active);
|
|
1082
|
+
const keys = Object.keys(patch);
|
|
1083
|
+
if (keys.length === 0) return "none";
|
|
1084
|
+
if (react) {
|
|
1085
|
+
applySettings(patch);
|
|
1086
|
+
return "none";
|
|
1087
|
+
}
|
|
1088
|
+
ctx.dispatch({
|
|
1089
|
+
type: "PATCH",
|
|
1090
|
+
patch
|
|
1091
|
+
});
|
|
1092
|
+
const rank = {
|
|
1093
|
+
none: 0,
|
|
1094
|
+
reclamp: 1,
|
|
1095
|
+
refit: 2,
|
|
1096
|
+
scene: 3,
|
|
1097
|
+
reflow: 4
|
|
1098
|
+
};
|
|
1099
|
+
let strongest = "none";
|
|
1100
|
+
for (const k of keys) if (rank[SETTINGS_EFFECT[k]] > rank[strongest]) strongest = SETTINGS_EFFECT[k];
|
|
1101
|
+
if (strongest === "scene" || strongest === "reflow") sceneCache = null;
|
|
1102
|
+
return strongest;
|
|
1103
|
+
};
|
|
656
1104
|
const initialViewProviders = [];
|
|
657
|
-
let hasPlaced = false;
|
|
658
1105
|
const api = {
|
|
659
1106
|
camera: cam,
|
|
660
1107
|
viewport: vp,
|
|
@@ -672,6 +1119,7 @@ function createStageCapability(ctx, config = {}) {
|
|
|
672
1119
|
label: p.label ?? null
|
|
673
1120
|
})),
|
|
674
1121
|
pageRect: (pon) => {
|
|
1122
|
+
if (!ctx.getState().placed) return null;
|
|
675
1123
|
const meta = ctx.document();
|
|
676
1124
|
const index = meta ? meta.pages.findIndex((p) => p.pageObjectNumber === pon) : -1;
|
|
677
1125
|
if (index < 0) return null;
|
|
@@ -686,7 +1134,7 @@ function createStageCapability(ctx, config = {}) {
|
|
|
686
1134
|
const ly = screen.y - p.screenY;
|
|
687
1135
|
if (lx >= 0 && ly >= 0 && lx <= p.transform.viewWidth && ly <= p.transform.viewHeight) return {
|
|
688
1136
|
pon: p.pon,
|
|
689
|
-
point: p.transform.
|
|
1137
|
+
point: p.transform.viewToContent({
|
|
690
1138
|
x: lx,
|
|
691
1139
|
y: ly
|
|
692
1140
|
}),
|
|
@@ -700,7 +1148,7 @@ function createStageCapability(ctx, config = {}) {
|
|
|
700
1148
|
pointOnPage: (pon, screen) => {
|
|
701
1149
|
const p = visiblePages().find((v) => v.pon === pon);
|
|
702
1150
|
if (!p) return null;
|
|
703
|
-
return p.transform.
|
|
1151
|
+
return p.transform.viewToContent({
|
|
704
1152
|
x: screen.x - p.screenX,
|
|
705
1153
|
y: screen.y - p.screenY
|
|
706
1154
|
});
|
|
@@ -772,11 +1220,12 @@ function createStageCapability(ctx, config = {}) {
|
|
|
772
1220
|
anchor: currentAnchor()
|
|
773
1221
|
}),
|
|
774
1222
|
setViewport: (v) => {
|
|
775
|
-
if (!
|
|
1223
|
+
if (!placementStarted) {
|
|
776
1224
|
ctx.dispatch({
|
|
777
1225
|
type: "VP",
|
|
778
1226
|
vp: v
|
|
779
1227
|
});
|
|
1228
|
+
syncResponsive(false);
|
|
780
1229
|
if (v.width > 0 && v.height > 0 && (ctx.document()?.pageCount ?? 0) > 0) api.placeInitial();
|
|
781
1230
|
return;
|
|
782
1231
|
}
|
|
@@ -786,7 +1235,8 @@ function createStageCapability(ctx, config = {}) {
|
|
|
786
1235
|
type: "VP",
|
|
787
1236
|
vp: v
|
|
788
1237
|
});
|
|
789
|
-
|
|
1238
|
+
if (syncResponsive(false) === "reflow") goToTarget(ctx.getState().cursor, { behavior: "instant" });
|
|
1239
|
+
else reapply(anchor);
|
|
790
1240
|
},
|
|
791
1241
|
setDevicePixelRatio: (ratio) => {
|
|
792
1242
|
if (ratio > 0 && ratio !== dpr()) ctx.dispatch({
|
|
@@ -795,16 +1245,22 @@ function createStageCapability(ctx, config = {}) {
|
|
|
795
1245
|
});
|
|
796
1246
|
},
|
|
797
1247
|
setCamera: (c) => {
|
|
1248
|
+
markCause("user");
|
|
798
1249
|
cancelAnim();
|
|
799
1250
|
setCam(c);
|
|
800
1251
|
syncCursorFromCamera();
|
|
801
1252
|
},
|
|
802
1253
|
panBy: (dx, dy) => {
|
|
1254
|
+
markCause("user");
|
|
803
1255
|
cancelAnim();
|
|
804
|
-
|
|
1256
|
+
if (gestureDepth > 0 && gestureElastic) {
|
|
1257
|
+
gestureRaw = _embedpdf_core_stage.panByScreen(gestureRaw ?? unrubberize(cam()), dx, dy);
|
|
1258
|
+
setCamRaw(rubberize(gestureRaw));
|
|
1259
|
+
} else setCam(_embedpdf_core_stage.panByScreen(cam(), dx, dy));
|
|
805
1260
|
syncCursorFromCamera();
|
|
806
1261
|
},
|
|
807
1262
|
scrollTo: (opts) => {
|
|
1263
|
+
markCause("user");
|
|
808
1264
|
cancelAnim();
|
|
809
1265
|
if (!buildScene().itemCount) return;
|
|
810
1266
|
const next = _embedpdf_core_stage.cameraFromScroll(cam(), stayBounds(), vp(), pad(), opts);
|
|
@@ -827,13 +1283,71 @@ function createStageCapability(ctx, config = {}) {
|
|
|
827
1283
|
const before = buildScene();
|
|
828
1284
|
const focal = before.itemCount ? _embedpdf_core_stage.anchorAtPoint(before, _embedpdf_core_stage.toWorld(cam(), pt)) : null;
|
|
829
1285
|
setCam(_embedpdf_core_stage.zoomAround(cam(), pt, factor));
|
|
830
|
-
ctx.dispatch({
|
|
1286
|
+
if (gestureDepth === 0) ctx.dispatch({
|
|
831
1287
|
type: "PATCH",
|
|
832
1288
|
patch: { zoom: { level: cam().zoom } }
|
|
833
1289
|
});
|
|
834
1290
|
const after = buildScene();
|
|
835
1291
|
if (after !== before && focal && after.itemCount) setCam(_embedpdf_core_stage.cameraForAnchorAtScreen(focal, after, pt, cam().zoom));
|
|
1292
|
+
if (gestureDepth > 0 && gestureElastic) gestureRaw = unrubberize(cam());
|
|
1293
|
+
syncCursorFromCamera();
|
|
1294
|
+
},
|
|
1295
|
+
beginGesture: (options) => {
|
|
1296
|
+
gestureDepth++;
|
|
1297
|
+
if (gestureDepth === 1) {
|
|
1298
|
+
cancelAnim();
|
|
1299
|
+
gestureZoomed = false;
|
|
1300
|
+
gestureElastic = options?.elastic === true;
|
|
1301
|
+
gestureRaw = null;
|
|
1302
|
+
}
|
|
1303
|
+
},
|
|
1304
|
+
endGesture: () => {
|
|
1305
|
+
if (gestureDepth === 0) return;
|
|
1306
|
+
gestureDepth--;
|
|
1307
|
+
if (gestureDepth > 0) return;
|
|
1308
|
+
gestureRaw = null;
|
|
1309
|
+
const wasElastic = gestureElastic;
|
|
1310
|
+
gestureElastic = false;
|
|
1311
|
+
if (gestureZoomed) {
|
|
1312
|
+
gestureZoomed = false;
|
|
1313
|
+
ctx.dispatch({
|
|
1314
|
+
type: "PATCH",
|
|
1315
|
+
patch: { zoom: { level: cam().zoom } }
|
|
1316
|
+
});
|
|
1317
|
+
armRest();
|
|
1318
|
+
}
|
|
836
1319
|
syncCursorFromCamera();
|
|
1320
|
+
if (wasElastic) {
|
|
1321
|
+
const c = cam();
|
|
1322
|
+
const cl = _embedpdf_core_stage.clampCamera(c, stayBounds(), vp(), constraint());
|
|
1323
|
+
if (cl.x !== c.x || cl.y !== c.y) startCameraPhysics(0, 0);
|
|
1324
|
+
}
|
|
1325
|
+
},
|
|
1326
|
+
fling: (vx, vy) => startCameraPhysics(vx, vy),
|
|
1327
|
+
cameraInMotion: () => raf !== 0,
|
|
1328
|
+
doubleTapZoom: (pt) => {
|
|
1329
|
+
cancelAnim();
|
|
1330
|
+
const sc0 = buildScene();
|
|
1331
|
+
if (!sc0.itemCount) return;
|
|
1332
|
+
const item = paged() ? sc0.items[0] : sc0.items[itemIndexOfPage(ctx.getState().cursor)];
|
|
1333
|
+
const fit = fitBox(item);
|
|
1334
|
+
const auto = _embedpdf_core_stage.resolveZoom({ mode: _embedpdf_core_stage.ZoomMode.Automatic }, fit, vp(), pad());
|
|
1335
|
+
const stops = [
|
|
1336
|
+
auto,
|
|
1337
|
+
_embedpdf_core_stage.resolveZoom({ mode: _embedpdf_core_stage.ZoomMode.FitWidth }, fit, vp(), pad()),
|
|
1338
|
+
Math.min(auto * 2.5, _embedpdf_core_stage.ZOOM_MAX)
|
|
1339
|
+
].sort((a, b) => a - b).filter((z, i, all) => i === 0 || z > all[i - 1] * 1.1);
|
|
1340
|
+
const near = (z, stop) => z > stop / 1.1 && z < stop * 1.1;
|
|
1341
|
+
const onRung = stops.findIndex((s) => near(cam().zoom, s));
|
|
1342
|
+
const target = onRung >= 0 ? stops[(onRung + 1) % stops.length] : stops[0];
|
|
1343
|
+
ctx.dispatch({
|
|
1344
|
+
type: "PATCH",
|
|
1345
|
+
patch: { zoom: { level: target } }
|
|
1346
|
+
});
|
|
1347
|
+
const sc = buildScene();
|
|
1348
|
+
if (!sc.itemCount) return;
|
|
1349
|
+
const focal = _embedpdf_core_stage.anchorAtPoint(sc, _embedpdf_core_stage.toWorld(cam(), pt));
|
|
1350
|
+
animateZoomAnchored(focal, pt, target, 240, syncCursorFromCamera);
|
|
837
1351
|
},
|
|
838
1352
|
zoomIn: () => api.zoomAround(alignPoint(ctx.getState().zoomAlign, vp()), 1.2),
|
|
839
1353
|
zoomOut: () => api.zoomAround(alignPoint(ctx.getState().zoomAlign, vp()), 1 / 1.2),
|
|
@@ -843,16 +1357,17 @@ function createStageCapability(ctx, config = {}) {
|
|
|
843
1357
|
fitAll: () => api.update({ zoom: { mode: _embedpdf_core_stage.ZoomMode.FitAll } }),
|
|
844
1358
|
automatic: () => api.update({ zoom: { mode: _embedpdf_core_stage.ZoomMode.Automatic } }),
|
|
845
1359
|
refit: () => {
|
|
846
|
-
if (!
|
|
1360
|
+
if (!placementStarted) return;
|
|
847
1361
|
cancelAnim();
|
|
848
1362
|
reapply(currentAnchor());
|
|
849
1363
|
},
|
|
850
1364
|
goToPage: (pageIndex, opts) => goToTarget(pageIndex, opts),
|
|
851
1365
|
reveal: (pageIndex, opts) => {
|
|
1366
|
+
markCause("programmatic");
|
|
852
1367
|
const doc = ctx.document();
|
|
853
1368
|
if (!doc || doc.pageCount === 0) return;
|
|
854
1369
|
const target = Math.max(0, Math.min(pageIndex, doc.pageCount - 1));
|
|
855
|
-
if (!(!!opts && (opts.rect
|
|
1370
|
+
if (!(!!opts && (opts.rect != null || opts.anchor !== void 0 || opts.zoom !== void 0 && opts.zoom !== "keep"))) {
|
|
856
1371
|
if (paged()) {
|
|
857
1372
|
goToTarget(target, opts);
|
|
858
1373
|
return;
|
|
@@ -918,20 +1433,17 @@ function createStageCapability(ctx, config = {}) {
|
|
|
918
1433
|
},
|
|
919
1434
|
next: (opts) => step(1, opts),
|
|
920
1435
|
prev: (opts) => step(-1, opts),
|
|
1436
|
+
lensId: () => ctx.id,
|
|
921
1437
|
update: (patch) => {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
patch
|
|
929
|
-
});
|
|
930
|
-
if (touched("scene")) sceneCache = null;
|
|
931
|
-
if (touched("reflow")) goToTarget(ctx.getState().cursor, { behavior: "instant" });
|
|
932
|
-
else if (touched("scene") || touched("refit")) reapply(anchor, align);
|
|
933
|
-
else if (touched("reclamp")) setCam(cam());
|
|
1438
|
+
base = mergeSettings(base, patch);
|
|
1439
|
+
syncResponsive(true);
|
|
1440
|
+
},
|
|
1441
|
+
setResponsive: (next) => {
|
|
1442
|
+
rules = next;
|
|
1443
|
+
syncResponsive(true);
|
|
934
1444
|
},
|
|
1445
|
+
matches: (name) => ctx.getState().activeRules.includes(name),
|
|
1446
|
+
activeRules: () => ctx.getState().activeRules,
|
|
935
1447
|
setFlow: (flow) => api.update({ flow }),
|
|
936
1448
|
setLayout: (layout) => api.update({ layout }),
|
|
937
1449
|
setSpread: (spread) => api.update({ spread }),
|
|
@@ -951,9 +1463,13 @@ function createStageCapability(ctx, config = {}) {
|
|
|
951
1463
|
setScrollBehavior: (behavior) => api.update({ scrollBehavior: behavior }),
|
|
952
1464
|
applyViewState: (view) => {
|
|
953
1465
|
cancelAnim();
|
|
1466
|
+
base = mergeSettings(base, pickSettings(view));
|
|
1467
|
+
const { effective, active } = resolveResponsive(base, rules, boxOf(vp()));
|
|
1468
|
+
lastEffective = effective;
|
|
1469
|
+
publishActive(active);
|
|
954
1470
|
ctx.dispatch({
|
|
955
1471
|
type: "PATCH",
|
|
956
|
-
patch:
|
|
1472
|
+
patch: effective
|
|
957
1473
|
});
|
|
958
1474
|
ctx.dispatch({
|
|
959
1475
|
type: "CURSOR",
|
|
@@ -969,97 +1485,92 @@ function createStageCapability(ctx, config = {}) {
|
|
|
969
1485
|
});
|
|
970
1486
|
},
|
|
971
1487
|
placeInitial: () => {
|
|
972
|
-
|
|
1488
|
+
placementStarted = true;
|
|
973
1489
|
const sorted = [...initialViewProviders].sort((a, b) => b.priority - a.priority);
|
|
974
1490
|
for (const p of sorted) {
|
|
975
1491
|
const view = p.fn();
|
|
976
1492
|
if (view) {
|
|
977
1493
|
api.applyViewState(view);
|
|
1494
|
+
ctx.dispatch({ type: "PLACED" });
|
|
978
1495
|
return;
|
|
979
1496
|
}
|
|
980
1497
|
}
|
|
981
1498
|
api.resetView();
|
|
1499
|
+
ctx.dispatch({ type: "PLACED" });
|
|
982
1500
|
},
|
|
983
1501
|
resetView: () => goToTarget(0, { behavior: "instant" })
|
|
984
1502
|
};
|
|
985
1503
|
return api;
|
|
986
1504
|
}
|
|
987
1505
|
//#endregion
|
|
988
|
-
//#region src/scroll-handler.ts
|
|
989
|
-
/**
|
|
990
|
-
* Pan-the-camera as an interaction handler. Live under the built-in `pan` tool
|
|
991
|
-
* (the `scroll` tag) and — with `panFallback` — under every tool, where it then
|
|
992
|
-
* captures ONLY page-gap drags (on-page downs belong to the active tool, which
|
|
993
|
-
* claims them first at higher priority). It uses the sample's viewport delta, so
|
|
994
|
-
* it pans over the whole viewport (pages and gaps).
|
|
995
|
-
*
|
|
996
|
-
* Cursor feel: a closed hand (`grabbing`) while dragging; an open hand (`grab`)
|
|
997
|
-
* hovering a gap under a non-pan tool. The pan tool's own resting `grab` comes
|
|
998
|
-
* from its `Tool.cursor`, so hovering it needs no claim here.
|
|
999
|
-
*/
|
|
1000
|
-
function createScrollHandler(stage, interaction, options = {}) {
|
|
1001
|
-
const panFallback = options.panFallback ?? true;
|
|
1002
|
-
let last = {
|
|
1003
|
-
x: 0,
|
|
1004
|
-
y: 0
|
|
1005
|
-
};
|
|
1006
|
-
const isPanTool = () => interaction.activeTool().enables.has("scroll");
|
|
1007
|
-
return {
|
|
1008
|
-
id: "stage-scroll",
|
|
1009
|
-
priority: 10,
|
|
1010
|
-
enabledFor: (tool) => tool.enables.has("scroll") || panFallback,
|
|
1011
|
-
onDown: (s) => {
|
|
1012
|
-
if (!isPanTool() && s.page) return false;
|
|
1013
|
-
last = s.viewport;
|
|
1014
|
-
interaction.setCursor("stage-grab", "grabbing", 40);
|
|
1015
|
-
return true;
|
|
1016
|
-
},
|
|
1017
|
-
onMove: (s) => {
|
|
1018
|
-
stage.panBy(s.viewport.x - last.x, s.viewport.y - last.y);
|
|
1019
|
-
last = s.viewport;
|
|
1020
|
-
},
|
|
1021
|
-
onUp: () => interaction.setCursor("stage-grab", null),
|
|
1022
|
-
onHover: (s) => {
|
|
1023
|
-
if (panFallback && !isPanTool()) interaction.setCursor("stage-pan-fallback", s.page ? null : "grab", 5);
|
|
1024
|
-
}
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
//#endregion
|
|
1028
1506
|
//#region src/effects.ts
|
|
1029
1507
|
/**
|
|
1030
|
-
*
|
|
1031
|
-
* (rotate/move/delete bump `DocumentMeta.revision`), the page geometry under
|
|
1032
|
-
* this lens just changed, so re-fit — re-resolve the active zoom intent and
|
|
1033
|
-
* re-place against the new footprint.
|
|
1508
|
+
* Stage lens effects.
|
|
1034
1509
|
*
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1038
|
-
* only
|
|
1510
|
+
* 1. Re-fit on page-registry mutations: when rotate/move/delete bump
|
|
1511
|
+
* `DocumentMeta.revision`, the page geometry under this lens changed, so
|
|
1512
|
+
* re-resolve the active zoom intent and re-place against the new
|
|
1513
|
+
* footprint. Steady-state only — it reacts to revision CHANGES, never the
|
|
1514
|
+
* initial value, so it can't race the capability's level-triggered
|
|
1515
|
+
* initial placement.
|
|
1039
1516
|
*
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1517
|
+
* 2. The page-state feed (main lens only, actions plugin present): stage is
|
|
1518
|
+
* AUTHORITATIVE for what page the viewer is on; the action engine's
|
|
1519
|
+
* lifecycle coordinator owns WHEN page-lifecycle triggers fire. This feed
|
|
1520
|
+
* pushes `{ currentPon, visiblePons, placed, cause }` snapshots — pons,
|
|
1521
|
+
* not indexes (reorder-safe) — on placement and on every change; the
|
|
1522
|
+
* coordinator buffers them behind the document-open barrier and diffs
|
|
1523
|
+
* against its last-emitted state, so this side stays a dumb reporter.
|
|
1043
1524
|
*/
|
|
1044
|
-
function registerStageEffects(ctx, token) {
|
|
1525
|
+
function registerStageEffects(ctx, token, feedActions = false) {
|
|
1045
1526
|
ctx.watch(() => ctx.document()?.revision ?? 0, () => ctx.get(token).refit());
|
|
1527
|
+
if (!feedActions) return;
|
|
1528
|
+
const actions = ctx.tryGet(_embedpdf_plugin_actions_contract_host.ActionsToken);
|
|
1529
|
+
if (!actions) return;
|
|
1530
|
+
const snapshot = () => {
|
|
1531
|
+
const state = ctx.getState();
|
|
1532
|
+
if (!state.placed) return null;
|
|
1533
|
+
const stage = ctx.get(token);
|
|
1534
|
+
return {
|
|
1535
|
+
currentPon: stage.pages()[state.cursor]?.pon ?? null,
|
|
1536
|
+
visiblePons: stage.visiblePages().map((page) => page.pon),
|
|
1537
|
+
cause: state.motionCause
|
|
1538
|
+
};
|
|
1539
|
+
};
|
|
1540
|
+
ctx.watch(() => {
|
|
1541
|
+
const s = snapshot();
|
|
1542
|
+
return s === null ? "unplaced" : `${s.currentPon ?? -1}|${[...s.visiblePons].sort((a, b) => a - b).join(",")}`;
|
|
1543
|
+
}, () => {
|
|
1544
|
+
const s = snapshot();
|
|
1545
|
+
if (s === null) return;
|
|
1546
|
+
actions.reportPageState({
|
|
1547
|
+
currentPon: s.currentPon,
|
|
1548
|
+
visiblePons: s.visiblePons,
|
|
1549
|
+
placed: true,
|
|
1550
|
+
cause: s.cause
|
|
1551
|
+
});
|
|
1552
|
+
});
|
|
1046
1553
|
}
|
|
1047
1554
|
//#endregion
|
|
1048
1555
|
//#region src/reducer.ts
|
|
1049
1556
|
const initialStageState = (config) => {
|
|
1050
|
-
const { scheduler: _scheduler, ...overrides } = config;
|
|
1557
|
+
const { scheduler: _scheduler, responsive: _responsive, ...overrides } = config;
|
|
1051
1558
|
return {
|
|
1052
1559
|
camera: {
|
|
1053
1560
|
x: 0,
|
|
1054
1561
|
y: 0,
|
|
1055
1562
|
zoom: 1
|
|
1056
1563
|
},
|
|
1564
|
+
placed: false,
|
|
1565
|
+
cameraResting: true,
|
|
1057
1566
|
vp: {
|
|
1058
1567
|
width: 0,
|
|
1059
1568
|
height: 0
|
|
1060
1569
|
},
|
|
1061
1570
|
dpr: 1,
|
|
1062
1571
|
cursor: 0,
|
|
1572
|
+
motionCause: "user",
|
|
1573
|
+
activeRules: [],
|
|
1063
1574
|
...DEFAULT_SETTINGS,
|
|
1064
1575
|
...overrides
|
|
1065
1576
|
};
|
|
@@ -1085,6 +1596,14 @@ const stageReducer = (state, a) => {
|
|
|
1085
1596
|
...state,
|
|
1086
1597
|
camera: a.camera
|
|
1087
1598
|
};
|
|
1599
|
+
case "CAMERA_REST": return state.cameraResting === a.resting ? state : {
|
|
1600
|
+
...state,
|
|
1601
|
+
cameraResting: a.resting
|
|
1602
|
+
};
|
|
1603
|
+
case "PLACED": return state.placed ? state : {
|
|
1604
|
+
...state,
|
|
1605
|
+
placed: true
|
|
1606
|
+
};
|
|
1088
1607
|
case "VP": return {
|
|
1089
1608
|
...state,
|
|
1090
1609
|
vp: a.vp
|
|
@@ -1097,14 +1616,19 @@ const stageReducer = (state, a) => {
|
|
|
1097
1616
|
...state,
|
|
1098
1617
|
cursor: a.cursor
|
|
1099
1618
|
};
|
|
1619
|
+
case "MOTION_CAUSE": return {
|
|
1620
|
+
...state,
|
|
1621
|
+
motionCause: a.cause
|
|
1622
|
+
};
|
|
1100
1623
|
case "PATCH": return applyPatch(state, a.patch);
|
|
1624
|
+
case "RESPONSIVE": return {
|
|
1625
|
+
...state,
|
|
1626
|
+
activeRules: a.active
|
|
1627
|
+
};
|
|
1101
1628
|
default: return state;
|
|
1102
1629
|
}
|
|
1103
1630
|
};
|
|
1104
1631
|
//#endregion
|
|
1105
|
-
//#region src/types.ts
|
|
1106
|
-
const StageToken = (0, _embedpdf_core.createCapabilityToken)("stage");
|
|
1107
|
-
//#endregion
|
|
1108
1632
|
//#region src/stage.plugin.ts
|
|
1109
1633
|
/**
|
|
1110
1634
|
* Wires the parts into a kernel plugin. This file is the "manifest": it says what
|
|
@@ -1112,189 +1636,126 @@ const StageToken = (0, _embedpdf_core.createCapabilityToken)("stage");
|
|
|
1112
1636
|
* sibling files.
|
|
1113
1637
|
*/
|
|
1114
1638
|
const stagePlugin = (options = {}) => {
|
|
1115
|
-
const { id = "stage", token = StageToken,
|
|
1639
|
+
const { id = "stage", token = require_contract.StageToken, ...config } = options;
|
|
1116
1640
|
return (0, _embedpdf_core.definePlugin)({
|
|
1117
1641
|
id,
|
|
1118
1642
|
token,
|
|
1119
1643
|
scope: "document",
|
|
1120
|
-
optional:
|
|
1644
|
+
optional: [_embedpdf_plugin_actions_contract.ActionsToken],
|
|
1121
1645
|
initialState: () => initialStageState(config),
|
|
1122
1646
|
reduce: stageReducer,
|
|
1123
1647
|
capability: (ctx) => createStageCapability(ctx, config),
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
if (
|
|
1127
|
-
|
|
1128
|
-
|
|
1648
|
+
effects: (ctx) => registerStageEffects(ctx, token, id === "stage"),
|
|
1649
|
+
init: (ctx) => {
|
|
1650
|
+
if (id !== "stage") return;
|
|
1651
|
+
const actions = ctx.tryGet(_embedpdf_plugin_actions_contract_host.ActionsToken);
|
|
1652
|
+
if (!actions) return;
|
|
1653
|
+
ctx.cleanup(actions.registerExecutor("goto", (node) => {
|
|
1654
|
+
if (node.type !== "goto") return {
|
|
1655
|
+
status: "inert",
|
|
1656
|
+
reason: "not a goto node"
|
|
1657
|
+
};
|
|
1658
|
+
const stage = ctx.tryGet(token);
|
|
1659
|
+
const layout = ctx.document()?.pages.find((p) => p.pageObjectNumber === node.destination.pageObjectNumber);
|
|
1660
|
+
if (!stage || !layout) return {
|
|
1661
|
+
status: "failed",
|
|
1662
|
+
error: "no stage or destination page available"
|
|
1663
|
+
};
|
|
1664
|
+
const { pageIndex, options: reveal } = require_destination.destinationToReveal(node.destination, layout);
|
|
1665
|
+
stage.reveal(pageIndex, {
|
|
1666
|
+
...reveal,
|
|
1667
|
+
behavior: "smooth"
|
|
1668
|
+
});
|
|
1669
|
+
return { status: "executed" };
|
|
1670
|
+
}));
|
|
1671
|
+
ctx.cleanup(actions.registerExecutor("named", (node) => {
|
|
1672
|
+
if (node.type !== "named") return {
|
|
1673
|
+
status: "inert",
|
|
1674
|
+
reason: "not a named node"
|
|
1675
|
+
};
|
|
1676
|
+
const stage = ctx.tryGet(token);
|
|
1677
|
+
if (!stage) return {
|
|
1678
|
+
status: "failed",
|
|
1679
|
+
error: "no stage available"
|
|
1680
|
+
};
|
|
1681
|
+
switch (node.name) {
|
|
1682
|
+
case "NextPage":
|
|
1683
|
+
stage.next({ behavior: "smooth" });
|
|
1684
|
+
return { status: "executed" };
|
|
1685
|
+
case "PrevPage":
|
|
1686
|
+
stage.prev({ behavior: "smooth" });
|
|
1687
|
+
return { status: "executed" };
|
|
1688
|
+
case "FirstPage":
|
|
1689
|
+
stage.goToPage(0, { behavior: "smooth" });
|
|
1690
|
+
return { status: "executed" };
|
|
1691
|
+
case "LastPage":
|
|
1692
|
+
stage.goToPage(Math.max(0, stage.pageCount() - 1), { behavior: "smooth" });
|
|
1693
|
+
return { status: "executed" };
|
|
1694
|
+
default: return {
|
|
1695
|
+
status: "inert",
|
|
1696
|
+
reason: `unknown named action '${node.name}'`
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
}));
|
|
1700
|
+
}
|
|
1129
1701
|
});
|
|
1130
1702
|
};
|
|
1131
1703
|
//#endregion
|
|
1132
|
-
//#region src/
|
|
1704
|
+
//#region src/scroll-handler.ts
|
|
1133
1705
|
/**
|
|
1134
|
-
*
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
1139
|
-
* code exists anywhere else.
|
|
1140
|
-
*
|
|
1141
|
-
* The whole protocol collapses onto three knobs:
|
|
1142
|
-
* rect — what to look at (a point for /XYZ, a rect for /FitR, the
|
|
1143
|
-
* page for /Fit, the content bounding box for /FitB*)
|
|
1144
|
-
* zoom — 'keep' | {level} | 'fit' | 'fit-width' | 'fit-height'
|
|
1145
|
-
* anchor — where it lands; 'keep' encodes the spec's null-means-retain
|
|
1706
|
+
* Pan-the-camera as an interaction handler. Live under the built-in `pan` tool
|
|
1707
|
+
* (the `scroll` tag) and — with `panFallback` — under every tool, where it then
|
|
1708
|
+
* captures ONLY page-gap drags (on-page downs belong to the active tool, which
|
|
1709
|
+
* claims them first at higher priority). It uses the sample's viewport delta, so
|
|
1710
|
+
* it pans over the whole viewport (pages and gaps).
|
|
1146
1711
|
*
|
|
1147
|
-
*
|
|
1148
|
-
*
|
|
1149
|
-
*
|
|
1150
|
-
*/
|
|
1151
|
-
/**
|
|
1152
|
-
* Translate one explicit destination against its target page's layout.
|
|
1153
|
-
* `bbox` is the page's content BOUNDING box in CONTENT space, for the
|
|
1154
|
-
* `/FitB*` kinds — pass it when known; the crop box is the spec-tolerated
|
|
1155
|
-
* fallback until the engine exposes it.
|
|
1712
|
+
* Cursor feel: a closed hand (`grabbing`) while dragging; an open hand (`grab`)
|
|
1713
|
+
* hovering a gap under a non-pan tool. The pan tool's own resting `grab` comes
|
|
1714
|
+
* from its `Tool.cursor`, so hovering it needs no claim here.
|
|
1156
1715
|
*/
|
|
1157
|
-
function
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
rotation: layout.rotation,
|
|
1161
|
-
userUnit: layout.userUnit
|
|
1162
|
-
}, 1);
|
|
1163
|
-
const crop = layout.boxes.crop;
|
|
1164
|
-
const toContent = (x, y) => (0, _embedpdf_core_geometry.applyPoint)(geo.pdfToContent, {
|
|
1165
|
-
x,
|
|
1166
|
-
y
|
|
1167
|
-
});
|
|
1168
|
-
const page = {
|
|
1716
|
+
function createScrollHandler(stage, interaction, options = {}) {
|
|
1717
|
+
const panFallback = options.panFallback ?? true;
|
|
1718
|
+
let last = {
|
|
1169
1719
|
x: 0,
|
|
1170
|
-
y: 0
|
|
1171
|
-
width: layout.size.width,
|
|
1172
|
-
height: layout.size.height
|
|
1720
|
+
y: 0
|
|
1173
1721
|
};
|
|
1174
|
-
const
|
|
1175
|
-
const options = (() => {
|
|
1176
|
-
switch (dest.kind) {
|
|
1177
|
-
case "xyz": {
|
|
1178
|
-
const hasLeft = dest.left != null;
|
|
1179
|
-
const hasTop = dest.top != null;
|
|
1180
|
-
const p = toContent(dest.left ?? crop.left, dest.top ?? crop.top);
|
|
1181
|
-
return {
|
|
1182
|
-
rect: {
|
|
1183
|
-
x: p.x,
|
|
1184
|
-
y: p.y,
|
|
1185
|
-
width: 0,
|
|
1186
|
-
height: 0
|
|
1187
|
-
},
|
|
1188
|
-
anchor: {
|
|
1189
|
-
x: hasLeft ? "start" : "keep",
|
|
1190
|
-
y: hasTop ? "start" : "keep"
|
|
1191
|
-
},
|
|
1192
|
-
zoom: dest.zoom != null && dest.zoom !== 0 ? { level: dest.zoom } : "keep"
|
|
1193
|
-
};
|
|
1194
|
-
}
|
|
1195
|
-
case "fit": return { zoom: "fit" };
|
|
1196
|
-
case "fitH": {
|
|
1197
|
-
const hasTop = dest.top != null;
|
|
1198
|
-
return {
|
|
1199
|
-
rect: {
|
|
1200
|
-
x: 0,
|
|
1201
|
-
y: hasTop ? toContent(crop.left, dest.top).y : 0,
|
|
1202
|
-
width: page.width,
|
|
1203
|
-
height: 0
|
|
1204
|
-
},
|
|
1205
|
-
zoom: "fit-width",
|
|
1206
|
-
anchor: { y: hasTop ? "start" : "keep" }
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
case "fitV": {
|
|
1210
|
-
const hasLeft = dest.left != null;
|
|
1211
|
-
return {
|
|
1212
|
-
rect: {
|
|
1213
|
-
x: hasLeft ? toContent(dest.left, crop.top).x : 0,
|
|
1214
|
-
y: 0,
|
|
1215
|
-
width: 0,
|
|
1216
|
-
height: page.height
|
|
1217
|
-
},
|
|
1218
|
-
zoom: "fit-height",
|
|
1219
|
-
anchor: { x: hasLeft ? "start" : "keep" }
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
case "fitR": {
|
|
1223
|
-
const tl = toContent(dest.left, dest.top);
|
|
1224
|
-
const br = toContent(dest.right, dest.bottom);
|
|
1225
|
-
return {
|
|
1226
|
-
rect: {
|
|
1227
|
-
x: Math.min(tl.x, br.x),
|
|
1228
|
-
y: Math.min(tl.y, br.y),
|
|
1229
|
-
width: Math.abs(br.x - tl.x),
|
|
1230
|
-
height: Math.abs(br.y - tl.y)
|
|
1231
|
-
},
|
|
1232
|
-
zoom: "fit"
|
|
1233
|
-
};
|
|
1234
|
-
}
|
|
1235
|
-
case "fitB": return {
|
|
1236
|
-
rect: box,
|
|
1237
|
-
zoom: "fit"
|
|
1238
|
-
};
|
|
1239
|
-
case "fitBH": {
|
|
1240
|
-
const hasTop = dest.top != null;
|
|
1241
|
-
const y = hasTop ? toContent(crop.left, dest.top).y : box.y;
|
|
1242
|
-
return {
|
|
1243
|
-
rect: {
|
|
1244
|
-
x: box.x,
|
|
1245
|
-
y,
|
|
1246
|
-
width: box.width,
|
|
1247
|
-
height: 0
|
|
1248
|
-
},
|
|
1249
|
-
zoom: "fit-width",
|
|
1250
|
-
anchor: { y: hasTop ? "start" : "keep" }
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
case "fitBV": {
|
|
1254
|
-
const hasLeft = dest.left != null;
|
|
1255
|
-
return {
|
|
1256
|
-
rect: {
|
|
1257
|
-
x: hasLeft ? toContent(dest.left, crop.top).x : box.x,
|
|
1258
|
-
y: box.y,
|
|
1259
|
-
width: 0,
|
|
1260
|
-
height: box.height
|
|
1261
|
-
},
|
|
1262
|
-
zoom: "fit-height",
|
|
1263
|
-
anchor: { x: hasLeft ? "start" : "keep" }
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
})();
|
|
1722
|
+
const isPanTool = () => interaction.activeTool().enables.has("scroll");
|
|
1268
1723
|
return {
|
|
1269
|
-
|
|
1270
|
-
|
|
1724
|
+
id: "stage-scroll",
|
|
1725
|
+
priority: 10,
|
|
1726
|
+
enabledFor: (tool) => tool.enables.has("scroll") || panFallback,
|
|
1727
|
+
onDown: (s) => {
|
|
1728
|
+
if (!isPanTool() && s.page) return false;
|
|
1729
|
+
last = s.viewport;
|
|
1730
|
+
interaction.setCursor("stage-grab", "grabbing", 40);
|
|
1731
|
+
return true;
|
|
1732
|
+
},
|
|
1733
|
+
onMove: (s) => {
|
|
1734
|
+
stage.panBy(s.viewport.x - last.x, s.viewport.y - last.y);
|
|
1735
|
+
last = s.viewport;
|
|
1736
|
+
},
|
|
1737
|
+
onUp: () => interaction.setCursor("stage-grab", null),
|
|
1738
|
+
onHover: (s) => {
|
|
1739
|
+
if (panFallback && !isPanTool()) interaction.setCursor("stage-pan-fallback", s.page ? null : "grab", 5);
|
|
1740
|
+
}
|
|
1271
1741
|
};
|
|
1272
1742
|
}
|
|
1273
1743
|
//#endregion
|
|
1274
|
-
|
|
1275
|
-
/** The per-notch step for discrete wheels — the zoom buttons' ratio. */
|
|
1276
|
-
const NOTCH = 1.2;
|
|
1277
|
-
/** Zoom factor for a ctrl/cmd wheel event (the caller decides zoom vs pan). */
|
|
1278
|
-
function wheelZoomFactor(e) {
|
|
1279
|
-
if (e.deltaMode !== 0) return Math.pow(NOTCH, -Math.sign(e.deltaY));
|
|
1280
|
-
if (e.ctrlKey) {
|
|
1281
|
-
if (Math.abs(e.deltaY) >= 100) return Math.pow(NOTCH, -Math.sign(e.deltaY));
|
|
1282
|
-
return Math.exp(-e.deltaY / 100);
|
|
1283
|
-
}
|
|
1284
|
-
return Math.exp(-e.deltaY * .0015);
|
|
1285
|
-
}
|
|
1286
|
-
//#endregion
|
|
1744
|
+
exports.DEFAULT_RESPONSIVE = DEFAULT_RESPONSIVE;
|
|
1287
1745
|
exports.DEFAULT_SETTINGS = DEFAULT_SETTINGS;
|
|
1288
|
-
exports.StageToken = StageToken;
|
|
1746
|
+
exports.StageToken = require_contract.StageToken;
|
|
1289
1747
|
Object.defineProperty(exports, "ZoomMode", {
|
|
1290
1748
|
enumerable: true,
|
|
1291
1749
|
get: function() {
|
|
1292
1750
|
return _embedpdf_core_stage.ZoomMode;
|
|
1293
1751
|
}
|
|
1294
1752
|
});
|
|
1295
|
-
exports.
|
|
1753
|
+
exports.boxOf = boxOf;
|
|
1754
|
+
exports.createScrollHandler = createScrollHandler;
|
|
1755
|
+
exports.destinationToReveal = require_destination.destinationToReveal;
|
|
1756
|
+
exports.matchesQuery = matchesQuery;
|
|
1757
|
+
exports.resolveResponsive = resolveResponsive;
|
|
1296
1758
|
exports.settingsEqual = settingsEqual;
|
|
1297
1759
|
exports.stagePlugin = stagePlugin;
|
|
1298
|
-
exports.wheelZoomFactor = wheelZoomFactor;
|
|
1299
1760
|
|
|
1300
1761
|
//# sourceMappingURL=index.cjs.map
|