Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<button id="diff-files">Diff Two Files</button>
<button id="load-diff">Load Large-ish Diff</button>
<button id="worker-load-diff">Worker Render Diff</button>
<button id="clean">Clean</button>
<label>
<input id="unified" type="checkbox" />
Unified Diffs
Expand Down
9 changes: 9 additions & 0 deletions apps/demo/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,15 @@ function getThemeType() {
: 'system';
}

const cleanButton = document.getElementById('clean');
cleanButton?.addEventListener('click', () => {
const container = document.getElementById('wrapper');
if (container == null) {
return;
}
cleanupInstances(container);
});

// For quick testing diffs
// FAKE_DIFF_LINE_ANNOTATIONS.length = 0;
// (() => {
Expand Down
11 changes: 9 additions & 2 deletions packages/diffs/src/components/VirtualizedFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export class VirtualizedFile<
// Compute the approximate size of the file using cached line heights.
// Uses lineHeight for lines without cached measurements.
private computeApproximateSize(): void {
const isFirstCompute = this.height === 0;
this.height = 0;
if (this.file == null) {
return;
Expand Down Expand Up @@ -189,7 +190,11 @@ export class VirtualizedFile<
this.height += fileGap;
}

if (this.fileContainer != null && this.virtualizer.config.resizeDebugging) {
if (
this.fileContainer != null &&
this.virtualizer.config.resizeDebugging &&
!isFirstCompute
) {
const rect = this.fileContainer.getBoundingClientRect();
if (rect.height !== this.height) {
console.log(
Expand Down Expand Up @@ -241,14 +246,16 @@ export class VirtualizedFile<
return false;
}

this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
if (isFirstRender) {
this.computeApproximateSize();
this.virtualizer.connect(fileContainer, this);
this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
this.isVisible = this.virtualizer.isInstanceVisible(
this.top,
this.height
);
} else {
this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
}

if (!this.isVisible) {
Expand Down
13 changes: 10 additions & 3 deletions packages/diffs/src/components/VirtualizedFileDiff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ export class VirtualizedFileDiff<
if (this.fileContainer == null) {
return;
}
this.renderRange = undefined;
if (visible && !this.isVisible) {
this.top = this.virtualizer.getOffsetInScrollContainer(
this.fileContainer
Expand All @@ -222,6 +223,7 @@ export class VirtualizedFileDiff<
// dynamically change for a number of reasons so we can never be fully sure
// if the height is 100% accurate
private computeApproximateSize(): void {
const isFirstCompute = this.height === 0;
this.height = 0;
if (this.fileDiff == null) {
return;
Expand Down Expand Up @@ -296,7 +298,11 @@ export class VirtualizedFileDiff<
this.height += fileGap;
}

if (this.fileContainer != null && this.virtualizer.config.resizeDebugging) {
if (
this.fileContainer != null &&
this.virtualizer.config.resizeDebugging &&
!isFirstCompute
) {
const rect = this.fileContainer.getBoundingClientRect();
if (rect.height !== this.height) {
console.log(
Expand Down Expand Up @@ -345,15 +351,16 @@ export class VirtualizedFileDiff<
return false;
}

this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
if (isFirstRender) {
this.computeApproximateSize();
// Figure out how to properly manage this...
this.virtualizer.connect(fileContainer, this);
this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
this.isVisible = this.virtualizer.isInstanceVisible(
this.top,
this.height
);
} else {
this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
}

if (!this.isVisible) {
Expand Down
33 changes: 28 additions & 5 deletions packages/diffs/src/components/Virtualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,22 @@ const DEFAULT_VIRTUALIZER_CONFIG: VirtualizerConfig = {

let lastSize = 0;

let instance = -1;

export class Virtualizer {
static __STOP: boolean = false;
static __lastScrollPosition = 0;

public type = 'basic';
public readonly __id: string = `virtualizer-${++instance}`;
public readonly config: VirtualizerConfig;
public type = 'basic';
private intersectionObserver: IntersectionObserver | undefined;
private scrollTop: number = 0;
private height: number = 0;
private scrollHeight: number = 0;
private windowSpecs: VirtualWindowSpecs = { top: 0, bottom: 0 };
private root: HTMLElement | Document | undefined;
private contentContainer: HTMLElement | undefined;

private resizeObserver: ResizeObserver | undefined;
private observers: Map<HTMLElement, SubscribedInstance> = new Map();
Expand Down Expand Up @@ -146,7 +150,7 @@ export class Virtualizer {
this.scrollHeightDirty = true;
shouldQueueUpdate = true;
if (this.config.resizeDebugging) {
console.log('Virtualizer: content size change', {
console.log('Virtualizer: content size change', this.__id, {
sizeChange: blockSize - lastSize,
newSize: blockSize,
});
Expand All @@ -159,11 +163,11 @@ export class Virtualizer {
this.heightDirty = true;
shouldQueueUpdate = true;
}
} else if (entry.target === this.root.firstElementChild) {
} else if (entry.target === this.contentContainer) {
this.scrollHeightDirty = true;
shouldQueueUpdate = true;
if (this.config.resizeDebugging) {
console.log('Virtualizer: scroller size change', {
console.log('Virtualizer: scroller size change', this.__id, {
sizeChange: blockSize - lastSize,
newSize: blockSize,
});
Expand Down Expand Up @@ -200,15 +204,31 @@ export class Virtualizer {
});
this.resizeObserver?.observe(this.root);
contentContainer ??= this.root.firstElementChild ?? undefined;
if (contentContainer != null) {
if (contentContainer instanceof HTMLElement) {
this.contentContainer = contentContainer;
this.resizeObserver?.observe(contentContainer);
}
}

cleanUp(): void {
this.resizeObserver?.disconnect();
this.resizeObserver = undefined;
this.intersectionObserver?.disconnect();
this.intersectionObserver = undefined;
this.root?.removeEventListener('scroll', this.handleElementScroll);
window.removeEventListener('scroll', this.handleWindowScroll);
window.removeEventListener('resize', this.handleWindowResize);
this.root = undefined;
this.contentContainer = undefined;
this.observers.clear();
this.visibleInstances.clear();
this.instancesChanged.clear();
this.connectQueue.clear();
this.visibleInstancesDirty = false;
this.windowSpecs = { top: 0, bottom: 0 };
this.scrollTop = 0;
this.height = 0;
this.scrollHeight = 0;
}

getOffsetInScrollContainer(element: HTMLElement): number {
Expand Down Expand Up @@ -245,6 +265,9 @@ export class Virtualizer {
}
this.intersectionObserver?.unobserve(container);
this.observers.delete(container);
if (this.visibleInstances.delete(container)) {
this.visibleInstancesDirty = true;
}
this.markDOMDirty();
queueRender(this.computeRenderRangeAndEmit);
}
Expand Down
14 changes: 10 additions & 4 deletions packages/diffs/src/utils/createWindowFromScrollPosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ export function createWindowFromScrollPosition({
overscrollSize,
}: WindowFromScrollPositionProps): VirtualWindowSpecs {
const windowHeight = height + overscrollSize * 2;
if (windowHeight > scrollHeight || fitPerfectly) {
const effectiveHeight = fitPerfectly ? height : windowHeight;
scrollHeight = Math.max(scrollHeight, effectiveHeight);

if (windowHeight >= scrollHeight || fitPerfectly) {
const top = Math.max(scrollTop - containerOffset, 0);
const bottom =
Math.min(scrollTop + effectiveHeight, scrollHeight) - containerOffset;
return {
top: Math.max(scrollTop - containerOffset, 0),
bottom:
scrollTop + (fitPerfectly ? height : windowHeight) - containerOffset,
top,
bottom: Math.max(bottom, top),
};
}

const scrollCenter = scrollTop + height / 2;
let top = scrollCenter - windowHeight / 2;
let bottom = top + windowHeight;
Expand Down