@embedpdf/plugin-stage 3.0.0-next.5 → 3.0.0-next.6

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/index.d.cts CHANGED
@@ -186,6 +186,16 @@ interface VisiblePage extends PageBox {
186
186
  }
187
187
  interface StageState extends StageSettings {
188
188
  camera: Camera;
189
+ /**
190
+ * False while the ZOOM is in motion, true once it has rested (~150ms of
191
+ * frames without a zoom write). Device-snapping of page origins is gated
192
+ * on this: a continuous zoom and a snapped origin cannot coexist without
193
+ * the anchor point jittering ±0.5 device px per step (the rounding lands
194
+ * differently every frame), so pages place fractionally while zooming and
195
+ * snap once at rest — when crispness actually matters. Pans always snap
196
+ * (a pure translation snap has no anchor error). Transient like `camera`.
197
+ */
198
+ cameraResting: boolean;
189
199
  vp: Size;
190
200
  /**
191
201
  * Device pixels per view pixel (web: `window.devicePixelRatio`). Reported by
@@ -204,6 +214,9 @@ interface StageState extends StageSettings {
204
214
  type StageAction = {
205
215
  type: 'CAMERA';
206
216
  camera: Camera;
217
+ } | {
218
+ type: 'CAMERA_REST';
219
+ resting: boolean;
207
220
  } | {
208
221
  type: 'VP';
209
222
  vp: Size;
package/dist/index.d.ts CHANGED
@@ -186,6 +186,16 @@ interface VisiblePage extends PageBox {
186
186
  }
187
187
  interface StageState extends StageSettings {
188
188
  camera: Camera;
189
+ /**
190
+ * False while the ZOOM is in motion, true once it has rested (~150ms of
191
+ * frames without a zoom write). Device-snapping of page origins is gated
192
+ * on this: a continuous zoom and a snapped origin cannot coexist without
193
+ * the anchor point jittering ±0.5 device px per step (the rounding lands
194
+ * differently every frame), so pages place fractionally while zooming and
195
+ * snap once at rest — when crispness actually matters. Pans always snap
196
+ * (a pure translation snap has no anchor error). Transient like `camera`.
197
+ */
198
+ cameraResting: boolean;
189
199
  vp: Size;
190
200
  /**
191
201
  * Device pixels per view pixel (web: `window.devicePixelRatio`). Reported by
@@ -204,6 +214,9 @@ interface StageState extends StageSettings {
204
214
  type StageAction = {
205
215
  type: 'CAMERA';
206
216
  camera: Camera;
217
+ } | {
218
+ type: 'CAMERA_REST';
219
+ resting: boolean;
207
220
  } | {
208
221
  type: 'VP';
209
222
  vp: Size;
package/dist/index.js CHANGED
@@ -373,10 +373,37 @@ function createStageCapability(ctx, config = {}) {
373
373
  fitAlign: ctx.getState().fitAlign,
374
374
  direction: ctx.getState().direction
375
375
  });
376
+ let hasPlaced = false;
377
+ const REST_MS = 150;
378
+ let restRaf = 0;
379
+ const armRest = () => {
380
+ if (!canAnimate || !hasPlaced) return;
381
+ if (ctx.getState().cameraResting) ctx.dispatch({
382
+ type: "CAMERA_REST",
383
+ resting: false
384
+ });
385
+ if (restRaf) scheduler.caf(restRaf);
386
+ let t0 = 0;
387
+ const tick = (ts) => {
388
+ restRaf = 0;
389
+ if (!t0) t0 = ts;
390
+ if (ts - t0 >= REST_MS) {
391
+ ctx.dispatch({
392
+ type: "CAMERA_REST",
393
+ resting: true
394
+ });
395
+ return;
396
+ }
397
+ restRaf = scheduler.raf(tick);
398
+ };
399
+ restRaf = scheduler.raf(tick);
400
+ };
376
401
  const setCam = (next, bounds = stayBounds()) => {
402
+ const clamped = S.clampCamera(next, bounds, vp(), constraint());
403
+ if (clamped.zoom !== cam().zoom) armRest();
377
404
  ctx.dispatch({
378
405
  type: "CAMERA",
379
- camera: S.clampCamera(next, bounds, vp(), constraint())
406
+ camera: clamped
380
407
  });
381
408
  };
382
409
  /**
@@ -561,8 +588,11 @@ function createStageCapability(ctx, config = {}) {
561
588
  } : unrotatedPoints(box);
562
589
  const c = cam();
563
590
  const ratio = dpr();
564
- const screenX = snapToDevice((box.x - c.x) * c.zoom, ratio);
565
- const screenY = snapToDevice((box.y - c.y) * c.zoom, ratio);
591
+ const rawX = (box.x - c.x) * c.zoom;
592
+ const rawY = (box.y - c.y) * c.zoom;
593
+ const resting = ctx.getState().cameraResting;
594
+ const screenX = resting ? snapToDevice(rawX, ratio) : rawX;
595
+ const screenY = resting ? snapToDevice(rawY, ratio) : rawY;
566
596
  const transform = pageTransform({
567
597
  pageSize,
568
598
  rotation: box.rotation,
@@ -618,7 +648,7 @@ function createStageCapability(ctx, config = {}) {
618
648
  const v = vp();
619
649
  const sc = buildScene();
620
650
  const items = paged() ? sc.items.slice(0, 1) : sc.query(S.cameraWorldRect(c, v));
621
- const sig = `${sceneCache.key}|${ctx.getState().flow}|${c.x},${c.y},${c.zoom}|${v.width}x${v.height}|${dpr()}`;
651
+ 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}`;
622
652
  if (sig === visSig) return vis;
623
653
  visSig = sig;
624
654
  vis = items.flatMap((it) => it.pages).map(withTransform);
@@ -631,7 +661,6 @@ function createStageCapability(ctx, config = {}) {
631
661
  };
632
662
  const snapshotSettings = () => pickSettings(ctx.getState());
633
663
  const initialViewProviders = [];
634
- let hasPlaced = false;
635
664
  const api = {
636
665
  camera: cam,
637
666
  viewport: vp,
@@ -1031,6 +1060,7 @@ const initialStageState = (config) => {
1031
1060
  y: 0,
1032
1061
  zoom: 1
1033
1062
  },
1063
+ cameraResting: true,
1034
1064
  vp: {
1035
1065
  width: 0,
1036
1066
  height: 0
@@ -1062,6 +1092,10 @@ const stageReducer = (state, a) => {
1062
1092
  ...state,
1063
1093
  camera: a.camera
1064
1094
  };
1095
+ case "CAMERA_REST": return state.cameraResting === a.resting ? state : {
1096
+ ...state,
1097
+ cameraResting: a.resting
1098
+ };
1065
1099
  case "VP": return {
1066
1100
  ...state,
1067
1101
  vp: a.vp