snapshot of https://github.com/excalidraw/excalidraw.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Tests / test (push) Failing after 15s
Some checks failed
Tests / test (push) Failing after 15s
This commit is contained in:
124
packages/excalidraw/hooks/useTextEditorFocus.ts
Normal file
124
packages/excalidraw/hooks/useTextEditorFocus.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
// Utility type for caret position
|
||||
export type CaretPosition = {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
|
||||
// Utility function to get text editor element
|
||||
const getTextEditor = (ownerDocument: Document): HTMLTextAreaElement | null => {
|
||||
return ownerDocument.querySelector(
|
||||
".excalidraw-wysiwyg",
|
||||
) as HTMLTextAreaElement;
|
||||
};
|
||||
|
||||
// Utility functions for caret position management
|
||||
export const saveCaretPosition = (
|
||||
ownerDocument: Document = document,
|
||||
): CaretPosition | null => {
|
||||
const textEditor = getTextEditor(ownerDocument);
|
||||
if (textEditor) {
|
||||
return {
|
||||
start: textEditor.selectionStart,
|
||||
end: textEditor.selectionEnd,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const restoreCaretPosition = (
|
||||
position: CaretPosition | null,
|
||||
ownerDocument: Document = document,
|
||||
): void => {
|
||||
const ownerWindow = ownerDocument.defaultView ?? window;
|
||||
ownerWindow.setTimeout(() => {
|
||||
const textEditor = getTextEditor(ownerDocument);
|
||||
if (textEditor) {
|
||||
textEditor.focus();
|
||||
if (position) {
|
||||
textEditor.selectionStart = position.start;
|
||||
textEditor.selectionEnd = position.end;
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
export const withCaretPositionPreservation = (
|
||||
callback: () => void,
|
||||
isCompactMode: boolean,
|
||||
isEditingText: boolean,
|
||||
onPreventClose?: () => void,
|
||||
ownerDocument: Document = document,
|
||||
): void => {
|
||||
// Prevent popover from closing in compact mode
|
||||
if (isCompactMode && onPreventClose) {
|
||||
onPreventClose();
|
||||
}
|
||||
|
||||
// Save caret position if editing text
|
||||
const savedPosition =
|
||||
isCompactMode && isEditingText ? saveCaretPosition(ownerDocument) : null;
|
||||
|
||||
// Execute the callback
|
||||
callback();
|
||||
|
||||
// Restore caret position if needed
|
||||
if (isCompactMode && isEditingText) {
|
||||
restoreCaretPosition(savedPosition, ownerDocument);
|
||||
}
|
||||
};
|
||||
|
||||
// Hook for managing text editor caret position with state
|
||||
export const useTextEditorFocus = (ownerDocument: Document = document) => {
|
||||
const [savedCaretPosition, setSavedCaretPosition] =
|
||||
useState<CaretPosition | null>(null);
|
||||
|
||||
const saveCaretPositionToState = useCallback(() => {
|
||||
const position = saveCaretPosition(ownerDocument);
|
||||
setSavedCaretPosition(position);
|
||||
}, [ownerDocument]);
|
||||
|
||||
const restoreCaretPositionFromState = useCallback(() => {
|
||||
const ownerWindow = ownerDocument.defaultView ?? window;
|
||||
ownerWindow.setTimeout(() => {
|
||||
const textEditor = getTextEditor(ownerDocument);
|
||||
if (textEditor) {
|
||||
textEditor.focus();
|
||||
if (savedCaretPosition) {
|
||||
textEditor.selectionStart = savedCaretPosition.start;
|
||||
textEditor.selectionEnd = savedCaretPosition.end;
|
||||
setSavedCaretPosition(null);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}, [ownerDocument, savedCaretPosition]);
|
||||
|
||||
const clearSavedPosition = useCallback(() => {
|
||||
setSavedCaretPosition(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
saveCaretPosition: saveCaretPositionToState,
|
||||
restoreCaretPosition: restoreCaretPositionFromState,
|
||||
clearSavedPosition,
|
||||
hasSavedPosition: !!savedCaretPosition,
|
||||
};
|
||||
};
|
||||
|
||||
// Utility function to temporarily disable text editor blur
|
||||
export const temporarilyDisableTextEditorBlur = (
|
||||
ownerDocument: Document = document,
|
||||
duration: number = 100,
|
||||
): void => {
|
||||
const textEditor = getTextEditor(ownerDocument);
|
||||
if (textEditor) {
|
||||
const originalOnBlur = textEditor.onblur;
|
||||
textEditor.onblur = null;
|
||||
|
||||
const ownerWindow = ownerDocument.defaultView ?? window;
|
||||
ownerWindow.setTimeout(() => {
|
||||
textEditor.onblur = originalOnBlur;
|
||||
}, duration);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user