@pierre/diffs 1.1.0-beta.11 → 1.1.0-beta.12
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/components/File.d.ts.map +1 -1
- package/dist/components/VirtualizedFile.js +4 -3
- package/dist/components/VirtualizedFile.js.map +1 -1
- package/dist/components/VirtualizedFileDiff.js +5 -3
- package/dist/components/VirtualizedFileDiff.js.map +1 -1
- package/dist/components/Virtualizer.d.ts +3 -1
- package/dist/components/Virtualizer.d.ts.map +1 -1
- package/dist/components/Virtualizer.js +49 -24
- package/dist/components/Virtualizer.js.map +1 -1
- package/dist/react/WorkerPoolContext.d.ts.map +1 -1
- package/dist/utils/createWindowFromScrollPosition.js +10 -4
- package/dist/utils/createWindowFromScrollPosition.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"File.d.ts","names":["LineSelectionManager","LineSelectionOptions","SelectedLineRange","GetHoveredLineResult","MouseEventManager","MouseEventManagerBaseOptions","ResizeManager","FileRenderer","BaseCodeOptions","FileContents","LineAnnotation","PrePropertiesConfig","RenderFileMetadata","RenderRange","ThemeTypes","WorkerPoolManager","FileRenderProps","LAnnotation","HTMLElement","FileHyrdateProps","Omit","FileOptions","AnnotationElementCache","File","SVGElement","HTMLPreElement","HTMLStyleElement","Map","file","fileContainer","forceRender","containerWrapper","lineAnnotations","renderRange"],"sources":["../../src/components/File.d.ts"],"sourcesContent":["import { LineSelectionManager, type LineSelectionOptions, type SelectedLineRange } from '../managers/LineSelectionManager';\nimport { type GetHoveredLineResult, MouseEventManager, type MouseEventManagerBaseOptions } from '../managers/MouseEventManager';\nimport { ResizeManager } from '../managers/ResizeManager';\nimport { FileRenderer } from '../renderers/FileRenderer';\nimport type { BaseCodeOptions, FileContents, LineAnnotation, PrePropertiesConfig, RenderFileMetadata, RenderRange, ThemeTypes } from '../types';\nimport type { WorkerPoolManager } from '../worker';\nexport interface FileRenderProps<LAnnotation> {\n file: FileContents;\n fileContainer?: HTMLElement;\n containerWrapper?: HTMLElement;\n forceRender?: boolean;\n lineAnnotations?: LineAnnotation<LAnnotation>[];\n renderRange?: RenderRange;\n}\nexport interface FileHyrdateProps<LAnnotation> extends Omit<FileRenderProps<LAnnotation>, 'fileContainer'> {\n fileContainer: HTMLElement;\n prerenderedHTML?: string;\n}\nexport interface FileOptions<LAnnotation> extends BaseCodeOptions, MouseEventManagerBaseOptions<'file'>, LineSelectionOptions {\n disableFileHeader?: boolean;\n renderCustomMetadata?: RenderFileMetadata;\n /**\n * When true, errors during rendering are rethrown instead of being caught\n * and displayed in the DOM. Useful for testing or when you want to handle\n * errors yourself.\n */\n disableErrorHandling?: boolean;\n renderAnnotation?(annotation: LineAnnotation<LAnnotation>): HTMLElement | undefined;\n renderHoverUtility?(getHoveredRow: () => GetHoveredLineResult<'file'> | undefined): HTMLElement | null;\n}\ninterface AnnotationElementCache<LAnnotation> {\n element: HTMLElement;\n annotation: LineAnnotation<LAnnotation>;\n}\nexport declare class File<LAnnotation = undefined> {\n options: FileOptions<LAnnotation>;\n private workerManager?;\n private isContainerManaged;\n static LoadedCustomComponent: boolean;\n readonly __id: string;\n protected fileContainer: HTMLElement | undefined;\n protected spriteSVG: SVGElement | undefined;\n protected pre: HTMLPreElement | undefined;\n protected code: HTMLElement | undefined;\n protected bufferBefore: HTMLElement | undefined;\n protected bufferAfter: HTMLElement | undefined;\n protected unsafeCSSStyle: HTMLStyleElement | undefined;\n protected hoverContent: HTMLElement | undefined;\n protected errorWrapper: HTMLElement | undefined;\n protected placeHolder: HTMLElement | undefined;\n protected lastRenderedHeaderHTML: string | undefined;\n protected appliedPreAttributes: PrePropertiesConfig | undefined;\n protected lastRowCount: number | undefined;\n protected headerElement: HTMLElement | undefined;\n protected headerMetadata: HTMLElement | undefined;\n protected fileRenderer: FileRenderer<LAnnotation>;\n protected resizeManager: ResizeManager;\n protected mouseEventManager: MouseEventManager<'file'>;\n protected lineSelectionManager: LineSelectionManager;\n protected annotationCache: Map<string, AnnotationElementCache<LAnnotation>>;\n protected lineAnnotations: LineAnnotation<LAnnotation>[];\n protected file: FileContents | undefined;\n protected renderRange: RenderRange | undefined;\n constructor(options?: FileOptions<LAnnotation>, workerManager?: WorkerPoolManager | undefined, isContainerManaged?: boolean);\n private handleHighlightRender;\n rerender(): void;\n setOptions(options: FileOptions<LAnnotation> | undefined): void;\n private mergeOptions;\n setThemeType(themeType: ThemeTypes): void;\n getHoveredLine: () => {\n lineNumber: number;\n } | undefined;\n setLineAnnotations(lineAnnotations: LineAnnotation<LAnnotation>[]): void;\n setSelectedLines(range: SelectedLineRange | null): void;\n cleanUp(): void;\n hydrate(props: FileHyrdateProps<LAnnotation>): void;\n getOrCreateLineCache(file?: FileContents | undefined): string[];\n render({ file, fileContainer, forceRender, containerWrapper, lineAnnotations, renderRange }: FileRenderProps<LAnnotation>): boolean;\n private canPartiallyRender;\n renderPlaceholder(height: number): boolean;\n private cleanChildNodes;\n private renderAnnotations;\n private renderHoverUtility;\n private injectUnsafeCSS;\n private applyFullRender;\n private applyPartialRender;\n private getColumns;\n private trimDOMToOverlap;\n private getDOMBoundaryIndices;\n private getLineIndexFromDOMNode;\n private applyBuffers;\n private applyHeaderToDOM;\n protected getOrCreateFileContainerNode(fileContainer?: HTMLElement, parentNode?: HTMLElement): HTMLElement;\n private getOrCreatePreNode;\n private applyPreNodeAttributes;\n private applyErrorToDOM;\n private cleanupErrorWrapper;\n}\nexport {};\n//# sourceMappingURL=File.d.ts.map"],"mappings":";;;;;;;;;UAMiBgB;QACPP;kBACUS;EAFHF,gBAAAA,CAAAA,EAGME,WAHSD;EACtBR,WAAAA,CAAAA,EAAAA,OAAAA;EACUS,eAAAA,CAAAA,EAGER,cAHFQ,CAGiBD,WAHjBC,CAAAA,EAAAA;EACGA,WAAAA,CAAAA,EAGLL,WAHKK;;AAEDR,UAGLS,gBAHKT,CAAAA,WAAAA,CAAAA,SAGiCU,IAHjCV,CAGsCM,eAHtCN,CAGsDO,WAHtDP,CAAAA,EAAAA,eAAAA,CAAAA,CAAAA;EACJG,aAAAA,EAGCK,WAHDL;EAAW,eAAA,CAAA,EAAA,MAAA;AAE7B;AAA4EI,UAI3DI,WAJ2DJ,CAAAA,WAAAA,CAAAA,SAI1BT,eAJ0BS,EAITZ,4BAJSY,CAAAA,MAAAA,CAAAA,EAI6BhB,oBAJ7BgB,CAAAA;EAAhBD,iBAAAA,CAAAA,EAAAA,OAAAA;EACzCE,oBAAAA,CAAAA,EAKQN,kBALRM;EADoCE;;AAIvD;;;EASkCV,oBAAAA,CAAAA,EAAAA,OAAAA;EAA8BQ,gBAAAA,EAAAA,UAAAA,EAA9BR,cAA8BQ,CAAfD,WAAeC,CAAAA,CAAAA,EAAAA,WAAAA,GAAAA,SAAAA;EACnBf,kBAAAA,EAAAA,aAAAA,EAAAA,GAAAA,GAAAA,oBAAAA,CAAAA,MAAAA,CAAAA,GAAAA,SAAAA,CAAAA,EAA2Ce,WAA3Cf,GAAAA,IAAAA;;UAEnCmB,sBAZwCd,CAAAA,WAAAA,CAAAA,CAAAA;EAAiBH,OAAAA,EAatDa,WAbsDb;EAAsCJ,UAAAA,EAczFS,cAdyFT,CAc1EgB,WAd0EhB,CAAAA;;AAY/FqB,cAIWC,IAJXD,CAAAA,cAAsBL,SAAAA,CAAAA,CAAA;EACnBC,OAAAA,EAIAG,WAJAH,CAIYD,WAJZC,CAAAA;EACkBD,QAAAA,aAAAA;EAAfP,QAAAA,kBAAAA;EAAc,OAAA,qBAAA,EAAA,OAAA;EAETa,SAAI,
|
|
1
|
+
{"version":3,"file":"File.d.ts","names":["LineSelectionManager","LineSelectionOptions","SelectedLineRange","GetHoveredLineResult","MouseEventManager","MouseEventManagerBaseOptions","ResizeManager","FileRenderer","BaseCodeOptions","FileContents","LineAnnotation","PrePropertiesConfig","RenderFileMetadata","RenderRange","ThemeTypes","WorkerPoolManager","FileRenderProps","LAnnotation","HTMLElement","FileHyrdateProps","Omit","FileOptions","AnnotationElementCache","File","SVGElement","HTMLPreElement","HTMLStyleElement","Map","file","fileContainer","forceRender","containerWrapper","lineAnnotations","renderRange"],"sources":["../../src/components/File.d.ts"],"sourcesContent":["import { LineSelectionManager, type LineSelectionOptions, type SelectedLineRange } from '../managers/LineSelectionManager';\nimport { type GetHoveredLineResult, MouseEventManager, type MouseEventManagerBaseOptions } from '../managers/MouseEventManager';\nimport { ResizeManager } from '../managers/ResizeManager';\nimport { FileRenderer } from '../renderers/FileRenderer';\nimport type { BaseCodeOptions, FileContents, LineAnnotation, PrePropertiesConfig, RenderFileMetadata, RenderRange, ThemeTypes } from '../types';\nimport type { WorkerPoolManager } from '../worker';\nexport interface FileRenderProps<LAnnotation> {\n file: FileContents;\n fileContainer?: HTMLElement;\n containerWrapper?: HTMLElement;\n forceRender?: boolean;\n lineAnnotations?: LineAnnotation<LAnnotation>[];\n renderRange?: RenderRange;\n}\nexport interface FileHyrdateProps<LAnnotation> extends Omit<FileRenderProps<LAnnotation>, 'fileContainer'> {\n fileContainer: HTMLElement;\n prerenderedHTML?: string;\n}\nexport interface FileOptions<LAnnotation> extends BaseCodeOptions, MouseEventManagerBaseOptions<'file'>, LineSelectionOptions {\n disableFileHeader?: boolean;\n renderCustomMetadata?: RenderFileMetadata;\n /**\n * When true, errors during rendering are rethrown instead of being caught\n * and displayed in the DOM. Useful for testing or when you want to handle\n * errors yourself.\n */\n disableErrorHandling?: boolean;\n renderAnnotation?(annotation: LineAnnotation<LAnnotation>): HTMLElement | undefined;\n renderHoverUtility?(getHoveredRow: () => GetHoveredLineResult<'file'> | undefined): HTMLElement | null;\n}\ninterface AnnotationElementCache<LAnnotation> {\n element: HTMLElement;\n annotation: LineAnnotation<LAnnotation>;\n}\nexport declare class File<LAnnotation = undefined> {\n options: FileOptions<LAnnotation>;\n private workerManager?;\n private isContainerManaged;\n static LoadedCustomComponent: boolean;\n readonly __id: string;\n protected fileContainer: HTMLElement | undefined;\n protected spriteSVG: SVGElement | undefined;\n protected pre: HTMLPreElement | undefined;\n protected code: HTMLElement | undefined;\n protected bufferBefore: HTMLElement | undefined;\n protected bufferAfter: HTMLElement | undefined;\n protected unsafeCSSStyle: HTMLStyleElement | undefined;\n protected hoverContent: HTMLElement | undefined;\n protected errorWrapper: HTMLElement | undefined;\n protected placeHolder: HTMLElement | undefined;\n protected lastRenderedHeaderHTML: string | undefined;\n protected appliedPreAttributes: PrePropertiesConfig | undefined;\n protected lastRowCount: number | undefined;\n protected headerElement: HTMLElement | undefined;\n protected headerMetadata: HTMLElement | undefined;\n protected fileRenderer: FileRenderer<LAnnotation>;\n protected resizeManager: ResizeManager;\n protected mouseEventManager: MouseEventManager<'file'>;\n protected lineSelectionManager: LineSelectionManager;\n protected annotationCache: Map<string, AnnotationElementCache<LAnnotation>>;\n protected lineAnnotations: LineAnnotation<LAnnotation>[];\n protected file: FileContents | undefined;\n protected renderRange: RenderRange | undefined;\n constructor(options?: FileOptions<LAnnotation>, workerManager?: WorkerPoolManager | undefined, isContainerManaged?: boolean);\n private handleHighlightRender;\n rerender(): void;\n setOptions(options: FileOptions<LAnnotation> | undefined): void;\n private mergeOptions;\n setThemeType(themeType: ThemeTypes): void;\n getHoveredLine: () => {\n lineNumber: number;\n } | undefined;\n setLineAnnotations(lineAnnotations: LineAnnotation<LAnnotation>[]): void;\n setSelectedLines(range: SelectedLineRange | null): void;\n cleanUp(): void;\n hydrate(props: FileHyrdateProps<LAnnotation>): void;\n getOrCreateLineCache(file?: FileContents | undefined): string[];\n render({ file, fileContainer, forceRender, containerWrapper, lineAnnotations, renderRange }: FileRenderProps<LAnnotation>): boolean;\n private canPartiallyRender;\n renderPlaceholder(height: number): boolean;\n private cleanChildNodes;\n private renderAnnotations;\n private renderHoverUtility;\n private injectUnsafeCSS;\n private applyFullRender;\n private applyPartialRender;\n private getColumns;\n private trimDOMToOverlap;\n private getDOMBoundaryIndices;\n private getLineIndexFromDOMNode;\n private applyBuffers;\n private applyHeaderToDOM;\n protected getOrCreateFileContainerNode(fileContainer?: HTMLElement, parentNode?: HTMLElement): HTMLElement;\n private getOrCreatePreNode;\n private applyPreNodeAttributes;\n private applyErrorToDOM;\n private cleanupErrorWrapper;\n}\nexport {};\n//# sourceMappingURL=File.d.ts.map"],"mappings":"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"}
|
|
@@ -73,6 +73,7 @@ var VirtualizedFile = class extends File {
|
|
|
73
73
|
super.cleanUp();
|
|
74
74
|
}
|
|
75
75
|
computeApproximateSize() {
|
|
76
|
+
const isFirstCompute = this.height === 0;
|
|
76
77
|
this.height = 0;
|
|
77
78
|
if (this.file == null) return;
|
|
78
79
|
const { disableFileHeader = false, overflow = "scroll" } = this.options;
|
|
@@ -88,7 +89,7 @@ var VirtualizedFile = class extends File {
|
|
|
88
89
|
}
|
|
89
90
|
});
|
|
90
91
|
if (lines.length > 0) this.height += fileGap;
|
|
91
|
-
if (this.fileContainer != null && this.virtualizer.config.resizeDebugging) {
|
|
92
|
+
if (this.fileContainer != null && this.virtualizer.config.resizeDebugging && !isFirstCompute) {
|
|
92
93
|
const rect = this.fileContainer.getBoundingClientRect();
|
|
93
94
|
if (rect.height !== this.height) console.log("VirtualizedFile.computeApproximateSize: computed height doesnt match", {
|
|
94
95
|
name: this.file.name,
|
|
@@ -116,12 +117,12 @@ var VirtualizedFile = class extends File {
|
|
|
116
117
|
console.error("VirtualizedFile.render: attempting to virtually render when we dont have file");
|
|
117
118
|
return false;
|
|
118
119
|
}
|
|
119
|
-
this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
|
|
120
120
|
if (isFirstRender) {
|
|
121
121
|
this.computeApproximateSize();
|
|
122
122
|
this.virtualizer.connect(fileContainer, this);
|
|
123
|
+
this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
|
|
123
124
|
this.isVisible = this.virtualizer.isInstanceVisible(this.top, this.height);
|
|
124
|
-
}
|
|
125
|
+
} else this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
|
|
125
126
|
if (!this.isVisible) return this.renderPlaceholder(this.height);
|
|
126
127
|
const windowSpecs = this.virtualizer.getWindowSpecs();
|
|
127
128
|
const renderRange = this.computeRenderRangeFromWindow(this.file, this.top, windowSpecs);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VirtualizedFile.js","names":["virtualizer: Virtualizer","metrics: VirtualFileMetrics","idealStartHunk","startingLine","clampedTotalLines","bufferBefore","hunkOffsets: number[]","firstVisibleHunk: number | undefined","centerHunk: number | undefined","overflowCounter: number | undefined","lineHeight"],"sources":["../../src/components/VirtualizedFile.ts"],"sourcesContent":["import { DEFAULT_VIRTUAL_FILE_METRICS } from '../constants';\nimport type {\n FileContents,\n RenderRange,\n RenderWindow,\n VirtualFileMetrics,\n} from '../types';\nimport { iterateOverFile } from '../utils/iterateOverFile';\nimport type { WorkerPoolManager } from '../worker';\nimport { File, type FileOptions, type FileRenderProps } from './File';\nimport type { Virtualizer } from './Virtualizer';\n\nlet instanceId = -1;\n\nexport class VirtualizedFile<\n LAnnotation = undefined,\n> extends File<LAnnotation> {\n override readonly __id: string = `virtualized-file:${++instanceId}`;\n\n public top: number | undefined;\n public height: number = 0;\n // Sparse map: line index -> measured height\n // Only stores lines that differ from what is returned from default line\n // height\n private heightCache: Map<number, number> = new Map();\n private isVisible: boolean = false;\n\n constructor(\n options: FileOptions<LAnnotation> | undefined,\n private virtualizer: Virtualizer,\n private metrics: VirtualFileMetrics = DEFAULT_VIRTUAL_FILE_METRICS,\n workerManager?: WorkerPoolManager,\n isContainerManaged = false\n ) {\n super(options, workerManager, isContainerManaged);\n }\n\n // Get the height for a line, using cached value if available.\n // If not cached and hasMetadataLine is true, adds lineHeight for the\n // metadata.\n public getLineHeight(lineIndex: number, hasMetadataLine = false): number {\n const cached = this.heightCache.get(lineIndex);\n if (cached != null) {\n return cached;\n }\n const multiplier = hasMetadataLine ? 2 : 1;\n return this.metrics.lineHeight * multiplier;\n }\n\n // Override setOptions to clear height cache when overflow changes\n override setOptions(options: FileOptions<LAnnotation> | undefined): void {\n if (options == null) return;\n const previousOverflow = this.options.overflow;\n\n super.setOptions(options);\n\n if (previousOverflow !== this.options.overflow) {\n this.heightCache.clear();\n this.computeApproximateSize();\n this.renderRange = undefined;\n }\n this.virtualizer.instanceChanged(this);\n }\n\n // Measure rendered lines and update height cache.\n // Called after render to reconcile estimated vs actual heights.\n public reconcileHeights(): void {\n if (this.fileContainer == null || this.file == null) {\n this.height = 0;\n return;\n }\n const { overflow = 'scroll' } = this.options;\n this.top = this.virtualizer.getOffsetInScrollContainer(this.fileContainer);\n\n // If the file has no annotations and we are using the scroll variant, then\n // we can probably skip everything\n if (\n overflow === 'scroll' &&\n this.lineAnnotations.length === 0 &&\n !this.virtualizer.config.resizeDebugging\n ) {\n return;\n }\n\n let hasLineHeightChange = false;\n\n // Single code element (no split mode)\n if (this.code == null) return;\n const content = this.code.children[1]; // Content column (gutter is [0])\n if (!(content instanceof HTMLElement)) return;\n\n for (const line of content.children) {\n if (!(line instanceof HTMLElement)) continue;\n\n const lineIndexAttr = line.dataset.lineIndex;\n if (lineIndexAttr == null) continue;\n\n const lineIndex = Number(lineIndexAttr);\n let measuredHeight = line.getBoundingClientRect().height;\n let hasMetadata = false;\n\n // Annotations or noNewline metadata increase the size of their attached line\n if (\n line.nextElementSibling instanceof HTMLElement &&\n ('lineAnnotation' in line.nextElementSibling.dataset ||\n 'noNewline' in line.nextElementSibling.dataset)\n ) {\n if ('noNewline' in line.nextElementSibling.dataset) {\n hasMetadata = true;\n }\n measuredHeight +=\n line.nextElementSibling.getBoundingClientRect().height;\n }\n\n const expectedHeight = this.getLineHeight(lineIndex, hasMetadata);\n\n if (measuredHeight === expectedHeight) {\n continue;\n }\n\n hasLineHeightChange = true;\n // Line is back to standard height (e.g., after window resize)\n // Remove from cache\n if (measuredHeight === this.metrics.lineHeight * (hasMetadata ? 2 : 1)) {\n this.heightCache.delete(lineIndex);\n }\n // Non-standard height, cache it\n else {\n this.heightCache.set(lineIndex, measuredHeight);\n }\n }\n\n if (hasLineHeightChange || this.virtualizer.config.resizeDebugging) {\n this.computeApproximateSize();\n }\n }\n\n public onRender = (dirty: boolean): boolean => {\n if (this.fileContainer == null || this.file == null) {\n return false;\n }\n if (dirty) {\n this.top = this.virtualizer.getOffsetInScrollContainer(\n this.fileContainer\n );\n }\n return this.render({ file: this.file });\n };\n\n override cleanUp(): void {\n if (this.fileContainer != null) {\n this.virtualizer.disconnect(this.fileContainer);\n }\n super.cleanUp();\n }\n\n // Compute the approximate size of the file using cached line heights.\n // Uses lineHeight for lines without cached measurements.\n private computeApproximateSize(): void {\n this.height = 0;\n if (this.file == null) {\n return;\n }\n\n const { disableFileHeader = false, overflow = 'scroll' } = this.options;\n const { diffHeaderHeight, fileGap, lineHeight } = this.metrics;\n const lines = this.getOrCreateLineCache(this.file);\n\n // Header or initial padding\n if (!disableFileHeader) {\n this.height += diffHeaderHeight;\n } else {\n this.height += fileGap;\n }\n\n if (overflow === 'scroll' && this.lineAnnotations.length === 0) {\n this.height += this.getOrCreateLineCache(this.file).length * lineHeight;\n } else {\n iterateOverFile({\n lines,\n callback: ({ lineIndex }) => {\n this.height += this.getLineHeight(lineIndex, false);\n },\n });\n }\n\n // Bottom padding\n if (lines.length > 0) {\n this.height += fileGap;\n }\n\n if (this.fileContainer != null && this.virtualizer.config.resizeDebugging) {\n const rect = this.fileContainer.getBoundingClientRect();\n if (rect.height !== this.height) {\n console.log(\n 'VirtualizedFile.computeApproximateSize: computed height doesnt match',\n {\n name: this.file.name,\n elementHeight: rect.height,\n computedHeight: this.height,\n }\n );\n } else {\n console.log(\n 'VirtualizedFile.computeApproximateSize: computed height IS CORRECT'\n );\n }\n }\n }\n\n public setVisibility(visible: boolean): void {\n if (this.fileContainer == null) {\n return;\n }\n if (visible && !this.isVisible) {\n this.top = this.virtualizer.getOffsetInScrollContainer(\n this.fileContainer\n );\n this.isVisible = true;\n } else if (!visible && this.isVisible) {\n this.isVisible = false;\n this.rerender();\n }\n }\n\n override render({\n fileContainer,\n file,\n ...props\n }: FileRenderProps<LAnnotation>): boolean {\n const isFirstRender = this.fileContainer == null;\n\n this.file ??= file;\n\n fileContainer = this.getOrCreateFileContainerNode(fileContainer);\n\n if (this.file == null) {\n console.error(\n 'VirtualizedFile.render: attempting to virtually render when we dont have file'\n );\n return false;\n }\n\n this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);\n if (isFirstRender) {\n this.computeApproximateSize();\n this.virtualizer.connect(fileContainer, this);\n this.isVisible = this.virtualizer.isInstanceVisible(\n this.top,\n this.height\n );\n }\n\n if (!this.isVisible) {\n return this.renderPlaceholder(this.height);\n }\n\n const windowSpecs = this.virtualizer.getWindowSpecs();\n const renderRange = this.computeRenderRangeFromWindow(\n this.file,\n this.top,\n windowSpecs\n );\n return super.render({\n file: this.file,\n fileContainer,\n renderRange,\n ...props,\n });\n }\n\n private computeRenderRangeFromWindow(\n file: FileContents,\n fileTop: number,\n { top, bottom }: RenderWindow\n ): RenderRange {\n const { disableFileHeader = false, overflow = 'scroll' } = this.options;\n const { diffHeaderHeight, fileGap, hunkLineCount, lineHeight } =\n this.metrics;\n const lines = this.getOrCreateLineCache(file);\n const lineCount = lines.length;\n const fileHeight = this.height;\n const headerRegion = disableFileHeader ? fileGap : diffHeaderHeight;\n\n // File is outside render window\n if (fileTop < top - fileHeight || fileTop > bottom) {\n return {\n startingLine: 0,\n totalLines: 0,\n bufferBefore: 0,\n bufferAfter: fileHeight - headerRegion - fileGap,\n };\n }\n\n // Small file, just render it all\n if (lineCount <= hunkLineCount) {\n return {\n startingLine: 0,\n totalLines: hunkLineCount,\n bufferBefore: 0,\n bufferAfter: 0,\n };\n }\n\n // Calculate totalLines based on viewport size\n const estimatedTargetLines = Math.ceil(\n Math.max(bottom - top, 0) / lineHeight\n );\n const totalLines =\n Math.ceil(estimatedTargetLines / hunkLineCount) * hunkLineCount +\n hunkLineCount * 2;\n const totalHunks = totalLines / hunkLineCount;\n const viewportCenter = (top + bottom) / 2;\n\n // Simple case: overflow scroll with no annotations - pure math!\n if (overflow === 'scroll' && this.lineAnnotations.length === 0) {\n // Find which line is at viewport center\n const centerLine = Math.floor(\n (viewportCenter - (fileTop + headerRegion)) / lineHeight\n );\n const centerHunk = Math.floor(centerLine / hunkLineCount);\n\n // Calculate ideal start centered around viewport\n const idealStartHunk = centerHunk - Math.floor(totalHunks / 2);\n const totalHunksInFile = Math.ceil(lineCount / hunkLineCount);\n const startingLine =\n Math.max(0, Math.min(idealStartHunk, totalHunksInFile)) * hunkLineCount;\n\n const clampedTotalLines =\n idealStartHunk < 0\n ? totalLines + idealStartHunk * hunkLineCount\n : totalLines;\n\n const bufferBefore = startingLine * lineHeight;\n const renderedLines = Math.min(\n clampedTotalLines,\n lineCount - startingLine\n );\n const bufferAfter = Math.max(\n 0,\n (lineCount - startingLine - renderedLines) * lineHeight\n );\n\n return {\n startingLine,\n totalLines: clampedTotalLines,\n bufferBefore,\n bufferAfter,\n };\n }\n\n // Complex case: need to account for line annotations or wrap overflow\n const overflowHunks = totalHunks;\n const hunkOffsets: number[] = [];\n\n let absoluteLineTop = fileTop + headerRegion;\n let currentLine = 0;\n let firstVisibleHunk: number | undefined;\n let centerHunk: number | undefined;\n let overflowCounter: number | undefined;\n\n iterateOverFile({\n lines,\n callback: ({ lineIndex }) => {\n const isAtHunkBoundary = currentLine % hunkLineCount === 0;\n\n if (isAtHunkBoundary) {\n hunkOffsets.push(absoluteLineTop - (fileTop + headerRegion));\n\n if (overflowCounter != null) {\n if (overflowCounter <= 0) {\n return true;\n }\n overflowCounter--;\n }\n }\n\n const lineHeight = this.getLineHeight(lineIndex, false);\n const currentHunk = Math.floor(currentLine / hunkLineCount);\n\n // Track visible region\n if (absoluteLineTop > top - lineHeight && absoluteLineTop < bottom) {\n firstVisibleHunk ??= currentHunk;\n }\n\n // Track which hunk contains the viewport center\n if (absoluteLineTop + lineHeight > viewportCenter) {\n centerHunk ??= currentHunk;\n }\n\n // Start overflow when we are out of the viewport at a hunk boundary\n if (\n overflowCounter == null &&\n absoluteLineTop >= bottom &&\n isAtHunkBoundary\n ) {\n overflowCounter = overflowHunks;\n }\n\n currentLine++;\n absoluteLineTop += lineHeight;\n\n return false;\n },\n });\n\n // No visible lines found\n if (firstVisibleHunk == null) {\n return {\n startingLine: 0,\n totalLines: 0,\n bufferBefore: 0,\n bufferAfter: fileHeight - headerRegion - fileGap,\n };\n }\n\n // Calculate balanced startingLine centered around the viewport center\n const collectedHunks = hunkOffsets.length;\n centerHunk ??= firstVisibleHunk;\n const idealStartHunk = Math.round(centerHunk - totalHunks / 2);\n\n // Clamp startHunk: at the beginning, reduce totalLines; at the end, shift startHunk back\n const maxStartHunk = Math.max(0, collectedHunks - totalHunks);\n const startHunk = Math.max(0, Math.min(idealStartHunk, maxStartHunk));\n const startingLine = startHunk * hunkLineCount;\n\n // If we wanted to start before 0, reduce totalLines by the clamped amount\n const clampedTotalLines =\n idealStartHunk < 0\n ? totalLines + idealStartHunk * hunkLineCount\n : totalLines;\n\n // Use hunkOffsets array for efficient buffer calculations\n const bufferBefore = hunkOffsets[startHunk] ?? 0;\n\n // Calculate bufferAfter\n const finalHunkIndex = startHunk + clampedTotalLines / hunkLineCount;\n const bufferAfter =\n finalHunkIndex < hunkOffsets.length\n ? fileHeight - headerRegion - hunkOffsets[finalHunkIndex] - fileGap\n : fileHeight - (absoluteLineTop - fileTop) - fileGap;\n\n return {\n startingLine,\n totalLines: clampedTotalLines,\n bufferBefore,\n bufferAfter,\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VirtualizedFile.js","names":["virtualizer: Virtualizer","metrics: VirtualFileMetrics","idealStartHunk","startingLine","clampedTotalLines","bufferBefore","hunkOffsets: number[]","firstVisibleHunk: number | undefined","centerHunk: number | undefined","overflowCounter: number | undefined","lineHeight"],"sources":["../../src/components/VirtualizedFile.ts"],"sourcesContent":["import { DEFAULT_VIRTUAL_FILE_METRICS } from '../constants';\nimport type {\n FileContents,\n RenderRange,\n RenderWindow,\n VirtualFileMetrics,\n} from '../types';\nimport { iterateOverFile } from '../utils/iterateOverFile';\nimport type { WorkerPoolManager } from '../worker';\nimport { File, type FileOptions, type FileRenderProps } from './File';\nimport type { Virtualizer } from './Virtualizer';\n\nlet instanceId = -1;\n\nexport class VirtualizedFile<\n LAnnotation = undefined,\n> extends File<LAnnotation> {\n override readonly __id: string = `virtualized-file:${++instanceId}`;\n\n public top: number | undefined;\n public height: number = 0;\n // Sparse map: line index -> measured height\n // Only stores lines that differ from what is returned from default line\n // height\n private heightCache: Map<number, number> = new Map();\n private isVisible: boolean = false;\n\n constructor(\n options: FileOptions<LAnnotation> | undefined,\n private virtualizer: Virtualizer,\n private metrics: VirtualFileMetrics = DEFAULT_VIRTUAL_FILE_METRICS,\n workerManager?: WorkerPoolManager,\n isContainerManaged = false\n ) {\n super(options, workerManager, isContainerManaged);\n }\n\n // Get the height for a line, using cached value if available.\n // If not cached and hasMetadataLine is true, adds lineHeight for the\n // metadata.\n public getLineHeight(lineIndex: number, hasMetadataLine = false): number {\n const cached = this.heightCache.get(lineIndex);\n if (cached != null) {\n return cached;\n }\n const multiplier = hasMetadataLine ? 2 : 1;\n return this.metrics.lineHeight * multiplier;\n }\n\n // Override setOptions to clear height cache when overflow changes\n override setOptions(options: FileOptions<LAnnotation> | undefined): void {\n if (options == null) return;\n const previousOverflow = this.options.overflow;\n\n super.setOptions(options);\n\n if (previousOverflow !== this.options.overflow) {\n this.heightCache.clear();\n this.computeApproximateSize();\n this.renderRange = undefined;\n }\n this.virtualizer.instanceChanged(this);\n }\n\n // Measure rendered lines and update height cache.\n // Called after render to reconcile estimated vs actual heights.\n public reconcileHeights(): void {\n if (this.fileContainer == null || this.file == null) {\n this.height = 0;\n return;\n }\n const { overflow = 'scroll' } = this.options;\n this.top = this.virtualizer.getOffsetInScrollContainer(this.fileContainer);\n\n // If the file has no annotations and we are using the scroll variant, then\n // we can probably skip everything\n if (\n overflow === 'scroll' &&\n this.lineAnnotations.length === 0 &&\n !this.virtualizer.config.resizeDebugging\n ) {\n return;\n }\n\n let hasLineHeightChange = false;\n\n // Single code element (no split mode)\n if (this.code == null) return;\n const content = this.code.children[1]; // Content column (gutter is [0])\n if (!(content instanceof HTMLElement)) return;\n\n for (const line of content.children) {\n if (!(line instanceof HTMLElement)) continue;\n\n const lineIndexAttr = line.dataset.lineIndex;\n if (lineIndexAttr == null) continue;\n\n const lineIndex = Number(lineIndexAttr);\n let measuredHeight = line.getBoundingClientRect().height;\n let hasMetadata = false;\n\n // Annotations or noNewline metadata increase the size of their attached line\n if (\n line.nextElementSibling instanceof HTMLElement &&\n ('lineAnnotation' in line.nextElementSibling.dataset ||\n 'noNewline' in line.nextElementSibling.dataset)\n ) {\n if ('noNewline' in line.nextElementSibling.dataset) {\n hasMetadata = true;\n }\n measuredHeight +=\n line.nextElementSibling.getBoundingClientRect().height;\n }\n\n const expectedHeight = this.getLineHeight(lineIndex, hasMetadata);\n\n if (measuredHeight === expectedHeight) {\n continue;\n }\n\n hasLineHeightChange = true;\n // Line is back to standard height (e.g., after window resize)\n // Remove from cache\n if (measuredHeight === this.metrics.lineHeight * (hasMetadata ? 2 : 1)) {\n this.heightCache.delete(lineIndex);\n }\n // Non-standard height, cache it\n else {\n this.heightCache.set(lineIndex, measuredHeight);\n }\n }\n\n if (hasLineHeightChange || this.virtualizer.config.resizeDebugging) {\n this.computeApproximateSize();\n }\n }\n\n public onRender = (dirty: boolean): boolean => {\n if (this.fileContainer == null || this.file == null) {\n return false;\n }\n if (dirty) {\n this.top = this.virtualizer.getOffsetInScrollContainer(\n this.fileContainer\n );\n }\n return this.render({ file: this.file });\n };\n\n override cleanUp(): void {\n if (this.fileContainer != null) {\n this.virtualizer.disconnect(this.fileContainer);\n }\n super.cleanUp();\n }\n\n // Compute the approximate size of the file using cached line heights.\n // Uses lineHeight for lines without cached measurements.\n private computeApproximateSize(): void {\n const isFirstCompute = this.height === 0;\n this.height = 0;\n if (this.file == null) {\n return;\n }\n\n const { disableFileHeader = false, overflow = 'scroll' } = this.options;\n const { diffHeaderHeight, fileGap, lineHeight } = this.metrics;\n const lines = this.getOrCreateLineCache(this.file);\n\n // Header or initial padding\n if (!disableFileHeader) {\n this.height += diffHeaderHeight;\n } else {\n this.height += fileGap;\n }\n\n if (overflow === 'scroll' && this.lineAnnotations.length === 0) {\n this.height += this.getOrCreateLineCache(this.file).length * lineHeight;\n } else {\n iterateOverFile({\n lines,\n callback: ({ lineIndex }) => {\n this.height += this.getLineHeight(lineIndex, false);\n },\n });\n }\n\n // Bottom padding\n if (lines.length > 0) {\n this.height += fileGap;\n }\n\n if (\n this.fileContainer != null &&\n this.virtualizer.config.resizeDebugging &&\n !isFirstCompute\n ) {\n const rect = this.fileContainer.getBoundingClientRect();\n if (rect.height !== this.height) {\n console.log(\n 'VirtualizedFile.computeApproximateSize: computed height doesnt match',\n {\n name: this.file.name,\n elementHeight: rect.height,\n computedHeight: this.height,\n }\n );\n } else {\n console.log(\n 'VirtualizedFile.computeApproximateSize: computed height IS CORRECT'\n );\n }\n }\n }\n\n public setVisibility(visible: boolean): void {\n if (this.fileContainer == null) {\n return;\n }\n if (visible && !this.isVisible) {\n this.top = this.virtualizer.getOffsetInScrollContainer(\n this.fileContainer\n );\n this.isVisible = true;\n } else if (!visible && this.isVisible) {\n this.isVisible = false;\n this.rerender();\n }\n }\n\n override render({\n fileContainer,\n file,\n ...props\n }: FileRenderProps<LAnnotation>): boolean {\n const isFirstRender = this.fileContainer == null;\n\n this.file ??= file;\n\n fileContainer = this.getOrCreateFileContainerNode(fileContainer);\n\n if (this.file == null) {\n console.error(\n 'VirtualizedFile.render: attempting to virtually render when we dont have file'\n );\n return false;\n }\n\n if (isFirstRender) {\n this.computeApproximateSize();\n this.virtualizer.connect(fileContainer, this);\n this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);\n this.isVisible = this.virtualizer.isInstanceVisible(\n this.top,\n this.height\n );\n } else {\n this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);\n }\n\n if (!this.isVisible) {\n return this.renderPlaceholder(this.height);\n }\n\n const windowSpecs = this.virtualizer.getWindowSpecs();\n const renderRange = this.computeRenderRangeFromWindow(\n this.file,\n this.top,\n windowSpecs\n );\n return super.render({\n file: this.file,\n fileContainer,\n renderRange,\n ...props,\n });\n }\n\n private computeRenderRangeFromWindow(\n file: FileContents,\n fileTop: number,\n { top, bottom }: RenderWindow\n ): RenderRange {\n const { disableFileHeader = false, overflow = 'scroll' } = this.options;\n const { diffHeaderHeight, fileGap, hunkLineCount, lineHeight } =\n this.metrics;\n const lines = this.getOrCreateLineCache(file);\n const lineCount = lines.length;\n const fileHeight = this.height;\n const headerRegion = disableFileHeader ? fileGap : diffHeaderHeight;\n\n // File is outside render window\n if (fileTop < top - fileHeight || fileTop > bottom) {\n return {\n startingLine: 0,\n totalLines: 0,\n bufferBefore: 0,\n bufferAfter: fileHeight - headerRegion - fileGap,\n };\n }\n\n // Small file, just render it all\n if (lineCount <= hunkLineCount) {\n return {\n startingLine: 0,\n totalLines: hunkLineCount,\n bufferBefore: 0,\n bufferAfter: 0,\n };\n }\n\n // Calculate totalLines based on viewport size\n const estimatedTargetLines = Math.ceil(\n Math.max(bottom - top, 0) / lineHeight\n );\n const totalLines =\n Math.ceil(estimatedTargetLines / hunkLineCount) * hunkLineCount +\n hunkLineCount * 2;\n const totalHunks = totalLines / hunkLineCount;\n const viewportCenter = (top + bottom) / 2;\n\n // Simple case: overflow scroll with no annotations - pure math!\n if (overflow === 'scroll' && this.lineAnnotations.length === 0) {\n // Find which line is at viewport center\n const centerLine = Math.floor(\n (viewportCenter - (fileTop + headerRegion)) / lineHeight\n );\n const centerHunk = Math.floor(centerLine / hunkLineCount);\n\n // Calculate ideal start centered around viewport\n const idealStartHunk = centerHunk - Math.floor(totalHunks / 2);\n const totalHunksInFile = Math.ceil(lineCount / hunkLineCount);\n const startingLine =\n Math.max(0, Math.min(idealStartHunk, totalHunksInFile)) * hunkLineCount;\n\n const clampedTotalLines =\n idealStartHunk < 0\n ? totalLines + idealStartHunk * hunkLineCount\n : totalLines;\n\n const bufferBefore = startingLine * lineHeight;\n const renderedLines = Math.min(\n clampedTotalLines,\n lineCount - startingLine\n );\n const bufferAfter = Math.max(\n 0,\n (lineCount - startingLine - renderedLines) * lineHeight\n );\n\n return {\n startingLine,\n totalLines: clampedTotalLines,\n bufferBefore,\n bufferAfter,\n };\n }\n\n // Complex case: need to account for line annotations or wrap overflow\n const overflowHunks = totalHunks;\n const hunkOffsets: number[] = [];\n\n let absoluteLineTop = fileTop + headerRegion;\n let currentLine = 0;\n let firstVisibleHunk: number | undefined;\n let centerHunk: number | undefined;\n let overflowCounter: number | undefined;\n\n iterateOverFile({\n lines,\n callback: ({ lineIndex }) => {\n const isAtHunkBoundary = currentLine % hunkLineCount === 0;\n\n if (isAtHunkBoundary) {\n hunkOffsets.push(absoluteLineTop - (fileTop + headerRegion));\n\n if (overflowCounter != null) {\n if (overflowCounter <= 0) {\n return true;\n }\n overflowCounter--;\n }\n }\n\n const lineHeight = this.getLineHeight(lineIndex, false);\n const currentHunk = Math.floor(currentLine / hunkLineCount);\n\n // Track visible region\n if (absoluteLineTop > top - lineHeight && absoluteLineTop < bottom) {\n firstVisibleHunk ??= currentHunk;\n }\n\n // Track which hunk contains the viewport center\n if (absoluteLineTop + lineHeight > viewportCenter) {\n centerHunk ??= currentHunk;\n }\n\n // Start overflow when we are out of the viewport at a hunk boundary\n if (\n overflowCounter == null &&\n absoluteLineTop >= bottom &&\n isAtHunkBoundary\n ) {\n overflowCounter = overflowHunks;\n }\n\n currentLine++;\n absoluteLineTop += lineHeight;\n\n return false;\n },\n });\n\n // No visible lines found\n if (firstVisibleHunk == null) {\n return {\n startingLine: 0,\n totalLines: 0,\n bufferBefore: 0,\n bufferAfter: fileHeight - headerRegion - fileGap,\n };\n }\n\n // Calculate balanced startingLine centered around the viewport center\n const collectedHunks = hunkOffsets.length;\n centerHunk ??= firstVisibleHunk;\n const idealStartHunk = Math.round(centerHunk - totalHunks / 2);\n\n // Clamp startHunk: at the beginning, reduce totalLines; at the end, shift startHunk back\n const maxStartHunk = Math.max(0, collectedHunks - totalHunks);\n const startHunk = Math.max(0, Math.min(idealStartHunk, maxStartHunk));\n const startingLine = startHunk * hunkLineCount;\n\n // If we wanted to start before 0, reduce totalLines by the clamped amount\n const clampedTotalLines =\n idealStartHunk < 0\n ? totalLines + idealStartHunk * hunkLineCount\n : totalLines;\n\n // Use hunkOffsets array for efficient buffer calculations\n const bufferBefore = hunkOffsets[startHunk] ?? 0;\n\n // Calculate bufferAfter\n const finalHunkIndex = startHunk + clampedTotalLines / hunkLineCount;\n const bufferAfter =\n finalHunkIndex < hunkOffsets.length\n ? fileHeight - headerRegion - hunkOffsets[finalHunkIndex] - fileGap\n : fileHeight - (absoluteLineTop - fileTop) - fileGap;\n\n return {\n startingLine,\n totalLines: clampedTotalLines,\n bufferBefore,\n bufferAfter,\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -90,6 +90,7 @@ var VirtualizedFileDiff = class extends FileDiff {
|
|
|
90
90
|
}
|
|
91
91
|
setVisibility(visible) {
|
|
92
92
|
if (this.fileContainer == null) return;
|
|
93
|
+
this.renderRange = void 0;
|
|
93
94
|
if (visible && !this.isVisible) {
|
|
94
95
|
this.top = this.virtualizer.getOffsetInScrollContainer(this.fileContainer);
|
|
95
96
|
this.isVisible = true;
|
|
@@ -99,6 +100,7 @@ var VirtualizedFileDiff = class extends FileDiff {
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
computeApproximateSize() {
|
|
103
|
+
const isFirstCompute = this.height === 0;
|
|
102
104
|
this.height = 0;
|
|
103
105
|
if (this.fileDiff == null) return;
|
|
104
106
|
const { disableFileHeader = false, expandUnchanged = false, collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD, hunkSeparators = "line-info" } = this.options;
|
|
@@ -125,7 +127,7 @@ var VirtualizedFileDiff = class extends FileDiff {
|
|
|
125
127
|
}
|
|
126
128
|
});
|
|
127
129
|
if (this.fileDiff.hunks.length > 0) this.height += fileGap;
|
|
128
|
-
if (this.fileContainer != null && this.virtualizer.config.resizeDebugging) {
|
|
130
|
+
if (this.fileContainer != null && this.virtualizer.config.resizeDebugging && !isFirstCompute) {
|
|
129
131
|
const rect = this.fileContainer.getBoundingClientRect();
|
|
130
132
|
if (rect.height !== this.height) console.log("VirtualizedFileDiff.computeApproximateSize: computed height doesnt match", {
|
|
131
133
|
name: this.fileDiff.name,
|
|
@@ -143,12 +145,12 @@ var VirtualizedFileDiff = class extends FileDiff {
|
|
|
143
145
|
console.error("VirtualizedFileDiff.render: attempting to virtually render when we dont have the correct data");
|
|
144
146
|
return false;
|
|
145
147
|
}
|
|
146
|
-
this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
|
|
147
148
|
if (isFirstRender) {
|
|
148
149
|
this.computeApproximateSize();
|
|
149
150
|
this.virtualizer.connect(fileContainer, this);
|
|
151
|
+
this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
|
|
150
152
|
this.isVisible = this.virtualizer.isInstanceVisible(this.top, this.height);
|
|
151
|
-
}
|
|
153
|
+
} else this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
|
|
152
154
|
if (!this.isVisible) return this.renderPlaceholder(this.height);
|
|
153
155
|
const windowSpecs = this.virtualizer.getWindowSpecs();
|
|
154
156
|
const renderRange = this.computeRenderRangeFromWindow(this.fileDiff, this.top, windowSpecs);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VirtualizedFileDiff.js","names":["hunkOffsets: number[]","firstVisibleHunk: number | undefined","centerHunk: number | undefined","overflowCounter: number | undefined","lineHeight"],"sources":["../../src/components/VirtualizedFileDiff.ts"],"sourcesContent":["import { DEFAULT_COLLAPSED_CONTEXT_THRESHOLD } from '../constants';\nimport type {\n ExpansionDirections,\n FileDiffMetadata,\n RenderRange,\n RenderWindow,\n VirtualFileMetrics,\n} from '../types';\nimport { iterateOverDiff } from '../utils/iterateOverDiff';\nimport { parseDiffFromFile } from '../utils/parseDiffFromFile';\nimport { resolveVirtualFileMetrics } from '../utils/resolveVirtualFileMetrics';\nimport type { WorkerPoolManager } from '../worker';\nimport {\n FileDiff,\n type FileDiffOptions,\n type FileDiffRenderProps,\n} from './FileDiff';\nimport type { Virtualizer } from './Virtualizer';\n\ninterface ExpandedRegionSpecs {\n fromStart: number;\n fromEnd: number;\n collapsedLines: number;\n renderAll: boolean;\n}\n\nlet instanceId = -1;\n\nexport class VirtualizedFileDiff<\n LAnnotation = undefined,\n> extends FileDiff<LAnnotation> {\n override readonly __id: string = `little-virtualized-file-diff:${++instanceId}`;\n\n public top: number | undefined;\n public height: number = 0;\n private metrics: VirtualFileMetrics;\n // Sparse map: view-specific line index -> measured height\n // Only stores lines that differ what is returned from `getLineHeight`\n private heightCache: Map<number, number> = new Map();\n private isVisible: boolean = false;\n private virtualizer: Virtualizer;\n\n constructor(\n options: FileDiffOptions<LAnnotation> | undefined,\n virtualizer: Virtualizer,\n metrics?: Partial<VirtualFileMetrics>,\n workerManager?: WorkerPoolManager,\n isContainerManaged = false\n ) {\n super(options, workerManager, isContainerManaged);\n const { hunkSeparators = 'line-info' } = this.options;\n this.virtualizer = virtualizer;\n this.metrics = resolveVirtualFileMetrics(\n typeof hunkSeparators === 'function' ? 'custom' : hunkSeparators,\n metrics\n );\n }\n\n // Get the height for a line, using cached value if available.\n // If not cached and hasMetadataLine is true, adds lineHeight for the metadata.\n private getLineHeight(lineIndex: number, hasMetadataLine = false): number {\n const cached = this.heightCache.get(lineIndex);\n if (cached != null) {\n return cached;\n }\n const multiplier = hasMetadataLine ? 2 : 1;\n return this.metrics.lineHeight * multiplier;\n }\n\n // Override setOptions to clear height cache when diffStyle changes\n override setOptions(options: FileDiffOptions<LAnnotation> | undefined): void {\n if (options == null) return;\n const previousDiffStyle = this.options.diffStyle;\n const previousOverflow = this.options.overflow;\n\n super.setOptions(options);\n\n if (\n previousDiffStyle !== this.options.diffStyle ||\n previousOverflow !== this.options.overflow\n ) {\n this.heightCache.clear();\n this.computeApproximateSize();\n this.renderRange = undefined;\n }\n this.virtualizer.instanceChanged(this);\n }\n\n // Measure rendered lines and update height cache.\n // Called after render to reconcile estimated vs actual heights.\n // Definitely need to optimize this in cases where there aren't any custom\n // line heights or in cases of extremely large files...\n public reconcileHeights(): void {\n const { overflow = 'scroll' } = this.options;\n if (this.fileContainer != null) {\n this.top = this.virtualizer.getOffsetInScrollContainer(\n this.fileContainer\n );\n }\n if (this.fileContainer == null || this.fileDiff == null) {\n this.height = 0;\n return;\n }\n // NOTE(amadeus): We can probably be a lot smarter about this, and we\n // should be thinking about ways to improve this\n // If the file has no annotations and we are using the scroll variant, then\n // we can probably skip everything\n if (\n overflow === 'scroll' &&\n this.lineAnnotations.length === 0 &&\n !this.virtualizer.config.resizeDebugging\n ) {\n return;\n }\n const diffStyle = this.getDiffStyle();\n let hasLineHeightChange = false;\n const codeGroups =\n diffStyle === 'split'\n ? [this.codeDeletions, this.codeAdditions]\n : [this.codeUnified];\n\n for (const codeGroup of codeGroups) {\n if (codeGroup == null) continue;\n const content = codeGroup.children[1];\n if (!(content instanceof HTMLElement)) continue;\n for (const line of content.children) {\n if (!(line instanceof HTMLElement)) continue;\n\n const lineIndexAttr = line.dataset.lineIndex;\n if (lineIndexAttr == null) continue;\n\n const lineIndex = parseLineIndex(lineIndexAttr, diffStyle);\n let measuredHeight = line.getBoundingClientRect().height;\n let hasMetadata = false;\n // Annotations or noNewline metadata increase the size of the their\n // attached line\n if (\n line.nextElementSibling instanceof HTMLElement &&\n ('lineAnnotation' in line.nextElementSibling.dataset ||\n 'noNewline' in line.nextElementSibling.dataset)\n ) {\n if ('noNewline' in line.nextElementSibling.dataset) {\n hasMetadata = true;\n }\n measuredHeight +=\n line.nextElementSibling.getBoundingClientRect().height;\n }\n const expectedHeight = this.getLineHeight(lineIndex, hasMetadata);\n\n if (measuredHeight === expectedHeight) {\n continue;\n }\n\n hasLineHeightChange = true;\n // Line is back to standard height (e.g., after window resize)\n // Remove from cache\n if (\n measuredHeight ===\n this.metrics.lineHeight * (hasMetadata ? 2 : 1)\n ) {\n this.heightCache.delete(lineIndex);\n }\n // Non-standard height, cache it\n else {\n this.heightCache.set(lineIndex, measuredHeight);\n }\n }\n }\n\n if (hasLineHeightChange || this.virtualizer.config.resizeDebugging) {\n this.computeApproximateSize();\n }\n }\n\n public onRender = (dirty: boolean): boolean => {\n if (this.fileContainer == null) {\n return false;\n }\n if (dirty) {\n this.top = this.virtualizer.getOffsetInScrollContainer(\n this.fileContainer\n );\n }\n return this.render();\n };\n\n override cleanUp(): void {\n if (this.fileContainer != null) {\n this.virtualizer.disconnect(this.fileContainer);\n }\n super.cleanUp();\n }\n\n override expandHunk(hunkIndex: number, direction: ExpansionDirections): void {\n this.hunksRenderer.expandHunk(hunkIndex, direction);\n this.computeApproximateSize();\n this.renderRange = undefined;\n this.virtualizer.instanceChanged(this);\n // NOTE(amadeus): We should probably defer to the virtualizer to re-render\n // this.rerender();\n }\n\n public setVisibility(visible: boolean): void {\n if (this.fileContainer == null) {\n return;\n }\n if (visible && !this.isVisible) {\n this.top = this.virtualizer.getOffsetInScrollContainer(\n this.fileContainer\n );\n this.isVisible = true;\n } else if (!visible && this.isVisible) {\n this.isVisible = false;\n this.rerender();\n }\n }\n\n // Compute the approximate size of the file using cached line heights.\n // Uses lineHeight for lines without cached measurements.\n // We should probably optimize this if there are no custom line heights...\n // The reason we refer to this as `approximate size` is because heights my\n // dynamically change for a number of reasons so we can never be fully sure\n // if the height is 100% accurate\n private computeApproximateSize(): void {\n this.height = 0;\n if (this.fileDiff == null) {\n return;\n }\n\n const {\n disableFileHeader = false,\n expandUnchanged = false,\n collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD,\n hunkSeparators = 'line-info',\n } = this.options;\n const { diffHeaderHeight, fileGap, hunkSeparatorHeight } = this.metrics;\n const diffStyle = this.getDiffStyle();\n const separatorGap =\n hunkSeparators !== 'simple' &&\n hunkSeparators !== 'metadata' &&\n hunkSeparators !== 'line-info-basic'\n ? fileGap\n : 0;\n\n // Header or initial padding\n if (!disableFileHeader) {\n this.height += diffHeaderHeight;\n } else if (hunkSeparators !== 'simple' && hunkSeparators !== 'metadata') {\n this.height += fileGap;\n }\n\n iterateOverDiff({\n diff: this.fileDiff,\n diffStyle,\n expandedHunks: expandUnchanged\n ? true\n : this.hunksRenderer.getExpandedHunksMap(),\n collapsedContextThreshold,\n callback: ({\n hunkIndex,\n collapsedBefore,\n collapsedAfter,\n deletionLine,\n additionLine,\n }) => {\n const splitLineIndex =\n additionLine != null\n ? additionLine.splitLineIndex\n : deletionLine.splitLineIndex;\n const unifiedLineIndex =\n additionLine != null\n ? additionLine.unifiedLineIndex\n : deletionLine.unifiedLineIndex;\n const hasMetadata =\n (additionLine?.noEOFCR ?? false) || (deletionLine?.noEOFCR ?? false);\n if (collapsedBefore > 0) {\n if (hunkIndex > 0) {\n this.height += separatorGap;\n }\n this.height += hunkSeparatorHeight + separatorGap;\n }\n\n this.height += this.getLineHeight(\n diffStyle === 'split' ? splitLineIndex : unifiedLineIndex,\n hasMetadata\n );\n\n if (collapsedAfter > 0 && hunkSeparators !== 'simple') {\n this.height += separatorGap + hunkSeparatorHeight;\n }\n },\n });\n\n // Bottom padding\n if (this.fileDiff.hunks.length > 0) {\n this.height += fileGap;\n }\n\n if (this.fileContainer != null && this.virtualizer.config.resizeDebugging) {\n const rect = this.fileContainer.getBoundingClientRect();\n if (rect.height !== this.height) {\n console.log(\n 'VirtualizedFileDiff.computeApproximateSize: computed height doesnt match',\n {\n name: this.fileDiff.name,\n elementHeight: rect.height,\n computedHeight: this.height,\n }\n );\n } else {\n console.log(\n 'VirtualizedFileDiff.computeApproximateSize: computed height IS CORRECT'\n );\n }\n }\n }\n\n override render({\n fileContainer,\n oldFile,\n newFile,\n fileDiff,\n ...props\n }: FileDiffRenderProps<LAnnotation> = {}): boolean {\n // NOTE(amadeus): Probably not the safest way to determine first render...\n // but for now...\n const isFirstRender = this.fileContainer == null;\n\n this.fileDiff ??=\n fileDiff ??\n (oldFile != null && newFile != null\n ? // NOTE(amadeus): We might be forcing ourselves to double up the\n // computation of fileDiff (in the super.render() call), so we might want\n // to figure out a way to avoid that. That also could be just as simple as\n // passing through fileDiff though... so maybe we good?\n parseDiffFromFile(oldFile, newFile)\n : undefined);\n\n fileContainer = this.getOrCreateFileContainer(fileContainer);\n\n if (this.fileDiff == null) {\n console.error(\n 'VirtualizedFileDiff.render: attempting to virtually render when we dont have the correct data'\n );\n return false;\n }\n\n this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);\n if (isFirstRender) {\n this.computeApproximateSize();\n // Figure out how to properly manage this...\n this.virtualizer.connect(fileContainer, this);\n this.isVisible = this.virtualizer.isInstanceVisible(\n this.top,\n this.height\n );\n }\n\n if (!this.isVisible) {\n return this.renderPlaceholder(this.height);\n }\n\n const windowSpecs = this.virtualizer.getWindowSpecs();\n const renderRange = this.computeRenderRangeFromWindow(\n this.fileDiff,\n this.top,\n windowSpecs\n );\n return super.render({\n fileDiff: this.fileDiff,\n fileContainer,\n renderRange,\n oldFile,\n newFile,\n ...props,\n });\n }\n\n private getDiffStyle(): 'split' | 'unified' {\n return this.options.diffStyle ?? 'split';\n }\n\n private getExpandedRegion(\n isPartial: boolean,\n hunkIndex: number,\n rangeSize: number\n ): ExpandedRegionSpecs {\n if (rangeSize <= 0 || isPartial) {\n return {\n fromStart: 0,\n fromEnd: 0,\n collapsedLines: Math.max(rangeSize, 0),\n renderAll: false,\n };\n }\n const {\n expandUnchanged = false,\n collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD,\n } = this.options;\n if (expandUnchanged || rangeSize <= collapsedContextThreshold) {\n return {\n fromStart: rangeSize,\n fromEnd: 0,\n collapsedLines: 0,\n renderAll: true,\n };\n }\n const region = this.hunksRenderer.getExpandedHunk(hunkIndex);\n const fromStart = Math.min(Math.max(region.fromStart, 0), rangeSize);\n const fromEnd = Math.min(Math.max(region.fromEnd, 0), rangeSize);\n const expandedCount = fromStart + fromEnd;\n const renderAll = expandedCount >= rangeSize;\n return {\n fromStart,\n fromEnd,\n collapsedLines: Math.max(rangeSize - expandedCount, 0),\n renderAll,\n };\n }\n\n private getExpandedLineCount(\n fileDiff: FileDiffMetadata,\n diffStyle: 'split' | 'unified'\n ): number {\n let count = 0;\n if (fileDiff.isPartial) {\n for (const hunk of fileDiff.hunks) {\n count +=\n diffStyle === 'split' ? hunk.splitLineCount : hunk.unifiedLineCount;\n }\n return count;\n }\n\n for (const [hunkIndex, hunk] of fileDiff.hunks.entries()) {\n const hunkCount =\n diffStyle === 'split' ? hunk.splitLineCount : hunk.unifiedLineCount;\n count += hunkCount;\n const collapsedBefore = Math.max(hunk.collapsedBefore, 0);\n const { fromStart, fromEnd, renderAll } = this.getExpandedRegion(\n fileDiff.isPartial,\n hunkIndex,\n collapsedBefore\n );\n if (collapsedBefore > 0) {\n count += renderAll ? collapsedBefore : fromStart + fromEnd;\n }\n }\n\n const lastHunk = fileDiff.hunks.at(-1);\n if (lastHunk != null && hasFinalHunk(fileDiff)) {\n const additionRemaining =\n fileDiff.additionLines.length -\n (lastHunk.additionLineIndex + lastHunk.additionCount);\n const deletionRemaining =\n fileDiff.deletionLines.length -\n (lastHunk.deletionLineIndex + lastHunk.deletionCount);\n if (lastHunk != null && additionRemaining !== deletionRemaining) {\n throw new Error(\n `VirtualizedFileDiff: trailing context mismatch (additions=${additionRemaining}, deletions=${deletionRemaining}) for ${fileDiff.name}`\n );\n }\n const trailingRangeSize = Math.min(additionRemaining, deletionRemaining);\n if (lastHunk != null && trailingRangeSize > 0) {\n const { fromStart, renderAll } = this.getExpandedRegion(\n fileDiff.isPartial,\n fileDiff.hunks.length,\n trailingRangeSize\n );\n count += renderAll ? trailingRangeSize : fromStart;\n }\n }\n\n return count;\n }\n\n private computeRenderRangeFromWindow(\n fileDiff: FileDiffMetadata,\n fileTop: number,\n { top, bottom }: RenderWindow\n ): RenderRange {\n const {\n disableFileHeader = false,\n expandUnchanged = false,\n collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD,\n hunkSeparators = 'line-info',\n } = this.options;\n const {\n diffHeaderHeight,\n fileGap,\n hunkLineCount,\n hunkSeparatorHeight,\n lineHeight,\n } = this.metrics;\n const diffStyle = this.getDiffStyle();\n const fileHeight = this.height;\n const lineCount = this.getExpandedLineCount(fileDiff, diffStyle);\n\n // Calculate headerRegion before early returns\n const headerRegion = disableFileHeader ? fileGap : diffHeaderHeight;\n\n // File is outside render window\n if (fileTop < top - fileHeight || fileTop > bottom) {\n return {\n startingLine: 0,\n totalLines: 0,\n bufferBefore: 0,\n bufferAfter:\n fileHeight -\n headerRegion -\n // This last file gap represents the bottom padding that buffers\n // should not account for\n fileGap,\n };\n }\n\n // Whole file is under hunkLineCount, just render it all\n if (lineCount <= hunkLineCount || fileDiff.hunks.length === 0) {\n return {\n startingLine: 0,\n totalLines: hunkLineCount,\n bufferBefore: 0,\n bufferAfter: 0,\n };\n }\n const estimatedTargetLines = Math.ceil(\n Math.max(bottom - top, 0) / lineHeight\n );\n const totalLines =\n Math.ceil(estimatedTargetLines / hunkLineCount) * hunkLineCount +\n hunkLineCount;\n const totalHunks = totalLines / hunkLineCount;\n const overflowHunks = totalHunks;\n const hunkOffsets: number[] = [];\n // Halfway between top & bottom, represented as an absolute position\n const viewportCenter = (top + bottom) / 2;\n const separatorGap =\n hunkSeparators === 'simple' ||\n hunkSeparators === 'metadata' ||\n hunkSeparators === 'line-info-basic'\n ? 0\n : fileGap;\n\n let absoluteLineTop = fileTop + headerRegion;\n let currentLine = 0;\n let firstVisibleHunk: number | undefined;\n let centerHunk: number | undefined;\n let overflowCounter: number | undefined;\n\n iterateOverDiff({\n diff: fileDiff,\n diffStyle,\n expandedHunks: expandUnchanged\n ? true\n : this.hunksRenderer.getExpandedHunksMap(),\n collapsedContextThreshold,\n callback: ({\n hunkIndex,\n collapsedBefore,\n collapsedAfter,\n deletionLine,\n additionLine,\n }) => {\n const splitLineIndex =\n additionLine != null\n ? additionLine.splitLineIndex\n : deletionLine.splitLineIndex;\n const unifiedLineIndex =\n additionLine != null\n ? additionLine.unifiedLineIndex\n : deletionLine.unifiedLineIndex;\n const hasMetadata =\n (additionLine?.noEOFCR ?? false) || (deletionLine?.noEOFCR ?? false);\n let gapAdjustment =\n collapsedBefore > 0\n ? hunkSeparatorHeight +\n separatorGap +\n (hunkIndex > 0 ? separatorGap : 0)\n : 0;\n if (hunkIndex === 0 && hunkSeparators === 'simple') {\n gapAdjustment = 0;\n }\n\n absoluteLineTop += gapAdjustment;\n\n const isAtHunkBoundary = currentLine % hunkLineCount === 0;\n\n // Track the boundary positional offset at a hunk\n if (isAtHunkBoundary) {\n hunkOffsets.push(\n absoluteLineTop - (fileTop + headerRegion + gapAdjustment)\n );\n\n // Check if we should bail (overflow complete)\n if (overflowCounter != null) {\n if (overflowCounter <= 0) {\n return true;\n }\n overflowCounter--;\n }\n }\n\n const lineHeight = this.getLineHeight(\n diffStyle === 'split' ? splitLineIndex : unifiedLineIndex,\n hasMetadata\n );\n\n const currentHunk = Math.floor(currentLine / hunkLineCount);\n\n // Track visible region\n if (absoluteLineTop > top - lineHeight && absoluteLineTop < bottom) {\n firstVisibleHunk ??= currentHunk;\n }\n\n // Track which hunk contains the viewport center\n // If viewport center is above this line and we haven't set centerHunk yet,\n // this is the first line at or past the center\n if (\n centerHunk == null &&\n absoluteLineTop + lineHeight > viewportCenter\n ) {\n centerHunk = currentHunk;\n }\n\n // Start overflow when we are out of the viewport at a hunk boundary\n if (\n overflowCounter == null &&\n absoluteLineTop >= bottom &&\n isAtHunkBoundary\n ) {\n overflowCounter = overflowHunks;\n }\n\n currentLine++;\n absoluteLineTop += lineHeight;\n\n if (collapsedAfter > 0 && hunkSeparators !== 'simple') {\n absoluteLineTop += hunkSeparatorHeight + separatorGap;\n }\n\n return false;\n },\n });\n\n // No visible lines found\n if (firstVisibleHunk == null) {\n return {\n startingLine: 0,\n totalLines: 0,\n bufferBefore: 0,\n bufferAfter:\n fileHeight -\n headerRegion -\n // We gotta subtract the bottom padding off of the buffer\n fileGap,\n };\n }\n\n // Calculate balanced startingLine centered around the viewport center\n // Fall back to firstVisibleHunk if center wasn't found (e.g., center in a gap)\n const collectedHunks = hunkOffsets.length;\n centerHunk ??= firstVisibleHunk;\n const idealStartHunk = Math.round(centerHunk - totalHunks / 2);\n\n // Clamp startHunk: at the beginning, reduce totalLines; at the end, shift startHunk back\n const maxStartHunk = Math.max(0, collectedHunks - totalHunks);\n const startHunk = Math.max(0, Math.min(idealStartHunk, maxStartHunk));\n const startingLine = startHunk * hunkLineCount;\n\n // If we wanted to start before 0, reduce totalLines by the clamped amount\n const clampedTotalLines =\n idealStartHunk < 0\n ? totalLines + idealStartHunk * hunkLineCount\n : totalLines;\n\n // Use hunkOffsets array for efficient buffer calculations\n const bufferBefore = hunkOffsets[startHunk] ?? 0;\n\n // Calculate bufferAfter using hunkOffset if available, otherwise use cumulative height\n const finalHunkIndex = startHunk + clampedTotalLines / hunkLineCount;\n const bufferAfter =\n finalHunkIndex < hunkOffsets.length\n ? fileHeight -\n headerRegion -\n hunkOffsets[finalHunkIndex] -\n // We gotta subtract the bottom padding off of the buffer\n fileGap\n : // We stopped early, calculate from current position\n fileHeight -\n (absoluteLineTop - fileTop) -\n // We gotta subtract the bottom padding off of the buffer\n fileGap;\n\n return {\n startingLine,\n totalLines: clampedTotalLines,\n bufferBefore,\n bufferAfter,\n };\n }\n}\n\nfunction hasFinalHunk(fileDiff: FileDiffMetadata): boolean {\n const lastHunk = fileDiff.hunks.at(-1);\n if (\n lastHunk == null ||\n fileDiff.isPartial ||\n fileDiff.additionLines.length === 0 ||\n fileDiff.deletionLines.length === 0\n ) {\n return false;\n }\n\n return (\n lastHunk.additionLineIndex + lastHunk.additionCount <\n fileDiff.additionLines.length ||\n lastHunk.deletionLineIndex + lastHunk.deletionCount <\n fileDiff.deletionLines.length\n );\n}\n\n// Extracts the view-specific line index from the data-line-index attribute.\n// Format is \"unifiedIndex,splitIndex\"\nfunction parseLineIndex(\n lineIndexAttr: string,\n diffStyle: 'split' | 'unified'\n): number {\n const [unifiedIndex, splitIndex] = lineIndexAttr.split(',').map(Number);\n return diffStyle === 'split' ? splitIndex : unifiedIndex;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VirtualizedFileDiff.js","names":["hunkOffsets: number[]","firstVisibleHunk: number | undefined","centerHunk: number | undefined","overflowCounter: number | undefined","lineHeight"],"sources":["../../src/components/VirtualizedFileDiff.ts"],"sourcesContent":["import { DEFAULT_COLLAPSED_CONTEXT_THRESHOLD } from '../constants';\nimport type {\n ExpansionDirections,\n FileDiffMetadata,\n RenderRange,\n RenderWindow,\n VirtualFileMetrics,\n} from '../types';\nimport { iterateOverDiff } from '../utils/iterateOverDiff';\nimport { parseDiffFromFile } from '../utils/parseDiffFromFile';\nimport { resolveVirtualFileMetrics } from '../utils/resolveVirtualFileMetrics';\nimport type { WorkerPoolManager } from '../worker';\nimport {\n FileDiff,\n type FileDiffOptions,\n type FileDiffRenderProps,\n} from './FileDiff';\nimport type { Virtualizer } from './Virtualizer';\n\ninterface ExpandedRegionSpecs {\n fromStart: number;\n fromEnd: number;\n collapsedLines: number;\n renderAll: boolean;\n}\n\nlet instanceId = -1;\n\nexport class VirtualizedFileDiff<\n LAnnotation = undefined,\n> extends FileDiff<LAnnotation> {\n override readonly __id: string = `little-virtualized-file-diff:${++instanceId}`;\n\n public top: number | undefined;\n public height: number = 0;\n private metrics: VirtualFileMetrics;\n // Sparse map: view-specific line index -> measured height\n // Only stores lines that differ what is returned from `getLineHeight`\n private heightCache: Map<number, number> = new Map();\n private isVisible: boolean = false;\n private virtualizer: Virtualizer;\n\n constructor(\n options: FileDiffOptions<LAnnotation> | undefined,\n virtualizer: Virtualizer,\n metrics?: Partial<VirtualFileMetrics>,\n workerManager?: WorkerPoolManager,\n isContainerManaged = false\n ) {\n super(options, workerManager, isContainerManaged);\n const { hunkSeparators = 'line-info' } = this.options;\n this.virtualizer = virtualizer;\n this.metrics = resolveVirtualFileMetrics(\n typeof hunkSeparators === 'function' ? 'custom' : hunkSeparators,\n metrics\n );\n }\n\n // Get the height for a line, using cached value if available.\n // If not cached and hasMetadataLine is true, adds lineHeight for the metadata.\n private getLineHeight(lineIndex: number, hasMetadataLine = false): number {\n const cached = this.heightCache.get(lineIndex);\n if (cached != null) {\n return cached;\n }\n const multiplier = hasMetadataLine ? 2 : 1;\n return this.metrics.lineHeight * multiplier;\n }\n\n // Override setOptions to clear height cache when diffStyle changes\n override setOptions(options: FileDiffOptions<LAnnotation> | undefined): void {\n if (options == null) return;\n const previousDiffStyle = this.options.diffStyle;\n const previousOverflow = this.options.overflow;\n\n super.setOptions(options);\n\n if (\n previousDiffStyle !== this.options.diffStyle ||\n previousOverflow !== this.options.overflow\n ) {\n this.heightCache.clear();\n this.computeApproximateSize();\n this.renderRange = undefined;\n }\n this.virtualizer.instanceChanged(this);\n }\n\n // Measure rendered lines and update height cache.\n // Called after render to reconcile estimated vs actual heights.\n // Definitely need to optimize this in cases where there aren't any custom\n // line heights or in cases of extremely large files...\n public reconcileHeights(): void {\n const { overflow = 'scroll' } = this.options;\n if (this.fileContainer != null) {\n this.top = this.virtualizer.getOffsetInScrollContainer(\n this.fileContainer\n );\n }\n if (this.fileContainer == null || this.fileDiff == null) {\n this.height = 0;\n return;\n }\n // NOTE(amadeus): We can probably be a lot smarter about this, and we\n // should be thinking about ways to improve this\n // If the file has no annotations and we are using the scroll variant, then\n // we can probably skip everything\n if (\n overflow === 'scroll' &&\n this.lineAnnotations.length === 0 &&\n !this.virtualizer.config.resizeDebugging\n ) {\n return;\n }\n const diffStyle = this.getDiffStyle();\n let hasLineHeightChange = false;\n const codeGroups =\n diffStyle === 'split'\n ? [this.codeDeletions, this.codeAdditions]\n : [this.codeUnified];\n\n for (const codeGroup of codeGroups) {\n if (codeGroup == null) continue;\n const content = codeGroup.children[1];\n if (!(content instanceof HTMLElement)) continue;\n for (const line of content.children) {\n if (!(line instanceof HTMLElement)) continue;\n\n const lineIndexAttr = line.dataset.lineIndex;\n if (lineIndexAttr == null) continue;\n\n const lineIndex = parseLineIndex(lineIndexAttr, diffStyle);\n let measuredHeight = line.getBoundingClientRect().height;\n let hasMetadata = false;\n // Annotations or noNewline metadata increase the size of the their\n // attached line\n if (\n line.nextElementSibling instanceof HTMLElement &&\n ('lineAnnotation' in line.nextElementSibling.dataset ||\n 'noNewline' in line.nextElementSibling.dataset)\n ) {\n if ('noNewline' in line.nextElementSibling.dataset) {\n hasMetadata = true;\n }\n measuredHeight +=\n line.nextElementSibling.getBoundingClientRect().height;\n }\n const expectedHeight = this.getLineHeight(lineIndex, hasMetadata);\n\n if (measuredHeight === expectedHeight) {\n continue;\n }\n\n hasLineHeightChange = true;\n // Line is back to standard height (e.g., after window resize)\n // Remove from cache\n if (\n measuredHeight ===\n this.metrics.lineHeight * (hasMetadata ? 2 : 1)\n ) {\n this.heightCache.delete(lineIndex);\n }\n // Non-standard height, cache it\n else {\n this.heightCache.set(lineIndex, measuredHeight);\n }\n }\n }\n\n if (hasLineHeightChange || this.virtualizer.config.resizeDebugging) {\n this.computeApproximateSize();\n }\n }\n\n public onRender = (dirty: boolean): boolean => {\n if (this.fileContainer == null) {\n return false;\n }\n if (dirty) {\n this.top = this.virtualizer.getOffsetInScrollContainer(\n this.fileContainer\n );\n }\n return this.render();\n };\n\n override cleanUp(): void {\n if (this.fileContainer != null) {\n this.virtualizer.disconnect(this.fileContainer);\n }\n super.cleanUp();\n }\n\n override expandHunk(hunkIndex: number, direction: ExpansionDirections): void {\n this.hunksRenderer.expandHunk(hunkIndex, direction);\n this.computeApproximateSize();\n this.renderRange = undefined;\n this.virtualizer.instanceChanged(this);\n // NOTE(amadeus): We should probably defer to the virtualizer to re-render\n // this.rerender();\n }\n\n public setVisibility(visible: boolean): void {\n if (this.fileContainer == null) {\n return;\n }\n this.renderRange = undefined;\n if (visible && !this.isVisible) {\n this.top = this.virtualizer.getOffsetInScrollContainer(\n this.fileContainer\n );\n this.isVisible = true;\n } else if (!visible && this.isVisible) {\n this.isVisible = false;\n this.rerender();\n }\n }\n\n // Compute the approximate size of the file using cached line heights.\n // Uses lineHeight for lines without cached measurements.\n // We should probably optimize this if there are no custom line heights...\n // The reason we refer to this as `approximate size` is because heights my\n // dynamically change for a number of reasons so we can never be fully sure\n // if the height is 100% accurate\n private computeApproximateSize(): void {\n const isFirstCompute = this.height === 0;\n this.height = 0;\n if (this.fileDiff == null) {\n return;\n }\n\n const {\n disableFileHeader = false,\n expandUnchanged = false,\n collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD,\n hunkSeparators = 'line-info',\n } = this.options;\n const { diffHeaderHeight, fileGap, hunkSeparatorHeight } = this.metrics;\n const diffStyle = this.getDiffStyle();\n const separatorGap =\n hunkSeparators !== 'simple' &&\n hunkSeparators !== 'metadata' &&\n hunkSeparators !== 'line-info-basic'\n ? fileGap\n : 0;\n\n // Header or initial padding\n if (!disableFileHeader) {\n this.height += diffHeaderHeight;\n } else if (hunkSeparators !== 'simple' && hunkSeparators !== 'metadata') {\n this.height += fileGap;\n }\n\n iterateOverDiff({\n diff: this.fileDiff,\n diffStyle,\n expandedHunks: expandUnchanged\n ? true\n : this.hunksRenderer.getExpandedHunksMap(),\n collapsedContextThreshold,\n callback: ({\n hunkIndex,\n collapsedBefore,\n collapsedAfter,\n deletionLine,\n additionLine,\n }) => {\n const splitLineIndex =\n additionLine != null\n ? additionLine.splitLineIndex\n : deletionLine.splitLineIndex;\n const unifiedLineIndex =\n additionLine != null\n ? additionLine.unifiedLineIndex\n : deletionLine.unifiedLineIndex;\n const hasMetadata =\n (additionLine?.noEOFCR ?? false) || (deletionLine?.noEOFCR ?? false);\n if (collapsedBefore > 0) {\n if (hunkIndex > 0) {\n this.height += separatorGap;\n }\n this.height += hunkSeparatorHeight + separatorGap;\n }\n\n this.height += this.getLineHeight(\n diffStyle === 'split' ? splitLineIndex : unifiedLineIndex,\n hasMetadata\n );\n\n if (collapsedAfter > 0 && hunkSeparators !== 'simple') {\n this.height += separatorGap + hunkSeparatorHeight;\n }\n },\n });\n\n // Bottom padding\n if (this.fileDiff.hunks.length > 0) {\n this.height += fileGap;\n }\n\n if (\n this.fileContainer != null &&\n this.virtualizer.config.resizeDebugging &&\n !isFirstCompute\n ) {\n const rect = this.fileContainer.getBoundingClientRect();\n if (rect.height !== this.height) {\n console.log(\n 'VirtualizedFileDiff.computeApproximateSize: computed height doesnt match',\n {\n name: this.fileDiff.name,\n elementHeight: rect.height,\n computedHeight: this.height,\n }\n );\n } else {\n console.log(\n 'VirtualizedFileDiff.computeApproximateSize: computed height IS CORRECT'\n );\n }\n }\n }\n\n override render({\n fileContainer,\n oldFile,\n newFile,\n fileDiff,\n ...props\n }: FileDiffRenderProps<LAnnotation> = {}): boolean {\n // NOTE(amadeus): Probably not the safest way to determine first render...\n // but for now...\n const isFirstRender = this.fileContainer == null;\n\n this.fileDiff ??=\n fileDiff ??\n (oldFile != null && newFile != null\n ? // NOTE(amadeus): We might be forcing ourselves to double up the\n // computation of fileDiff (in the super.render() call), so we might want\n // to figure out a way to avoid that. That also could be just as simple as\n // passing through fileDiff though... so maybe we good?\n parseDiffFromFile(oldFile, newFile)\n : undefined);\n\n fileContainer = this.getOrCreateFileContainer(fileContainer);\n\n if (this.fileDiff == null) {\n console.error(\n 'VirtualizedFileDiff.render: attempting to virtually render when we dont have the correct data'\n );\n return false;\n }\n\n if (isFirstRender) {\n this.computeApproximateSize();\n this.virtualizer.connect(fileContainer, this);\n this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);\n this.isVisible = this.virtualizer.isInstanceVisible(\n this.top,\n this.height\n );\n } else {\n this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);\n }\n\n if (!this.isVisible) {\n return this.renderPlaceholder(this.height);\n }\n\n const windowSpecs = this.virtualizer.getWindowSpecs();\n const renderRange = this.computeRenderRangeFromWindow(\n this.fileDiff,\n this.top,\n windowSpecs\n );\n return super.render({\n fileDiff: this.fileDiff,\n fileContainer,\n renderRange,\n oldFile,\n newFile,\n ...props,\n });\n }\n\n private getDiffStyle(): 'split' | 'unified' {\n return this.options.diffStyle ?? 'split';\n }\n\n private getExpandedRegion(\n isPartial: boolean,\n hunkIndex: number,\n rangeSize: number\n ): ExpandedRegionSpecs {\n if (rangeSize <= 0 || isPartial) {\n return {\n fromStart: 0,\n fromEnd: 0,\n collapsedLines: Math.max(rangeSize, 0),\n renderAll: false,\n };\n }\n const {\n expandUnchanged = false,\n collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD,\n } = this.options;\n if (expandUnchanged || rangeSize <= collapsedContextThreshold) {\n return {\n fromStart: rangeSize,\n fromEnd: 0,\n collapsedLines: 0,\n renderAll: true,\n };\n }\n const region = this.hunksRenderer.getExpandedHunk(hunkIndex);\n const fromStart = Math.min(Math.max(region.fromStart, 0), rangeSize);\n const fromEnd = Math.min(Math.max(region.fromEnd, 0), rangeSize);\n const expandedCount = fromStart + fromEnd;\n const renderAll = expandedCount >= rangeSize;\n return {\n fromStart,\n fromEnd,\n collapsedLines: Math.max(rangeSize - expandedCount, 0),\n renderAll,\n };\n }\n\n private getExpandedLineCount(\n fileDiff: FileDiffMetadata,\n diffStyle: 'split' | 'unified'\n ): number {\n let count = 0;\n if (fileDiff.isPartial) {\n for (const hunk of fileDiff.hunks) {\n count +=\n diffStyle === 'split' ? hunk.splitLineCount : hunk.unifiedLineCount;\n }\n return count;\n }\n\n for (const [hunkIndex, hunk] of fileDiff.hunks.entries()) {\n const hunkCount =\n diffStyle === 'split' ? hunk.splitLineCount : hunk.unifiedLineCount;\n count += hunkCount;\n const collapsedBefore = Math.max(hunk.collapsedBefore, 0);\n const { fromStart, fromEnd, renderAll } = this.getExpandedRegion(\n fileDiff.isPartial,\n hunkIndex,\n collapsedBefore\n );\n if (collapsedBefore > 0) {\n count += renderAll ? collapsedBefore : fromStart + fromEnd;\n }\n }\n\n const lastHunk = fileDiff.hunks.at(-1);\n if (lastHunk != null && hasFinalHunk(fileDiff)) {\n const additionRemaining =\n fileDiff.additionLines.length -\n (lastHunk.additionLineIndex + lastHunk.additionCount);\n const deletionRemaining =\n fileDiff.deletionLines.length -\n (lastHunk.deletionLineIndex + lastHunk.deletionCount);\n if (lastHunk != null && additionRemaining !== deletionRemaining) {\n throw new Error(\n `VirtualizedFileDiff: trailing context mismatch (additions=${additionRemaining}, deletions=${deletionRemaining}) for ${fileDiff.name}`\n );\n }\n const trailingRangeSize = Math.min(additionRemaining, deletionRemaining);\n if (lastHunk != null && trailingRangeSize > 0) {\n const { fromStart, renderAll } = this.getExpandedRegion(\n fileDiff.isPartial,\n fileDiff.hunks.length,\n trailingRangeSize\n );\n count += renderAll ? trailingRangeSize : fromStart;\n }\n }\n\n return count;\n }\n\n private computeRenderRangeFromWindow(\n fileDiff: FileDiffMetadata,\n fileTop: number,\n { top, bottom }: RenderWindow\n ): RenderRange {\n const {\n disableFileHeader = false,\n expandUnchanged = false,\n collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD,\n hunkSeparators = 'line-info',\n } = this.options;\n const {\n diffHeaderHeight,\n fileGap,\n hunkLineCount,\n hunkSeparatorHeight,\n lineHeight,\n } = this.metrics;\n const diffStyle = this.getDiffStyle();\n const fileHeight = this.height;\n const lineCount = this.getExpandedLineCount(fileDiff, diffStyle);\n\n // Calculate headerRegion before early returns\n const headerRegion = disableFileHeader ? fileGap : diffHeaderHeight;\n\n // File is outside render window\n if (fileTop < top - fileHeight || fileTop > bottom) {\n return {\n startingLine: 0,\n totalLines: 0,\n bufferBefore: 0,\n bufferAfter:\n fileHeight -\n headerRegion -\n // This last file gap represents the bottom padding that buffers\n // should not account for\n fileGap,\n };\n }\n\n // Whole file is under hunkLineCount, just render it all\n if (lineCount <= hunkLineCount || fileDiff.hunks.length === 0) {\n return {\n startingLine: 0,\n totalLines: hunkLineCount,\n bufferBefore: 0,\n bufferAfter: 0,\n };\n }\n const estimatedTargetLines = Math.ceil(\n Math.max(bottom - top, 0) / lineHeight\n );\n const totalLines =\n Math.ceil(estimatedTargetLines / hunkLineCount) * hunkLineCount +\n hunkLineCount;\n const totalHunks = totalLines / hunkLineCount;\n const overflowHunks = totalHunks;\n const hunkOffsets: number[] = [];\n // Halfway between top & bottom, represented as an absolute position\n const viewportCenter = (top + bottom) / 2;\n const separatorGap =\n hunkSeparators === 'simple' ||\n hunkSeparators === 'metadata' ||\n hunkSeparators === 'line-info-basic'\n ? 0\n : fileGap;\n\n let absoluteLineTop = fileTop + headerRegion;\n let currentLine = 0;\n let firstVisibleHunk: number | undefined;\n let centerHunk: number | undefined;\n let overflowCounter: number | undefined;\n\n iterateOverDiff({\n diff: fileDiff,\n diffStyle,\n expandedHunks: expandUnchanged\n ? true\n : this.hunksRenderer.getExpandedHunksMap(),\n collapsedContextThreshold,\n callback: ({\n hunkIndex,\n collapsedBefore,\n collapsedAfter,\n deletionLine,\n additionLine,\n }) => {\n const splitLineIndex =\n additionLine != null\n ? additionLine.splitLineIndex\n : deletionLine.splitLineIndex;\n const unifiedLineIndex =\n additionLine != null\n ? additionLine.unifiedLineIndex\n : deletionLine.unifiedLineIndex;\n const hasMetadata =\n (additionLine?.noEOFCR ?? false) || (deletionLine?.noEOFCR ?? false);\n let gapAdjustment =\n collapsedBefore > 0\n ? hunkSeparatorHeight +\n separatorGap +\n (hunkIndex > 0 ? separatorGap : 0)\n : 0;\n if (hunkIndex === 0 && hunkSeparators === 'simple') {\n gapAdjustment = 0;\n }\n\n absoluteLineTop += gapAdjustment;\n\n const isAtHunkBoundary = currentLine % hunkLineCount === 0;\n\n // Track the boundary positional offset at a hunk\n if (isAtHunkBoundary) {\n hunkOffsets.push(\n absoluteLineTop - (fileTop + headerRegion + gapAdjustment)\n );\n\n // Check if we should bail (overflow complete)\n if (overflowCounter != null) {\n if (overflowCounter <= 0) {\n return true;\n }\n overflowCounter--;\n }\n }\n\n const lineHeight = this.getLineHeight(\n diffStyle === 'split' ? splitLineIndex : unifiedLineIndex,\n hasMetadata\n );\n\n const currentHunk = Math.floor(currentLine / hunkLineCount);\n\n // Track visible region\n if (absoluteLineTop > top - lineHeight && absoluteLineTop < bottom) {\n firstVisibleHunk ??= currentHunk;\n }\n\n // Track which hunk contains the viewport center\n // If viewport center is above this line and we haven't set centerHunk yet,\n // this is the first line at or past the center\n if (\n centerHunk == null &&\n absoluteLineTop + lineHeight > viewportCenter\n ) {\n centerHunk = currentHunk;\n }\n\n // Start overflow when we are out of the viewport at a hunk boundary\n if (\n overflowCounter == null &&\n absoluteLineTop >= bottom &&\n isAtHunkBoundary\n ) {\n overflowCounter = overflowHunks;\n }\n\n currentLine++;\n absoluteLineTop += lineHeight;\n\n if (collapsedAfter > 0 && hunkSeparators !== 'simple') {\n absoluteLineTop += hunkSeparatorHeight + separatorGap;\n }\n\n return false;\n },\n });\n\n // No visible lines found\n if (firstVisibleHunk == null) {\n return {\n startingLine: 0,\n totalLines: 0,\n bufferBefore: 0,\n bufferAfter:\n fileHeight -\n headerRegion -\n // We gotta subtract the bottom padding off of the buffer\n fileGap,\n };\n }\n\n // Calculate balanced startingLine centered around the viewport center\n // Fall back to firstVisibleHunk if center wasn't found (e.g., center in a gap)\n const collectedHunks = hunkOffsets.length;\n centerHunk ??= firstVisibleHunk;\n const idealStartHunk = Math.round(centerHunk - totalHunks / 2);\n\n // Clamp startHunk: at the beginning, reduce totalLines; at the end, shift startHunk back\n const maxStartHunk = Math.max(0, collectedHunks - totalHunks);\n const startHunk = Math.max(0, Math.min(idealStartHunk, maxStartHunk));\n const startingLine = startHunk * hunkLineCount;\n\n // If we wanted to start before 0, reduce totalLines by the clamped amount\n const clampedTotalLines =\n idealStartHunk < 0\n ? totalLines + idealStartHunk * hunkLineCount\n : totalLines;\n\n // Use hunkOffsets array for efficient buffer calculations\n const bufferBefore = hunkOffsets[startHunk] ?? 0;\n\n // Calculate bufferAfter using hunkOffset if available, otherwise use cumulative height\n const finalHunkIndex = startHunk + clampedTotalLines / hunkLineCount;\n const bufferAfter =\n finalHunkIndex < hunkOffsets.length\n ? fileHeight -\n headerRegion -\n hunkOffsets[finalHunkIndex] -\n // We gotta subtract the bottom padding off of the buffer\n fileGap\n : // We stopped early, calculate from current position\n fileHeight -\n (absoluteLineTop - fileTop) -\n // We gotta subtract the bottom padding off of the buffer\n fileGap;\n\n return {\n startingLine,\n totalLines: clampedTotalLines,\n bufferBefore,\n bufferAfter,\n };\n }\n}\n\nfunction hasFinalHunk(fileDiff: FileDiffMetadata): boolean {\n const lastHunk = fileDiff.hunks.at(-1);\n if (\n lastHunk == null ||\n fileDiff.isPartial ||\n fileDiff.additionLines.length === 0 ||\n fileDiff.deletionLines.length === 0\n ) {\n return false;\n }\n\n return (\n lastHunk.additionLineIndex + lastHunk.additionCount <\n fileDiff.additionLines.length ||\n lastHunk.deletionLineIndex + lastHunk.deletionCount <\n fileDiff.deletionLines.length\n );\n}\n\n// Extracts the view-specific line index from the data-line-index attribute.\n// Format is \"unifiedIndex,splitIndex\"\nfunction parseLineIndex(\n lineIndexAttr: string,\n diffStyle: 'split' | 'unified'\n): number {\n const [unifiedIndex, splitIndex] = lineIndexAttr.split(',').map(Number);\n return diffStyle === 'split' ? splitIndex : unifiedIndex;\n}\n"],"mappings":"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"}
|
|
@@ -14,14 +14,16 @@ interface VirtualizerConfig {
|
|
|
14
14
|
declare class Virtualizer {
|
|
15
15
|
static __STOP: boolean;
|
|
16
16
|
static __lastScrollPosition: number;
|
|
17
|
-
|
|
17
|
+
readonly __id: string;
|
|
18
18
|
readonly config: VirtualizerConfig;
|
|
19
|
+
type: string;
|
|
19
20
|
private intersectionObserver;
|
|
20
21
|
private scrollTop;
|
|
21
22
|
private height;
|
|
22
23
|
private scrollHeight;
|
|
23
24
|
private windowSpecs;
|
|
24
25
|
private root;
|
|
26
|
+
private contentContainer;
|
|
25
27
|
private resizeObserver;
|
|
26
28
|
private observers;
|
|
27
29
|
private visibleInstances;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Virtualizer.d.ts","names":["VirtualWindowSpecs","SubscribedInstance","VirtualizerConfig","Virtualizer","Partial","HTMLElement","Document","Element"],"sources":["../../src/components/Virtualizer.d.ts"],"sourcesContent":["import type { VirtualWindowSpecs } from '../types';\ninterface SubscribedInstance {\n onRender(dirty: boolean): boolean;\n reconcileHeights(): void;\n setVisibility(visible: boolean): void;\n}\nexport interface VirtualizerConfig {\n overscrollSize: number;\n intersectionObserverMargin: number;\n resizeDebugging: boolean;\n}\nexport declare class Virtualizer {\n static __STOP: boolean;\n static __lastScrollPosition: number;\n
|
|
1
|
+
{"version":3,"file":"Virtualizer.d.ts","names":["VirtualWindowSpecs","SubscribedInstance","VirtualizerConfig","Virtualizer","Partial","HTMLElement","Document","Element"],"sources":["../../src/components/Virtualizer.d.ts"],"sourcesContent":["import type { VirtualWindowSpecs } from '../types';\ninterface SubscribedInstance {\n onRender(dirty: boolean): boolean;\n reconcileHeights(): void;\n setVisibility(visible: boolean): void;\n}\nexport interface VirtualizerConfig {\n overscrollSize: number;\n intersectionObserverMargin: number;\n resizeDebugging: boolean;\n}\nexport declare class Virtualizer {\n static __STOP: boolean;\n static __lastScrollPosition: number;\n readonly __id: string;\n readonly config: VirtualizerConfig;\n type: string;\n private intersectionObserver;\n private scrollTop;\n private height;\n private scrollHeight;\n private windowSpecs;\n private root;\n private contentContainer;\n private resizeObserver;\n private observers;\n private visibleInstances;\n private visibleInstancesDirty;\n private instancesChanged;\n private scrollDirty;\n private heightDirty;\n private scrollHeightDirty;\n private renderedObservers;\n private connectQueue;\n constructor(config?: Partial<VirtualizerConfig>);\n setup(root: HTMLElement | Document, contentContainer?: Element): void;\n instanceChanged(instance: SubscribedInstance): void;\n getWindowSpecs(): VirtualWindowSpecs;\n isInstanceVisible(elementTop: number, elementHeight: number): boolean;\n private handleContainerResize;\n private setupWindow;\n private setupElement;\n cleanUp(): void;\n getOffsetInScrollContainer(element: HTMLElement): number;\n connect(container: HTMLElement, instance: SubscribedInstance): () => void;\n disconnect(container: HTMLElement): void;\n private handleWindowResize;\n private handleWindowScroll;\n private handleElementScroll;\n private computeRenderRangeAndEmit;\n private scrollFix;\n private applyScrollFix;\n private getScrollAnchor;\n private handleIntersectionChange;\n private getScrollTop;\n private getScrollHeight;\n private getHeight;\n private markDOMDirty;\n private getScrollContainerElement;\n}\nexport {};\n//# sourceMappingURL=Virtualizer.d.ts.map"],"mappings":";;;UACUC,kBAAAA;;EAAAA,gBAAAA,EAAAA,EAAAA,IAAkB;EAKXC,aAAAA,CAAAA,OAAiB,EAAA,OAAA,CAAA,EAAA,IAAA;AAKlC;AAIqBA,UATJA,iBAAAA,CASIA;EAmBYA,cAAAA,EAAAA,MAAAA;EAARE,0BAAAA,EAAAA,MAAAA;EACTC,eAAAA,EAAAA,OAAAA;;AAA2CE,cAxBtCJ,WAAAA,CAwBsCI;EAC7BN,OAAAA,MAAAA,EAAAA,OAAAA;EACRD,OAAAA,oBAAAA,EAAAA,MAAAA;EAMkBK,SAAAA,IAAAA,EAAAA,MAAAA;EACjBA,SAAAA,MAAAA,EA7BFH,iBA6BEG;EAAuBJ,IAAAA,EAAAA,MAAAA;EACpBI,QAAAA,oBAAAA;EAAW,QAAA,SAAA;;;;;;;;;;;;;;;;uBAXZD,QAAQF;cACjBG,cAAcC,6BAA6BC;4BAC7BN;oBACRD;;;;;;sCAMkBK;qBACjBA,uBAAuBJ;wBACpBI"}
|
|
@@ -17,11 +17,13 @@ const DEFAULT_VIRTUALIZER_CONFIG = {
|
|
|
17
17
|
resizeDebugging: false
|
|
18
18
|
};
|
|
19
19
|
let lastSize = 0;
|
|
20
|
+
let instance = -1;
|
|
20
21
|
var Virtualizer = class Virtualizer {
|
|
21
22
|
static __STOP = false;
|
|
22
23
|
static __lastScrollPosition = 0;
|
|
23
|
-
|
|
24
|
+
__id = `virtualizer-${++instance}`;
|
|
24
25
|
config;
|
|
26
|
+
type = "basic";
|
|
25
27
|
intersectionObserver;
|
|
26
28
|
scrollTop = 0;
|
|
27
29
|
height = 0;
|
|
@@ -31,6 +33,7 @@ var Virtualizer = class Virtualizer {
|
|
|
31
33
|
bottom: 0
|
|
32
34
|
};
|
|
33
35
|
root;
|
|
36
|
+
contentContainer;
|
|
34
37
|
resizeObserver;
|
|
35
38
|
observers = /* @__PURE__ */ new Map();
|
|
36
39
|
visibleInstances = /* @__PURE__ */ new Map();
|
|
@@ -69,13 +72,13 @@ var Virtualizer = class Virtualizer {
|
|
|
69
72
|
Virtualizer.__STOP = true;
|
|
70
73
|
}
|
|
71
74
|
};
|
|
72
|
-
for (const [container, instance] of this.connectQueue.entries()) this.connect(container, instance);
|
|
75
|
+
for (const [container, instance$1] of this.connectQueue.entries()) this.connect(container, instance$1);
|
|
73
76
|
this.connectQueue.clear();
|
|
74
77
|
this.markDOMDirty();
|
|
75
78
|
queueRender(this.computeRenderRangeAndEmit);
|
|
76
79
|
}
|
|
77
|
-
instanceChanged(instance) {
|
|
78
|
-
this.instancesChanged.add(instance);
|
|
80
|
+
instanceChanged(instance$1) {
|
|
81
|
+
this.instancesChanged.add(instance$1);
|
|
79
82
|
queueRender(this.computeRenderRangeAndEmit);
|
|
80
83
|
}
|
|
81
84
|
getWindowSpecs() {
|
|
@@ -106,7 +109,7 @@ var Virtualizer = class Virtualizer {
|
|
|
106
109
|
this.scrollHeightDirty = true;
|
|
107
110
|
shouldQueueUpdate = true;
|
|
108
111
|
if (this.config.resizeDebugging) {
|
|
109
|
-
console.log("Virtualizer: content size change", {
|
|
112
|
+
console.log("Virtualizer: content size change", this.__id, {
|
|
110
113
|
sizeChange: blockSize - lastSize,
|
|
111
114
|
newSize: blockSize
|
|
112
115
|
});
|
|
@@ -118,11 +121,11 @@ var Virtualizer = class Virtualizer {
|
|
|
118
121
|
this.heightDirty = true;
|
|
119
122
|
shouldQueueUpdate = true;
|
|
120
123
|
}
|
|
121
|
-
} else if (entry.target === this.
|
|
124
|
+
} else if (entry.target === this.contentContainer) {
|
|
122
125
|
this.scrollHeightDirty = true;
|
|
123
126
|
shouldQueueUpdate = true;
|
|
124
127
|
if (this.config.resizeDebugging) {
|
|
125
|
-
console.log("Virtualizer: scroller size change", {
|
|
128
|
+
console.log("Virtualizer: scroller size change", this.__id, {
|
|
126
129
|
sizeChange: blockSize - lastSize,
|
|
127
130
|
newSize: blockSize
|
|
128
131
|
});
|
|
@@ -143,34 +146,56 @@ var Virtualizer = class Virtualizer {
|
|
|
143
146
|
this.root.addEventListener("scroll", this.handleElementScroll, { passive: true });
|
|
144
147
|
this.resizeObserver?.observe(this.root);
|
|
145
148
|
contentContainer ??= this.root.firstElementChild ?? void 0;
|
|
146
|
-
if (contentContainer
|
|
149
|
+
if (contentContainer instanceof HTMLElement) {
|
|
150
|
+
this.contentContainer = contentContainer;
|
|
151
|
+
this.resizeObserver?.observe(contentContainer);
|
|
152
|
+
}
|
|
147
153
|
}
|
|
148
154
|
cleanUp() {
|
|
155
|
+
this.resizeObserver?.disconnect();
|
|
156
|
+
this.resizeObserver = void 0;
|
|
149
157
|
this.intersectionObserver?.disconnect();
|
|
150
158
|
this.intersectionObserver = void 0;
|
|
159
|
+
this.root?.removeEventListener("scroll", this.handleElementScroll);
|
|
160
|
+
window.removeEventListener("scroll", this.handleWindowScroll);
|
|
161
|
+
window.removeEventListener("resize", this.handleWindowResize);
|
|
151
162
|
this.root = void 0;
|
|
163
|
+
this.contentContainer = void 0;
|
|
164
|
+
this.observers.clear();
|
|
165
|
+
this.visibleInstances.clear();
|
|
166
|
+
this.instancesChanged.clear();
|
|
167
|
+
this.connectQueue.clear();
|
|
168
|
+
this.visibleInstancesDirty = false;
|
|
169
|
+
this.windowSpecs = {
|
|
170
|
+
top: 0,
|
|
171
|
+
bottom: 0
|
|
172
|
+
};
|
|
173
|
+
this.scrollTop = 0;
|
|
174
|
+
this.height = 0;
|
|
175
|
+
this.scrollHeight = 0;
|
|
152
176
|
}
|
|
153
177
|
getOffsetInScrollContainer(element) {
|
|
154
178
|
return this.getScrollTop() + getRelativeBoundingTop(element, this.getScrollContainerElement());
|
|
155
179
|
}
|
|
156
|
-
connect(container, instance) {
|
|
180
|
+
connect(container, instance$1) {
|
|
157
181
|
if (this.observers.has(container)) throw new Error("Virtualizer.connect: instance is already connected...");
|
|
158
|
-
if (this.intersectionObserver == null) this.connectQueue.set(container, instance);
|
|
182
|
+
if (this.intersectionObserver == null) this.connectQueue.set(container, instance$1);
|
|
159
183
|
else {
|
|
160
184
|
this.intersectionObserver.observe(container);
|
|
161
|
-
this.observers.set(container, instance);
|
|
162
|
-
this.instancesChanged.add(instance);
|
|
185
|
+
this.observers.set(container, instance$1);
|
|
186
|
+
this.instancesChanged.add(instance$1);
|
|
163
187
|
this.markDOMDirty();
|
|
164
188
|
queueRender(this.computeRenderRangeAndEmit);
|
|
165
189
|
}
|
|
166
190
|
return () => this.disconnect(container);
|
|
167
191
|
}
|
|
168
192
|
disconnect(container) {
|
|
169
|
-
const instance = this.observers.get(container);
|
|
193
|
+
const instance$1 = this.observers.get(container);
|
|
170
194
|
this.connectQueue.delete(container);
|
|
171
|
-
if (instance == null) return;
|
|
195
|
+
if (instance$1 == null) return;
|
|
172
196
|
this.intersectionObserver?.unobserve(container);
|
|
173
197
|
this.observers.delete(container);
|
|
198
|
+
if (this.visibleInstances.delete(container)) this.visibleInstancesDirty = true;
|
|
174
199
|
this.markDOMDirty();
|
|
175
200
|
queueRender(this.computeRenderRangeAndEmit);
|
|
176
201
|
}
|
|
@@ -208,14 +233,14 @@ var Virtualizer = class Virtualizer {
|
|
|
208
233
|
this.renderedObservers = this.observers.size;
|
|
209
234
|
const anchor = this.getScrollAnchor(this.height);
|
|
210
235
|
const updatedInstances = /* @__PURE__ */ new Set();
|
|
211
|
-
for (const instance of this.visibleInstances.values()) if (instance.onRender(wrapperDirty)) updatedInstances.add(instance);
|
|
212
|
-
for (const instance of this.instancesChanged) {
|
|
213
|
-
if (updatedInstances.has(instance)) continue;
|
|
214
|
-
if (instance.onRender(wrapperDirty)) updatedInstances.add(instance);
|
|
236
|
+
for (const instance$1 of this.visibleInstances.values()) if (instance$1.onRender(wrapperDirty)) updatedInstances.add(instance$1);
|
|
237
|
+
for (const instance$1 of this.instancesChanged) {
|
|
238
|
+
if (updatedInstances.has(instance$1)) continue;
|
|
239
|
+
if (instance$1.onRender(wrapperDirty)) updatedInstances.add(instance$1);
|
|
215
240
|
}
|
|
216
241
|
this.scrollFix(anchor);
|
|
217
242
|
if (this.instancesChanged.size > 0) this.markDOMDirty();
|
|
218
|
-
for (const instance of updatedInstances) instance.reconcileHeights();
|
|
243
|
+
for (const instance$1 of updatedInstances) instance$1.reconcileHeights();
|
|
219
244
|
if (this.instancesChanged.size > 0 || wrapperDirty) queueRender(this.computeRenderRangeAndEmit);
|
|
220
245
|
updatedInstances.clear();
|
|
221
246
|
this.instancesChanged.clear();
|
|
@@ -303,14 +328,14 @@ var Virtualizer = class Virtualizer {
|
|
|
303
328
|
this.scrollDirty = true;
|
|
304
329
|
for (const { target, isIntersecting } of entries) {
|
|
305
330
|
if (!(target instanceof HTMLElement)) throw new Error("Virtualizer.handleIntersectionChange: target not an HTMLElement");
|
|
306
|
-
const instance = this.observers.get(target);
|
|
307
|
-
if (instance == null) throw new Error("Virtualizer.handleIntersectionChange: no instance for target");
|
|
331
|
+
const instance$1 = this.observers.get(target);
|
|
332
|
+
if (instance$1 == null) throw new Error("Virtualizer.handleIntersectionChange: no instance for target");
|
|
308
333
|
if (isIntersecting && !this.visibleInstances.has(target)) {
|
|
309
|
-
instance.setVisibility(true);
|
|
310
|
-
this.visibleInstances.set(target, instance);
|
|
334
|
+
instance$1.setVisibility(true);
|
|
335
|
+
this.visibleInstances.set(target, instance$1);
|
|
311
336
|
this.visibleInstancesDirty = true;
|
|
312
337
|
} else if (!isIntersecting && this.visibleInstances.has(target)) {
|
|
313
|
-
instance.setVisibility(false);
|
|
338
|
+
instance$1.setVisibility(false);
|
|
314
339
|
this.visibleInstances.delete(target);
|
|
315
340
|
this.visibleInstancesDirty = true;
|
|
316
341
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Virtualizer.js","names":["DEFAULT_VIRTUALIZER_CONFIG: VirtualizerConfig","top","bestAnchor: ScrollAnchor | undefined","fileOffset: number","fileTypeOffset: 'top' | 'bottom'","bestLineIndex: string | undefined","bestLineOffset: number | undefined"],"sources":["../../src/components/Virtualizer.ts"],"sourcesContent":["import { queueRender } from '../managers/UniversalRenderingManager';\nimport type { VirtualWindowSpecs } from '../types';\nimport { areVirtualWindowSpecsEqual } from '../utils/areVirtualWindowSpecsEqual';\nimport { createWindowFromScrollPosition } from '../utils/createWindowFromScrollPosition';\n\ninterface SubscribedInstance {\n onRender(dirty: boolean): boolean;\n reconcileHeights(): void;\n setVisibility(visible: boolean): void;\n}\n\ninterface ScrollAnchor {\n fileElement: HTMLElement;\n fileTypeOffset: 'top' | 'bottom';\n fileOffset: number;\n lineIndex: string | undefined;\n lineOffset: number | undefined;\n}\n\n// 800 seems like the healthy overscan required to\n// keep safari from blanking... if we catch it tho, maybe 900\nconst DEFAULT_OVERSCROLL_SIZE = 1000;\nconst INTERSECTION_OBSERVER_MARGIN = DEFAULT_OVERSCROLL_SIZE * 4;\nconst INTERSECTION_OBSERVER_THRESHOLD = [0, 0.000001, 0.99999, 1];\n\nexport interface VirtualizerConfig {\n overscrollSize: number;\n intersectionObserverMargin: number;\n resizeDebugging: boolean;\n}\n\nconst DEFAULT_VIRTUALIZER_CONFIG: VirtualizerConfig = {\n overscrollSize: DEFAULT_OVERSCROLL_SIZE,\n intersectionObserverMargin: INTERSECTION_OBSERVER_MARGIN,\n resizeDebugging: false,\n};\n\nlet lastSize = 0;\n\nexport class Virtualizer {\n static __STOP: boolean = false;\n static __lastScrollPosition = 0;\n\n public type = 'basic';\n public readonly config: VirtualizerConfig;\n private intersectionObserver: IntersectionObserver | undefined;\n private scrollTop: number = 0;\n private height: number = 0;\n private scrollHeight: number = 0;\n private windowSpecs: VirtualWindowSpecs = { top: 0, bottom: 0 };\n private root: HTMLElement | Document | undefined;\n\n private resizeObserver: ResizeObserver | undefined;\n private observers: Map<HTMLElement, SubscribedInstance> = new Map();\n private visibleInstances: Map<HTMLElement, SubscribedInstance> = new Map();\n private visibleInstancesDirty: boolean = false;\n private instancesChanged: Set<SubscribedInstance> = new Set();\n\n private scrollDirty = true;\n private heightDirty = true;\n private scrollHeightDirty = true;\n private renderedObservers = 0;\n private connectQueue: Map<HTMLElement, SubscribedInstance> = new Map();\n\n constructor(config?: Partial<VirtualizerConfig>) {\n this.config = { ...DEFAULT_VIRTUALIZER_CONFIG, ...config };\n }\n\n setup(root: HTMLElement | Document, contentContainer?: Element): void {\n if (this.root != null) {\n return;\n }\n this.root = root;\n this.resizeObserver = new ResizeObserver(this.handleContainerResize);\n this.intersectionObserver = new IntersectionObserver(\n this.handleIntersectionChange,\n {\n root: this.root,\n threshold: INTERSECTION_OBSERVER_THRESHOLD,\n rootMargin: `${this.config.intersectionObserverMargin}px 0px ${this.config.intersectionObserverMargin}px 0px`,\n // FIXME(amadeus): Figure out the other settings we'll want in here, or\n // if we should make them configurable...\n }\n );\n if (root instanceof Document) {\n this.setupWindow();\n } else {\n this.setupElement(contentContainer);\n }\n\n // FIXME(amadeus): Remove me before release\n window.__INSTANCE = this;\n window.__TOGGLE = () => {\n if (Virtualizer.__STOP) {\n Virtualizer.__STOP = false;\n const scroller = this.getScrollContainerElement() ?? window;\n scroller.scrollTo({ top: Virtualizer.__lastScrollPosition });\n queueRender(this.computeRenderRangeAndEmit);\n } else {\n Virtualizer.__lastScrollPosition = this.getScrollTop();\n Virtualizer.__STOP = true;\n }\n };\n for (const [container, instance] of this.connectQueue.entries()) {\n this.connect(container, instance);\n }\n this.connectQueue.clear();\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n instanceChanged(instance: SubscribedInstance): void {\n this.instancesChanged.add(instance);\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n getWindowSpecs(): VirtualWindowSpecs {\n if (this.windowSpecs.top === 0 && this.windowSpecs.bottom === 0) {\n this.windowSpecs = createWindowFromScrollPosition({\n scrollTop: this.getScrollTop(),\n height: this.getHeight(),\n scrollHeight: this.getScrollHeight(),\n fitPerfectly: false,\n overscrollSize: this.config.overscrollSize,\n });\n }\n return this.windowSpecs;\n }\n\n isInstanceVisible(elementTop: number, elementHeight: number): boolean {\n const scrollTop = this.getScrollTop();\n const height = this.getHeight();\n const margin = this.config.intersectionObserverMargin;\n const top = scrollTop - margin;\n const bottom = scrollTop + height + margin;\n return !(elementTop < top - elementHeight || elementTop > bottom);\n }\n\n private handleContainerResize = (entries: ResizeObserverEntry[]) => {\n if (this.root == null) return;\n let shouldQueueUpdate = false;\n for (const entry of entries) {\n const blockSize = entry.borderBoxSize[0].blockSize;\n if (this.root instanceof Document) {\n if (blockSize !== this.scrollHeight) {\n this.scrollHeightDirty = true;\n shouldQueueUpdate = true;\n if (this.config.resizeDebugging) {\n console.log('Virtualizer: content size change', {\n sizeChange: blockSize - lastSize,\n newSize: blockSize,\n });\n lastSize = blockSize;\n }\n }\n } else {\n if (entry.target === this.root) {\n if (blockSize !== this.height) {\n this.heightDirty = true;\n shouldQueueUpdate = true;\n }\n } else if (entry.target === this.root.firstElementChild) {\n this.scrollHeightDirty = true;\n shouldQueueUpdate = true;\n if (this.config.resizeDebugging) {\n console.log('Virtualizer: scroller size change', {\n sizeChange: blockSize - lastSize,\n newSize: blockSize,\n });\n lastSize = blockSize;\n }\n }\n }\n }\n\n if (shouldQueueUpdate) {\n queueRender(this.computeRenderRangeAndEmit);\n }\n };\n\n private setupWindow() {\n if (this.root == null || !(this.root instanceof Document)) {\n throw new Error('Virtualizer.setupWindow: Invalid setup method');\n }\n window.addEventListener('scroll', this.handleWindowScroll, {\n passive: true,\n });\n window.addEventListener('resize', this.handleWindowResize, {\n passive: true,\n });\n this.resizeObserver?.observe(this.root.documentElement);\n }\n\n private setupElement(contentContainer: Element | undefined) {\n if (this.root == null || this.root instanceof Document) {\n throw new Error('Virtualizer.setupElement: Invalid setup method');\n }\n this.root.addEventListener('scroll', this.handleElementScroll, {\n passive: true,\n });\n this.resizeObserver?.observe(this.root);\n contentContainer ??= this.root.firstElementChild ?? undefined;\n if (contentContainer != null) {\n this.resizeObserver?.observe(contentContainer);\n }\n }\n\n cleanUp(): void {\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = undefined;\n this.root = undefined;\n }\n\n getOffsetInScrollContainer(element: HTMLElement): number {\n return (\n this.getScrollTop() +\n getRelativeBoundingTop(element, this.getScrollContainerElement())\n );\n }\n\n connect(container: HTMLElement, instance: SubscribedInstance): () => void {\n if (this.observers.has(container)) {\n throw new Error('Virtualizer.connect: instance is already connected...');\n }\n // If we are racing against the intersectionObserver, then we should just\n // queue up the connection for when the observer does get set up\n if (this.intersectionObserver == null) {\n this.connectQueue.set(container, instance);\n } else {\n // FIXME(amadeus): Go through the connection phase a bit more closely...\n this.intersectionObserver.observe(container);\n this.observers.set(container, instance);\n this.instancesChanged.add(instance);\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n return () => this.disconnect(container);\n }\n\n disconnect(container: HTMLElement): void {\n const instance = this.observers.get(container);\n this.connectQueue.delete(container);\n if (instance == null) {\n return;\n }\n this.intersectionObserver?.unobserve(container);\n this.observers.delete(container);\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n private handleWindowResize = () => {\n if (Virtualizer.__STOP || window.innerHeight === this.height) {\n return;\n }\n this.heightDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private handleWindowScroll = () => {\n if (\n Virtualizer.__STOP ||\n this.root == null ||\n !(this.root instanceof Document)\n ) {\n return;\n }\n this.scrollDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private handleElementScroll = () => {\n if (\n Virtualizer.__STOP ||\n this.root == null ||\n this.root instanceof Document\n ) {\n return;\n }\n this.scrollDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private computeRenderRangeAndEmit = () => {\n if (Virtualizer.__STOP) {\n return;\n }\n const wrapperDirty = this.heightDirty || this.scrollHeightDirty;\n if (\n !this.scrollDirty &&\n !this.scrollHeightDirty &&\n !this.heightDirty &&\n this.renderedObservers === this.observers.size &&\n !this.visibleInstancesDirty &&\n this.instancesChanged.size === 0\n ) {\n // NOTE(amadeus): Is this a safe assumption/optimization?\n return;\n }\n\n // If we got an emitted update from a bunch of instances, we should skip\n // the window check first and attempt to render with existing logic first\n // and then queue up a corrected render after\n if (this.instancesChanged.size === 0) {\n const windowSpecs = createWindowFromScrollPosition({\n scrollTop: this.getScrollTop(),\n height: this.getHeight(),\n scrollHeight: this.getScrollHeight(),\n fitPerfectly: false,\n overscrollSize: this.config.overscrollSize,\n });\n if (\n areVirtualWindowSpecsEqual(this.windowSpecs, windowSpecs) &&\n this.renderedObservers === this.observers.size &&\n !this.visibleInstancesDirty &&\n this.instancesChanged.size === 0\n ) {\n return;\n }\n this.windowSpecs = windowSpecs;\n }\n this.visibleInstancesDirty = false;\n this.renderedObservers = this.observers.size;\n const anchor = this.getScrollAnchor(this.height);\n const updatedInstances = new Set<SubscribedInstance>();\n for (const instance of this.visibleInstances.values()) {\n if (instance.onRender(wrapperDirty)) {\n updatedInstances.add(instance);\n }\n }\n for (const instance of this.instancesChanged) {\n if (updatedInstances.has(instance)) continue;\n if (instance.onRender(wrapperDirty)) {\n updatedInstances.add(instance);\n }\n }\n\n this.scrollFix(anchor);\n // Scroll fix may have marked the dom as dirty, but if there instance\n // changes, we should definitely mark as dirty\n if (this.instancesChanged.size > 0) {\n this.markDOMDirty();\n }\n\n for (const instance of updatedInstances) {\n instance.reconcileHeights();\n }\n\n if (this.instancesChanged.size > 0 || wrapperDirty) {\n queueRender(this.computeRenderRangeAndEmit);\n }\n updatedInstances.clear();\n this.instancesChanged.clear();\n };\n\n private scrollFix(anchor: ScrollAnchor | undefined) {\n if (anchor == null) {\n return;\n }\n const scrollContainer = this.getScrollContainerElement();\n const { lineIndex, lineOffset, fileElement, fileOffset, fileTypeOffset } =\n anchor;\n if (lineIndex != null && lineOffset != null) {\n const element = fileElement.shadowRoot?.querySelector(\n `[data-line][data-line-index=\"${lineIndex}\"]`\n );\n if (element instanceof HTMLElement) {\n const top = getRelativeBoundingTop(element, scrollContainer);\n if (top !== lineOffset) {\n const scrollOffset = top - lineOffset;\n this.applyScrollFix(scrollOffset);\n }\n return;\n }\n }\n const top = getRelativeBoundingTop(fileElement, scrollContainer);\n if (fileTypeOffset === 'top') {\n if (top !== fileOffset) {\n this.applyScrollFix(top - fileOffset);\n }\n } else {\n const bottom = top + fileElement.getBoundingClientRect().height;\n if (bottom !== fileOffset) {\n this.applyScrollFix(bottom - fileOffset);\n }\n }\n }\n\n private applyScrollFix(scrollOffset: number) {\n if (this.root == null || this.root instanceof Document) {\n window.scrollTo({\n top: window.scrollY + scrollOffset,\n behavior: 'instant',\n });\n } else {\n this.root.scrollTo({\n top: this.root.scrollTop + scrollOffset,\n behavior: 'instant',\n });\n }\n // Because we fixed our scroll positions, it means something resized or\n // moved around, so we should mark everything as dirty so the\n // reconciliation call will get the latest data when figuring calling\n // .getOffsetInScrollContainer\n this.markDOMDirty();\n }\n\n // This function tries to figure out the closest file or line to the viewport\n // top that's visible to use as a relative marker for how to fix scroll\n // position after issuing dom updates\n private getScrollAnchor(viewportHeight: number): ScrollAnchor | undefined {\n const scrollContainer = this.getScrollContainerElement();\n let bestAnchor: ScrollAnchor | undefined;\n\n for (const [fileElement] of this.visibleInstances.entries()) {\n const fileTop = getRelativeBoundingTop(fileElement, scrollContainer);\n const fileBottom = fileTop + fileElement.offsetHeight;\n\n // Determine file offset and type based on position\n // Only use bottom anchor when entire file is above viewport\n let fileOffset: number;\n let fileTypeOffset: 'top' | 'bottom';\n if (fileBottom <= 0) {\n // Entire file is above viewport - use bottom as anchor\n fileOffset = fileBottom;\n fileTypeOffset = 'bottom';\n } else {\n // File is at least partially visible or below - use top\n fileOffset = fileTop;\n fileTypeOffset = 'top';\n }\n\n // Find the best line (first fully visible) within this file\n let bestLineIndex: string | undefined;\n let bestLineOffset: number | undefined;\n\n // Only search for lines if file potentially intersects viewport\n if (fileBottom > 0 && fileTop < viewportHeight) {\n for (const line of fileElement.shadowRoot?.querySelectorAll(\n '[data-line][data-line-index]'\n ) ?? []) {\n if (!(line instanceof HTMLElement)) continue;\n const lineIndex = line.dataset.lineIndex;\n if (lineIndex == null) continue;\n\n const lineOffset = getRelativeBoundingTop(line, scrollContainer);\n\n // Ignore lines with negative offsets (above viewport top)\n if (lineOffset < 0) continue;\n\n // First visible line in DOM order is the best one because\n // querySelectorAll will grab lines in order as they appear in the\n // DOM\n bestLineIndex = lineIndex;\n bestLineOffset = lineOffset;\n break;\n }\n }\n\n // If we already have an anchor with a visible line, skip files without one\n if (bestAnchor?.lineOffset != null && bestLineOffset == null) {\n continue;\n }\n\n // Decide if this file should become the new best anchor\n let shouldReplace = false;\n // If we don't already have an anchor we should set one\n if (bestAnchor == null) {\n shouldReplace = true;\n }\n // If we found a better line anchor, we should replace the old one\n else if (\n bestLineOffset != null &&\n (bestAnchor.lineOffset == null ||\n bestLineOffset < bestAnchor.lineOffset)\n ) {\n shouldReplace = true;\n }\n // Otherwise we need to compare file only anchors\n else if (bestLineOffset == null && bestAnchor.lineOffset == null) {\n // Favor files with their tops in view\n if (\n fileOffset >= 0 &&\n (bestAnchor.fileOffset < 0 || fileOffset < bestAnchor.fileOffset)\n ) {\n shouldReplace = true;\n }\n // Or the closest file\n else if (\n fileOffset < 0 &&\n bestAnchor.fileOffset < 0 &&\n fileOffset > bestAnchor.fileOffset\n ) {\n shouldReplace = true;\n }\n }\n\n if (shouldReplace) {\n bestAnchor = {\n fileElement,\n fileTypeOffset,\n fileOffset,\n lineIndex: bestLineIndex,\n lineOffset: bestLineOffset,\n };\n }\n }\n\n return bestAnchor;\n }\n\n private handleIntersectionChange = (\n entries: IntersectionObserverEntry[]\n ): void => {\n this.scrollDirty = true;\n for (const { target, isIntersecting } of entries) {\n if (!(target instanceof HTMLElement)) {\n throw new Error(\n 'Virtualizer.handleIntersectionChange: target not an HTMLElement'\n );\n }\n const instance = this.observers.get(target);\n if (instance == null) {\n throw new Error(\n 'Virtualizer.handleIntersectionChange: no instance for target'\n );\n }\n if (isIntersecting && !this.visibleInstances.has(target)) {\n instance.setVisibility(true);\n this.visibleInstances.set(target, instance);\n this.visibleInstancesDirty = true;\n } else if (!isIntersecting && this.visibleInstances.has(target)) {\n instance.setVisibility(false);\n this.visibleInstances.delete(target);\n this.visibleInstancesDirty = true;\n }\n }\n\n if (this.visibleInstancesDirty) {\n // Since this call is already debounced, should we just call\n // computeRenderRangeAndEmit directly?\n queueRender(this.computeRenderRangeAndEmit);\n }\n // Debug logging for visible instances\n // console.log(\n // 'handleIntersectionChange',\n // ...Array.from(this.visibleInstances.keys())\n // );\n };\n\n private getScrollTop() {\n if (!this.scrollDirty) {\n return this.scrollTop;\n }\n this.scrollDirty = false;\n let scrollTop = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return window.scrollY;\n }\n return this.root.scrollTop;\n })();\n\n // Lets always make sure to clamp scroll position cases of\n // over/bounce scroll\n scrollTop = Math.max(\n 0,\n Math.min(scrollTop, this.getScrollHeight() - this.getHeight())\n );\n this.scrollTop = scrollTop;\n return scrollTop;\n }\n\n private getScrollHeight() {\n if (!this.scrollHeightDirty) {\n return this.scrollHeight;\n }\n this.scrollHeightDirty = false;\n this.scrollHeight = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return this.root.documentElement.scrollHeight;\n }\n return this.root.scrollHeight;\n })();\n return this.scrollHeight;\n }\n\n private getHeight() {\n if (!this.heightDirty) {\n return this.height;\n }\n this.heightDirty = false;\n this.height = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return globalThis.innerHeight;\n }\n return this.root.getBoundingClientRect().height;\n })();\n return this.height;\n }\n\n private markDOMDirty() {\n this.scrollDirty = true;\n this.scrollHeightDirty = true;\n this.heightDirty = true;\n }\n\n private getScrollContainerElement(): HTMLElement | undefined {\n return this.root == null || this.root instanceof Document\n ? undefined\n : this.root;\n }\n}\n\n// This function is like a generalized getBoundingClientRect for it's relative\n// scroll container\nfunction getRelativeBoundingTop(\n element: HTMLElement,\n scrollContainer: HTMLElement | undefined\n) {\n const rect = element.getBoundingClientRect();\n const scrollContainerTop = scrollContainer?.getBoundingClientRect().top ?? 0;\n return rect.top - scrollContainerTop;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Virtualizer.js","names":["DEFAULT_VIRTUALIZER_CONFIG: VirtualizerConfig","instance","top","bestAnchor: ScrollAnchor | undefined","fileOffset: number","fileTypeOffset: 'top' | 'bottom'","bestLineIndex: string | undefined","bestLineOffset: number | undefined"],"sources":["../../src/components/Virtualizer.ts"],"sourcesContent":["import { queueRender } from '../managers/UniversalRenderingManager';\nimport type { VirtualWindowSpecs } from '../types';\nimport { areVirtualWindowSpecsEqual } from '../utils/areVirtualWindowSpecsEqual';\nimport { createWindowFromScrollPosition } from '../utils/createWindowFromScrollPosition';\n\ninterface SubscribedInstance {\n onRender(dirty: boolean): boolean;\n reconcileHeights(): void;\n setVisibility(visible: boolean): void;\n}\n\ninterface ScrollAnchor {\n fileElement: HTMLElement;\n fileTypeOffset: 'top' | 'bottom';\n fileOffset: number;\n lineIndex: string | undefined;\n lineOffset: number | undefined;\n}\n\n// 800 seems like the healthy overscan required to\n// keep safari from blanking... if we catch it tho, maybe 900\nconst DEFAULT_OVERSCROLL_SIZE = 1000;\nconst INTERSECTION_OBSERVER_MARGIN = DEFAULT_OVERSCROLL_SIZE * 4;\nconst INTERSECTION_OBSERVER_THRESHOLD = [0, 0.000001, 0.99999, 1];\n\nexport interface VirtualizerConfig {\n overscrollSize: number;\n intersectionObserverMargin: number;\n resizeDebugging: boolean;\n}\n\nconst DEFAULT_VIRTUALIZER_CONFIG: VirtualizerConfig = {\n overscrollSize: DEFAULT_OVERSCROLL_SIZE,\n intersectionObserverMargin: INTERSECTION_OBSERVER_MARGIN,\n resizeDebugging: false,\n};\n\nlet lastSize = 0;\n\nlet instance = -1;\n\nexport class Virtualizer {\n static __STOP: boolean = false;\n static __lastScrollPosition = 0;\n\n public readonly __id: string = `virtualizer-${++instance}`;\n public readonly config: VirtualizerConfig;\n public type = 'basic';\n private intersectionObserver: IntersectionObserver | undefined;\n private scrollTop: number = 0;\n private height: number = 0;\n private scrollHeight: number = 0;\n private windowSpecs: VirtualWindowSpecs = { top: 0, bottom: 0 };\n private root: HTMLElement | Document | undefined;\n private contentContainer: HTMLElement | undefined;\n\n private resizeObserver: ResizeObserver | undefined;\n private observers: Map<HTMLElement, SubscribedInstance> = new Map();\n private visibleInstances: Map<HTMLElement, SubscribedInstance> = new Map();\n private visibleInstancesDirty: boolean = false;\n private instancesChanged: Set<SubscribedInstance> = new Set();\n\n private scrollDirty = true;\n private heightDirty = true;\n private scrollHeightDirty = true;\n private renderedObservers = 0;\n private connectQueue: Map<HTMLElement, SubscribedInstance> = new Map();\n\n constructor(config?: Partial<VirtualizerConfig>) {\n this.config = { ...DEFAULT_VIRTUALIZER_CONFIG, ...config };\n }\n\n setup(root: HTMLElement | Document, contentContainer?: Element): void {\n if (this.root != null) {\n return;\n }\n this.root = root;\n this.resizeObserver = new ResizeObserver(this.handleContainerResize);\n this.intersectionObserver = new IntersectionObserver(\n this.handleIntersectionChange,\n {\n root: this.root,\n threshold: INTERSECTION_OBSERVER_THRESHOLD,\n rootMargin: `${this.config.intersectionObserverMargin}px 0px ${this.config.intersectionObserverMargin}px 0px`,\n // FIXME(amadeus): Figure out the other settings we'll want in here, or\n // if we should make them configurable...\n }\n );\n if (root instanceof Document) {\n this.setupWindow();\n } else {\n this.setupElement(contentContainer);\n }\n\n // FIXME(amadeus): Remove me before release\n window.__INSTANCE = this;\n window.__TOGGLE = () => {\n if (Virtualizer.__STOP) {\n Virtualizer.__STOP = false;\n const scroller = this.getScrollContainerElement() ?? window;\n scroller.scrollTo({ top: Virtualizer.__lastScrollPosition });\n queueRender(this.computeRenderRangeAndEmit);\n } else {\n Virtualizer.__lastScrollPosition = this.getScrollTop();\n Virtualizer.__STOP = true;\n }\n };\n for (const [container, instance] of this.connectQueue.entries()) {\n this.connect(container, instance);\n }\n this.connectQueue.clear();\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n instanceChanged(instance: SubscribedInstance): void {\n this.instancesChanged.add(instance);\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n getWindowSpecs(): VirtualWindowSpecs {\n if (this.windowSpecs.top === 0 && this.windowSpecs.bottom === 0) {\n this.windowSpecs = createWindowFromScrollPosition({\n scrollTop: this.getScrollTop(),\n height: this.getHeight(),\n scrollHeight: this.getScrollHeight(),\n fitPerfectly: false,\n overscrollSize: this.config.overscrollSize,\n });\n }\n return this.windowSpecs;\n }\n\n isInstanceVisible(elementTop: number, elementHeight: number): boolean {\n const scrollTop = this.getScrollTop();\n const height = this.getHeight();\n const margin = this.config.intersectionObserverMargin;\n const top = scrollTop - margin;\n const bottom = scrollTop + height + margin;\n return !(elementTop < top - elementHeight || elementTop > bottom);\n }\n\n private handleContainerResize = (entries: ResizeObserverEntry[]) => {\n if (this.root == null) return;\n let shouldQueueUpdate = false;\n for (const entry of entries) {\n const blockSize = entry.borderBoxSize[0].blockSize;\n if (this.root instanceof Document) {\n if (blockSize !== this.scrollHeight) {\n this.scrollHeightDirty = true;\n shouldQueueUpdate = true;\n if (this.config.resizeDebugging) {\n console.log('Virtualizer: content size change', this.__id, {\n sizeChange: blockSize - lastSize,\n newSize: blockSize,\n });\n lastSize = blockSize;\n }\n }\n } else {\n if (entry.target === this.root) {\n if (blockSize !== this.height) {\n this.heightDirty = true;\n shouldQueueUpdate = true;\n }\n } else if (entry.target === this.contentContainer) {\n this.scrollHeightDirty = true;\n shouldQueueUpdate = true;\n if (this.config.resizeDebugging) {\n console.log('Virtualizer: scroller size change', this.__id, {\n sizeChange: blockSize - lastSize,\n newSize: blockSize,\n });\n lastSize = blockSize;\n }\n }\n }\n }\n\n if (shouldQueueUpdate) {\n queueRender(this.computeRenderRangeAndEmit);\n }\n };\n\n private setupWindow() {\n if (this.root == null || !(this.root instanceof Document)) {\n throw new Error('Virtualizer.setupWindow: Invalid setup method');\n }\n window.addEventListener('scroll', this.handleWindowScroll, {\n passive: true,\n });\n window.addEventListener('resize', this.handleWindowResize, {\n passive: true,\n });\n this.resizeObserver?.observe(this.root.documentElement);\n }\n\n private setupElement(contentContainer: Element | undefined) {\n if (this.root == null || this.root instanceof Document) {\n throw new Error('Virtualizer.setupElement: Invalid setup method');\n }\n this.root.addEventListener('scroll', this.handleElementScroll, {\n passive: true,\n });\n this.resizeObserver?.observe(this.root);\n contentContainer ??= this.root.firstElementChild ?? undefined;\n if (contentContainer instanceof HTMLElement) {\n this.contentContainer = contentContainer;\n this.resizeObserver?.observe(contentContainer);\n }\n }\n\n cleanUp(): void {\n this.resizeObserver?.disconnect();\n this.resizeObserver = undefined;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = undefined;\n this.root?.removeEventListener('scroll', this.handleElementScroll);\n window.removeEventListener('scroll', this.handleWindowScroll);\n window.removeEventListener('resize', this.handleWindowResize);\n this.root = undefined;\n this.contentContainer = undefined;\n this.observers.clear();\n this.visibleInstances.clear();\n this.instancesChanged.clear();\n this.connectQueue.clear();\n this.visibleInstancesDirty = false;\n this.windowSpecs = { top: 0, bottom: 0 };\n this.scrollTop = 0;\n this.height = 0;\n this.scrollHeight = 0;\n }\n\n getOffsetInScrollContainer(element: HTMLElement): number {\n return (\n this.getScrollTop() +\n getRelativeBoundingTop(element, this.getScrollContainerElement())\n );\n }\n\n connect(container: HTMLElement, instance: SubscribedInstance): () => void {\n if (this.observers.has(container)) {\n throw new Error('Virtualizer.connect: instance is already connected...');\n }\n // If we are racing against the intersectionObserver, then we should just\n // queue up the connection for when the observer does get set up\n if (this.intersectionObserver == null) {\n this.connectQueue.set(container, instance);\n } else {\n // FIXME(amadeus): Go through the connection phase a bit more closely...\n this.intersectionObserver.observe(container);\n this.observers.set(container, instance);\n this.instancesChanged.add(instance);\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n return () => this.disconnect(container);\n }\n\n disconnect(container: HTMLElement): void {\n const instance = this.observers.get(container);\n this.connectQueue.delete(container);\n if (instance == null) {\n return;\n }\n this.intersectionObserver?.unobserve(container);\n this.observers.delete(container);\n if (this.visibleInstances.delete(container)) {\n this.visibleInstancesDirty = true;\n }\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n private handleWindowResize = () => {\n if (Virtualizer.__STOP || window.innerHeight === this.height) {\n return;\n }\n this.heightDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private handleWindowScroll = () => {\n if (\n Virtualizer.__STOP ||\n this.root == null ||\n !(this.root instanceof Document)\n ) {\n return;\n }\n this.scrollDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private handleElementScroll = () => {\n if (\n Virtualizer.__STOP ||\n this.root == null ||\n this.root instanceof Document\n ) {\n return;\n }\n this.scrollDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private computeRenderRangeAndEmit = () => {\n if (Virtualizer.__STOP) {\n return;\n }\n const wrapperDirty = this.heightDirty || this.scrollHeightDirty;\n if (\n !this.scrollDirty &&\n !this.scrollHeightDirty &&\n !this.heightDirty &&\n this.renderedObservers === this.observers.size &&\n !this.visibleInstancesDirty &&\n this.instancesChanged.size === 0\n ) {\n // NOTE(amadeus): Is this a safe assumption/optimization?\n return;\n }\n\n // If we got an emitted update from a bunch of instances, we should skip\n // the window check first and attempt to render with existing logic first\n // and then queue up a corrected render after\n if (this.instancesChanged.size === 0) {\n const windowSpecs = createWindowFromScrollPosition({\n scrollTop: this.getScrollTop(),\n height: this.getHeight(),\n scrollHeight: this.getScrollHeight(),\n fitPerfectly: false,\n overscrollSize: this.config.overscrollSize,\n });\n if (\n areVirtualWindowSpecsEqual(this.windowSpecs, windowSpecs) &&\n this.renderedObservers === this.observers.size &&\n !this.visibleInstancesDirty &&\n this.instancesChanged.size === 0\n ) {\n return;\n }\n this.windowSpecs = windowSpecs;\n }\n this.visibleInstancesDirty = false;\n this.renderedObservers = this.observers.size;\n const anchor = this.getScrollAnchor(this.height);\n const updatedInstances = new Set<SubscribedInstance>();\n for (const instance of this.visibleInstances.values()) {\n if (instance.onRender(wrapperDirty)) {\n updatedInstances.add(instance);\n }\n }\n for (const instance of this.instancesChanged) {\n if (updatedInstances.has(instance)) continue;\n if (instance.onRender(wrapperDirty)) {\n updatedInstances.add(instance);\n }\n }\n\n this.scrollFix(anchor);\n // Scroll fix may have marked the dom as dirty, but if there instance\n // changes, we should definitely mark as dirty\n if (this.instancesChanged.size > 0) {\n this.markDOMDirty();\n }\n\n for (const instance of updatedInstances) {\n instance.reconcileHeights();\n }\n\n if (this.instancesChanged.size > 0 || wrapperDirty) {\n queueRender(this.computeRenderRangeAndEmit);\n }\n updatedInstances.clear();\n this.instancesChanged.clear();\n };\n\n private scrollFix(anchor: ScrollAnchor | undefined) {\n if (anchor == null) {\n return;\n }\n const scrollContainer = this.getScrollContainerElement();\n const { lineIndex, lineOffset, fileElement, fileOffset, fileTypeOffset } =\n anchor;\n if (lineIndex != null && lineOffset != null) {\n const element = fileElement.shadowRoot?.querySelector(\n `[data-line][data-line-index=\"${lineIndex}\"]`\n );\n if (element instanceof HTMLElement) {\n const top = getRelativeBoundingTop(element, scrollContainer);\n if (top !== lineOffset) {\n const scrollOffset = top - lineOffset;\n this.applyScrollFix(scrollOffset);\n }\n return;\n }\n }\n const top = getRelativeBoundingTop(fileElement, scrollContainer);\n if (fileTypeOffset === 'top') {\n if (top !== fileOffset) {\n this.applyScrollFix(top - fileOffset);\n }\n } else {\n const bottom = top + fileElement.getBoundingClientRect().height;\n if (bottom !== fileOffset) {\n this.applyScrollFix(bottom - fileOffset);\n }\n }\n }\n\n private applyScrollFix(scrollOffset: number) {\n if (this.root == null || this.root instanceof Document) {\n window.scrollTo({\n top: window.scrollY + scrollOffset,\n behavior: 'instant',\n });\n } else {\n this.root.scrollTo({\n top: this.root.scrollTop + scrollOffset,\n behavior: 'instant',\n });\n }\n // Because we fixed our scroll positions, it means something resized or\n // moved around, so we should mark everything as dirty so the\n // reconciliation call will get the latest data when figuring calling\n // .getOffsetInScrollContainer\n this.markDOMDirty();\n }\n\n // This function tries to figure out the closest file or line to the viewport\n // top that's visible to use as a relative marker for how to fix scroll\n // position after issuing dom updates\n private getScrollAnchor(viewportHeight: number): ScrollAnchor | undefined {\n const scrollContainer = this.getScrollContainerElement();\n let bestAnchor: ScrollAnchor | undefined;\n\n for (const [fileElement] of this.visibleInstances.entries()) {\n const fileTop = getRelativeBoundingTop(fileElement, scrollContainer);\n const fileBottom = fileTop + fileElement.offsetHeight;\n\n // Determine file offset and type based on position\n // Only use bottom anchor when entire file is above viewport\n let fileOffset: number;\n let fileTypeOffset: 'top' | 'bottom';\n if (fileBottom <= 0) {\n // Entire file is above viewport - use bottom as anchor\n fileOffset = fileBottom;\n fileTypeOffset = 'bottom';\n } else {\n // File is at least partially visible or below - use top\n fileOffset = fileTop;\n fileTypeOffset = 'top';\n }\n\n // Find the best line (first fully visible) within this file\n let bestLineIndex: string | undefined;\n let bestLineOffset: number | undefined;\n\n // Only search for lines if file potentially intersects viewport\n if (fileBottom > 0 && fileTop < viewportHeight) {\n for (const line of fileElement.shadowRoot?.querySelectorAll(\n '[data-line][data-line-index]'\n ) ?? []) {\n if (!(line instanceof HTMLElement)) continue;\n const lineIndex = line.dataset.lineIndex;\n if (lineIndex == null) continue;\n\n const lineOffset = getRelativeBoundingTop(line, scrollContainer);\n\n // Ignore lines with negative offsets (above viewport top)\n if (lineOffset < 0) continue;\n\n // First visible line in DOM order is the best one because\n // querySelectorAll will grab lines in order as they appear in the\n // DOM\n bestLineIndex = lineIndex;\n bestLineOffset = lineOffset;\n break;\n }\n }\n\n // If we already have an anchor with a visible line, skip files without one\n if (bestAnchor?.lineOffset != null && bestLineOffset == null) {\n continue;\n }\n\n // Decide if this file should become the new best anchor\n let shouldReplace = false;\n // If we don't already have an anchor we should set one\n if (bestAnchor == null) {\n shouldReplace = true;\n }\n // If we found a better line anchor, we should replace the old one\n else if (\n bestLineOffset != null &&\n (bestAnchor.lineOffset == null ||\n bestLineOffset < bestAnchor.lineOffset)\n ) {\n shouldReplace = true;\n }\n // Otherwise we need to compare file only anchors\n else if (bestLineOffset == null && bestAnchor.lineOffset == null) {\n // Favor files with their tops in view\n if (\n fileOffset >= 0 &&\n (bestAnchor.fileOffset < 0 || fileOffset < bestAnchor.fileOffset)\n ) {\n shouldReplace = true;\n }\n // Or the closest file\n else if (\n fileOffset < 0 &&\n bestAnchor.fileOffset < 0 &&\n fileOffset > bestAnchor.fileOffset\n ) {\n shouldReplace = true;\n }\n }\n\n if (shouldReplace) {\n bestAnchor = {\n fileElement,\n fileTypeOffset,\n fileOffset,\n lineIndex: bestLineIndex,\n lineOffset: bestLineOffset,\n };\n }\n }\n\n return bestAnchor;\n }\n\n private handleIntersectionChange = (\n entries: IntersectionObserverEntry[]\n ): void => {\n this.scrollDirty = true;\n for (const { target, isIntersecting } of entries) {\n if (!(target instanceof HTMLElement)) {\n throw new Error(\n 'Virtualizer.handleIntersectionChange: target not an HTMLElement'\n );\n }\n const instance = this.observers.get(target);\n if (instance == null) {\n throw new Error(\n 'Virtualizer.handleIntersectionChange: no instance for target'\n );\n }\n if (isIntersecting && !this.visibleInstances.has(target)) {\n instance.setVisibility(true);\n this.visibleInstances.set(target, instance);\n this.visibleInstancesDirty = true;\n } else if (!isIntersecting && this.visibleInstances.has(target)) {\n instance.setVisibility(false);\n this.visibleInstances.delete(target);\n this.visibleInstancesDirty = true;\n }\n }\n\n if (this.visibleInstancesDirty) {\n // Since this call is already debounced, should we just call\n // computeRenderRangeAndEmit directly?\n queueRender(this.computeRenderRangeAndEmit);\n }\n // Debug logging for visible instances\n // console.log(\n // 'handleIntersectionChange',\n // ...Array.from(this.visibleInstances.keys())\n // );\n };\n\n private getScrollTop() {\n if (!this.scrollDirty) {\n return this.scrollTop;\n }\n this.scrollDirty = false;\n let scrollTop = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return window.scrollY;\n }\n return this.root.scrollTop;\n })();\n\n // Lets always make sure to clamp scroll position cases of\n // over/bounce scroll\n scrollTop = Math.max(\n 0,\n Math.min(scrollTop, this.getScrollHeight() - this.getHeight())\n );\n this.scrollTop = scrollTop;\n return scrollTop;\n }\n\n private getScrollHeight() {\n if (!this.scrollHeightDirty) {\n return this.scrollHeight;\n }\n this.scrollHeightDirty = false;\n this.scrollHeight = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return this.root.documentElement.scrollHeight;\n }\n return this.root.scrollHeight;\n })();\n return this.scrollHeight;\n }\n\n private getHeight() {\n if (!this.heightDirty) {\n return this.height;\n }\n this.heightDirty = false;\n this.height = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return globalThis.innerHeight;\n }\n return this.root.getBoundingClientRect().height;\n })();\n return this.height;\n }\n\n private markDOMDirty() {\n this.scrollDirty = true;\n this.scrollHeightDirty = true;\n this.heightDirty = true;\n }\n\n private getScrollContainerElement(): HTMLElement | undefined {\n return this.root == null || this.root instanceof Document\n ? undefined\n : this.root;\n }\n}\n\n// This function is like a generalized getBoundingClientRect for it's relative\n// scroll container\nfunction getRelativeBoundingTop(\n element: HTMLElement,\n scrollContainer: HTMLElement | undefined\n) {\n const rect = element.getBoundingClientRect();\n const scrollContainerTop = scrollContainer?.getBoundingClientRect().top ?? 0;\n return rect.top - scrollContainerTop;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkerPoolContext.d.ts","names":["Context","ReactNode","SetupWorkerPoolProps","WorkerInitializationRenderOptions","WorkerPoolManager","WorkerPoolOptions","WorkerPoolContext","WorkerPoolContextProps","WorkerPoolContextProvider","children","poolOptions","highlighterOptions","React","JSX","Element","useWorkerPool"],"sources":["../../src/react/WorkerPoolContext.d.ts"],"sourcesContent":["import { type Context, type ReactNode } from 'react';\nimport { type SetupWorkerPoolProps, type WorkerInitializationRenderOptions, type WorkerPoolManager, type WorkerPoolOptions } from '../worker';\nexport type { WorkerPoolOptions, WorkerInitializationRenderOptions };\nexport declare const WorkerPoolContext: Context<WorkerPoolManager | undefined>;\ninterface WorkerPoolContextProps extends SetupWorkerPoolProps {\n children: ReactNode;\n}\nexport declare function WorkerPoolContextProvider({ children, poolOptions, highlighterOptions }: WorkerPoolContextProps): React.JSX.Element;\nexport declare function useWorkerPool(): WorkerPoolManager | undefined;\n//# sourceMappingURL=WorkerPoolContext.d.ts.map"],"mappings":";;;;;;;cAGqBM,mBAAmBN,QAAQI;UACtCG,sBAAAA,SAA+BL;YAC3BD;;AAFOK,iBAIGE,yBAAAA,CAJwBJ;EAAAA,QAARJ;
|
|
1
|
+
{"version":3,"file":"WorkerPoolContext.d.ts","names":["Context","ReactNode","SetupWorkerPoolProps","WorkerInitializationRenderOptions","WorkerPoolManager","WorkerPoolOptions","WorkerPoolContext","WorkerPoolContextProps","WorkerPoolContextProvider","children","poolOptions","highlighterOptions","React","JSX","Element","useWorkerPool"],"sources":["../../src/react/WorkerPoolContext.d.ts"],"sourcesContent":["import { type Context, type ReactNode } from 'react';\nimport { type SetupWorkerPoolProps, type WorkerInitializationRenderOptions, type WorkerPoolManager, type WorkerPoolOptions } from '../worker';\nexport type { WorkerPoolOptions, WorkerInitializationRenderOptions };\nexport declare const WorkerPoolContext: Context<WorkerPoolManager | undefined>;\ninterface WorkerPoolContextProps extends SetupWorkerPoolProps {\n children: ReactNode;\n}\nexport declare function WorkerPoolContextProvider({ children, poolOptions, highlighterOptions }: WorkerPoolContextProps): React.JSX.Element;\nexport declare function useWorkerPool(): WorkerPoolManager | undefined;\n//# sourceMappingURL=WorkerPoolContext.d.ts.map"],"mappings":";;;;;;;cAGqBM,mBAAmBN,QAAQI;UACtCG,sBAAAA,SAA+BL;YAC3BD;;AAFOK,iBAIGE,yBAAAA,CAJwBJ;EAAAA,QAARJ;EAAO,WAAA;EAAA;AAAA,CAAA,EAIkDO,sBAJlD,CAAA,EAI2EK,KAAAA,CAAMC,GAAAA,CAAIC,OAJrF;AACrCP,iBAIcQ,aAAAA,CAAAA,CAJQ,EAISX,iBAJAF,GAAAA,SAAAA"}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
//#region src/utils/createWindowFromScrollPosition.ts
|
|
2
2
|
function createWindowFromScrollPosition({ scrollTop, scrollHeight, height, containerOffset = 0, fitPerfectly, overscrollSize }) {
|
|
3
3
|
const windowHeight = height + overscrollSize * 2;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const effectiveHeight = fitPerfectly ? height : windowHeight;
|
|
5
|
+
scrollHeight = Math.max(scrollHeight, effectiveHeight);
|
|
6
|
+
if (windowHeight >= scrollHeight || fitPerfectly) {
|
|
7
|
+
const top$1 = Math.max(scrollTop - containerOffset, 0);
|
|
8
|
+
const bottom$1 = Math.min(scrollTop + effectiveHeight, scrollHeight) - containerOffset;
|
|
9
|
+
return {
|
|
10
|
+
top: top$1,
|
|
11
|
+
bottom: Math.max(bottom$1, top$1)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
8
14
|
let top = scrollTop + height / 2 - windowHeight / 2;
|
|
9
15
|
let bottom = top + windowHeight;
|
|
10
16
|
if (top < 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createWindowFromScrollPosition.js","names":[],"sources":["../../src/utils/createWindowFromScrollPosition.ts"],"sourcesContent":["import type { VirtualWindowSpecs } from '../types';\n\ninterface WindowFromScrollPositionProps {\n scrollTop: number;\n height: number;\n scrollHeight: number;\n containerOffset?: number;\n fitPerfectly: boolean;\n overscrollSize: number;\n}\n\nexport function createWindowFromScrollPosition({\n scrollTop,\n scrollHeight,\n height,\n containerOffset = 0,\n fitPerfectly,\n overscrollSize,\n}: WindowFromScrollPositionProps): VirtualWindowSpecs {\n const windowHeight = height + overscrollSize * 2;\n if (windowHeight
|
|
1
|
+
{"version":3,"file":"createWindowFromScrollPosition.js","names":["top","bottom"],"sources":["../../src/utils/createWindowFromScrollPosition.ts"],"sourcesContent":["import type { VirtualWindowSpecs } from '../types';\n\ninterface WindowFromScrollPositionProps {\n scrollTop: number;\n height: number;\n scrollHeight: number;\n containerOffset?: number;\n fitPerfectly: boolean;\n overscrollSize: number;\n}\n\nexport function createWindowFromScrollPosition({\n scrollTop,\n scrollHeight,\n height,\n containerOffset = 0,\n fitPerfectly,\n overscrollSize,\n}: WindowFromScrollPositionProps): VirtualWindowSpecs {\n const windowHeight = height + overscrollSize * 2;\n const effectiveHeight = fitPerfectly ? height : windowHeight;\n scrollHeight = Math.max(scrollHeight, effectiveHeight);\n\n if (windowHeight >= scrollHeight || fitPerfectly) {\n const top = Math.max(scrollTop - containerOffset, 0);\n const bottom =\n Math.min(scrollTop + effectiveHeight, scrollHeight) - containerOffset;\n return {\n top,\n bottom: Math.max(bottom, top),\n };\n }\n\n const scrollCenter = scrollTop + height / 2;\n let top = scrollCenter - windowHeight / 2;\n let bottom = top + windowHeight;\n if (top < 0) {\n top = 0;\n bottom = Math.min(windowHeight, scrollHeight);\n } else if (bottom > scrollHeight) {\n bottom = scrollHeight;\n top = Math.max(bottom - windowHeight, 0);\n }\n top = Math.floor(Math.max(top - containerOffset, 0));\n return {\n top,\n bottom: Math.ceil(\n Math.max(Math.min(bottom, scrollHeight) - containerOffset, top)\n ),\n };\n}\n"],"mappings":";AAWA,SAAgB,+BAA+B,EAC7C,WACA,cACA,QACA,kBAAkB,GAClB,cACA,kBACoD;CACpD,MAAM,eAAe,SAAS,iBAAiB;CAC/C,MAAM,kBAAkB,eAAe,SAAS;AAChD,gBAAe,KAAK,IAAI,cAAc,gBAAgB;AAEtD,KAAI,gBAAgB,gBAAgB,cAAc;EAChD,MAAMA,QAAM,KAAK,IAAI,YAAY,iBAAiB,EAAE;EACpD,MAAMC,WACJ,KAAK,IAAI,YAAY,iBAAiB,aAAa,GAAG;AACxD,SAAO;GACL;GACA,QAAQ,KAAK,IAAIA,UAAQD,MAAI;GAC9B;;CAIH,IAAI,MADiB,YAAY,SAAS,IACjB,eAAe;CACxC,IAAI,SAAS,MAAM;AACnB,KAAI,MAAM,GAAG;AACX,QAAM;AACN,WAAS,KAAK,IAAI,cAAc,aAAa;YACpC,SAAS,cAAc;AAChC,WAAS;AACT,QAAM,KAAK,IAAI,SAAS,cAAc,EAAE;;AAE1C,OAAM,KAAK,MAAM,KAAK,IAAI,MAAM,iBAAiB,EAAE,CAAC;AACpD,QAAO;EACL;EACA,QAAQ,KAAK,KACX,KAAK,IAAI,KAAK,IAAI,QAAQ,aAAa,GAAG,iBAAiB,IAAI,CAChE;EACF"}
|