Break out helper functions from the frontend's managers and stores (#3920)

* Move destructor call to each manager/store constructor for safety

* Break out utility functions
This commit is contained in:
Keavon Chambers
2026-03-20 14:22:46 -07:00
committed by GitHub
parent 0c7b5cd534
commit 64fd12a1a0
19 changed files with 846 additions and 826 deletions
+3 -82
View File
@@ -1,8 +1,11 @@
import type { Editor } from "@graphite/editor";
import { insertAtCaret, readAtCaret } from "@graphite/utility-functions/clipboard";
let editorRef: Editor | undefined = undefined;
export function createClipboardManager(editor: Editor) {
destroyClipboardManager();
editorRef = editor;
editor.subscriptions.subscribeFrontendMessage("TriggerClipboardWrite", (data) => {
@@ -28,89 +31,7 @@ export function destroyClipboardManager() {
editor.subscriptions.unsubscribeFrontendMessage("TriggerSelectionWrite");
}
function readAtCaret(cut: boolean): string | undefined {
const element = window.document.activeElement;
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
const start = element.selectionStart;
const end = element.selectionEnd;
if ((!start && start !== 0) || (!end && end !== 0) || start === end) {
return undefined;
}
const value = element.value;
const selectedText = value.slice(start, end);
if (cut) {
element.value = value.slice(0, start) + value.slice(end);
element.selectionStart = element.selectionEnd = start;
element.dispatchEvent(new Event("input", { bubbles: true }));
}
return selectedText;
}
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) {
return undefined;
}
const selectedText = String(selection);
if (!selectedText) return undefined;
if (cut) {
const range = selection.getRangeAt(0);
range.deleteContents();
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
return selectedText;
}
function insertAtCaret(text: string) {
const element = window.document.activeElement;
if (!element) return;
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
const start = element.selectionStart;
const end = element.selectionEnd;
if ((!start && start !== 0) || (!end && end !== 0)) return;
const value = element.value;
element.value = value.slice(0, start) + text + value.slice(end);
const newPos = start + text.length;
element.selectionStart = element.selectionEnd = newPos;
} else if (element instanceof HTMLElement && element.isContentEditable) {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) return;
const range = selection.getRangeAt(0);
range.deleteContents();
const textNode = window.document.createTextNode(text);
range.insertNode(textNode);
range.setStartAfter(textNode);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
element.dispatchEvent(new Event("input", { bubbles: true }));
}
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
import.meta.hot?.accept((newModule) => {
destroyClipboardManager();
if (editorRef) newModule?.createClipboardManager(editorRef);
});