fix(workspace): fix cursor jump while typing by preventing useLayoutEffect from overriding active element cursor
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m6s

This commit is contained in:
sandrodach committed 2026-10-26 16:00:52 +01:00
1 parent 53e1ba322d
commit 80bb74b5b2
1 file changed
+64 -11
@@ -2190,23 +2190,20 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
if (el instanceof HTMLTextAreaElement) {
autoResizeTextarea(el);
}
if ('setSelectionRange' in el && typeof el.value === 'string') {
// Only programmatically adjust selection if the element is NOT already the actively focused element!
if (document.activeElement !== el && 'setSelectionRange' in el && typeof el.value === 'string') {
const segStartAttr = el.getAttribute('data-seg-start');
const segStart = segStartAttr !== null ? parseInt(segStartAttr, 10) || 0 : 0;
const desiredOffset = activeCursorOffsetRef.current;
const localPos = Math.max(0, Math.min(el.value.length, desiredOffset - segStart));
if (document.activeElement !== el) {
el.focus();
}
try {
if (el.selectionStart !== localPos || el.selectionEnd !== localPos) {
el.setSelectionRange(localPos, localPos);
}
} catch {
// Ignore selection range errors on unsupported inputs
}
}
}, [activeEditingBlockId, activeCursorOffset, selectedDoc]);
}, [activeEditingBlockId]);
// Keyboard Navigation: Enter, Shift+Enter, Tab, Shift+Tab, Backspace, / Slash Commands
const handleKeyDown = (
@@ -3073,7 +3070,29 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
onClick={(e) => {
if (isReadOnly) return;
if ((e.target as HTMLElement).closest('button, a')) return;
focusBlock(block.id);
let clickOffset: number | undefined = undefined;
try {
if (document.caretRangeFromPoint) {
const range = document.caretRangeFromPoint(e.clientX, e.clientY);
if (range && e.currentTarget.contains(range.startContainer)) {
const preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(e.currentTarget);
preCaretRange.setEnd(range.endContainer, range.endOffset);
clickOffset = preCaretRange.toString().length;
}
} else if ((document as any).caretPositionFromPoint) {
const pos = (document as any).caretPositionFromPoint(e.clientX, e.clientY);
if (pos && pos.offsetNode && e.currentTarget.contains(pos.offsetNode)) {
const range = document.createRange();
range.selectNodeContents(e.currentTarget);
range.setEnd(pos.offsetNode, pos.offset);
clickOffset = range.toString().length;
}
}
} catch {
// fallback to end of text
}
focusBlock(block.id, clickOffset);
}}
className={`w-full text-sm leading-relaxed break-words whitespace-pre-wrap min-h-[22px] cursor-text ${extraTextClass}`}
>
@@ -3099,7 +3118,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
value={block.content || ''}
placeholder="Notiz eingeben… (/ für Befehle)"
onFocus={() => {
handleBlockFocus();
handleBlockFocus(block.id);
}}
onBlur={() => {
setActiveEditingBlockId((prev) => (prev === block.id ? null : prev));
@@ -3107,8 +3126,24 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
}}
onChange={(e) => {
autoResizeTextarea(e.target);
const pos = e.target.selectionStart;
if (typeof pos === 'number') {
activeCursorOffsetRef.current = pos;
}
handleContentChange(block, e.target.value);
}}
onSelect={(e) => {
const target = e.target as HTMLTextAreaElement;
if (typeof target.selectionStart === 'number') {
activeCursorOffsetRef.current = target.selectionStart;
}
}}
onClick={(e) => {
const target = e.target as HTMLTextAreaElement;
if (typeof target.selectionStart === 'number') {
activeCursorOffsetRef.current = target.selectionStart;
}
}}
onKeyDown={(e) => handleKeyDown(e, block, originalIndex)}
className={`w-full bg-transparent text-sm leading-relaxed focus:outline-none resize-none overflow-hidden break-words whitespace-pre-wrap placeholder:text-faint p-0 m-0 border-0 ${extraTextClass}`}
/>
@@ -4642,12 +4677,18 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
rows={1}
readOnly={isReadOnly}
value={block.content}
onFocus={handleBlockFocus}
onFocus={() => handleBlockFocus(block.id)}
onBlur={handleBlockBlur}
onChange={(e) => {
autoResizeTextarea(e.target);
const pos = e.target.selectionStart;
if (typeof pos === 'number') activeCursorOffsetRef.current = pos;
handleContentChange(block, e.target.value);
}}
onSelect={(e) => {
const target = e.target as HTMLTextAreaElement;
if (typeof target.selectionStart === 'number') activeCursorOffsetRef.current = target.selectionStart;
}}
onKeyDown={(e) => handleKeyDown(e, block, originalIndex)}
className={`w-full bg-transparent text-lg font-bold text-main focus:outline-none resize-none overflow-hidden break-words whitespace-pre-wrap p-0 m-0 border-0 ${
isReadOnly ? 'cursor-default' : ''
@@ -4665,12 +4706,18 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
rows={1}
readOnly={isReadOnly}
value={block.content}
onFocus={handleBlockFocus}
onFocus={() => handleBlockFocus(block.id)}
onBlur={handleBlockBlur}
onChange={(e) => {
autoResizeTextarea(e.target);
const pos = e.target.selectionStart;
if (typeof pos === 'number') activeCursorOffsetRef.current = pos;
handleContentChange(block, e.target.value);
}}
onSelect={(e) => {
const target = e.target as HTMLTextAreaElement;
if (typeof target.selectionStart === 'number') activeCursorOffsetRef.current = target.selectionStart;
}}
onKeyDown={(e) => handleKeyDown(e, block, originalIndex)}
className={`w-full bg-transparent text-base font-semibold text-accent focus:outline-none resize-none overflow-hidden break-words whitespace-pre-wrap p-0 m-0 border-0 ${
isReadOnly ? 'cursor-default' : ''
@@ -4688,12 +4735,18 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
rows={1}
readOnly={isReadOnly}
value={block.content}
onFocus={handleBlockFocus}
onFocus={() => handleBlockFocus(block.id)}
onBlur={handleBlockBlur}
onChange={(e) => {
autoResizeTextarea(e.target);
const pos = e.target.selectionStart;
if (typeof pos === 'number') activeCursorOffsetRef.current = pos;
handleContentChange(block, e.target.value);
}}
onSelect={(e) => {
const target = e.target as HTMLTextAreaElement;
if (typeof target.selectionStart === 'number') activeCursorOffsetRef.current = target.selectionStart;
}}
onKeyDown={(e) => handleKeyDown(e, block, originalIndex)}
className={`w-full bg-transparent text-sm font-semibold text-main focus:outline-none resize-none overflow-hidden break-words whitespace-pre-wrap p-0 m-0 border-0 ${
isReadOnly ? 'cursor-default' : ''