@pierre/diffs 1.3.0-beta.8 → 1.3.0-beta.9

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.
@@ -1 +1 @@
1
- {"version":3,"file":"FileStream.d.ts","names":[],"sources":["../../src/components/FileStream.ts"],"mappings":";;;UAsBiB,iBAAA,SAA0B,eAAA;EACzC,IAAA,GAAO,kBAAA;EACP,iBAAA;EAEA,WAAA,EAAa,QAAA,EAAU,UAAA;EACvB,YAAA,EAAc,QAAA,EAAU,UAAA;EAExB,aAAA,EAAe,UAAA,EAAY,+BAAA;EAC3B,aAAA,EAAe,KAAA,EAAO,WAAA,GAAc,WAAA;EACpC,aAAA;EACA,aAAA,EAAe,MAAA;AAAA;AAAA,cAKJ,UAAA;EAeQ,OAAA,EAAS,iBAAA;EAAA,SAdnB,IAAA;EAAA,QAED,WAAA;EAAA,QACA,MAAA;EAAA,QACA,eAAA;EAAA,QACA,aAAA;EAAA,QACA,GAAA;EAAA,QACA,IAAA;EAAA,QACA,aAAA;EAAA,QACA,cAAA;EAAA,QACA,aAAA;EAAA,QACA,eAAA;EAAA,QACA,eAAA;EAER,WAAA,CAAmB,OAAA,GAAS,iBAAA;EAI5B,OAAA;EAKA,YAAA,CAAa,SAAA,EAAW,UAAA;EAAA,QAoBV,qBAAA;EAAA,QAON,eAAA;EACF,KAAA,CACJ,OAAA,EAAS,cAAA,UACT,QAAA,EAAU,WAAA,GACT,OAAA;EAAA,QAkBK,WAAA;EAAA,QA+FA,YAAA;EAAA,QACA,WAAA;EAAA,QAYA,gBAAA;EAAA,QACA,kBAAA;EAAA,QACA,MAAA;EAAA,QA8CA,wBAAA;EAAA,QAqBA,aAAA;EAAA,QASA,UAAA;EAAA,QAwBA,wBAAA;EAAA,QAoBA,eAAA;AAAA"}
1
+ {"version":3,"file":"FileStream.d.ts","names":[],"sources":["../../src/components/FileStream.ts"],"mappings":";;;UAyBiB,iBAAA,SAA0B,eAAA;EACzC,IAAA,GAAO,kBAAA;EACP,iBAAA;EAEA,WAAA,EAAa,QAAA,EAAU,UAAA;EACvB,YAAA,EAAc,QAAA,EAAU,UAAA;EAExB,aAAA,EAAe,UAAA,EAAY,+BAAA;EAC3B,aAAA,EAAe,KAAA,EAAO,WAAA,GAAc,WAAA;EACpC,aAAA;EACA,aAAA,EAAe,MAAA;AAAA;AAAA,cAKJ,UAAA;EAeQ,OAAA,EAAS,iBAAA;EAAA,SAdnB,IAAA;EAAA,QAED,WAAA;EAAA,QACA,MAAA;EAAA,QACA,eAAA;EAAA,QACA,aAAA;EAAA,QACA,GAAA;EAAA,QACA,IAAA;EAAA,QACA,aAAA;EAAA,QACA,cAAA;EAAA,QACA,aAAA;EAAA,QACA,eAAA;EAAA,QACA,eAAA;EAER,WAAA,CAAmB,OAAA,GAAS,iBAAA;EAI5B,OAAA;EAMA,YAAA,CAAa,SAAA,EAAW,UAAA;EAAA,QAoBV,qBAAA;EAAA,QAON,eAAA;EACF,KAAA,CACJ,OAAA,EAAS,cAAA,UACT,QAAA,EAAU,WAAA,GACT,OAAA;EAAA,QAkBK,WAAA;EAAA,QA+FA,YAAA;EAAA,QACA,WAAA;EAAA,QAYA,gBAAA;EAAA,QACA,kBAAA;EAAA,QACA,MAAA;EAAA,QA8CA,wBAAA;EAAA,QAqBA,aAAA;EAAA,QASA,UAAA;EAAA,QAwBA,wBAAA;EAAA,QAoBA,eAAA;AAAA"}
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_THEMES } from "../constants.js";
2
- import { queueRender } from "../managers/UniversalRenderingManager.js";
2
+ import { dequeueRender, queueRender } from "../managers/UniversalRenderingManager.js";
3
3
  import { getSharedHighlighter } from "../highlighter/shared_highlighter.js";
4
4
  import { getHighlighterOptions } from "../utils/getHighlighterOptions.js";
5
5
  import { formatCSSVariablePrefix } from "../utils/formatCSSVariablePrefix.js";
@@ -32,6 +32,7 @@ var FileStream = class {
32
32
  this.currentLineIndex = this.options.startingLineIndex ?? 1;
33
33
  }
34
34
  cleanUp() {
35
+ dequeueRender(this.render);
35
36
  this.abortController?.abort();
36
37
  this.abortController = void 0;
37
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FileStream.js","names":[],"sources":["../../src/components/FileStream.ts"],"sourcesContent":["import { DEFAULT_THEMES, DIFFS_TAG_NAME } from '../constants';\nimport { getSharedHighlighter } from '../highlighter/shared_highlighter';\nimport { queueRender } from '../managers/UniversalRenderingManager';\nimport { CodeToTokenTransformStream, type RecallToken } from '../shiki-stream';\nimport type {\n AppliedThemeStyleCache,\n BaseCodeOptions,\n DiffsHighlighter,\n SupportedLanguages,\n ThemedToken,\n ThemeTypes,\n} from '../types';\nimport { createSpanFromToken } from '../utils/createSpanNodeFromToken';\nimport { wrapThemeCSS } from '../utils/cssWrappers';\nimport { formatCSSVariablePrefix } from '../utils/formatCSSVariablePrefix';\nimport { getHighlighterOptions } from '../utils/getHighlighterOptions';\nimport { getHighlighterThemeStyles } from '../utils/getHighlighterThemeStyles';\nimport { getOrCreateCodeNode } from '../utils/getOrCreateCodeNode';\nimport { upsertHostThemeStyle } from '../utils/hostTheme';\nimport { getMeasuredScrollbarGutter } from '../utils/scrollbarGutter';\nimport { setPreNodeProperties } from '../utils/setWrapperNodeProps';\n\nexport interface FileStreamOptions extends BaseCodeOptions {\n lang?: SupportedLanguages;\n startingLineIndex?: number;\n\n onPreRender?(instance: FileStream): unknown;\n onPostRender?(instance: FileStream): unknown;\n\n onStreamStart?(controller: WritableStreamDefaultController): unknown;\n onStreamWrite?(token: ThemedToken | RecallToken): unknown;\n onStreamClose?(): unknown;\n onStreamAbort?(reason: unknown): unknown;\n}\n\nlet instanceId = -1;\n\nexport class FileStream {\n readonly __id: string = `file-stream:${++instanceId}`;\n\n private highlighter: DiffsHighlighter | undefined;\n private stream: ReadableStream<string> | undefined;\n private abortController: AbortController | undefined;\n private fileContainer: HTMLElement | undefined;\n private pre: HTMLPreElement | undefined;\n private code: HTMLElement | undefined;\n private gutterElement: HTMLElement | undefined;\n private contentElement: HTMLElement | undefined;\n private themeCSSStyle: HTMLStyleElement | undefined;\n private appliedThemeCSS: AppliedThemeStyleCache | undefined;\n private currentRowCount = 0;\n\n constructor(public options: FileStreamOptions = { theme: DEFAULT_THEMES }) {\n this.currentLineIndex = this.options.startingLineIndex ?? 1;\n }\n\n cleanUp(): void {\n this.abortController?.abort();\n this.abortController = undefined;\n }\n\n setThemeType(themeType: ThemeTypes): void {\n if ((this.options.themeType ?? 'system') === themeType) {\n return;\n }\n this.options = { ...this.options, themeType };\n if (\n typeof this.options.theme === 'string' ||\n this.fileContainer == null ||\n this.appliedThemeCSS == null\n ) {\n return;\n }\n this.applyThemeState(\n this.fileContainer,\n this.appliedThemeCSS.themeStyles,\n themeType,\n this.appliedThemeCSS.baseThemeType\n );\n }\n\n private async initializeHighlighter(): Promise<DiffsHighlighter> {\n this.highlighter = await getSharedHighlighter(\n getHighlighterOptions(this.options.lang, this.options)\n );\n return this.highlighter;\n }\n\n private queuedSetupArgs: [ReadableStream<string>, HTMLElement] | undefined;\n async setup(\n _source: ReadableStream<string>,\n _wrapper: HTMLElement\n ): Promise<void> {\n const isSettingUp = this.queuedSetupArgs != null;\n this.queuedSetupArgs = [_source, _wrapper];\n if (isSettingUp) {\n // TODO(amadeus): Make it so that this function can be properly\n // awaitable, maybe?\n return;\n }\n this.highlighter ??= await this.initializeHighlighter();\n\n const [source, wrapper] = this.queuedSetupArgs;\n this.queuedSetupArgs = undefined;\n\n const stream = source;\n\n this.setupStream(stream, wrapper, this.highlighter);\n }\n\n private setupStream(\n stream: ReadableStream<string>,\n wrapper: HTMLElement,\n highlighter: DiffsHighlighter\n ): void {\n const {\n disableLineNumbers = false,\n overflow = 'scroll',\n theme = DEFAULT_THEMES,\n themeType = 'system',\n } = this.options;\n const fileContainer = this.getOrCreateFileContainer();\n if (fileContainer.parentElement == null) {\n wrapper.appendChild(fileContainer);\n }\n this.pre ??= document.createElement('pre');\n if (this.pre.parentElement == null) {\n fileContainer.shadowRoot?.appendChild(this.pre);\n }\n const baseThemeType =\n typeof theme === 'string' ? highlighter.getTheme(theme).type : undefined;\n const themeStyles = getHighlighterThemeStyles({ theme, highlighter });\n this.applyThemeState(fileContainer, themeStyles, themeType, baseThemeType);\n const pre = setPreNodeProperties(this.pre, {\n type: 'file',\n diffIndicators: 'none',\n disableBackground: true,\n disableLineNumbers,\n overflow,\n split: false,\n totalLines: 0,\n });\n pre.textContent = '';\n\n this.pre = pre;\n this.code = getOrCreateCodeNode({ code: this.code, pre });\n this.gutterElement = undefined;\n this.contentElement = undefined;\n this.currentRowCount = 0;\n this.currentLineElement = undefined;\n this.currentLineIndex = this.options.startingLineIndex ?? 1;\n this.abortController?.abort();\n this.abortController = new AbortController();\n const { onStreamStart, onStreamClose, onStreamAbort } = this.options;\n // Cancel the prior source so upstream producers stop generating tokens.\n // Swallow AbortError / locked-stream rejections since we're tearing down.\n this.stream?.cancel().catch(() => {});\n this.stream = stream;\n this.stream\n // tokenizeTimeLimit: 0 — never trade silently-wrong token colors for\n // latency; see renderFileWithHighlighter for the full rationale.\n .pipeThrough(\n typeof theme === 'string'\n ? new CodeToTokenTransformStream({\n ...this.options,\n theme,\n highlighter,\n allowRecalls: true,\n defaultColor: false,\n cssVariablePrefix: formatCSSVariablePrefix('token'),\n tokenizeTimeLimit: 0,\n })\n : new CodeToTokenTransformStream({\n ...this.options,\n themes: theme,\n highlighter,\n allowRecalls: true,\n defaultColor: false,\n cssVariablePrefix: formatCSSVariablePrefix('token'),\n tokenizeTimeLimit: 0,\n })\n )\n .pipeTo(\n new WritableStream({\n start(controller) {\n onStreamStart?.(controller);\n },\n close() {\n onStreamClose?.();\n },\n abort(reason) {\n onStreamAbort?.(reason);\n },\n write: this.handleWrite,\n }),\n { signal: this.abortController.signal }\n )\n .catch((error) => {\n // Ignore AbortError - it's expected when cleaning up\n if (error.name !== 'AbortError') {\n console.error('FileStream pipe error:', error);\n }\n });\n }\n\n private queuedTokens: (ThemedToken | RecallToken)[] = [];\n private handleWrite = (token: ThemedToken | RecallToken) => {\n // If we've recalled tokens we haven't rendered yet, we can just yeet them\n // and never apply them\n if ('recall' in token && this.queuedTokens.length >= token.recall) {\n this.queuedTokens.length = this.queuedTokens.length - token.recall;\n } else {\n this.queuedTokens.push(token);\n }\n queueRender(this.render);\n this.options.onStreamWrite?.(token);\n };\n\n private currentLineIndex: number;\n private currentLineElement: HTMLElement | undefined;\n private render = () => {\n this.options.onPreRender?.(this);\n const { gutter, content } = this.getOrCreateStreamColumns();\n const gutterFragment = document.createDocumentFragment();\n const contentFragment = document.createDocumentFragment();\n for (const token of this.queuedTokens) {\n if ('recall' in token) {\n if (this.currentLineElement == null) {\n throw new Error(\n 'FileStream.render: no current line element, shouldnt be possible to get here'\n );\n }\n if (token.recall > this.currentLineElement.childNodes.length) {\n throw new Error(\n `FileStream.render: Token recall exceed the current line, there's probably a bug...`\n );\n }\n for (let i = 0; i < token.recall; i++) {\n this.currentLineElement.lastChild?.remove();\n }\n } else {\n const span = createSpanFromToken(token);\n if (this.currentLineElement == null) {\n const { gutterLine, contentLine } = this.createLine();\n gutterFragment.appendChild(gutterLine);\n contentFragment.appendChild(contentLine);\n }\n this.currentLineElement?.appendChild(span);\n if (token.content === '\\n') {\n this.currentLineIndex++;\n const { gutterLine, contentLine } = this.createLine();\n gutterFragment.appendChild(gutterLine);\n contentFragment.appendChild(contentLine);\n }\n }\n }\n if (gutterFragment.childNodes.length > 0) {\n gutter.appendChild(gutterFragment);\n }\n if (contentFragment.childNodes.length > 0) {\n content.appendChild(contentFragment);\n }\n this.queuedTokens.length = 0;\n this.options.onPostRender?.(this);\n };\n\n private getOrCreateStreamColumns(): {\n gutter: HTMLElement;\n content: HTMLElement;\n } {\n if (this.code == null) {\n throw new Error('FileStream: expected code element to exist');\n }\n if (this.gutterElement != null && this.contentElement != null) {\n return { gutter: this.gutterElement, content: this.contentElement };\n }\n const gutter = document.createElement('div');\n gutter.dataset.gutter = '';\n const content = document.createElement('div');\n content.dataset.content = '';\n this.code.appendChild(gutter);\n this.code.appendChild(content);\n this.gutterElement = gutter;\n this.contentElement = content;\n return { gutter, content };\n }\n\n private updateRowSpan(): void {\n if (this.gutterElement != null) {\n this.gutterElement.style.gridRow = `span ${this.currentRowCount}`;\n }\n if (this.contentElement != null) {\n this.contentElement.style.gridRow = `span ${this.currentRowCount}`;\n }\n }\n\n private createLine(): { gutterLine: HTMLElement; contentLine: HTMLElement } {\n const lineNumber = this.currentLineIndex;\n const lineIndex = `${lineNumber - 1}`;\n const gutterLine = document.createElement('div');\n gutterLine.dataset.columnNumber = `${lineNumber}`;\n gutterLine.dataset.lineType = 'context';\n gutterLine.dataset.lineIndex = lineIndex;\n\n const numberContent = document.createElement('span');\n numberContent.dataset.lineNumberContent = '';\n numberContent.textContent = `${lineNumber}`;\n gutterLine.appendChild(numberContent);\n\n const contentLine = document.createElement('div');\n contentLine.dataset.line = `${lineNumber}`;\n contentLine.dataset.lineType = 'context';\n contentLine.dataset.lineIndex = lineIndex;\n\n this.currentRowCount += 1;\n this.updateRowSpan();\n this.currentLineElement = contentLine;\n return { gutterLine, contentLine };\n }\n\n private getOrCreateFileContainer(fileContainer?: HTMLElement): HTMLElement {\n if (\n (fileContainer != null && fileContainer === this.fileContainer) ||\n (fileContainer == null && this.fileContainer != null)\n ) {\n return this.fileContainer;\n }\n if (\n this.fileContainer != null &&\n fileContainer != null &&\n fileContainer !== this.fileContainer\n ) {\n this.themeCSSStyle = undefined;\n this.appliedThemeCSS = undefined;\n }\n this.fileContainer =\n fileContainer ?? document.createElement(DIFFS_TAG_NAME);\n return this.fileContainer;\n }\n\n private applyThemeState(\n container: HTMLElement,\n themeStyles: string,\n themeType: ThemeTypes,\n baseThemeType?: 'light' | 'dark'\n ): void {\n const shadowRoot =\n container.shadowRoot ?? container.attachShadow({ mode: 'open' });\n const effectiveThemeType = baseThemeType ?? themeType;\n const currentTheme = this.options.theme ?? DEFAULT_THEMES;\n const theme =\n typeof currentTheme === 'string' ? currentTheme : { ...currentTheme };\n const scrollbarGutter = getMeasuredScrollbarGutter(shadowRoot);\n if (\n this.themeCSSStyle?.parentNode === shadowRoot &&\n this.appliedThemeCSS?.themeStyles === themeStyles &&\n this.appliedThemeCSS.themeType === effectiveThemeType &&\n this.appliedThemeCSS.scrollbarGutter === scrollbarGutter\n ) {\n this.appliedThemeCSS.theme = theme;\n return;\n }\n this.themeCSSStyle = upsertHostThemeStyle({\n shadowRoot,\n currentNode: this.themeCSSStyle,\n themeCSS: wrapThemeCSS(themeStyles, effectiveThemeType, scrollbarGutter),\n });\n this.appliedThemeCSS =\n this.themeCSSStyle != null\n ? {\n theme,\n themeStyles,\n themeType: effectiveThemeType,\n baseThemeType,\n scrollbarGutter,\n }\n : undefined;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"FileStream.js","names":[],"sources":["../../src/components/FileStream.ts"],"sourcesContent":["import { DEFAULT_THEMES, DIFFS_TAG_NAME } from '../constants';\nimport { getSharedHighlighter } from '../highlighter/shared_highlighter';\nimport {\n dequeueRender,\n queueRender,\n} from '../managers/UniversalRenderingManager';\nimport { CodeToTokenTransformStream, type RecallToken } from '../shiki-stream';\nimport type {\n AppliedThemeStyleCache,\n BaseCodeOptions,\n DiffsHighlighter,\n SupportedLanguages,\n ThemedToken,\n ThemeTypes,\n} from '../types';\nimport { createSpanFromToken } from '../utils/createSpanNodeFromToken';\nimport { wrapThemeCSS } from '../utils/cssWrappers';\nimport { formatCSSVariablePrefix } from '../utils/formatCSSVariablePrefix';\nimport { getHighlighterOptions } from '../utils/getHighlighterOptions';\nimport { getHighlighterThemeStyles } from '../utils/getHighlighterThemeStyles';\nimport { getOrCreateCodeNode } from '../utils/getOrCreateCodeNode';\nimport { upsertHostThemeStyle } from '../utils/hostTheme';\nimport { getMeasuredScrollbarGutter } from '../utils/scrollbarGutter';\nimport { setPreNodeProperties } from '../utils/setWrapperNodeProps';\n\nexport interface FileStreamOptions extends BaseCodeOptions {\n lang?: SupportedLanguages;\n startingLineIndex?: number;\n\n onPreRender?(instance: FileStream): unknown;\n onPostRender?(instance: FileStream): unknown;\n\n onStreamStart?(controller: WritableStreamDefaultController): unknown;\n onStreamWrite?(token: ThemedToken | RecallToken): unknown;\n onStreamClose?(): unknown;\n onStreamAbort?(reason: unknown): unknown;\n}\n\nlet instanceId = -1;\n\nexport class FileStream {\n readonly __id: string = `file-stream:${++instanceId}`;\n\n private highlighter: DiffsHighlighter | undefined;\n private stream: ReadableStream<string> | undefined;\n private abortController: AbortController | undefined;\n private fileContainer: HTMLElement | undefined;\n private pre: HTMLPreElement | undefined;\n private code: HTMLElement | undefined;\n private gutterElement: HTMLElement | undefined;\n private contentElement: HTMLElement | undefined;\n private themeCSSStyle: HTMLStyleElement | undefined;\n private appliedThemeCSS: AppliedThemeStyleCache | undefined;\n private currentRowCount = 0;\n\n constructor(public options: FileStreamOptions = { theme: DEFAULT_THEMES }) {\n this.currentLineIndex = this.options.startingLineIndex ?? 1;\n }\n\n cleanUp(): void {\n dequeueRender(this.render);\n this.abortController?.abort();\n this.abortController = undefined;\n }\n\n setThemeType(themeType: ThemeTypes): void {\n if ((this.options.themeType ?? 'system') === themeType) {\n return;\n }\n this.options = { ...this.options, themeType };\n if (\n typeof this.options.theme === 'string' ||\n this.fileContainer == null ||\n this.appliedThemeCSS == null\n ) {\n return;\n }\n this.applyThemeState(\n this.fileContainer,\n this.appliedThemeCSS.themeStyles,\n themeType,\n this.appliedThemeCSS.baseThemeType\n );\n }\n\n private async initializeHighlighter(): Promise<DiffsHighlighter> {\n this.highlighter = await getSharedHighlighter(\n getHighlighterOptions(this.options.lang, this.options)\n );\n return this.highlighter;\n }\n\n private queuedSetupArgs: [ReadableStream<string>, HTMLElement] | undefined;\n async setup(\n _source: ReadableStream<string>,\n _wrapper: HTMLElement\n ): Promise<void> {\n const isSettingUp = this.queuedSetupArgs != null;\n this.queuedSetupArgs = [_source, _wrapper];\n if (isSettingUp) {\n // TODO(amadeus): Make it so that this function can be properly\n // awaitable, maybe?\n return;\n }\n this.highlighter ??= await this.initializeHighlighter();\n\n const [source, wrapper] = this.queuedSetupArgs;\n this.queuedSetupArgs = undefined;\n\n const stream = source;\n\n this.setupStream(stream, wrapper, this.highlighter);\n }\n\n private setupStream(\n stream: ReadableStream<string>,\n wrapper: HTMLElement,\n highlighter: DiffsHighlighter\n ): void {\n const {\n disableLineNumbers = false,\n overflow = 'scroll',\n theme = DEFAULT_THEMES,\n themeType = 'system',\n } = this.options;\n const fileContainer = this.getOrCreateFileContainer();\n if (fileContainer.parentElement == null) {\n wrapper.appendChild(fileContainer);\n }\n this.pre ??= document.createElement('pre');\n if (this.pre.parentElement == null) {\n fileContainer.shadowRoot?.appendChild(this.pre);\n }\n const baseThemeType =\n typeof theme === 'string' ? highlighter.getTheme(theme).type : undefined;\n const themeStyles = getHighlighterThemeStyles({ theme, highlighter });\n this.applyThemeState(fileContainer, themeStyles, themeType, baseThemeType);\n const pre = setPreNodeProperties(this.pre, {\n type: 'file',\n diffIndicators: 'none',\n disableBackground: true,\n disableLineNumbers,\n overflow,\n split: false,\n totalLines: 0,\n });\n pre.textContent = '';\n\n this.pre = pre;\n this.code = getOrCreateCodeNode({ code: this.code, pre });\n this.gutterElement = undefined;\n this.contentElement = undefined;\n this.currentRowCount = 0;\n this.currentLineElement = undefined;\n this.currentLineIndex = this.options.startingLineIndex ?? 1;\n this.abortController?.abort();\n this.abortController = new AbortController();\n const { onStreamStart, onStreamClose, onStreamAbort } = this.options;\n // Cancel the prior source so upstream producers stop generating tokens.\n // Swallow AbortError / locked-stream rejections since we're tearing down.\n this.stream?.cancel().catch(() => {});\n this.stream = stream;\n this.stream\n // tokenizeTimeLimit: 0 — never trade silently-wrong token colors for\n // latency; see renderFileWithHighlighter for the full rationale.\n .pipeThrough(\n typeof theme === 'string'\n ? new CodeToTokenTransformStream({\n ...this.options,\n theme,\n highlighter,\n allowRecalls: true,\n defaultColor: false,\n cssVariablePrefix: formatCSSVariablePrefix('token'),\n tokenizeTimeLimit: 0,\n })\n : new CodeToTokenTransformStream({\n ...this.options,\n themes: theme,\n highlighter,\n allowRecalls: true,\n defaultColor: false,\n cssVariablePrefix: formatCSSVariablePrefix('token'),\n tokenizeTimeLimit: 0,\n })\n )\n .pipeTo(\n new WritableStream({\n start(controller) {\n onStreamStart?.(controller);\n },\n close() {\n onStreamClose?.();\n },\n abort(reason) {\n onStreamAbort?.(reason);\n },\n write: this.handleWrite,\n }),\n { signal: this.abortController.signal }\n )\n .catch((error) => {\n // Ignore AbortError - it's expected when cleaning up\n if (error.name !== 'AbortError') {\n console.error('FileStream pipe error:', error);\n }\n });\n }\n\n private queuedTokens: (ThemedToken | RecallToken)[] = [];\n private handleWrite = (token: ThemedToken | RecallToken) => {\n // If we've recalled tokens we haven't rendered yet, we can just yeet them\n // and never apply them\n if ('recall' in token && this.queuedTokens.length >= token.recall) {\n this.queuedTokens.length = this.queuedTokens.length - token.recall;\n } else {\n this.queuedTokens.push(token);\n }\n queueRender(this.render);\n this.options.onStreamWrite?.(token);\n };\n\n private currentLineIndex: number;\n private currentLineElement: HTMLElement | undefined;\n private render = () => {\n this.options.onPreRender?.(this);\n const { gutter, content } = this.getOrCreateStreamColumns();\n const gutterFragment = document.createDocumentFragment();\n const contentFragment = document.createDocumentFragment();\n for (const token of this.queuedTokens) {\n if ('recall' in token) {\n if (this.currentLineElement == null) {\n throw new Error(\n 'FileStream.render: no current line element, shouldnt be possible to get here'\n );\n }\n if (token.recall > this.currentLineElement.childNodes.length) {\n throw new Error(\n `FileStream.render: Token recall exceed the current line, there's probably a bug...`\n );\n }\n for (let i = 0; i < token.recall; i++) {\n this.currentLineElement.lastChild?.remove();\n }\n } else {\n const span = createSpanFromToken(token);\n if (this.currentLineElement == null) {\n const { gutterLine, contentLine } = this.createLine();\n gutterFragment.appendChild(gutterLine);\n contentFragment.appendChild(contentLine);\n }\n this.currentLineElement?.appendChild(span);\n if (token.content === '\\n') {\n this.currentLineIndex++;\n const { gutterLine, contentLine } = this.createLine();\n gutterFragment.appendChild(gutterLine);\n contentFragment.appendChild(contentLine);\n }\n }\n }\n if (gutterFragment.childNodes.length > 0) {\n gutter.appendChild(gutterFragment);\n }\n if (contentFragment.childNodes.length > 0) {\n content.appendChild(contentFragment);\n }\n this.queuedTokens.length = 0;\n this.options.onPostRender?.(this);\n };\n\n private getOrCreateStreamColumns(): {\n gutter: HTMLElement;\n content: HTMLElement;\n } {\n if (this.code == null) {\n throw new Error('FileStream: expected code element to exist');\n }\n if (this.gutterElement != null && this.contentElement != null) {\n return { gutter: this.gutterElement, content: this.contentElement };\n }\n const gutter = document.createElement('div');\n gutter.dataset.gutter = '';\n const content = document.createElement('div');\n content.dataset.content = '';\n this.code.appendChild(gutter);\n this.code.appendChild(content);\n this.gutterElement = gutter;\n this.contentElement = content;\n return { gutter, content };\n }\n\n private updateRowSpan(): void {\n if (this.gutterElement != null) {\n this.gutterElement.style.gridRow = `span ${this.currentRowCount}`;\n }\n if (this.contentElement != null) {\n this.contentElement.style.gridRow = `span ${this.currentRowCount}`;\n }\n }\n\n private createLine(): { gutterLine: HTMLElement; contentLine: HTMLElement } {\n const lineNumber = this.currentLineIndex;\n const lineIndex = `${lineNumber - 1}`;\n const gutterLine = document.createElement('div');\n gutterLine.dataset.columnNumber = `${lineNumber}`;\n gutterLine.dataset.lineType = 'context';\n gutterLine.dataset.lineIndex = lineIndex;\n\n const numberContent = document.createElement('span');\n numberContent.dataset.lineNumberContent = '';\n numberContent.textContent = `${lineNumber}`;\n gutterLine.appendChild(numberContent);\n\n const contentLine = document.createElement('div');\n contentLine.dataset.line = `${lineNumber}`;\n contentLine.dataset.lineType = 'context';\n contentLine.dataset.lineIndex = lineIndex;\n\n this.currentRowCount += 1;\n this.updateRowSpan();\n this.currentLineElement = contentLine;\n return { gutterLine, contentLine };\n }\n\n private getOrCreateFileContainer(fileContainer?: HTMLElement): HTMLElement {\n if (\n (fileContainer != null && fileContainer === this.fileContainer) ||\n (fileContainer == null && this.fileContainer != null)\n ) {\n return this.fileContainer;\n }\n if (\n this.fileContainer != null &&\n fileContainer != null &&\n fileContainer !== this.fileContainer\n ) {\n this.themeCSSStyle = undefined;\n this.appliedThemeCSS = undefined;\n }\n this.fileContainer =\n fileContainer ?? document.createElement(DIFFS_TAG_NAME);\n return this.fileContainer;\n }\n\n private applyThemeState(\n container: HTMLElement,\n themeStyles: string,\n themeType: ThemeTypes,\n baseThemeType?: 'light' | 'dark'\n ): void {\n const shadowRoot =\n container.shadowRoot ?? container.attachShadow({ mode: 'open' });\n const effectiveThemeType = baseThemeType ?? themeType;\n const currentTheme = this.options.theme ?? DEFAULT_THEMES;\n const theme =\n typeof currentTheme === 'string' ? currentTheme : { ...currentTheme };\n const scrollbarGutter = getMeasuredScrollbarGutter(shadowRoot);\n if (\n this.themeCSSStyle?.parentNode === shadowRoot &&\n this.appliedThemeCSS?.themeStyles === themeStyles &&\n this.appliedThemeCSS.themeType === effectiveThemeType &&\n this.appliedThemeCSS.scrollbarGutter === scrollbarGutter\n ) {\n this.appliedThemeCSS.theme = theme;\n return;\n }\n this.themeCSSStyle = upsertHostThemeStyle({\n shadowRoot,\n currentNode: this.themeCSSStyle,\n themeCSS: wrapThemeCSS(themeStyles, effectiveThemeType, scrollbarGutter),\n });\n this.appliedThemeCSS =\n this.themeCSSStyle != null\n ? {\n theme,\n themeStyles,\n themeType: effectiveThemeType,\n baseThemeType,\n scrollbarGutter,\n }\n : undefined;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Virtualizer.d.ts","names":[],"sources":["../../src/components/Virtualizer.ts"],"mappings":";;;UAKU,kBAAA;EACR,QAAA,CAAS,KAAA;EACT,gBAAA;EACA,aAAA,CAAc,OAAA;AAAA;AAAA,UAiBC,iBAAA;EAnBf;EAqBA,cAAA;EApBA;EAsBA,0BAAA;EArBc;EAuBd,eAAA;AAAA;AAAA,cAaW,WAAA;EAAA,OACJ,MAAA;EAAA,OACA,oBAAA;EAAA,SAES,IAAA;EAAA,SACA,MAAA,EAAQ,iBAAA;EACjB,IAAA;EAAA,QACC,oBAAA;EAAA,QACA,SAAA;EAAA,QACA,MAAA;EAAA,QACA,YAAA;EAAA,QACA,WAAA;EAAA,QACA,IAAA;EAAA,QACA,gBAAA;EAAA,QAEA,cAAA;EAAA,QACA,SAAA;EAAA,QACA,gBAAA;EAAA,QACA,qBAAA;EAAA,QACA,gBAAA;EAAA,QAKA,cAAA;EAAA,QAEA,WAAA;EAAA,QACA,WAAA;EAAA,QACA,iBAAA;EAAA,QACA,iBAAA;EAAA,QACA,YAAA;EAER,WAAA,CAAY,MAAA,GAAS,OAAA,CAAQ,iBAAA;EAI7B,KAAA,CAAM,IAAA,EAAM,WAAA,GAAc,QAAA,EAAU,gBAAA,GAAmB,OAAA;EA2CvD,eAAA,CAAgB,QAAA,EAAU,kBAAA,EAAoB,QAAA;EAe9C,sBAAA,CAAuB,QAAA,EAAU,kBAAA;EAKjC,cAAA,IAAkB,kBAAA;EAYlB,iBAAA,CAAkB,UAAA,UAAoB,aAAA;EAAA,QAS9B,qBAAA;EAAA,QA0CA,WAAA;EAAA,QAaA,YAAA;EAeR,OAAA;EAsBA,0BAAA,CAA2B,OAAA,EAAS,WAAA;EAOpC,OAAA,CAAQ,SAAA,EAAW,WAAA,EAAa,QAAA,EAAU,kBAAA;EAmB1C,UAAA,CAAW,SAAA,EAAW,WAAA;EAAA,QAiBd,kBAAA;EAAA,QAQA,kBAAA;EAAA,QAYA,mBAAA;EAAA,QAYA,yBAAA;EAAA,QAwFA,SAAA;EAAA,QAiCA,cAAA;EAAA,QAsBA,eAAA;EAAA,QAqGA,wBAAA;EAAA,QAuCA,YAAA;EAAA,QAyBA,eAAA;EAAA,QAiBA,SAAA;EAiBR,YAAA;EAAA,QAMQ,yBAAA;AAAA"}
1
+ {"version":3,"file":"Virtualizer.d.ts","names":[],"sources":["../../src/components/Virtualizer.ts"],"mappings":";;;UAQU,kBAAA;EACR,QAAA,CAAS,KAAA;EACT,gBAAA;EACA,aAAA,CAAc,OAAA;AAAA;AAAA,UAiBC,iBAAA;EAnBf;EAqBA,cAAA;EApBA;EAsBA,0BAAA;EArBc;EAuBd,eAAA;AAAA;AAAA,cAaW,WAAA;EAAA,OACJ,MAAA;EAAA,OACA,oBAAA;EAAA,SAES,IAAA;EAAA,SACA,MAAA,EAAQ,iBAAA;EACjB,IAAA;EAAA,QACC,oBAAA;EAAA,QACA,SAAA;EAAA,QACA,MAAA;EAAA,QACA,YAAA;EAAA,QACA,WAAA;EAAA,QACA,IAAA;EAAA,QACA,gBAAA;EAAA,QAEA,cAAA;EAAA,QACA,SAAA;EAAA,QACA,gBAAA;EAAA,QACA,qBAAA;EAAA,QACA,gBAAA;EAAA,QAKA,cAAA;EAAA,QAEA,WAAA;EAAA,QACA,WAAA;EAAA,QACA,iBAAA;EAAA,QACA,iBAAA;EAAA,QACA,YAAA;EAER,WAAA,CAAY,MAAA,GAAS,OAAA,CAAQ,iBAAA;EAI7B,KAAA,CAAM,IAAA,EAAM,WAAA,GAAc,QAAA,EAAU,gBAAA,GAAmB,OAAA;EA2CvD,eAAA,CAAgB,QAAA,EAAU,kBAAA,EAAoB,QAAA;EAe9C,sBAAA,CAAuB,QAAA,EAAU,kBAAA;EAKjC,cAAA,IAAkB,kBAAA;EAYlB,iBAAA,CAAkB,UAAA,UAAoB,aAAA;EAAA,QAS9B,qBAAA;EAAA,QA0CA,WAAA;EAAA,QAaA,YAAA;EAeR,OAAA;EAuBA,0BAAA,CAA2B,OAAA,EAAS,WAAA;EAOpC,OAAA,CAAQ,SAAA,EAAW,WAAA,EAAa,QAAA,EAAU,kBAAA;EAmB1C,UAAA,CAAW,SAAA,EAAW,WAAA;EAAA,QAiBd,kBAAA;EAAA,QAQA,kBAAA;EAAA,QAYA,mBAAA;EAAA,QAYA,yBAAA;EAAA,QAwFA,SAAA;EAAA,QAiCA,cAAA;EAAA,QAsBA,eAAA;EAAA,QAqGA,wBAAA;EAAA,QAuCA,YAAA;EAAA,QAyBA,eAAA;EAAA,QAiBA,SAAA;EAiBR,YAAA;EAAA,QAMQ,yBAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { queueRender } from "../managers/UniversalRenderingManager.js";
1
+ import { dequeueRender, queueRender } from "../managers/UniversalRenderingManager.js";
2
2
  import { createWindowFromScrollPosition } from "../utils/createWindowFromScrollPosition.js";
3
3
  import { areVirtualWindowSpecsEqual } from "../utils/areVirtualWindowSpecsEqual.js";
4
4
  //#region src/components/Virtualizer.ts
@@ -156,6 +156,7 @@ var Virtualizer = class Virtualizer {
156
156
  }
157
157
  }
158
158
  cleanUp() {
159
+ dequeueRender(this.computeRenderRangeAndEmit);
159
160
  this.resizeObserver?.disconnect();
160
161
  this.resizeObserver = void 0;
161
162
  this.intersectionObserver?.disconnect();
@@ -1 +1 @@
1
- {"version":3,"file":"Virtualizer.js","names":[],"sources":["../../src/components/Virtualizer.ts"],"sourcesContent":["import { queueRender } from '../managers/UniversalRenderingManager';\nimport type { VirtualWindowSpecs } from '../types';\nimport { areVirtualWindowSpecsEqual } from '../utils/areVirtualWindowSpecsEqual';\nimport { createWindowFromScrollPosition } from '../utils/createWindowFromScrollPosition';\n\ninterface SubscribedInstance {\n onRender(dirty: boolean): boolean;\n reconcileHeights(): boolean;\n setVisibility(visible: boolean): void;\n}\n\ninterface ScrollAnchor {\n fileElement: HTMLElement;\n fileTypeOffset: 'top' | 'bottom';\n fileOffset: number;\n lineIndex: string | undefined;\n lineOffset: number | undefined;\n}\n\n// 800 seems like the healthy overscan required to\n// keep safari from blanking... if we catch it tho, maybe 900\nconst DEFAULT_OVERSCROLL_SIZE = 1000;\nconst INTERSECTION_OBSERVER_MARGIN = DEFAULT_OVERSCROLL_SIZE * 4;\nconst INTERSECTION_OBSERVER_THRESHOLD = [0, 0.000001, 0.99999, 1];\n\nexport interface VirtualizerConfig {\n /** Extra pixels rendered above and below the viewport to reduce blanking during fast scrolls. */\n overscrollSize: number;\n /** Margin used by IntersectionObserver to decide when items should be considered visible. */\n intersectionObserverMargin: number;\n /** Enables noisy resize logs to help tune metrics and investigate scroll jitter. */\n resizeDebugging: boolean;\n}\n\nconst DEFAULT_VIRTUALIZER_CONFIG: VirtualizerConfig = {\n overscrollSize: DEFAULT_OVERSCROLL_SIZE,\n intersectionObserverMargin: INTERSECTION_OBSERVER_MARGIN,\n resizeDebugging: false,\n};\n\nlet lastSize = 0;\n\nlet instance = -1;\n\nexport class Virtualizer {\n static __STOP: boolean = false;\n static __lastScrollPosition = 0;\n\n public readonly __id: string = `virtualizer-${++instance}`;\n public readonly config: VirtualizerConfig;\n public type = 'simple' as const;\n private intersectionObserver: IntersectionObserver | undefined;\n private scrollTop: number = 0;\n private height: number = 0;\n private scrollHeight: number = 0;\n private windowSpecs: VirtualWindowSpecs = { top: 0, bottom: 0 };\n private root: HTMLElement | Document | undefined;\n private contentContainer: HTMLElement | undefined;\n\n private resizeObserver: ResizeObserver | undefined;\n private observers: Map<HTMLElement, SubscribedInstance> = new Map();\n private visibleInstances: Map<HTMLElement, SubscribedInstance> = new Map();\n private visibleInstancesDirty: boolean = false;\n private instancesChanged: Set<SubscribedInstance> = new Set();\n // Instances whose content was (re)painted outside a virtualizer-driven\n // onRender — host/React render calls, async highlight completions — and\n // still need a measured-height reconciliation pass. See\n // requestHeightReconcile.\n private reconcileQueue: Set<SubscribedInstance> = new Set();\n\n private scrollDirty = true;\n private heightDirty = true;\n private scrollHeightDirty = true;\n private renderedObservers = 0;\n private connectQueue: Map<HTMLElement, SubscribedInstance> = new Map();\n\n constructor(config?: Partial<VirtualizerConfig>) {\n this.config = { ...DEFAULT_VIRTUALIZER_CONFIG, ...config };\n }\n\n setup(root: HTMLElement | Document, contentContainer?: Element): void {\n if (this.root != null) {\n return;\n }\n this.root = root;\n this.resizeObserver = new ResizeObserver(this.handleContainerResize);\n this.intersectionObserver = new IntersectionObserver(\n this.handleIntersectionChange,\n {\n root: this.root,\n threshold: INTERSECTION_OBSERVER_THRESHOLD,\n rootMargin: `${this.config.intersectionObserverMargin}px 0px ${this.config.intersectionObserverMargin}px 0px`,\n // FIXME(amadeus): Figure out the other settings we'll want in here, or\n // if we should make them configurable...\n }\n );\n if (root instanceof Document) {\n this.setupWindow();\n } else {\n this.setupElement(contentContainer);\n }\n\n // FIXME(amadeus): Remove me before release\n window.__INSTANCE = this;\n window.__TOGGLE = () => {\n if (Virtualizer.__STOP) {\n Virtualizer.__STOP = false;\n const scroller = this.getScrollContainerElement() ?? window;\n scroller.scrollTo({ top: Virtualizer.__lastScrollPosition });\n queueRender(this.computeRenderRangeAndEmit);\n } else {\n Virtualizer.__lastScrollPosition = this.getScrollTop();\n Virtualizer.__STOP = true;\n }\n };\n for (const [container, instance] of this.connectQueue.entries()) {\n this.connect(container, instance);\n }\n this.connectQueue.clear();\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n instanceChanged(instance: SubscribedInstance, domDirty: boolean): void {\n this.instancesChanged.add(instance);\n if (domDirty) {\n this.markDOMDirty();\n }\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n // The render loop only reconciles heights for instances it repainted\n // itself, but content can also land outside a virtualizer pass (a\n // host/React-driven render call, an async highlight completion). Those\n // renders queue themselves here so their measured line deltas — wrapped\n // lines, annotation heights — are re-captured; otherwise a layout reset\n // (e.g. an edit-mode expandUnchanged flip) leaves the instance stuck on\n // its baseline estimate and its placeholder renders the wrong height.\n requestHeightReconcile(instance: SubscribedInstance): void {\n this.reconcileQueue.add(instance);\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n getWindowSpecs(): VirtualWindowSpecs {\n if (this.windowSpecs.top === 0 && this.windowSpecs.bottom === 0) {\n this.windowSpecs = createWindowFromScrollPosition({\n scrollTop: this.getScrollTop(),\n height: this.getHeight(),\n scrollHeight: this.getScrollHeight(),\n overscrollSize: this.config.overscrollSize,\n });\n }\n return this.windowSpecs;\n }\n\n isInstanceVisible(elementTop: number, elementHeight: number): boolean {\n const scrollTop = this.getScrollTop();\n const height = this.getHeight();\n const margin = this.config.intersectionObserverMargin;\n const top = scrollTop - margin;\n const bottom = scrollTop + height + margin;\n return !(elementTop < top - elementHeight || elementTop > bottom);\n }\n\n private handleContainerResize = (entries: ResizeObserverEntry[]) => {\n if (this.root == null) return;\n let shouldQueueUpdate = false;\n for (const entry of entries) {\n const blockSize = entry.borderBoxSize[0].blockSize;\n if (this.root instanceof Document) {\n if (blockSize !== this.scrollHeight) {\n this.scrollHeightDirty = true;\n shouldQueueUpdate = true;\n if (this.config.resizeDebugging) {\n console.log('Virtualizer: content size change', this.__id, {\n sizeChange: blockSize - lastSize,\n newSize: blockSize,\n });\n lastSize = blockSize;\n }\n }\n } else {\n if (entry.target === this.root) {\n if (blockSize !== this.height) {\n this.heightDirty = true;\n shouldQueueUpdate = true;\n }\n } else if (entry.target === this.contentContainer) {\n this.scrollHeightDirty = true;\n shouldQueueUpdate = true;\n if (this.config.resizeDebugging) {\n console.log('Virtualizer: scroller size change', this.__id, {\n sizeChange: blockSize - lastSize,\n newSize: blockSize,\n });\n lastSize = blockSize;\n }\n }\n }\n }\n\n if (shouldQueueUpdate) {\n queueRender(this.computeRenderRangeAndEmit);\n }\n };\n\n private setupWindow() {\n if (this.root == null || !(this.root instanceof Document)) {\n throw new Error('Virtualizer.setupWindow: Invalid setup method');\n }\n window.addEventListener('scroll', this.handleWindowScroll, {\n passive: true,\n });\n window.addEventListener('resize', this.handleWindowResize, {\n passive: true,\n });\n this.resizeObserver?.observe(this.root.documentElement);\n }\n\n private setupElement(contentContainer: Element | undefined) {\n if (this.root == null || this.root instanceof Document) {\n throw new Error('Virtualizer.setupElement: Invalid setup method');\n }\n this.root.addEventListener('scroll', this.handleElementScroll, {\n passive: true,\n });\n this.resizeObserver?.observe(this.root);\n contentContainer ??= this.root.firstElementChild ?? undefined;\n if (contentContainer instanceof HTMLElement) {\n this.contentContainer = contentContainer;\n this.resizeObserver?.observe(contentContainer);\n }\n }\n\n cleanUp(): void {\n this.resizeObserver?.disconnect();\n this.resizeObserver = undefined;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = undefined;\n this.root?.removeEventListener('scroll', this.handleElementScroll);\n window.removeEventListener('scroll', this.handleWindowScroll);\n window.removeEventListener('resize', this.handleWindowResize);\n this.root = undefined;\n this.contentContainer = undefined;\n this.observers.clear();\n this.visibleInstances.clear();\n this.instancesChanged.clear();\n this.reconcileQueue.clear();\n this.connectQueue.clear();\n this.visibleInstancesDirty = false;\n this.windowSpecs = { top: 0, bottom: 0 };\n this.scrollTop = 0;\n this.height = 0;\n this.scrollHeight = 0;\n }\n\n getOffsetInScrollContainer(element: HTMLElement): number {\n return (\n this.getScrollTop() +\n getRelativeBoundingTop(element, this.getScrollContainerElement())\n );\n }\n\n connect(container: HTMLElement, instance: SubscribedInstance): () => void {\n if (this.observers.has(container)) {\n throw new Error('Virtualizer.connect: instance is already connected...');\n }\n // If we are racing against the intersectionObserver, then we should just\n // queue up the connection for when the observer does get set up\n if (this.intersectionObserver == null) {\n this.connectQueue.set(container, instance);\n } else {\n // FIXME(amadeus): Go through the connection phase a bit more closely...\n this.intersectionObserver.observe(container);\n this.observers.set(container, instance);\n this.instancesChanged.add(instance);\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n return () => this.disconnect(container);\n }\n\n disconnect(container: HTMLElement): void {\n const instance = this.observers.get(container);\n this.connectQueue.delete(container);\n if (instance == null) {\n return;\n }\n this.intersectionObserver?.unobserve(container);\n this.observers.delete(container);\n this.instancesChanged.delete(instance);\n this.reconcileQueue.delete(instance);\n if (this.visibleInstances.delete(container)) {\n this.visibleInstancesDirty = true;\n }\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n private handleWindowResize = () => {\n if (Virtualizer.__STOP || window.innerHeight === this.height) {\n return;\n }\n this.heightDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private handleWindowScroll = () => {\n if (\n Virtualizer.__STOP ||\n this.root == null ||\n !(this.root instanceof Document)\n ) {\n return;\n }\n this.scrollDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private handleElementScroll = () => {\n if (\n Virtualizer.__STOP ||\n this.root == null ||\n this.root instanceof Document\n ) {\n return;\n }\n this.scrollDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private computeRenderRangeAndEmit = () => {\n if (Virtualizer.__STOP) {\n return;\n }\n const wrapperDirty = this.heightDirty || this.scrollHeightDirty;\n if (\n !this.scrollDirty &&\n !this.scrollHeightDirty &&\n !this.heightDirty &&\n this.renderedObservers === this.observers.size &&\n !this.visibleInstancesDirty &&\n this.instancesChanged.size === 0 &&\n this.reconcileQueue.size === 0\n ) {\n // NOTE(amadeus): Is this a safe assumption/optimization?\n return;\n }\n let instancesHaveChanged = this.instancesChanged.size > 0;\n\n // If we got an emitted update from a bunch of instances, we should skip\n // the window check first and attempt to render with existing logic first\n // and then queue up a corrected render after\n if (this.instancesChanged.size === 0) {\n const windowSpecs = createWindowFromScrollPosition({\n scrollTop: this.getScrollTop(),\n height: this.getHeight(),\n scrollHeight: this.getScrollHeight(),\n overscrollSize: this.config.overscrollSize,\n });\n if (\n !wrapperDirty &&\n areVirtualWindowSpecsEqual(this.windowSpecs, windowSpecs) &&\n this.renderedObservers === this.observers.size &&\n !this.visibleInstancesDirty &&\n this.reconcileQueue.size === 0\n ) {\n return;\n }\n this.windowSpecs = windowSpecs;\n }\n this.visibleInstancesDirty = false;\n this.renderedObservers = this.observers.size;\n const anchor = this.getScrollAnchor(this.height);\n const updatedInstances = new Set<SubscribedInstance>();\n // NOTE(amadeus): If the wrapper is dirty, we need to force every component\n // to re-render\n for (const instance of wrapperDirty\n ? this.observers.values()\n : this.visibleInstances.values()) {\n if (instance.onRender(wrapperDirty)) {\n updatedInstances.add(instance);\n }\n }\n for (const instance of this.instancesChanged) {\n if (updatedInstances.has(instance)) continue;\n if (instance.onRender(wrapperDirty)) {\n updatedInstances.add(instance);\n }\n }\n\n this.scrollFix(anchor);\n\n for (const instance of updatedInstances) {\n this.reconcileQueue.delete(instance);\n instance.reconcileHeights();\n }\n // Reconcile externally-rendered instances that may have new heights\n for (const instance of this.reconcileQueue) {\n if (instance.reconcileHeights()) {\n instancesHaveChanged = true;\n }\n }\n this.reconcileQueue.clear();\n instancesHaveChanged ||= this.instancesChanged.size > 0;\n\n // Reconciliation reads virtualized offsets and can consume dirty geometry\n // flags, so mark after it when an instance update needs a corrected pass.\n if (instancesHaveChanged) {\n this.markDOMDirty();\n }\n\n if (instancesHaveChanged || wrapperDirty) {\n queueRender(this.computeRenderRangeAndEmit);\n }\n updatedInstances.clear();\n this.instancesChanged.clear();\n };\n\n private scrollFix(anchor: ScrollAnchor | undefined) {\n if (anchor == null) {\n return;\n }\n const scrollContainer = this.getScrollContainerElement();\n const { lineIndex, lineOffset, fileElement, fileOffset, fileTypeOffset } =\n anchor;\n if (lineIndex != null && lineOffset != null) {\n const element = fileElement.shadowRoot?.querySelector(\n `[data-line][data-line-index=\"${lineIndex}\"]`\n );\n if (element instanceof HTMLElement) {\n const top = getRelativeBoundingTop(element, scrollContainer);\n if (top !== lineOffset) {\n const scrollOffset = top - lineOffset;\n this.applyScrollFix(scrollOffset);\n }\n return;\n }\n }\n const top = getRelativeBoundingTop(fileElement, scrollContainer);\n if (fileTypeOffset === 'top') {\n if (top !== fileOffset) {\n this.applyScrollFix(top - fileOffset);\n }\n } else {\n const bottom = top + fileElement.getBoundingClientRect().height;\n if (bottom !== fileOffset) {\n this.applyScrollFix(bottom - fileOffset);\n }\n }\n }\n\n private applyScrollFix(scrollOffset: number) {\n if (this.root == null || this.root instanceof Document) {\n window.scrollTo({\n top: window.scrollY + scrollOffset,\n behavior: 'instant',\n });\n } else {\n this.root.scrollTo({\n top: this.root.scrollTop + scrollOffset,\n behavior: 'instant',\n });\n }\n // Because we fixed our scroll positions, it means something resized or\n // moved around, so we should mark everything as dirty so the\n // reconciliation call will get the latest data when figuring calling\n // .getOffsetInScrollContainer\n this.markDOMDirty();\n }\n\n // This function tries to figure out the closest file or line to the viewport\n // top that's visible to use as a relative marker for how to fix scroll\n // position after issuing dom updates\n private getScrollAnchor(viewportHeight: number): ScrollAnchor | undefined {\n const scrollContainer = this.getScrollContainerElement();\n let bestAnchor: ScrollAnchor | undefined;\n\n for (const [fileElement] of this.visibleInstances.entries()) {\n const fileTop = getRelativeBoundingTop(fileElement, scrollContainer);\n const fileBottom = fileTop + fileElement.offsetHeight;\n\n // Determine file offset and type based on position\n // Only use bottom anchor when entire file is above viewport\n let fileOffset: number;\n let fileTypeOffset: 'top' | 'bottom';\n if (fileBottom <= 0) {\n // Entire file is above viewport - use bottom as anchor\n fileOffset = fileBottom;\n fileTypeOffset = 'bottom';\n } else {\n // File is at least partially visible or below - use top\n fileOffset = fileTop;\n fileTypeOffset = 'top';\n }\n\n // Find the best line (first fully visible) within this file\n let bestLineIndex: string | undefined;\n let bestLineOffset: number | undefined;\n\n // Only search for lines if file potentially intersects viewport\n if (fileBottom > 0 && fileTop < viewportHeight) {\n for (const line of fileElement.shadowRoot?.querySelectorAll(\n '[data-line][data-line-index]'\n ) ?? []) {\n if (!(line instanceof HTMLElement)) continue;\n const lineIndex = line.dataset.lineIndex;\n if (lineIndex == null) continue;\n\n const lineOffset = getRelativeBoundingTop(line, scrollContainer);\n\n // Ignore lines with negative offsets (above viewport top)\n if (lineOffset < 0) continue;\n\n // First visible line in DOM order is the best one because\n // querySelectorAll will grab lines in order as they appear in the\n // DOM\n bestLineIndex = lineIndex;\n bestLineOffset = lineOffset;\n break;\n }\n }\n\n // If we already have an anchor with a visible line, skip files without one\n if (bestAnchor?.lineOffset != null && bestLineOffset == null) {\n continue;\n }\n\n // Decide if this file should become the new best anchor\n let shouldReplace = false;\n // If we don't already have an anchor we should set one\n if (bestAnchor == null) {\n shouldReplace = true;\n }\n // If we found a better line anchor, we should replace the old one\n else if (\n bestLineOffset != null &&\n (bestAnchor.lineOffset == null ||\n bestLineOffset < bestAnchor.lineOffset)\n ) {\n shouldReplace = true;\n }\n // Otherwise we need to compare file only anchors\n else if (bestLineOffset == null && bestAnchor.lineOffset == null) {\n // Favor files with their tops in view\n if (\n fileOffset >= 0 &&\n (bestAnchor.fileOffset < 0 || fileOffset < bestAnchor.fileOffset)\n ) {\n shouldReplace = true;\n }\n // Or the closest file\n else if (\n fileOffset < 0 &&\n bestAnchor.fileOffset < 0 &&\n fileOffset > bestAnchor.fileOffset\n ) {\n shouldReplace = true;\n }\n }\n\n if (shouldReplace) {\n bestAnchor = {\n fileElement,\n fileTypeOffset,\n fileOffset,\n lineIndex: bestLineIndex,\n lineOffset: bestLineOffset,\n };\n }\n }\n\n return bestAnchor;\n }\n\n private handleIntersectionChange = (\n entries: IntersectionObserverEntry[]\n ): void => {\n this.scrollDirty = true;\n for (const { target, isIntersecting } of entries) {\n if (!(target instanceof HTMLElement)) {\n throw new Error(\n 'Virtualizer.handleIntersectionChange: target not an HTMLElement'\n );\n }\n const instance = this.observers.get(target);\n // IntersectionObserver delivers entries asynchronously, so an entry can\n // arrive after the target was unobserved via disconnect() or releaseElement().\n if (instance == null) {\n continue;\n }\n if (isIntersecting && !this.visibleInstances.has(target)) {\n instance.setVisibility(true);\n this.visibleInstances.set(target, instance);\n this.visibleInstancesDirty = true;\n } else if (!isIntersecting && this.visibleInstances.has(target)) {\n instance.setVisibility(false);\n this.visibleInstances.delete(target);\n this.visibleInstancesDirty = true;\n }\n }\n\n if (this.visibleInstancesDirty) {\n // Since this call is already debounced, should we just call\n // computeRenderRangeAndEmit directly?\n queueRender(this.computeRenderRangeAndEmit);\n }\n // Debug logging for visible instances\n // console.log(\n // 'handleIntersectionChange',\n // ...Array.from(this.visibleInstances.keys())\n // );\n };\n\n private getScrollTop() {\n if (!this.scrollDirty) {\n return this.scrollTop;\n }\n this.scrollDirty = false;\n let scrollTop = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return window.scrollY;\n }\n return this.root.scrollTop;\n })();\n\n // Lets always make sure to clamp scroll position cases of\n // over/bounce scroll\n scrollTop = Math.max(\n 0,\n Math.min(scrollTop, this.getScrollHeight() - this.getHeight())\n );\n this.scrollTop = scrollTop;\n return scrollTop;\n }\n\n private getScrollHeight() {\n if (!this.scrollHeightDirty) {\n return this.scrollHeight;\n }\n this.scrollHeightDirty = false;\n this.scrollHeight = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return this.root.documentElement.scrollHeight;\n }\n return this.root.scrollHeight;\n })();\n return this.scrollHeight;\n }\n\n private getHeight() {\n if (!this.heightDirty) {\n return this.height;\n }\n this.heightDirty = false;\n this.height = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return globalThis.innerHeight;\n }\n return this.root.getBoundingClientRect().height;\n })();\n return this.height;\n }\n\n markDOMDirty(): void {\n this.scrollDirty = true;\n this.scrollHeightDirty = true;\n this.heightDirty = true;\n }\n\n private getScrollContainerElement(): HTMLElement | undefined {\n return this.root == null || this.root instanceof Document\n ? undefined\n : this.root;\n }\n}\n\n// This function is like a generalized getBoundingClientRect for it's relative\n// scroll container\nfunction getRelativeBoundingTop(\n element: HTMLElement,\n scrollContainer: HTMLElement | undefined\n) {\n const rect = element.getBoundingClientRect();\n const scrollContainerTop = scrollContainer?.getBoundingClientRect().top ?? 0;\n return rect.top - scrollContainerTop;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Virtualizer.js","names":[],"sources":["../../src/components/Virtualizer.ts"],"sourcesContent":["import {\n dequeueRender,\n queueRender,\n} from '../managers/UniversalRenderingManager';\nimport type { VirtualWindowSpecs } from '../types';\nimport { areVirtualWindowSpecsEqual } from '../utils/areVirtualWindowSpecsEqual';\nimport { createWindowFromScrollPosition } from '../utils/createWindowFromScrollPosition';\n\ninterface SubscribedInstance {\n onRender(dirty: boolean): boolean;\n reconcileHeights(): boolean;\n setVisibility(visible: boolean): void;\n}\n\ninterface ScrollAnchor {\n fileElement: HTMLElement;\n fileTypeOffset: 'top' | 'bottom';\n fileOffset: number;\n lineIndex: string | undefined;\n lineOffset: number | undefined;\n}\n\n// 800 seems like the healthy overscan required to\n// keep safari from blanking... if we catch it tho, maybe 900\nconst DEFAULT_OVERSCROLL_SIZE = 1000;\nconst INTERSECTION_OBSERVER_MARGIN = DEFAULT_OVERSCROLL_SIZE * 4;\nconst INTERSECTION_OBSERVER_THRESHOLD = [0, 0.000001, 0.99999, 1];\n\nexport interface VirtualizerConfig {\n /** Extra pixels rendered above and below the viewport to reduce blanking during fast scrolls. */\n overscrollSize: number;\n /** Margin used by IntersectionObserver to decide when items should be considered visible. */\n intersectionObserverMargin: number;\n /** Enables noisy resize logs to help tune metrics and investigate scroll jitter. */\n resizeDebugging: boolean;\n}\n\nconst DEFAULT_VIRTUALIZER_CONFIG: VirtualizerConfig = {\n overscrollSize: DEFAULT_OVERSCROLL_SIZE,\n intersectionObserverMargin: INTERSECTION_OBSERVER_MARGIN,\n resizeDebugging: false,\n};\n\nlet lastSize = 0;\n\nlet instance = -1;\n\nexport class Virtualizer {\n static __STOP: boolean = false;\n static __lastScrollPosition = 0;\n\n public readonly __id: string = `virtualizer-${++instance}`;\n public readonly config: VirtualizerConfig;\n public type = 'simple' as const;\n private intersectionObserver: IntersectionObserver | undefined;\n private scrollTop: number = 0;\n private height: number = 0;\n private scrollHeight: number = 0;\n private windowSpecs: VirtualWindowSpecs = { top: 0, bottom: 0 };\n private root: HTMLElement | Document | undefined;\n private contentContainer: HTMLElement | undefined;\n\n private resizeObserver: ResizeObserver | undefined;\n private observers: Map<HTMLElement, SubscribedInstance> = new Map();\n private visibleInstances: Map<HTMLElement, SubscribedInstance> = new Map();\n private visibleInstancesDirty: boolean = false;\n private instancesChanged: Set<SubscribedInstance> = new Set();\n // Instances whose content was (re)painted outside a virtualizer-driven\n // onRender — host/React render calls, async highlight completions — and\n // still need a measured-height reconciliation pass. See\n // requestHeightReconcile.\n private reconcileQueue: Set<SubscribedInstance> = new Set();\n\n private scrollDirty = true;\n private heightDirty = true;\n private scrollHeightDirty = true;\n private renderedObservers = 0;\n private connectQueue: Map<HTMLElement, SubscribedInstance> = new Map();\n\n constructor(config?: Partial<VirtualizerConfig>) {\n this.config = { ...DEFAULT_VIRTUALIZER_CONFIG, ...config };\n }\n\n setup(root: HTMLElement | Document, contentContainer?: Element): void {\n if (this.root != null) {\n return;\n }\n this.root = root;\n this.resizeObserver = new ResizeObserver(this.handleContainerResize);\n this.intersectionObserver = new IntersectionObserver(\n this.handleIntersectionChange,\n {\n root: this.root,\n threshold: INTERSECTION_OBSERVER_THRESHOLD,\n rootMargin: `${this.config.intersectionObserverMargin}px 0px ${this.config.intersectionObserverMargin}px 0px`,\n // FIXME(amadeus): Figure out the other settings we'll want in here, or\n // if we should make them configurable...\n }\n );\n if (root instanceof Document) {\n this.setupWindow();\n } else {\n this.setupElement(contentContainer);\n }\n\n // FIXME(amadeus): Remove me before release\n window.__INSTANCE = this;\n window.__TOGGLE = () => {\n if (Virtualizer.__STOP) {\n Virtualizer.__STOP = false;\n const scroller = this.getScrollContainerElement() ?? window;\n scroller.scrollTo({ top: Virtualizer.__lastScrollPosition });\n queueRender(this.computeRenderRangeAndEmit);\n } else {\n Virtualizer.__lastScrollPosition = this.getScrollTop();\n Virtualizer.__STOP = true;\n }\n };\n for (const [container, instance] of this.connectQueue.entries()) {\n this.connect(container, instance);\n }\n this.connectQueue.clear();\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n instanceChanged(instance: SubscribedInstance, domDirty: boolean): void {\n this.instancesChanged.add(instance);\n if (domDirty) {\n this.markDOMDirty();\n }\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n // The render loop only reconciles heights for instances it repainted\n // itself, but content can also land outside a virtualizer pass (a\n // host/React-driven render call, an async highlight completion). Those\n // renders queue themselves here so their measured line deltas — wrapped\n // lines, annotation heights — are re-captured; otherwise a layout reset\n // (e.g. an edit-mode expandUnchanged flip) leaves the instance stuck on\n // its baseline estimate and its placeholder renders the wrong height.\n requestHeightReconcile(instance: SubscribedInstance): void {\n this.reconcileQueue.add(instance);\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n getWindowSpecs(): VirtualWindowSpecs {\n if (this.windowSpecs.top === 0 && this.windowSpecs.bottom === 0) {\n this.windowSpecs = createWindowFromScrollPosition({\n scrollTop: this.getScrollTop(),\n height: this.getHeight(),\n scrollHeight: this.getScrollHeight(),\n overscrollSize: this.config.overscrollSize,\n });\n }\n return this.windowSpecs;\n }\n\n isInstanceVisible(elementTop: number, elementHeight: number): boolean {\n const scrollTop = this.getScrollTop();\n const height = this.getHeight();\n const margin = this.config.intersectionObserverMargin;\n const top = scrollTop - margin;\n const bottom = scrollTop + height + margin;\n return !(elementTop < top - elementHeight || elementTop > bottom);\n }\n\n private handleContainerResize = (entries: ResizeObserverEntry[]) => {\n if (this.root == null) return;\n let shouldQueueUpdate = false;\n for (const entry of entries) {\n const blockSize = entry.borderBoxSize[0].blockSize;\n if (this.root instanceof Document) {\n if (blockSize !== this.scrollHeight) {\n this.scrollHeightDirty = true;\n shouldQueueUpdate = true;\n if (this.config.resizeDebugging) {\n console.log('Virtualizer: content size change', this.__id, {\n sizeChange: blockSize - lastSize,\n newSize: blockSize,\n });\n lastSize = blockSize;\n }\n }\n } else {\n if (entry.target === this.root) {\n if (blockSize !== this.height) {\n this.heightDirty = true;\n shouldQueueUpdate = true;\n }\n } else if (entry.target === this.contentContainer) {\n this.scrollHeightDirty = true;\n shouldQueueUpdate = true;\n if (this.config.resizeDebugging) {\n console.log('Virtualizer: scroller size change', this.__id, {\n sizeChange: blockSize - lastSize,\n newSize: blockSize,\n });\n lastSize = blockSize;\n }\n }\n }\n }\n\n if (shouldQueueUpdate) {\n queueRender(this.computeRenderRangeAndEmit);\n }\n };\n\n private setupWindow() {\n if (this.root == null || !(this.root instanceof Document)) {\n throw new Error('Virtualizer.setupWindow: Invalid setup method');\n }\n window.addEventListener('scroll', this.handleWindowScroll, {\n passive: true,\n });\n window.addEventListener('resize', this.handleWindowResize, {\n passive: true,\n });\n this.resizeObserver?.observe(this.root.documentElement);\n }\n\n private setupElement(contentContainer: Element | undefined) {\n if (this.root == null || this.root instanceof Document) {\n throw new Error('Virtualizer.setupElement: Invalid setup method');\n }\n this.root.addEventListener('scroll', this.handleElementScroll, {\n passive: true,\n });\n this.resizeObserver?.observe(this.root);\n contentContainer ??= this.root.firstElementChild ?? undefined;\n if (contentContainer instanceof HTMLElement) {\n this.contentContainer = contentContainer;\n this.resizeObserver?.observe(contentContainer);\n }\n }\n\n cleanUp(): void {\n dequeueRender(this.computeRenderRangeAndEmit);\n this.resizeObserver?.disconnect();\n this.resizeObserver = undefined;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = undefined;\n this.root?.removeEventListener('scroll', this.handleElementScroll);\n window.removeEventListener('scroll', this.handleWindowScroll);\n window.removeEventListener('resize', this.handleWindowResize);\n this.root = undefined;\n this.contentContainer = undefined;\n this.observers.clear();\n this.visibleInstances.clear();\n this.instancesChanged.clear();\n this.reconcileQueue.clear();\n this.connectQueue.clear();\n this.visibleInstancesDirty = false;\n this.windowSpecs = { top: 0, bottom: 0 };\n this.scrollTop = 0;\n this.height = 0;\n this.scrollHeight = 0;\n }\n\n getOffsetInScrollContainer(element: HTMLElement): number {\n return (\n this.getScrollTop() +\n getRelativeBoundingTop(element, this.getScrollContainerElement())\n );\n }\n\n connect(container: HTMLElement, instance: SubscribedInstance): () => void {\n if (this.observers.has(container)) {\n throw new Error('Virtualizer.connect: instance is already connected...');\n }\n // If we are racing against the intersectionObserver, then we should just\n // queue up the connection for when the observer does get set up\n if (this.intersectionObserver == null) {\n this.connectQueue.set(container, instance);\n } else {\n // FIXME(amadeus): Go through the connection phase a bit more closely...\n this.intersectionObserver.observe(container);\n this.observers.set(container, instance);\n this.instancesChanged.add(instance);\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n return () => this.disconnect(container);\n }\n\n disconnect(container: HTMLElement): void {\n const instance = this.observers.get(container);\n this.connectQueue.delete(container);\n if (instance == null) {\n return;\n }\n this.intersectionObserver?.unobserve(container);\n this.observers.delete(container);\n this.instancesChanged.delete(instance);\n this.reconcileQueue.delete(instance);\n if (this.visibleInstances.delete(container)) {\n this.visibleInstancesDirty = true;\n }\n this.markDOMDirty();\n queueRender(this.computeRenderRangeAndEmit);\n }\n\n private handleWindowResize = () => {\n if (Virtualizer.__STOP || window.innerHeight === this.height) {\n return;\n }\n this.heightDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private handleWindowScroll = () => {\n if (\n Virtualizer.__STOP ||\n this.root == null ||\n !(this.root instanceof Document)\n ) {\n return;\n }\n this.scrollDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private handleElementScroll = () => {\n if (\n Virtualizer.__STOP ||\n this.root == null ||\n this.root instanceof Document\n ) {\n return;\n }\n this.scrollDirty = true;\n queueRender(this.computeRenderRangeAndEmit);\n };\n\n private computeRenderRangeAndEmit = () => {\n if (Virtualizer.__STOP) {\n return;\n }\n const wrapperDirty = this.heightDirty || this.scrollHeightDirty;\n if (\n !this.scrollDirty &&\n !this.scrollHeightDirty &&\n !this.heightDirty &&\n this.renderedObservers === this.observers.size &&\n !this.visibleInstancesDirty &&\n this.instancesChanged.size === 0 &&\n this.reconcileQueue.size === 0\n ) {\n // NOTE(amadeus): Is this a safe assumption/optimization?\n return;\n }\n let instancesHaveChanged = this.instancesChanged.size > 0;\n\n // If we got an emitted update from a bunch of instances, we should skip\n // the window check first and attempt to render with existing logic first\n // and then queue up a corrected render after\n if (this.instancesChanged.size === 0) {\n const windowSpecs = createWindowFromScrollPosition({\n scrollTop: this.getScrollTop(),\n height: this.getHeight(),\n scrollHeight: this.getScrollHeight(),\n overscrollSize: this.config.overscrollSize,\n });\n if (\n !wrapperDirty &&\n areVirtualWindowSpecsEqual(this.windowSpecs, windowSpecs) &&\n this.renderedObservers === this.observers.size &&\n !this.visibleInstancesDirty &&\n this.reconcileQueue.size === 0\n ) {\n return;\n }\n this.windowSpecs = windowSpecs;\n }\n this.visibleInstancesDirty = false;\n this.renderedObservers = this.observers.size;\n const anchor = this.getScrollAnchor(this.height);\n const updatedInstances = new Set<SubscribedInstance>();\n // NOTE(amadeus): If the wrapper is dirty, we need to force every component\n // to re-render\n for (const instance of wrapperDirty\n ? this.observers.values()\n : this.visibleInstances.values()) {\n if (instance.onRender(wrapperDirty)) {\n updatedInstances.add(instance);\n }\n }\n for (const instance of this.instancesChanged) {\n if (updatedInstances.has(instance)) continue;\n if (instance.onRender(wrapperDirty)) {\n updatedInstances.add(instance);\n }\n }\n\n this.scrollFix(anchor);\n\n for (const instance of updatedInstances) {\n this.reconcileQueue.delete(instance);\n instance.reconcileHeights();\n }\n // Reconcile externally-rendered instances that may have new heights\n for (const instance of this.reconcileQueue) {\n if (instance.reconcileHeights()) {\n instancesHaveChanged = true;\n }\n }\n this.reconcileQueue.clear();\n instancesHaveChanged ||= this.instancesChanged.size > 0;\n\n // Reconciliation reads virtualized offsets and can consume dirty geometry\n // flags, so mark after it when an instance update needs a corrected pass.\n if (instancesHaveChanged) {\n this.markDOMDirty();\n }\n\n if (instancesHaveChanged || wrapperDirty) {\n queueRender(this.computeRenderRangeAndEmit);\n }\n updatedInstances.clear();\n this.instancesChanged.clear();\n };\n\n private scrollFix(anchor: ScrollAnchor | undefined) {\n if (anchor == null) {\n return;\n }\n const scrollContainer = this.getScrollContainerElement();\n const { lineIndex, lineOffset, fileElement, fileOffset, fileTypeOffset } =\n anchor;\n if (lineIndex != null && lineOffset != null) {\n const element = fileElement.shadowRoot?.querySelector(\n `[data-line][data-line-index=\"${lineIndex}\"]`\n );\n if (element instanceof HTMLElement) {\n const top = getRelativeBoundingTop(element, scrollContainer);\n if (top !== lineOffset) {\n const scrollOffset = top - lineOffset;\n this.applyScrollFix(scrollOffset);\n }\n return;\n }\n }\n const top = getRelativeBoundingTop(fileElement, scrollContainer);\n if (fileTypeOffset === 'top') {\n if (top !== fileOffset) {\n this.applyScrollFix(top - fileOffset);\n }\n } else {\n const bottom = top + fileElement.getBoundingClientRect().height;\n if (bottom !== fileOffset) {\n this.applyScrollFix(bottom - fileOffset);\n }\n }\n }\n\n private applyScrollFix(scrollOffset: number) {\n if (this.root == null || this.root instanceof Document) {\n window.scrollTo({\n top: window.scrollY + scrollOffset,\n behavior: 'instant',\n });\n } else {\n this.root.scrollTo({\n top: this.root.scrollTop + scrollOffset,\n behavior: 'instant',\n });\n }\n // Because we fixed our scroll positions, it means something resized or\n // moved around, so we should mark everything as dirty so the\n // reconciliation call will get the latest data when figuring calling\n // .getOffsetInScrollContainer\n this.markDOMDirty();\n }\n\n // This function tries to figure out the closest file or line to the viewport\n // top that's visible to use as a relative marker for how to fix scroll\n // position after issuing dom updates\n private getScrollAnchor(viewportHeight: number): ScrollAnchor | undefined {\n const scrollContainer = this.getScrollContainerElement();\n let bestAnchor: ScrollAnchor | undefined;\n\n for (const [fileElement] of this.visibleInstances.entries()) {\n const fileTop = getRelativeBoundingTop(fileElement, scrollContainer);\n const fileBottom = fileTop + fileElement.offsetHeight;\n\n // Determine file offset and type based on position\n // Only use bottom anchor when entire file is above viewport\n let fileOffset: number;\n let fileTypeOffset: 'top' | 'bottom';\n if (fileBottom <= 0) {\n // Entire file is above viewport - use bottom as anchor\n fileOffset = fileBottom;\n fileTypeOffset = 'bottom';\n } else {\n // File is at least partially visible or below - use top\n fileOffset = fileTop;\n fileTypeOffset = 'top';\n }\n\n // Find the best line (first fully visible) within this file\n let bestLineIndex: string | undefined;\n let bestLineOffset: number | undefined;\n\n // Only search for lines if file potentially intersects viewport\n if (fileBottom > 0 && fileTop < viewportHeight) {\n for (const line of fileElement.shadowRoot?.querySelectorAll(\n '[data-line][data-line-index]'\n ) ?? []) {\n if (!(line instanceof HTMLElement)) continue;\n const lineIndex = line.dataset.lineIndex;\n if (lineIndex == null) continue;\n\n const lineOffset = getRelativeBoundingTop(line, scrollContainer);\n\n // Ignore lines with negative offsets (above viewport top)\n if (lineOffset < 0) continue;\n\n // First visible line in DOM order is the best one because\n // querySelectorAll will grab lines in order as they appear in the\n // DOM\n bestLineIndex = lineIndex;\n bestLineOffset = lineOffset;\n break;\n }\n }\n\n // If we already have an anchor with a visible line, skip files without one\n if (bestAnchor?.lineOffset != null && bestLineOffset == null) {\n continue;\n }\n\n // Decide if this file should become the new best anchor\n let shouldReplace = false;\n // If we don't already have an anchor we should set one\n if (bestAnchor == null) {\n shouldReplace = true;\n }\n // If we found a better line anchor, we should replace the old one\n else if (\n bestLineOffset != null &&\n (bestAnchor.lineOffset == null ||\n bestLineOffset < bestAnchor.lineOffset)\n ) {\n shouldReplace = true;\n }\n // Otherwise we need to compare file only anchors\n else if (bestLineOffset == null && bestAnchor.lineOffset == null) {\n // Favor files with their tops in view\n if (\n fileOffset >= 0 &&\n (bestAnchor.fileOffset < 0 || fileOffset < bestAnchor.fileOffset)\n ) {\n shouldReplace = true;\n }\n // Or the closest file\n else if (\n fileOffset < 0 &&\n bestAnchor.fileOffset < 0 &&\n fileOffset > bestAnchor.fileOffset\n ) {\n shouldReplace = true;\n }\n }\n\n if (shouldReplace) {\n bestAnchor = {\n fileElement,\n fileTypeOffset,\n fileOffset,\n lineIndex: bestLineIndex,\n lineOffset: bestLineOffset,\n };\n }\n }\n\n return bestAnchor;\n }\n\n private handleIntersectionChange = (\n entries: IntersectionObserverEntry[]\n ): void => {\n this.scrollDirty = true;\n for (const { target, isIntersecting } of entries) {\n if (!(target instanceof HTMLElement)) {\n throw new Error(\n 'Virtualizer.handleIntersectionChange: target not an HTMLElement'\n );\n }\n const instance = this.observers.get(target);\n // IntersectionObserver delivers entries asynchronously, so an entry can\n // arrive after the target was unobserved via disconnect() or releaseElement().\n if (instance == null) {\n continue;\n }\n if (isIntersecting && !this.visibleInstances.has(target)) {\n instance.setVisibility(true);\n this.visibleInstances.set(target, instance);\n this.visibleInstancesDirty = true;\n } else if (!isIntersecting && this.visibleInstances.has(target)) {\n instance.setVisibility(false);\n this.visibleInstances.delete(target);\n this.visibleInstancesDirty = true;\n }\n }\n\n if (this.visibleInstancesDirty) {\n // Since this call is already debounced, should we just call\n // computeRenderRangeAndEmit directly?\n queueRender(this.computeRenderRangeAndEmit);\n }\n // Debug logging for visible instances\n // console.log(\n // 'handleIntersectionChange',\n // ...Array.from(this.visibleInstances.keys())\n // );\n };\n\n private getScrollTop() {\n if (!this.scrollDirty) {\n return this.scrollTop;\n }\n this.scrollDirty = false;\n let scrollTop = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return window.scrollY;\n }\n return this.root.scrollTop;\n })();\n\n // Lets always make sure to clamp scroll position cases of\n // over/bounce scroll\n scrollTop = Math.max(\n 0,\n Math.min(scrollTop, this.getScrollHeight() - this.getHeight())\n );\n this.scrollTop = scrollTop;\n return scrollTop;\n }\n\n private getScrollHeight() {\n if (!this.scrollHeightDirty) {\n return this.scrollHeight;\n }\n this.scrollHeightDirty = false;\n this.scrollHeight = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return this.root.documentElement.scrollHeight;\n }\n return this.root.scrollHeight;\n })();\n return this.scrollHeight;\n }\n\n private getHeight() {\n if (!this.heightDirty) {\n return this.height;\n }\n this.heightDirty = false;\n this.height = (() => {\n if (this.root == null) {\n return 0;\n }\n if (this.root instanceof Document) {\n return globalThis.innerHeight;\n }\n return this.root.getBoundingClientRect().height;\n })();\n return this.height;\n }\n\n markDOMDirty(): void {\n this.scrollDirty = true;\n this.scrollHeightDirty = true;\n this.heightDirty = true;\n }\n\n private getScrollContainerElement(): HTMLElement | undefined {\n return this.root == null || this.root instanceof Document\n ? undefined\n : this.root;\n }\n}\n\n// This function is like a generalized getBoundingClientRect for it's relative\n// scroll container\nfunction getRelativeBoundingTop(\n element: HTMLElement,\n scrollContainer: HTMLElement | undefined\n) {\n const rect = element.getBoundingClientRect();\n const scrollContainerTop = scrollContainer?.getBoundingClientRect().top ?? 0;\n return rect.top - scrollContainerTop;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"editStack.d.ts","names":[],"sources":["../../src/editor/editStack.ts"],"mappings":";;;;;;UAQiB,cAAA;EAAA;EAEf,YAAA,EAAc,gBAAA;EAFe;EAI7B,YAAA,EAAc,gBAAA;EAAA;EAEd,aAAA;EAMkB;EAJlB,YAAA;EAMwB;EAJxB,gBAAA,GAAmB,eAAA;EAMI;EAJvB,eAAA,GAAkB,eAAA;EAIuB;EAFzC,qBAAA,GAAwB,kBAAA,CAAmB,WAAA;EAZ3C;EAcA,oBAAA,GAAuB,kBAAA,CAAmB,WAAA;EAZ1C;;;;;EAkBA,YAAA;AAAA;;UAIe,gBAAA;EAZS;EAcxB,UAAU;AAAA;;cAIC,SAAA;EAAA;EAKX,WAAA,CAAY,OAAA,GAAU,gBAAA;EAAA,IAOlB,OAAA;EAAA,IAIA,OAAA;EAtB2B;EA2B/B,KAAA;EAzBU;EA+BV,SAAA;EA3BW;EAgCX,IAAA,CAAK,KAAA,EAAO,cAAA,CAAe,WAAA;EAhCP;EAyCpB,0BAAA,CAA2B,UAAA,EAAY,eAAA;EATZ;EAmB3B,0BAAA,CACE,qBAAA,EAAuB,kBAAA,CAAmB,WAAA,KAC1C,oBAAA,EAAsB,kBAAA,CAAmB,WAAA;EAZJ;EAsBvC,QAAA,IAAY,cAAA,CAAe,WAAA;EAXF;EAgBzB,eAAA,CAAgB,KAAA,EAAO,cAAA,CAAe,WAAA;EAfd;EAyBxB,aAAA,IAAiB,cAAA,CAAe,WAAA;EAfpB;EAwBZ,aAAA,IAAiB,cAAA,CAAe,WAAA;AAAA;AAAA,iBASlB,oBAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,aAAA,EAAe,gBAAA,IACf,aAAA,UACA,YAAA,UACA,gBAAA,GAAmB,eAAA,IACnB,eAAA,GAAkB,eAAA,IAClB,qBAAA,GAAwB,kBAAA,CAAmB,WAAA,KAC3C,oBAAA,GAAuB,kBAAA,CAAmB,WAAA,MACzC,cAAA,CAAe,WAAA;;;;;;iBAiCF,4BAAA,cACd,aAAA,EAAe,cAAA,CAAe,WAAA,eAC9B,SAAA,EAAW,cAAA,CAAe,WAAA;;iBAgFZ,wBAAA,cACd,aAAA,EAAe,cAAA,CAAe,WAAA,GAC9B,SAAA,EAAW,cAAA,CAAe,WAAA,IACzB,cAAA,CAAe,WAAA"}
1
+ {"version":3,"file":"editStack.d.ts","names":[],"sources":["../../src/editor/editStack.ts"],"mappings":";;;;;;UAQiB,cAAA;EAAA;EAEf,YAAA,EAAc,gBAAA;EAFe;EAI7B,YAAA,EAAc,gBAAA;EAAA;EAEd,aAAA;EAMkB;EAJlB,YAAA;EAMwB;EAJxB,gBAAA,GAAmB,eAAA;EAMI;EAJvB,eAAA,GAAkB,eAAA;EAIuB;EAFzC,qBAAA,GAAwB,kBAAA,CAAmB,WAAA;EAZ3C;EAcA,oBAAA,GAAuB,kBAAA,CAAmB,WAAA;EAZ1C;;;;;EAkBA,YAAA;AAAA;;UAIe,gBAAA;EAZS;EAcxB,UAAU;AAAA;;cAIC,SAAA;EAAA;EAKX,WAAA,CAAY,OAAA,GAAU,gBAAA;EAAA,IAOlB,OAAA;EAAA,IAIA,OAAA;EAtB2B;EA2B/B,KAAA;EAzBU;EA+BV,SAAA;EA3BW;EAgCX,IAAA,CAAK,KAAA,EAAO,cAAA,CAAe,WAAA;EAhCP;EAyCpB,0BAAA,CAA2B,UAAA,EAAY,eAAA;EATZ;EAmB3B,0BAAA,CACE,qBAAA,EAAuB,kBAAA,CAAmB,WAAA,KAC1C,oBAAA,EAAsB,kBAAA,CAAmB,WAAA;EAZJ;EAsBvC,QAAA,IAAY,cAAA,CAAe,WAAA;EAXF;EAgBzB,eAAA,CAAgB,KAAA,EAAO,cAAA,CAAe,WAAA;EAfd;EAyBxB,aAAA,IAAiB,cAAA,CAAe,WAAA;EAfpB;EAwBZ,aAAA,IAAiB,cAAA,CAAe,WAAA;AAAA;AAAA,iBASlB,oBAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,aAAA,EAAe,gBAAA,IACf,aAAA,UACA,YAAA,UACA,gBAAA,GAAmB,eAAA,IACnB,eAAA,GAAkB,eAAA,IAClB,qBAAA,GAAwB,kBAAA,CAAmB,WAAA,KAC3C,oBAAA,GAAuB,kBAAA,CAAmB,WAAA,MACzC,cAAA,CAAe,WAAA;;;;;;iBAiCF,4BAAA,cACd,aAAA,EAAe,cAAA,CAAe,WAAA,eAC9B,SAAA,EAAW,cAAA,CAAe,WAAA;;iBAoFZ,wBAAA,cACd,aAAA,EAAe,cAAA,CAAe,WAAA,GAC9B,SAAA,EAAW,cAAA,CAAe,WAAA,IACzB,cAAA,CAAe,WAAA"}
@@ -115,7 +115,7 @@ function shouldCoalesceEditStackEntry(previousEntry, nextEntry) {
115
115
  const previousWasInsert = previousForward.start <= previousForward.end && previousForward.text.length > 0 && !previousForward.text.includes("\n") && !previousInverse.text.includes("\n");
116
116
  const nextIsInsert = nextForward.start === nextForward.end && nextForward.text.length > 0 && !nextForward.text.includes("\n") && nextInverse.text.length === 0;
117
117
  if (previousWasInsert && nextIsInsert) {
118
- if (mappedNextStart !== previousForward.end) return false;
118
+ if (nextForward.start !== previousInverse.end) return false;
119
119
  mode ??= "insert";
120
120
  if (mode !== "insert") return false;
121
121
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"editStack.js","names":["#maxEntries","#undoStack","#redoStack"],"sources":["../../src/editor/editStack.ts"],"sourcesContent":["import type { DiffLineAnnotation } from '../types';\nimport type { EditorSelection } from './selection';\nimport type { ResolvedTextEdit, TextDocument } from './textDocument';\n\n/** Largest number of undo or redo entries kept; oldest entries drop first once exceeded. */\nconst DEFAULT_EDIT_STACK_MAX_ENTRIES = 100;\n\n/** An entry in the edit stack. */\nexport interface EditStackEntry<LAnnotation> {\n /** Forward offset edits from the entry's base text to its final text. */\n forwardEdits: ResolvedTextEdit[];\n /** Inverse offset edits from the entry's final text back to its base text. */\n inverseEdits: ResolvedTextEdit[];\n /** Document version before the entry is applied. */\n versionBefore: number;\n /** Document version after the entry is applied. */\n versionAfter: number;\n /** Selection before the transaction. */\n selectionsBefore?: EditorSelection[];\n /** Selection after the transaction. */\n selectionsAfter?: EditorSelection[];\n /** Line annotations before the transaction. */\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[];\n /** Line annotations after the transaction. */\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[];\n /**\n * When `true`, this entry is its own undo step and never merges with the\n * entry before or after it. Set for paste and cut, which otherwise look like\n * typing and would merge into the previous keystroke.\n */\n undoBoundary?: boolean;\n}\n\n/** Options for the edit stack. */\nexport interface EditStackOptions {\n /** The maximum number of entries to keep in the undo stack. */\n maxEntries?: number;\n}\n\n/** A stack of edit entries. */\nexport class EditStack<LAnnotation> {\n #undoStack: EditStackEntry<LAnnotation>[] = [];\n #redoStack: EditStackEntry<LAnnotation>[] = [];\n #maxEntries: number;\n\n constructor(options?: EditStackOptions) {\n this.#maxEntries = Math.max(\n 1,\n options?.maxEntries ?? DEFAULT_EDIT_STACK_MAX_ENTRIES\n );\n }\n\n get canUndo(): boolean {\n return this.#undoStack.length > 0;\n }\n\n get canRedo(): boolean {\n return this.#redoStack.length > 0;\n }\n\n /** Clears both the undo and redo stacks. */\n clear(): void {\n this.#undoStack.length = 0;\n this.#redoStack.length = 0;\n }\n\n /** Clears the redo stack. */\n clearRedo(): void {\n this.#redoStack.length = 0;\n }\n\n /** Pushes a new entry onto the undo stack. */\n push(entry: EditStackEntry<LAnnotation>): void {\n this.#undoStack.push(entry);\n this.clearRedo();\n if (this.#undoStack.length > this.#maxEntries) {\n this.#undoStack.shift();\n }\n }\n\n /** Sets the selections after the last undo entry. */\n setLastUndoSelectionsAfter(selections: EditorSelection[]): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.selectionsAfter = selections.map((selection) => ({\n ...selection,\n }));\n }\n }\n\n /** Sets the line annotations after the last undo entry. */\n setLastUndoLineAnnotations(\n lineAnnotationsBefore: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter: DiffLineAnnotation<LAnnotation>[]\n ): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.lineAnnotationsBefore = lineAnnotationsBefore.slice();\n lastEntry.lineAnnotationsAfter = lineAnnotationsAfter.slice();\n }\n }\n\n /** Returns the last undo entry, or `undefined` if empty. */\n peekUndo(): EditStackEntry<LAnnotation> | undefined {\n return this.#undoStack[this.#undoStack.length - 1];\n }\n\n /** Replaces the last undo entry with the given entry. */\n replaceLastUndo(entry: EditStackEntry<LAnnotation>): void {\n if (this.#undoStack.length === 0) {\n this.push(entry);\n return;\n }\n this.#undoStack[this.#undoStack.length - 1] = entry;\n this.clearRedo();\n }\n\n /** Moves the latest undo entry to the redo stack and returns it, or `undefined` if empty. */\n popUndoToRedo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#undoStack.pop();\n if (entry !== undefined) {\n this.#redoStack.push(entry);\n return entry;\n }\n }\n\n /** Moves the latest redo entry back to the undo stack and returns it, or `undefined` if empty. */\n popRedoToUndo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#redoStack.pop();\n if (entry !== undefined) {\n this.#undoStack.push(entry);\n return entry;\n }\n }\n}\n\nexport function createEditStackEntry<LAnnotation>(\n textDocument: TextDocument<LAnnotation>,\n resolvedEdits: ResolvedTextEdit[],\n versionBefore: number,\n versionAfter: number,\n selectionsBefore?: EditorSelection[],\n selectionsAfter?: EditorSelection[],\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[]\n): EditStackEntry<LAnnotation> {\n const forwardEdits = [...resolvedEdits].sort((a, b) => a.start - b.start);\n const inverseEdits: ResolvedTextEdit[] = [];\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const replacedText = textDocument.getTextSlice(edit.start, edit.end);\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedText,\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return {\n forwardEdits: forwardEdits.map((edit) => ({ ...edit })),\n inverseEdits: inverseEdits,\n versionBefore,\n versionAfter,\n selectionsBefore: selectionsBefore?.map((selection) => ({\n ...selection,\n })),\n selectionsAfter: selectionsAfter?.map((selection) => ({ ...selection })),\n lineAnnotationsBefore: lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: lineAnnotationsAfter?.slice(),\n };\n}\n\n/** Determines if the change matches following modes:\n * - 'insert': simple typing\n * - 'backspace': backward delete\n * - 'delete': forward delete\n */\nexport function shouldCoalesceEditStackEntry<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation> | undefined,\n nextEntry: EditStackEntry<LAnnotation>\n): boolean {\n if (\n previousEntry === undefined ||\n previousEntry.undoBoundary === true ||\n nextEntry.undoBoundary === true ||\n previousEntry.forwardEdits.length === 0 ||\n previousEntry.forwardEdits.length !== previousEntry.inverseEdits.length ||\n previousEntry.forwardEdits.length !== nextEntry.forwardEdits.length ||\n nextEntry.forwardEdits.length !== nextEntry.inverseEdits.length\n ) {\n return false;\n }\n let mode: 'insert' | 'backspace' | 'delete' | undefined;\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n const previousWasInsert =\n previousForward.start <= previousForward.end &&\n previousForward.text.length > 0 &&\n !previousForward.text.includes('\\n') &&\n !previousInverse.text.includes('\\n');\n const nextIsInsert =\n nextForward.start === nextForward.end &&\n nextForward.text.length > 0 &&\n !nextForward.text.includes('\\n') &&\n nextInverse.text.length === 0;\n if (previousWasInsert && nextIsInsert) {\n const expectedMappedNextStart = previousForward.end;\n // Allow continuing typing after replacing a selection (e.g. \"hello\" -> \"w\")\n // while still requiring that the cursor extension maps inside the same base range.\n if (mappedNextStart !== expectedMappedNextStart) {\n return false;\n }\n mode ??= 'insert';\n if (mode !== 'insert') {\n return false;\n }\n continue;\n }\n const previousWasDelete =\n previousForward.text.length === 0 &&\n previousForward.end > previousForward.start &&\n previousInverse.text.length > 0;\n const nextIsDelete =\n nextForward.text.length === 0 &&\n nextForward.end > nextForward.start &&\n nextInverse.text.length > 0;\n if (previousWasDelete && nextIsDelete) {\n if (mappedNextStart === previousForward.end) {\n mode ??= 'delete';\n if (mode !== 'delete') {\n return false;\n }\n continue;\n }\n if (\n mappedNextStart + (nextForward.end - nextForward.start) !==\n previousForward.start\n ) {\n return false;\n }\n mode ??= 'backspace';\n if (mode !== 'backspace') {\n return false;\n }\n continue;\n }\n return false;\n }\n return mode !== undefined;\n}\n\n/** Coalesce edit stack entries for simple typing and single-character deletes. */\nexport function coalesceEditStackEntries<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation>,\n nextEntry: EditStackEntry<LAnnotation>\n): EditStackEntry<LAnnotation> {\n const forwardEdits: ResolvedTextEdit[] = [];\n const replacedTexts: string[] = [];\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n\n if (previousForward.text.length > 0) {\n forwardEdits.push({\n start: previousForward.start,\n end: previousForward.end,\n text: previousForward.text + nextForward.text,\n });\n replacedTexts.push(previousInverse.text);\n continue;\n }\n\n if (mappedNextStart === previousForward.end) {\n forwardEdits.push({\n start: previousForward.start,\n end: mappedNextStart + (nextForward.end - nextForward.start),\n text: '',\n });\n replacedTexts.push(previousInverse.text + nextInverse.text);\n continue;\n }\n\n forwardEdits.push({\n start: Math.min(previousForward.start, mappedNextStart),\n end: previousForward.end,\n text: '',\n });\n replacedTexts.push(nextInverse.text + previousInverse.text);\n }\n\n return {\n forwardEdits,\n inverseEdits: buildInverseEditsFromReplacedTexts(\n forwardEdits,\n replacedTexts\n ),\n versionBefore: previousEntry.versionBefore,\n versionAfter: nextEntry.versionAfter,\n selectionsBefore: previousEntry.selectionsBefore?.slice(),\n selectionsAfter: nextEntry.selectionsAfter?.slice(),\n lineAnnotationsBefore: previousEntry.lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: nextEntry.lineAnnotationsAfter?.slice(),\n };\n}\n\nfunction buildInverseEditsFromReplacedTexts(\n forwardEdits: readonly ResolvedTextEdit[],\n replacedTexts: readonly string[]\n): ResolvedTextEdit[] {\n const inverseEdits: ResolvedTextEdit[] = [];\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedTexts[i],\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return inverseEdits;\n}\n\nfunction mapOffsetAfterForwardBatchToBefore(\n offsetAfter: number,\n forwardEdits: readonly ResolvedTextEdit[]\n): number {\n let offset = offsetAfter;\n for (const edit of forwardEdits) {\n const oldLength = edit.end - edit.start;\n const newLength = edit.text.length;\n const delta = newLength - oldLength;\n if (offset < edit.start) {\n continue;\n }\n if (offset >= edit.start + newLength) {\n offset -= delta;\n continue;\n }\n offset = edit.start + Math.min(offset - edit.start, oldLength);\n }\n return offset;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"editStack.js","names":["#maxEntries","#undoStack","#redoStack"],"sources":["../../src/editor/editStack.ts"],"sourcesContent":["import type { DiffLineAnnotation } from '../types';\nimport type { EditorSelection } from './selection';\nimport type { ResolvedTextEdit, TextDocument } from './textDocument';\n\n/** Largest number of undo or redo entries kept; oldest entries drop first once exceeded. */\nconst DEFAULT_EDIT_STACK_MAX_ENTRIES = 100;\n\n/** An entry in the edit stack. */\nexport interface EditStackEntry<LAnnotation> {\n /** Forward offset edits from the entry's base text to its final text. */\n forwardEdits: ResolvedTextEdit[];\n /** Inverse offset edits from the entry's final text back to its base text. */\n inverseEdits: ResolvedTextEdit[];\n /** Document version before the entry is applied. */\n versionBefore: number;\n /** Document version after the entry is applied. */\n versionAfter: number;\n /** Selection before the transaction. */\n selectionsBefore?: EditorSelection[];\n /** Selection after the transaction. */\n selectionsAfter?: EditorSelection[];\n /** Line annotations before the transaction. */\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[];\n /** Line annotations after the transaction. */\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[];\n /**\n * When `true`, this entry is its own undo step and never merges with the\n * entry before or after it. Set for paste and cut, which otherwise look like\n * typing and would merge into the previous keystroke.\n */\n undoBoundary?: boolean;\n}\n\n/** Options for the edit stack. */\nexport interface EditStackOptions {\n /** The maximum number of entries to keep in the undo stack. */\n maxEntries?: number;\n}\n\n/** A stack of edit entries. */\nexport class EditStack<LAnnotation> {\n #undoStack: EditStackEntry<LAnnotation>[] = [];\n #redoStack: EditStackEntry<LAnnotation>[] = [];\n #maxEntries: number;\n\n constructor(options?: EditStackOptions) {\n this.#maxEntries = Math.max(\n 1,\n options?.maxEntries ?? DEFAULT_EDIT_STACK_MAX_ENTRIES\n );\n }\n\n get canUndo(): boolean {\n return this.#undoStack.length > 0;\n }\n\n get canRedo(): boolean {\n return this.#redoStack.length > 0;\n }\n\n /** Clears both the undo and redo stacks. */\n clear(): void {\n this.#undoStack.length = 0;\n this.#redoStack.length = 0;\n }\n\n /** Clears the redo stack. */\n clearRedo(): void {\n this.#redoStack.length = 0;\n }\n\n /** Pushes a new entry onto the undo stack. */\n push(entry: EditStackEntry<LAnnotation>): void {\n this.#undoStack.push(entry);\n this.clearRedo();\n if (this.#undoStack.length > this.#maxEntries) {\n this.#undoStack.shift();\n }\n }\n\n /** Sets the selections after the last undo entry. */\n setLastUndoSelectionsAfter(selections: EditorSelection[]): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.selectionsAfter = selections.map((selection) => ({\n ...selection,\n }));\n }\n }\n\n /** Sets the line annotations after the last undo entry. */\n setLastUndoLineAnnotations(\n lineAnnotationsBefore: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter: DiffLineAnnotation<LAnnotation>[]\n ): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.lineAnnotationsBefore = lineAnnotationsBefore.slice();\n lastEntry.lineAnnotationsAfter = lineAnnotationsAfter.slice();\n }\n }\n\n /** Returns the last undo entry, or `undefined` if empty. */\n peekUndo(): EditStackEntry<LAnnotation> | undefined {\n return this.#undoStack[this.#undoStack.length - 1];\n }\n\n /** Replaces the last undo entry with the given entry. */\n replaceLastUndo(entry: EditStackEntry<LAnnotation>): void {\n if (this.#undoStack.length === 0) {\n this.push(entry);\n return;\n }\n this.#undoStack[this.#undoStack.length - 1] = entry;\n this.clearRedo();\n }\n\n /** Moves the latest undo entry to the redo stack and returns it, or `undefined` if empty. */\n popUndoToRedo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#undoStack.pop();\n if (entry !== undefined) {\n this.#redoStack.push(entry);\n return entry;\n }\n }\n\n /** Moves the latest redo entry back to the undo stack and returns it, or `undefined` if empty. */\n popRedoToUndo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#redoStack.pop();\n if (entry !== undefined) {\n this.#undoStack.push(entry);\n return entry;\n }\n }\n}\n\nexport function createEditStackEntry<LAnnotation>(\n textDocument: TextDocument<LAnnotation>,\n resolvedEdits: ResolvedTextEdit[],\n versionBefore: number,\n versionAfter: number,\n selectionsBefore?: EditorSelection[],\n selectionsAfter?: EditorSelection[],\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[]\n): EditStackEntry<LAnnotation> {\n const forwardEdits = [...resolvedEdits].sort((a, b) => a.start - b.start);\n const inverseEdits: ResolvedTextEdit[] = [];\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const replacedText = textDocument.getTextSlice(edit.start, edit.end);\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedText,\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return {\n forwardEdits: forwardEdits.map((edit) => ({ ...edit })),\n inverseEdits: inverseEdits,\n versionBefore,\n versionAfter,\n selectionsBefore: selectionsBefore?.map((selection) => ({\n ...selection,\n })),\n selectionsAfter: selectionsAfter?.map((selection) => ({ ...selection })),\n lineAnnotationsBefore: lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: lineAnnotationsAfter?.slice(),\n };\n}\n\n/** Determines if the change matches following modes:\n * - 'insert': simple typing\n * - 'backspace': backward delete\n * - 'delete': forward delete\n */\nexport function shouldCoalesceEditStackEntry<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation> | undefined,\n nextEntry: EditStackEntry<LAnnotation>\n): boolean {\n if (\n previousEntry === undefined ||\n previousEntry.undoBoundary === true ||\n nextEntry.undoBoundary === true ||\n previousEntry.forwardEdits.length === 0 ||\n previousEntry.forwardEdits.length !== previousEntry.inverseEdits.length ||\n previousEntry.forwardEdits.length !== nextEntry.forwardEdits.length ||\n nextEntry.forwardEdits.length !== nextEntry.inverseEdits.length\n ) {\n return false;\n }\n let mode: 'insert' | 'backspace' | 'delete' | undefined;\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n const previousWasInsert =\n previousForward.start <= previousForward.end &&\n previousForward.text.length > 0 &&\n !previousForward.text.includes('\\n') &&\n !previousInverse.text.includes('\\n');\n const nextIsInsert =\n nextForward.start === nextForward.end &&\n nextForward.text.length > 0 &&\n !nextForward.text.includes('\\n') &&\n nextInverse.text.length === 0;\n if (previousWasInsert && nextIsInsert) {\n // Merge only when the next insert starts exactly where the previous\n // inserted text ends, in after-edit offsets. `previousInverse.end` marks\n // that point, since undo replaces exactly the inserted range. A\n // base-offset check can't tell this apart from a caret that jumped to the\n // left edge: both edges of a pure insert map back to the same base offset,\n // so typing \"a\" then \"b\" at the left edge would merge as \"ab\" while the\n // buffer holds \"ba\", corrupting redo.\n if (nextForward.start !== previousInverse.end) {\n return false;\n }\n mode ??= 'insert';\n if (mode !== 'insert') {\n return false;\n }\n continue;\n }\n const previousWasDelete =\n previousForward.text.length === 0 &&\n previousForward.end > previousForward.start &&\n previousInverse.text.length > 0;\n const nextIsDelete =\n nextForward.text.length === 0 &&\n nextForward.end > nextForward.start &&\n nextInverse.text.length > 0;\n if (previousWasDelete && nextIsDelete) {\n if (mappedNextStart === previousForward.end) {\n mode ??= 'delete';\n if (mode !== 'delete') {\n return false;\n }\n continue;\n }\n if (\n mappedNextStart + (nextForward.end - nextForward.start) !==\n previousForward.start\n ) {\n return false;\n }\n mode ??= 'backspace';\n if (mode !== 'backspace') {\n return false;\n }\n continue;\n }\n return false;\n }\n return mode !== undefined;\n}\n\n/** Coalesce edit stack entries for simple typing and single-character deletes. */\nexport function coalesceEditStackEntries<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation>,\n nextEntry: EditStackEntry<LAnnotation>\n): EditStackEntry<LAnnotation> {\n const forwardEdits: ResolvedTextEdit[] = [];\n const replacedTexts: string[] = [];\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n\n if (previousForward.text.length > 0) {\n forwardEdits.push({\n start: previousForward.start,\n end: previousForward.end,\n text: previousForward.text + nextForward.text,\n });\n replacedTexts.push(previousInverse.text);\n continue;\n }\n\n if (mappedNextStart === previousForward.end) {\n forwardEdits.push({\n start: previousForward.start,\n end: mappedNextStart + (nextForward.end - nextForward.start),\n text: '',\n });\n replacedTexts.push(previousInverse.text + nextInverse.text);\n continue;\n }\n\n forwardEdits.push({\n start: Math.min(previousForward.start, mappedNextStart),\n end: previousForward.end,\n text: '',\n });\n replacedTexts.push(nextInverse.text + previousInverse.text);\n }\n\n return {\n forwardEdits,\n inverseEdits: buildInverseEditsFromReplacedTexts(\n forwardEdits,\n replacedTexts\n ),\n versionBefore: previousEntry.versionBefore,\n versionAfter: nextEntry.versionAfter,\n selectionsBefore: previousEntry.selectionsBefore?.slice(),\n selectionsAfter: nextEntry.selectionsAfter?.slice(),\n lineAnnotationsBefore: previousEntry.lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: nextEntry.lineAnnotationsAfter?.slice(),\n };\n}\n\nfunction buildInverseEditsFromReplacedTexts(\n forwardEdits: readonly ResolvedTextEdit[],\n replacedTexts: readonly string[]\n): ResolvedTextEdit[] {\n const inverseEdits: ResolvedTextEdit[] = [];\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedTexts[i],\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return inverseEdits;\n}\n\nfunction mapOffsetAfterForwardBatchToBefore(\n offsetAfter: number,\n forwardEdits: readonly ResolvedTextEdit[]\n): number {\n let offset = offsetAfter;\n for (const edit of forwardEdits) {\n const oldLength = edit.end - edit.start;\n const newLength = edit.text.length;\n const delta = newLength - oldLength;\n if (offset < edit.start) {\n continue;\n }\n if (offset >= edit.start + newLength) {\n offset -= delta;\n continue;\n }\n offset = edit.start + Math.min(offset - edit.start, oldLength);\n }\n return offset;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","names":[],"sources":["../../src/editor/editor.ts"],"mappings":";;;;;;;UA4GiB,aAAA;;EAEf,iBAAA;EAF4B;EAI5B,gBAAA;EAOe;EALf,aAAA;EAqBkC;;;;EAhBlC,YAAA,GAAe,YAAA;EAqBwB;;;;EAhBvC,sBAAA;EAqBsC;;;;;EAftC,SAAA;IACE,QAAA,QAAgB,OAAA;EAAA;EAPlB;EAUA,qBAAA,IACE,OAAA,EAAS,sBAAA,CAAuB,WAAA,MAC7B,WAAA;EALH;EAOF,QAAA,IACE,MAAA,EAAQ,MAAA,CAAO,WAAA,GACf,YAAA,EAAc,sBAAA,CAAuB,WAAA;EANvC;EASA,QAAA,IACE,IAAA,EAAM,YAAA,EACN,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EAVL;EAalC,OAAA;EAZK;EAcL,MAAA;EAEA,OAAA;AAAA;AAAA,UAGe,WAAA;EACf,IAAA,EAAM,YAAA;EACN,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EACrC,UAAA,GAAa,eAAA;EACb,WAAA,GAAc,WAAA;AAAA;AAAA,cAUH,MAAA,yBAA+B,WAAA,CAAY,WAAA;EAAA;EAsHtD,WAAA,CAAY,OAAA,GAAS,aAAA,CAAc,WAAA;EAInC,UAAA,CAAW,OAAA,EAAS,aAAA,CAAc,WAAA;EAOlC,IAAA,CAAK,YAAA,EAAc,sBAAA,CAAuB,WAAA;EApJ1C;;;EAuLA,UAAA,CAAW,KAAA,EAAO,QAAA,IAAY,aAAA;EAlLf;EAAA,IA+PX,OAAA;EA/PsB;EAAA,IAoQtB,OAAA;EAlQiC;EAuQrC,IAAA;EAtQa;EA2Qb,IAAA;EAIA,QAAA,IAAY,WAAA,CAAY,WAAA;EAaxB,QAAA;IACE,IAAA;IACA,eAAA;IACA,WAAA;IACA;EAAA,GACC,WAAA,CAAY,WAAA;EAWf,aAAA,CAAc,UAAA,EAAY,oBAAA;EAoB1B,UAAA,CAAW,OAAA,EAAS,MAAA;EA6BpB,KAAA,CAAM,OAAA,GAAU,YAAA;EAchB,IAAA;EAIA,OAAA,CAAQ,OAAA;EAhX6B;EAqbrC,+BAAA;EApba;EA+bb,gBAAA,EAAkB,WAAA,CAAY,WAAA;AAAA"}
1
+ {"version":3,"file":"editor.d.ts","names":[],"sources":["../../src/editor/editor.ts"],"mappings":";;;;;;;UAyIiB,aAAA;;EAEf,iBAAA;EAF4B;EAI5B,gBAAA;EAOe;EALf,aAAA;EAqBkC;;;;EAhBlC,YAAA,GAAe,YAAA;EAqBwB;;;;EAhBvC,sBAAA;EAqBsC;;;;;EAftC,SAAA;IACE,QAAA,QAAgB,OAAA;EAAA;EAPlB;EAUA,qBAAA,IACE,OAAA,EAAS,sBAAA,CAAuB,WAAA,MAC7B,WAAA;EALH;EAOF,QAAA,IACE,MAAA,EAAQ,MAAA,CAAO,WAAA,GACf,YAAA,EAAc,sBAAA,CAAuB,WAAA;EANvC;EASA,QAAA,IACE,IAAA,EAAM,YAAA,EACN,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EAVL;EAalC,OAAA;EAZK;EAcL,MAAA;EAEA,OAAA;AAAA;AAAA,UAGe,WAAA;EACf,IAAA,EAAM,YAAA;EACN,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EACrC,UAAA,GAAa,eAAA;EACb,WAAA,GAAc,WAAA;AAAA;AAAA,cAUH,MAAA,yBAA+B,WAAA,CAAY,WAAA;EAAA;EAsHtD,WAAA,CAAY,OAAA,GAAS,aAAA,CAAc,WAAA;EAInC,UAAA,CAAW,OAAA,EAAS,aAAA,CAAc,WAAA;EAOlC,IAAA,CAAK,YAAA,EAAc,sBAAA,CAAuB,WAAA;EApJ1C;;;EAuLA,UAAA,CAAW,KAAA,EAAO,QAAA,IAAY,aAAA;EAlLf;EAAA,IA+PX,OAAA;EA/PsB;EAAA,IAoQtB,OAAA;EAlQiC;EAuQrC,IAAA;EAtQa;EA2Qb,IAAA;EAIA,QAAA,IAAY,WAAA,CAAY,WAAA;EAaxB,QAAA;IACE,IAAA;IACA,eAAA;IACA,WAAA;IACA;EAAA,GACC,WAAA,CAAY,WAAA;EAWf,aAAA,CAAc,UAAA,EAAY,oBAAA;EAoB1B,UAAA,CAAW,OAAA,EAAS,MAAA;EA6BpB,KAAA,CAAM,OAAA,GAAU,YAAA;EAchB,IAAA;EAIA,OAAA,CAAQ,OAAA;EAhX6B;EAsbrC,+BAAA;EArba;EAgcb,gBAAA,EAAkB,WAAA,CAAY,WAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { queueRender } from "../managers/UniversalRenderingManager.js";
1
+ import { dequeueRender, queueRender } from "../managers/UniversalRenderingManager.js";
2
2
  import { getFiletypeFromFileName } from "../utils/getFiletypeFromFileName.js";
3
3
  import { isMoveCursorShortcut, isPrimaryModifier, isSafari } from "./platform.js";
4
4
  import { resolveEditorCommandFromKeyboardEvent, resolveFindAgainShortcut } from "./command.js";
@@ -17,6 +17,18 @@ import { TextDocument } from "./textDocument.js";
17
17
  import { Metrics, getExpandedAsciiTextColumns, getUnicodeMeasurementOffsets, snapTextOffsetToUnicodeBoundary } from "./textMeasure.js";
18
18
  import { EditorTokenizer, renderLineTokens } from "./tokenzier.js";
19
19
  //#region src/editor/editor.ts
20
+ function getShadowRootRange(shadowRoot) {
21
+ const selection = shadowRoot.getSelection?.();
22
+ if (selection == null || selection.rangeCount === 0) return;
23
+ const range = selection.getRangeAt(0);
24
+ return {
25
+ collapsed: range.collapsed,
26
+ startContainer: range.startContainer,
27
+ startOffset: range.startOffset,
28
+ endContainer: range.endContainer,
29
+ endOffset: range.endOffset
30
+ };
31
+ }
20
32
  const MAX_EDIT_WIDEN_WINDOW_MULTIPLE = 2;
21
33
  var Editor = class {
22
34
  #options;
@@ -230,6 +242,7 @@ var Editor = class {
230
242
  this.#contentElement?.blur();
231
243
  }
232
244
  cleanUp(recycle = false) {
245
+ dequeueRender(this.#handleCustomPasteEvent);
233
246
  this.#tokenizer?.cleanUp();
234
247
  this.#tokenizer = void 0;
235
248
  if (!recycle) {
@@ -454,8 +467,8 @@ var Editor = class {
454
467
  if (this.#shouldIgnoreSelectionChange || this.#suppressNativeSelectionSync || shadowRoot == null || !this.#contentHasFocus) return;
455
468
  if (this.#selections !== void 0 && this.#selections.length > 1 && !this.#isContentMouseDown) return;
456
469
  const selectionRaw = document.getSelection();
457
- if (selectionRaw == null || typeof selectionRaw.getComposedRanges !== "function") return;
458
- const composedRange = selectionRaw.getComposedRanges({ shadowRoots: [shadowRoot] })?.[0];
470
+ if (selectionRaw == null) return;
471
+ const composedRange = typeof selectionRaw.getComposedRanges === "function" ? selectionRaw.getComposedRanges({ shadowRoots: [shadowRoot] })?.[0] : getShadowRootRange(shadowRoot);
459
472
  if (composedRange === void 0 || !this.#rangeBelongsToEditor(composedRange)) return;
460
473
  let selection = convertSelection(composedRange, 0);
461
474
  if (selection === void 0) return;
@@ -555,7 +568,7 @@ var Editor = class {
555
568
  addEventListener(contentEl, "keydown", (e) => {
556
569
  if (e.key === "Escape") {
557
570
  e.preventDefault();
558
- this.#searchPanel?.cleanup();
571
+ this.#searchPanel?.close();
559
572
  this.#searchPanel = void 0;
560
573
  this.#retainSearchPanelFocus = false;
561
574
  this.#selectionAction?.cleanup();