mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Clean up textarea measurement mirrors across plugin reloads
This commit is contained in:
parent
ee0cfa9891
commit
0cb39fb6dc
3 changed files with 61 additions and 3 deletions
|
|
@ -8,6 +8,7 @@ import { CodexTurnDiffView } from "./features/turn-diff/view.obsidian";
|
|||
import { CodexPanelRuntime } from "./plugin-runtime";
|
||||
import { type CodexPanelSettings, DEFAULT_SETTINGS, getVaultPath, normalizeSettings, settingsMatchStoredSettings } from "./settings/model";
|
||||
import { CodexPanelSettingTab } from "./settings/tab.obsidian";
|
||||
import { disposeTextareaHeightMirrors } from "./shared/dom/textarea-autogrow.measure";
|
||||
|
||||
export default class CodexPanelPlugin extends Plugin {
|
||||
settings: CodexPanelSettings = DEFAULT_SETTINGS;
|
||||
|
|
@ -19,6 +20,7 @@ export default class CodexPanelPlugin extends Plugin {
|
|||
});
|
||||
|
||||
override async onload(): Promise<void> {
|
||||
disposeTextareaHeightMirrors();
|
||||
this.runtime.reset();
|
||||
this.vaultPath = getVaultPath(this.app);
|
||||
await this.loadSettings();
|
||||
|
|
@ -77,6 +79,7 @@ export default class CodexPanelPlugin extends Plugin {
|
|||
|
||||
override onunload(): void {
|
||||
this.runtime.cancelWorkspacePanelReconcile();
|
||||
disposeTextareaHeightMirrors();
|
||||
}
|
||||
|
||||
async loadSettings(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export interface TextareaHeightOptions {
|
|||
}
|
||||
|
||||
const textareaHeightMirrors = new WeakMap<Document, HTMLTextAreaElement>();
|
||||
const textareaHeightMirrorDocuments = new Set<Document>();
|
||||
const TEXTAREA_HEIGHT_MIRROR_CLASS = "codex-panel-textarea-height-mirror";
|
||||
|
||||
export function syncTextareaHeight(textarea: HTMLTextAreaElement | null, options: TextareaHeightOptions): void {
|
||||
|
|
@ -21,6 +22,14 @@ export function syncTextareaHeight(textarea: HTMLTextAreaElement | null, options
|
|||
textarea.setCssProps(sizingProps);
|
||||
}
|
||||
|
||||
export function disposeTextareaHeightMirrors(): void {
|
||||
for (const doc of textareaHeightMirrorDocuments) {
|
||||
for (const mirror of doc.querySelectorAll(`.${TEXTAREA_HEIGHT_MIRROR_CLASS}`)) mirror.remove();
|
||||
textareaHeightMirrors.delete(doc);
|
||||
}
|
||||
textareaHeightMirrorDocuments.clear();
|
||||
}
|
||||
|
||||
function measureTextareaNaturalScrollHeight(textarea: HTMLTextAreaElement, style: CSSStyleDeclaration): number {
|
||||
const mirror = textareaHeightMirror(textarea.ownerDocument);
|
||||
mirror.value = textarea.value;
|
||||
|
|
@ -30,14 +39,17 @@ function measureTextareaNaturalScrollHeight(textarea: HTMLTextAreaElement, style
|
|||
|
||||
function textareaHeightMirror(doc: Document): HTMLTextAreaElement {
|
||||
const existing = textareaHeightMirrors.get(doc);
|
||||
if (existing) return existing;
|
||||
const mirror = doc.createElement("textarea");
|
||||
if (existing?.isConnected) return existing;
|
||||
const staleMirrors = [...doc.querySelectorAll<HTMLTextAreaElement>(`.${TEXTAREA_HEIGHT_MIRROR_CLASS}`)];
|
||||
const mirror = staleMirrors.shift() ?? doc.createElement("textarea");
|
||||
for (const duplicate of staleMirrors) duplicate.remove();
|
||||
mirror.tabIndex = -1;
|
||||
mirror.setAttribute("aria-hidden", "true");
|
||||
mirror.readOnly = true;
|
||||
mirror.addClass(TEXTAREA_HEIGHT_MIRROR_CLASS);
|
||||
doc.body.appendChild(mirror);
|
||||
if (!mirror.isConnected) doc.body.appendChild(mirror);
|
||||
textareaHeightMirrors.set(doc, mirror);
|
||||
textareaHeightMirrorDocuments.add(doc);
|
||||
return mirror;
|
||||
}
|
||||
|
||||
|
|
|
|||
43
tests/shared/dom/textarea-autogrow.test.ts
Normal file
43
tests/shared/dom/textarea-autogrow.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// @vitest-environment jsdom
|
||||
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { disposeTextareaHeightMirrors, syncTextareaHeight } from "../../../src/shared/dom/textarea-autogrow.measure";
|
||||
import { installObsidianDomShims } from "../../support/dom";
|
||||
|
||||
installObsidianDomShims();
|
||||
|
||||
const MIRROR_SELECTOR = ".codex-panel-textarea-height-mirror";
|
||||
|
||||
afterEach(() => {
|
||||
disposeTextareaHeightMirrors();
|
||||
document.body.replaceChildren();
|
||||
});
|
||||
|
||||
describe("textarea auto-grow measurement", () => {
|
||||
it("adopts one existing mirror and removes reload duplicates", () => {
|
||||
document.body.append(mirror(), mirror());
|
||||
const textarea = document.createElement("textarea");
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
syncTextareaHeight(textarea, { minHeightFallback: 32, maxHeightFallback: 120 });
|
||||
|
||||
expect(document.querySelectorAll(MIRROR_SELECTOR)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("removes tracked mirrors when the plugin lifecycle ends", () => {
|
||||
const textarea = document.createElement("textarea");
|
||||
document.body.appendChild(textarea);
|
||||
syncTextareaHeight(textarea, { minHeightFallback: 32, maxHeightFallback: 120 });
|
||||
|
||||
disposeTextareaHeightMirrors();
|
||||
|
||||
expect(document.querySelector(MIRROR_SELECTOR)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
function mirror(): HTMLTextAreaElement {
|
||||
const element = document.createElement("textarea");
|
||||
element.className = MIRROR_SELECTOR.slice(1);
|
||||
return element;
|
||||
}
|
||||
Loading…
Reference in a new issue