@pierre/diffs 1.1.0-beta.10 → 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.
Files changed (49) hide show
  1. package/README.md +7 -18
  2. package/dist/components/File.d.ts.map +1 -1
  3. package/dist/components/VirtualizedFile.js +4 -3
  4. package/dist/components/VirtualizedFile.js.map +1 -1
  5. package/dist/components/VirtualizedFileDiff.js +5 -3
  6. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  7. package/dist/components/Virtualizer.d.ts +3 -1
  8. package/dist/components/Virtualizer.d.ts.map +1 -1
  9. package/dist/components/Virtualizer.js +49 -24
  10. package/dist/components/Virtualizer.js.map +1 -1
  11. package/dist/highlighter/shared_highlighter.d.ts +4 -2
  12. package/dist/highlighter/shared_highlighter.d.ts.map +1 -1
  13. package/dist/highlighter/shared_highlighter.js +3 -3
  14. package/dist/highlighter/shared_highlighter.js.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/react/WorkerPoolContext.d.ts.map +1 -1
  17. package/dist/react/index.d.ts +2 -2
  18. package/dist/react/jsx.d.ts.map +1 -1
  19. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  20. package/dist/shiki-stream/stream.d.ts +1 -1
  21. package/dist/shiki-stream/stream.d.ts.map +1 -1
  22. package/dist/shiki-stream/stream.js.map +1 -1
  23. package/dist/shiki-stream/tokenizer.d.ts +1 -1
  24. package/dist/shiki-stream/tokenizer.d.ts.map +1 -1
  25. package/dist/shiki-stream/tokenizer.js.map +1 -1
  26. package/dist/shiki-stream/types.d.ts +1 -1
  27. package/dist/shiki-stream/types.d.ts.map +1 -1
  28. package/dist/ssr/index.d.ts +2 -2
  29. package/dist/types.d.ts +3 -1
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/utils/createWindowFromScrollPosition.js +10 -4
  32. package/dist/utils/createWindowFromScrollPosition.js.map +1 -1
  33. package/dist/utils/getHighlighterOptions.d.ts +7 -2
  34. package/dist/utils/getHighlighterOptions.d.ts.map +1 -1
  35. package/dist/utils/getHighlighterOptions.js +3 -2
  36. package/dist/utils/getHighlighterOptions.js.map +1 -1
  37. package/dist/worker/WorkerPoolManager.d.ts +3 -1
  38. package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
  39. package/dist/worker/WorkerPoolManager.js +8 -3
  40. package/dist/worker/WorkerPoolManager.js.map +1 -1
  41. package/dist/worker/types.d.ts +3 -1
  42. package/dist/worker/types.d.ts.map +1 -1
  43. package/dist/worker/wasm-BlUZCxHM.js +10 -0
  44. package/dist/worker/wasm-BlUZCxHM.js.map +1 -0
  45. package/dist/worker/worker-portable.js +10526 -10094
  46. package/dist/worker/worker-portable.js.map +1 -1
  47. package/dist/worker/worker.js +27 -19
  48. package/dist/worker/worker.js.map +1 -1
  49. package/package.json +1 -3
package/README.md CHANGED
@@ -72,26 +72,15 @@ cd packages/diffs
72
72
  bun publish
73
73
  ```
74
74
 
75
- ## Building Icons
75
+ ## Building the sprite
76
76
 
77
- **For Pierre team only.**
78
-
79
- To build all our SVG icons from Figma there's a couple preparation steps that
80
- you need to run first.
81
-
82
- Perform a full export of all `Published Icons` the `Pierre Design` Figma file
83
-
84
- Do this by selecting all the icons but not the art board and in the bottom right
85
- click `Export XXX Layers` and make sure to point it to a `./svg` folder at the
86
- root level of this project
87
-
88
- Once that's done, simply run:
77
+ The diff UI uses an SVG sprite built from `@pierre/icons`. From the monorepo
78
+ root:
89
79
 
90
80
  ```bash
91
- bun run icons:build
81
+ bun run icons:sprite
92
82
  ```
93
83
 
94
- This will run a full build of all the icons into
95
- `./apps/docs/components/icons/icons`
96
-
97
- Then make sure to check in any changes needed
84
+ This reads SVGs from `node_modules/@pierre/icons/svg` and writes
85
+ `packages/diffs/src/sprite.ts`. Run after updating `@pierre/icons` or changing
86
+ `sprite.config.js`.
@@ -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":"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"}
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
- type: string;
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 type: string;\n readonly config: VirtualizerConfig;\n private intersectionObserver;\n private scrollTop;\n private height;\n private scrollHeight;\n private windowSpecs;\n private root;\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;EAiBYA,cAAAA,EAAAA,MAAAA;EAARE,0BAAAA,EAAAA,MAAAA;EACTC,eAAAA,EAAAA,OAAAA;;AAA2CE,cAtBtCJ,WAAAA,CAsBsCI;EAC7BN,OAAAA,MAAAA,EAAAA,OAAAA;EACRD,OAAAA,oBAAAA,EAAAA,MAAAA;EAMkBK,IAAAA,EAAAA,MAAAA;EACjBA,SAAAA,MAAAA,EA3BFH,iBA2BEG;EAAuBJ,QAAAA,oBAAAA;EACpBI,QAAAA,SAAAA;EAAW,QAAA,MAAA;;;;;;;;;;;;;;uBAXZD,QAAQF;cACjBG,cAAcC,6BAA6BC;4BAC7BN;oBACRD;;;;;;sCAMkBK;qBACjBA,uBAAuBJ;wBACpBI"}
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
- type = "basic";
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.root.firstElementChild) {
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 != null) this.resizeObserver?.observe(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
  }