@pierre/diffs 1.2.0-beta.1 → 1.2.0-beta.3

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 (36) hide show
  1. package/dist/components/CodeView.d.ts +28 -5
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +213 -87
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts.map +1 -1
  6. package/dist/components/File.js +1 -1
  7. package/dist/components/FileDiff.js +1 -1
  8. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  9. package/dist/components/VirtualizedFile.d.ts +3 -2
  10. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  11. package/dist/components/VirtualizedFile.js +23 -17
  12. package/dist/components/VirtualizedFile.js.map +1 -1
  13. package/dist/components/VirtualizedFileDiff.d.ts +3 -2
  14. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  15. package/dist/components/VirtualizedFileDiff.js +24 -20
  16. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  17. package/dist/components/VirtulizerDevelopment.d.ts.map +1 -1
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +2 -2
  20. package/dist/react/CodeView.d.ts +5 -1
  21. package/dist/react/CodeView.d.ts.map +1 -1
  22. package/dist/react/CodeView.js +75 -16
  23. package/dist/react/CodeView.js.map +1 -1
  24. package/dist/react/index.d.ts +2 -2
  25. package/dist/react/jsx.d.ts.map +1 -1
  26. package/dist/react/utils/useFileDiffInstance.js +1 -1
  27. package/dist/react/utils/useFileInstance.js +1 -1
  28. package/dist/react/utils/useUnresolvedFileInstance.js +1 -1
  29. package/dist/renderers/FileRenderer.js +1 -1
  30. package/dist/ssr/index.d.ts +2 -2
  31. package/dist/style.js +1 -1
  32. package/dist/style.js.map +1 -1
  33. package/dist/types.d.ts +3 -2
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/worker/WorkerPoolManager.js +1 -1
  36. package/package.json +1 -1
@@ -81,6 +81,11 @@ const CODE_VIEW_SELECTION_CALLBACK_KEYS = [
81
81
  "onLineSelectionEnd"
82
82
  ];
83
83
  const DEFAULT_POINTER_EVENTS_RESTORE_DELAY_MS = 120;
84
+ const SCROLL_REBASE_CONTAINER_HEIGHT = 12e6;
85
+ const SCROLL_REBASE_TRIGGER_TOP = 1e6;
86
+ const SCROLL_REBASE_TARGET_TOP = 2e6;
87
+ const SCROLL_REBASE_TARGET_BOTTOM = SCROLL_REBASE_CONTAINER_HEIGHT - SCROLL_REBASE_TARGET_TOP;
88
+ const SCROLL_REBASE_THRESHOLD = SCROLL_REBASE_CONTAINER_HEIGHT - SCROLL_REBASE_TRIGGER_TOP;
84
89
  var CodeView = class CodeView {
85
90
  static __STOP = false;
86
91
  static __lastScrollPosition = 0;
@@ -101,6 +106,7 @@ var CodeView = class CodeView {
101
106
  scrollHeight = 0;
102
107
  containerHeight = -1;
103
108
  scrollTop = 0;
109
+ scrollPageOffset = 0;
104
110
  scrollDirty = true;
105
111
  pointerEventsRestoreTimer;
106
112
  pointerEventsDisabled = false;
@@ -185,7 +191,7 @@ var CodeView = class CodeView {
185
191
  this.root = root;
186
192
  this.root.style.overflowAnchor = "none";
187
193
  this.container ??= document.createElement("div");
188
- this.container.style.contain = "layout size style";
194
+ this.container.style.contain = "layout style";
189
195
  this.syncViewerMetrics();
190
196
  this.container.appendChild(this.stickyOffset);
191
197
  this.container.appendChild(this.stickyContainer);
@@ -227,6 +233,7 @@ var CodeView = class CodeView {
227
233
  this.stickyContainer.textContent = "";
228
234
  this.stickyOffset.style.height = "";
229
235
  this.container?.style.removeProperty("height");
236
+ this.containerHeight = -1;
230
237
  this.windowSpecs = {
231
238
  top: 0,
232
239
  bottom: 0
@@ -234,6 +241,7 @@ var CodeView = class CodeView {
234
241
  this.pendingLayoutAnchor = void 0;
235
242
  this.height = 0;
236
243
  this.scrollTop = 0;
244
+ this.scrollPageOffset = 0;
237
245
  this.scrollHeight = 0;
238
246
  this.scrollDirty = true;
239
247
  this.heightDirty = true;
@@ -296,6 +304,22 @@ var CodeView = class CodeView {
296
304
  clearSelectedLines(options) {
297
305
  this.applySelectedLines(null, options);
298
306
  }
307
+ getItem(itemId) {
308
+ return this.idToItem.get(itemId)?.item;
309
+ }
310
+ updateItem(input) {
311
+ const item = this.idToItem.get(input.id);
312
+ if (item == null) {
313
+ console.error(`CodeView.updateItem: unknown item id "${input.id}"`);
314
+ return false;
315
+ }
316
+ if (!this.syncItemRecord(item, input)) return false;
317
+ this.markItemLayoutDirty(item);
318
+ this.scrollDirty = true;
319
+ this.render();
320
+ this.syncSelection();
321
+ return true;
322
+ }
299
323
  addItem(input) {
300
324
  this.addItems([input]);
301
325
  this.syncSelection();
@@ -320,6 +344,7 @@ var CodeView = class CodeView {
320
344
  if (inputs.length === 0) return;
321
345
  const viewerMetrics = this.getViewerMetrics();
322
346
  let nextTop = this.items.length === 0 ? 0 : this.scrollHeight + viewerMetrics.gap;
347
+ const appendedTop = nextTop;
323
348
  for (let index = 0; index < inputs.length; index++) {
324
349
  const input = inputs[index];
325
350
  if (input == null) throw new Error("CodeView.appendItemsInternal: missing input item");
@@ -333,7 +358,11 @@ var CodeView = class CodeView {
333
358
  }
334
359
  this.scrollHeight = nextTop - viewerMetrics.gap;
335
360
  this.scrollDirty = true;
336
- if (render) this.render();
361
+ if (render) if (this.canSkipRenderForAppend(appendedTop)) this.syncContainerHeight();
362
+ else this.render();
363
+ }
364
+ canSkipRenderForAppend(appendedTop) {
365
+ return this.container != null && this.renderState.firstIndex !== -1 && this.pendingScrollTarget == null && this.scrollAnimation == null && this.layoutDirtyIndex == null && appendedTop > this.windowSpecs.bottom;
337
366
  }
338
367
  setOptions(options) {
339
368
  if (options == null) return;
@@ -419,15 +448,15 @@ var CodeView = class CodeView {
419
448
  this.scrollListeners.delete(listener);
420
449
  };
421
450
  }
422
- getTopForInstance(instance) {
451
+ getLocalTopForInstance(instance) {
423
452
  const item = this.instanceToItem.get(instance);
424
- if (item == null) throw new Error("CodeView.getTopForInstance: unknown virtualized instance");
453
+ if (item == null) throw new Error("CodeView.getLocalTopForInstance: unknown virtualized instance");
425
454
  return item.top;
426
455
  }
427
456
  getTopForItem(id) {
428
457
  const item = this.idToItem.get(id);
429
458
  if (item == null) return;
430
- return item.top;
459
+ return item.top + this.getViewerMetrics().paddingTop;
431
460
  }
432
461
  createItem(input, index, top) {
433
462
  const itemMetrics = this.getItemMetrics();
@@ -638,15 +667,81 @@ var CodeView = class CodeView {
638
667
  else item.instance.setOptions(this.createOptions(item.item));
639
668
  return true;
640
669
  }
670
+ getMaxScrollTopForHeight(scrollHeight) {
671
+ const { paddingBottom, paddingTop } = this.getViewerMetrics();
672
+ return Math.max(paddingTop + scrollHeight + paddingBottom - this.getHeight(), 0);
673
+ }
674
+ getMaxScrollTop() {
675
+ return this.getMaxScrollTopForHeight(this.getScrollHeight());
676
+ }
677
+ shouldRebaseScroll() {
678
+ return this.getMaxScrollTop() > SCROLL_REBASE_THRESHOLD;
679
+ }
680
+ getPagedScrollHeight() {
681
+ return this.shouldRebaseScroll() ? Math.min(this.getScrollHeight(), SCROLL_REBASE_CONTAINER_HEIGHT) : this.getScrollHeight();
682
+ }
683
+ getMaxPagedScrollTop() {
684
+ return this.getMaxScrollTopForHeight(this.getPagedScrollHeight());
685
+ }
686
+ clampPagedScrollTop(value) {
687
+ const maxScroll = this.getMaxPagedScrollTop();
688
+ return Math.max(0, Math.min(value, maxScroll));
689
+ }
641
690
  /**
642
- * Clamps a scroll position to the min/max allowable scroll range based on
643
- * the computed total height
691
+ * Clamps a logical scroll position to the min/max allowable scroll range
692
+ * based on the full computed content height.
644
693
  */
645
694
  clampScrollTop(value) {
646
- const { paddingBottom, paddingTop } = this.getViewerMetrics();
647
- const maxScroll = Math.max(paddingTop + this.getScrollHeight() + paddingBottom - this.getHeight(), 0);
695
+ const maxScroll = this.getMaxScrollTop();
648
696
  return Math.max(0, Math.min(value, maxScroll));
649
697
  }
698
+ getMaxScrollPageOffset() {
699
+ return Math.max(this.getMaxScrollTop() - this.getMaxPagedScrollTop(), 0);
700
+ }
701
+ clampScrollPageOffset(value) {
702
+ const maxOffset = this.getMaxScrollPageOffset();
703
+ return Math.max(0, Math.min(value, maxOffset));
704
+ }
705
+ resolveScrollPageWindow(scrollTop, preferredPagedScrollTop) {
706
+ let pagedScrollTop = roundToDevicePixel(this.clampPagedScrollTop(preferredPagedScrollTop));
707
+ let scrollPageOffset = this.clampScrollPageOffset(scrollTop - pagedScrollTop);
708
+ pagedScrollTop = roundToDevicePixel(this.clampPagedScrollTop(scrollTop - scrollPageOffset));
709
+ scrollPageOffset = this.clampScrollPageOffset(scrollTop - pagedScrollTop);
710
+ return {
711
+ pagedScrollTop,
712
+ scrollPageOffset
713
+ };
714
+ }
715
+ /**
716
+ * Resolve how a logical scrollTop maps onto the reusable paged scroll window
717
+ * without mutating the current page offset.
718
+ */
719
+ resolvePagedScrollPosition(logicalScrollTop) {
720
+ if (!this.shouldRebaseScroll()) return {
721
+ pagedScrollTop: this.clampPagedScrollTop(logicalScrollTop),
722
+ scrollPageOffset: 0
723
+ };
724
+ const currentPageOffset = this.clampScrollPageOffset(this.scrollPageOffset);
725
+ const pagedScrollTop = logicalScrollTop - currentPageOffset;
726
+ const pagedMaxScrollTop = this.getMaxPagedScrollTop();
727
+ const maxRebaseOffset = this.getMaxScrollPageOffset();
728
+ const shouldMoveDown = pagedScrollTop > SCROLL_REBASE_THRESHOLD && currentPageOffset < maxRebaseOffset;
729
+ const shouldMoveUp = pagedScrollTop < SCROLL_REBASE_TRIGGER_TOP && currentPageOffset > 0;
730
+ if (pagedScrollTop < 0 || pagedScrollTop > pagedMaxScrollTop || shouldMoveDown || shouldMoveUp) return this.resolveScrollPageWindow(logicalScrollTop, shouldMoveUp ? Math.min(SCROLL_REBASE_TARGET_BOTTOM, pagedMaxScrollTop) : SCROLL_REBASE_TARGET_TOP);
731
+ return {
732
+ pagedScrollTop: roundToDevicePixel(this.clampPagedScrollTop(pagedScrollTop)),
733
+ scrollPageOffset: currentPageOffset
734
+ };
735
+ }
736
+ needsScrollPageUpdate(logicalScrollTop) {
737
+ const roundedScrollTop = roundToDevicePixel(this.clampScrollTop(logicalScrollTop));
738
+ const { scrollPageOffset } = this.resolvePagedScrollPosition(roundedScrollTop);
739
+ return scrollPageOffset !== this.scrollPageOffset;
740
+ }
741
+ getPagedLayoutTop(logicalTop) {
742
+ if (!this.shouldRebaseScroll()) return logicalTop;
743
+ return Math.max(logicalTop - this.scrollPageOffset, 0);
744
+ }
650
745
  getStickyHeaderOffset() {
651
746
  return this.options.stickyHeaders === true && this.options.disableFileHeader !== true ? this.getItemMetrics().diffHeaderHeight : 0;
652
747
  }
@@ -739,7 +834,7 @@ var CodeView = class CodeView {
739
834
  * smooth scroll animation or not. If not just return the destination, or
740
835
  * compute next position given the smooth scroll spring physics
741
836
  */
742
- computeFrameScrollTop(scrollTop, frameTimestamp) {
837
+ computeTargetScrollTopForFrame(scrollTop, frameTimestamp) {
743
838
  if (this.pendingScrollTarget == null) return scrollTop;
744
839
  const destination = this.resolveScrollTargetTop(this.pendingScrollTarget);
745
840
  if (destination == null) return scrollTop;
@@ -798,51 +893,50 @@ var CodeView = class CodeView {
798
893
  }
799
894
  computeRenderRangeAndEmit = (timestamp = performance.now()) => {
800
895
  if (CodeView.__STOP || this.container == null) return;
801
- const height = this.getHeight();
802
- let currentScrollTop = this.getScrollTop();
803
- const currentRootScrollTop = currentScrollTop;
804
- let recomputeScrollTop = this.pendingLayoutAnchor != null;
805
- let anchor = this.getScrollAnchor(currentScrollTop);
896
+ const viewportHeight = this.getHeight();
897
+ const initialScrollTop = this.getScrollTop();
898
+ let scrollTopAfterLayout = initialScrollTop;
899
+ let computeScrollCorrection = this.pendingLayoutAnchor != null;
900
+ let scrollAnchor = this.getScrollAnchor(scrollTopAfterLayout);
806
901
  if (this.layoutDirtyIndex != null) {
807
902
  this.recomputeLayout(this.layoutDirtyIndex);
808
903
  this.layoutDirtyIndex = void 0;
809
- recomputeScrollTop = true;
904
+ computeScrollCorrection = true;
810
905
  }
811
- if (recomputeScrollTop && anchor != null) {
812
- const newScrollTop = this.resolveAnchoredScrollTop(anchor);
813
- if (newScrollTop != null) {
814
- const delta = newScrollTop - currentScrollTop;
815
- currentScrollTop = newScrollTop;
816
- if (this.scrollAnimation != null) this.scrollAnimation.position += delta;
906
+ if (computeScrollCorrection && scrollAnchor != null) {
907
+ const anchoredScrollTopAfterLayout = this.resolveAnchoredScrollTop(scrollAnchor);
908
+ if (anchoredScrollTopAfterLayout != null) {
909
+ const layoutAnchorDelta = anchoredScrollTopAfterLayout - scrollTopAfterLayout;
910
+ scrollTopAfterLayout = anchoredScrollTopAfterLayout;
911
+ if (this.scrollAnimation != null) this.scrollAnimation.position += layoutAnchorDelta;
817
912
  }
818
913
  }
819
- if (recomputeScrollTop) {
820
- currentScrollTop = this.clampScrollTop(currentScrollTop);
914
+ if (computeScrollCorrection) {
915
+ scrollTopAfterLayout = this.clampScrollTop(scrollTopAfterLayout);
821
916
  this.syncContainerHeight();
822
917
  }
823
- const frameScrollTop = this.computeFrameScrollTop(currentScrollTop, timestamp);
824
- const fitPerfectly = !recomputeScrollTop && (this.renderState.scrollTop === -1 || Math.abs(frameScrollTop - this.renderState.scrollTop) > height + this.config.overscrollSize * 2);
825
- let appliedScrollTop = currentRootScrollTop;
826
- if (this.pendingScrollTarget != null && frameScrollTop !== appliedScrollTop) {
827
- this.applyScrollFix(frameScrollTop, appliedScrollTop);
828
- appliedScrollTop = frameScrollTop;
829
- }
830
- if (fitPerfectly) anchor = void 0;
918
+ const targetScrollTop = this.computeTargetScrollTopForFrame(scrollTopAfterLayout, timestamp);
919
+ const fitPerfectly = !computeScrollCorrection && (this.renderState.scrollTop === -1 || Math.abs(targetScrollTop - this.renderState.scrollTop) > viewportHeight + this.config.overscrollSize * 2);
920
+ if (fitPerfectly) scrollAnchor = void 0;
831
921
  this.windowSpecs = createWindowFromScrollPosition({
832
- scrollTop: frameScrollTop,
833
- height,
922
+ scrollTop: targetScrollTop,
923
+ height: viewportHeight,
834
924
  scrollHeight: this.getScrollHeight(),
835
925
  fitPerfectly,
836
926
  fitPerfectlyOverscroll: this.getFitPerfectlyOverscroll(),
837
927
  overscrollSize: this.config.overscrollSize
838
928
  });
929
+ let syncedScrollTop = initialScrollTop;
930
+ if (this.pendingScrollTarget != null && targetScrollTop !== syncedScrollTop || this.needsScrollPageUpdate(targetScrollTop)) {
931
+ this.applyScrollFix(targetScrollTop, syncedScrollTop, this.windowSpecs);
932
+ syncedScrollTop = targetScrollTop;
933
+ }
839
934
  const { top, bottom } = this.windowSpecs;
840
935
  const { firstIndex, lastIndex } = this.renderState;
841
936
  if (firstIndex >= 0) for (let index = firstIndex; index <= lastIndex; index++) {
842
937
  const item = this.items[index];
843
938
  if (item == null) throw new Error(`CodeView.computeRenderRangeAndEmit: No item at index: ${index}`);
844
- const renderedTop = item.top;
845
- if (!(renderedTop > top - item.height && renderedTop <= bottom)) cleanRenderedItem(item);
939
+ if (!(item.top > top - item.height && item.top <= bottom)) cleanRenderedItem(item);
846
940
  }
847
941
  let prevElement;
848
942
  const updatedItems = /* @__PURE__ */ new Set();
@@ -870,25 +964,27 @@ var CodeView = class CodeView {
870
964
  this.reconcileRenderedItems(updatedItems);
871
965
  this.syncContainerHeight();
872
966
  this.updateStickyPositioning();
873
- const anchoredScrollTop = anchor != null ? this.resolveAnchoredScrollTop(anchor) : void 0;
874
- if (anchor === this.pendingLayoutAnchor) this.pendingLayoutAnchor = void 0;
875
- const anchorScrollDelta = anchoredScrollTop != null ? anchoredScrollTop - currentScrollTop : 0;
876
- let renderedScrollTop = frameScrollTop;
877
- if (this.pendingScrollTarget == null) {
878
- renderedScrollTop = anchoredScrollTop ?? frameScrollTop;
879
- if (renderedScrollTop !== appliedScrollTop) this.applyScrollFix(renderedScrollTop, appliedScrollTop);
880
- } else if (this.pendingScrollTarget != null) {
881
- const targetScrollTop = this.advanceScrollAnimation(timestamp, anchorScrollDelta);
882
- if (targetScrollTop != null) {
883
- if (targetScrollTop !== appliedScrollTop) this.applyScrollFix(targetScrollTop, appliedScrollTop);
884
- renderedScrollTop = targetScrollTop;
885
- if (this.pendingScrollTarget != null && this.isPendingTargetSettled(this.pendingScrollTarget)) {
886
- this.pendingScrollTarget = void 0;
887
- this.scrollAnimation = void 0;
888
- }
889
- } else renderedScrollTop = currentScrollTop;
967
+ const anchoredScrollTopAfterRender = scrollAnchor != null ? this.resolveAnchoredScrollTop(scrollAnchor) : void 0;
968
+ if (scrollAnchor === this.pendingLayoutAnchor) this.pendingLayoutAnchor = void 0;
969
+ const postRenderAnchorDelta = anchoredScrollTopAfterRender != null ? anchoredScrollTopAfterRender - scrollTopAfterLayout : 0;
970
+ let postRenderScrollTop = targetScrollTop;
971
+ let shouldCheckPendingTargetSettled = false;
972
+ if (this.pendingScrollTarget != null) {
973
+ const pendingTargetScrollTop = this.advanceScrollAnimation(timestamp, postRenderAnchorDelta);
974
+ if (pendingTargetScrollTop != null) {
975
+ postRenderScrollTop = pendingTargetScrollTop;
976
+ shouldCheckPendingTargetSettled = true;
977
+ } else postRenderScrollTop = scrollTopAfterLayout;
978
+ } else postRenderScrollTop = anchoredScrollTopAfterRender ?? targetScrollTop;
979
+ if (postRenderScrollTop !== syncedScrollTop) {
980
+ this.applyScrollFix(postRenderScrollTop, syncedScrollTop, this.windowSpecs);
981
+ syncedScrollTop = postRenderScrollTop;
982
+ }
983
+ if (shouldCheckPendingTargetSettled && this.pendingScrollTarget != null && this.isPendingTargetSettled(this.pendingScrollTarget)) {
984
+ this.pendingScrollTarget = void 0;
985
+ this.scrollAnimation = void 0;
890
986
  }
891
- this.renderState.scrollTop = roundToDevicePixel(renderedScrollTop);
987
+ this.renderState.scrollTop = roundToDevicePixel(syncedScrollTop);
892
988
  this.flushManagers(updatedItems);
893
989
  if (fitPerfectly || this.scrollAnimation != null) this.render();
894
990
  };
@@ -896,10 +992,43 @@ var CodeView = class CodeView {
896
992
  for (const item of updatedItems) item.instance.flushManagers();
897
993
  }
898
994
  syncContainerHeight() {
899
- const totalScrollHeight = this.getScrollHeight();
900
- if (this.container == null || this.containerHeight === totalScrollHeight) return;
901
- this.container.style.height = `${totalScrollHeight}px`;
902
- this.containerHeight = totalScrollHeight;
995
+ const pagedScrollHeight = this.getPagedScrollHeight();
996
+ if (this.container == null || this.containerHeight === pagedScrollHeight) return;
997
+ this.container.style.height = `${pagedScrollHeight}px`;
998
+ this.containerHeight = pagedScrollHeight;
999
+ }
1000
+ getStickyBounds(windowSpecs) {
1001
+ const { firstIndex, lastIndex } = windowSpecs != null ? {
1002
+ firstIndex: this.findFirstVisibleIndex(windowSpecs.top),
1003
+ lastIndex: this.findLastVisibleIndex(windowSpecs.bottom)
1004
+ } : this.renderState;
1005
+ if (firstIndex === -1 || lastIndex === -1 || firstIndex > lastIndex) return;
1006
+ const firstStickySpecs = this.items[firstIndex]?.instance.getAdvancedStickySpecs(windowSpecs);
1007
+ const lastStickySpecs = this.items[lastIndex]?.instance.getAdvancedStickySpecs(windowSpecs);
1008
+ if (firstStickySpecs == null || lastStickySpecs == null) return;
1009
+ return {
1010
+ stickyTop: this.getPagedLayoutTop(Math.max(firstStickySpecs.topOffset, 0)),
1011
+ stickyBottom: this.getPagedLayoutTop(lastStickySpecs.topOffset + lastStickySpecs.height)
1012
+ };
1013
+ }
1014
+ applyStickyPositioning({ stickyTop, stickyBottom }) {
1015
+ const height = this.getHeight();
1016
+ const itemMetrics = this.getItemMetrics();
1017
+ const stickyContainerHeight = stickyBottom - stickyTop;
1018
+ this.renderState.stickyHeight = stickyContainerHeight;
1019
+ this.renderState.stickyTop = stickyTop;
1020
+ this.renderState.stickyBottom = stickyBottom;
1021
+ this.stickyOffset.style.height = `${stickyTop}px`;
1022
+ const randomOffset = (Math.random() * itemMetrics.lineHeight >> 0) * -1;
1023
+ const stickyJitter = -Math.max(stickyContainerHeight + randomOffset, 0) + height;
1024
+ this.stickyContainer.style.top = `${stickyJitter}px`;
1025
+ this.stickyContainer.style.bottom = `${stickyJitter + itemMetrics.diffHeaderHeight}px`;
1026
+ }
1027
+ syncPagedScrollScaffolding(windowSpecs) {
1028
+ this.syncContainerHeight();
1029
+ const stickyBounds = this.getStickyBounds(windowSpecs);
1030
+ if (stickyBounds == null) return;
1031
+ this.applyStickyPositioning(stickyBounds);
903
1032
  }
904
1033
  reconcileRenderedItems(updatedItems) {
905
1034
  const { firstIndex, lastIndex } = this.renderState;
@@ -931,24 +1060,11 @@ var CodeView = class CodeView {
931
1060
  }
932
1061
  }
933
1062
  updateStickyPositioning() {
934
- const { firstIndex, lastIndex } = this.renderState;
935
- const firstStickySpecs = this.items[firstIndex]?.instance.getAdvancedStickySpecs();
936
- const lastStickySpecs = this.items[lastIndex]?.instance.getAdvancedStickySpecs();
937
- if (firstStickySpecs == null || lastStickySpecs == null) return;
938
- const height = this.getHeight();
939
- const itemMetrics = this.getItemMetrics();
940
- const stickyTop = Math.max(firstStickySpecs.topOffset, 0);
941
- const stickyBottom = lastStickySpecs.topOffset + lastStickySpecs.height;
942
- const stickyContainerHeight = stickyBottom - stickyTop;
943
- if (stickyContainerHeight === this.renderState.stickyHeight && stickyTop === this.renderState.stickyTop && stickyBottom === this.renderState.stickyBottom) return;
944
- this.renderState.stickyHeight = stickyContainerHeight;
945
- this.renderState.stickyTop = stickyTop;
946
- this.renderState.stickyBottom = stickyBottom;
947
- this.stickyOffset.style.height = `${stickyTop}px`;
948
- const randomOffset = (Math.random() * itemMetrics.lineHeight >> 0) * -1;
949
- const stickyJitter = -Math.max(stickyContainerHeight + randomOffset, 0) + height;
950
- this.stickyContainer.style.top = `${stickyJitter}px`;
951
- this.stickyContainer.style.bottom = `${stickyJitter + itemMetrics.diffHeaderHeight}px`;
1063
+ const stickyBounds = this.getStickyBounds();
1064
+ if (stickyBounds == null) return;
1065
+ const { stickyTop, stickyBottom } = stickyBounds;
1066
+ if (stickyBottom - stickyTop === this.renderState.stickyHeight && stickyTop === this.renderState.stickyTop && stickyBottom === this.renderState.stickyBottom) return;
1067
+ this.applyStickyPositioning(stickyBounds);
952
1068
  }
953
1069
  handleScroll = () => {
954
1070
  if (CodeView.__STOP) return;
@@ -972,7 +1088,7 @@ var CodeView = class CodeView {
972
1088
  const anchoredScrollTop = anchor != null ? this.resolveAnchoredScrollTop(anchor) : void 0;
973
1089
  if (anchoredScrollTop != null) {
974
1090
  const resizeAnchorDelta = anchoredScrollTop - currentScrollTop;
975
- this.applyScrollFix(anchoredScrollTop, currentScrollTop);
1091
+ this.applyScrollFix(anchoredScrollTop, currentScrollTop, this.windowSpecs);
976
1092
  if (this.scrollAnimation != null) this.scrollAnimation.position += resizeAnchorDelta;
977
1093
  }
978
1094
  if (this.pendingScrollTarget != null && this.isPendingTargetSettled(this.pendingScrollTarget)) {
@@ -1053,20 +1169,29 @@ var CodeView = class CodeView {
1053
1169
  }
1054
1170
  /**
1055
1171
  * Apply a device-pixel-rounded scroll position if it differs from the last
1056
- * rendered/applied scrollTop we've already recorded in renderState.
1172
+ * logical scrollTop synchronized into the paged scroll scaffold.
1057
1173
  */
1058
- applyScrollFix(target, currentScrollTop) {
1174
+ applyScrollFix(targetScrollTop, syncedScrollTop, windowSpecs) {
1059
1175
  if (this.root == null) return;
1060
- const rounded = roundToDevicePixel(this.clampScrollTop(target));
1061
- const roundedCurrentScrollTop = roundToDevicePixel(currentScrollTop ?? this.scrollTop);
1062
- if (rounded === this.renderState.scrollTop && rounded === roundedCurrentScrollTop) return;
1176
+ const roundedTargetScrollTop = roundToDevicePixel(this.clampScrollTop(targetScrollTop));
1177
+ const roundedSyncedScrollTop = roundToDevicePixel(syncedScrollTop);
1178
+ const { scrollPageOffset: previousPageOffset } = this;
1179
+ const syncedPagedScrollTop = roundToDevicePixel(this.clampPagedScrollTop(roundedSyncedScrollTop - previousPageOffset));
1180
+ const { pagedScrollTop, scrollPageOffset } = this.resolvePagedScrollPosition(roundedTargetScrollTop);
1181
+ const targetPagedScrollTop = pagedScrollTop;
1182
+ const rebaseChanged = previousPageOffset !== scrollPageOffset;
1183
+ if (roundedTargetScrollTop === this.renderState.scrollTop && roundedTargetScrollTop === roundedSyncedScrollTop && targetPagedScrollTop === syncedPagedScrollTop && !rebaseChanged) return;
1063
1184
  this.suspendPointerEvents();
1064
- if (rounded !== roundedCurrentScrollTop) this.root.scrollTo({
1065
- top: rounded,
1185
+ if (targetPagedScrollTop !== syncedPagedScrollTop || rebaseChanged) {
1186
+ this.scrollPageOffset = scrollPageOffset;
1187
+ this.syncPagedScrollScaffolding(windowSpecs);
1188
+ }
1189
+ if (targetPagedScrollTop !== syncedPagedScrollTop) this.root.scrollTo({
1190
+ top: targetPagedScrollTop,
1066
1191
  behavior: "instant"
1067
1192
  });
1068
- this.renderState.scrollTop = rounded;
1069
- this.scrollTop = rounded;
1193
+ this.renderState.scrollTop = roundedTargetScrollTop;
1194
+ this.scrollTop = roundedTargetScrollTop;
1070
1195
  this.scrollDirty = false;
1071
1196
  }
1072
1197
  /**
@@ -1081,7 +1206,8 @@ var CodeView = class CodeView {
1081
1206
  getScrollTop() {
1082
1207
  if (!this.scrollDirty) return this.scrollTop;
1083
1208
  this.scrollDirty = false;
1084
- this.scrollTop = this.clampScrollTop(this.root?.scrollTop ?? 0);
1209
+ const rootScrollTop = this.root?.scrollTop ?? 0;
1210
+ this.scrollTop = this.clampScrollTop(rootScrollTop + this.scrollPageOffset);
1085
1211
  return this.scrollTop;
1086
1212
  }
1087
1213
  getHeight() {