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
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m6s
This commit is contained in:
1 parent
53e1ba322d
commit
80bb74b5b2
1 file changed
+64
-11
@@ -2190,23 +2190,20 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
if (el instanceof HTMLTextAreaElement) {
|
if (el instanceof HTMLTextAreaElement) {
|
||||||
autoResizeTextarea(el);
|
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 segStartAttr = el.getAttribute('data-seg-start');
|
||||||
const segStart = segStartAttr !== null ? parseInt(segStartAttr, 10) || 0 : 0;
|
const segStart = segStartAttr !== null ? parseInt(segStartAttr, 10) || 0 : 0;
|
||||||
const desiredOffset = activeCursorOffsetRef.current;
|
const desiredOffset = activeCursorOffsetRef.current;
|
||||||
const localPos = Math.max(0, Math.min(el.value.length, desiredOffset - segStart));
|
const localPos = Math.max(0, Math.min(el.value.length, desiredOffset - segStart));
|
||||||
if (document.activeElement !== el) {
|
|
||||||
el.focus();
|
el.focus();
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
if (el.selectionStart !== localPos || el.selectionEnd !== localPos) {
|
|
||||||
el.setSelectionRange(localPos, localPos);
|
el.setSelectionRange(localPos, localPos);
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore selection range errors on unsupported inputs
|
// Ignore selection range errors on unsupported inputs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [activeEditingBlockId, activeCursorOffset, selectedDoc]);
|
}, [activeEditingBlockId]);
|
||||||
|
|
||||||
// Keyboard Navigation: Enter, Shift+Enter, Tab, Shift+Tab, Backspace, / Slash Commands
|
// Keyboard Navigation: Enter, Shift+Enter, Tab, Shift+Tab, Backspace, / Slash Commands
|
||||||
const handleKeyDown = (
|
const handleKeyDown = (
|
||||||
@@ -3073,7 +3070,29 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (isReadOnly) return;
|
if (isReadOnly) return;
|
||||||
if ((e.target as HTMLElement).closest('button, a')) 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}`}
|
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 || ''}
|
value={block.content || ''}
|
||||||
placeholder="Notiz eingeben… (/ für Befehle)"
|
placeholder="Notiz eingeben… (/ für Befehle)"
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
handleBlockFocus();
|
handleBlockFocus(block.id);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
setActiveEditingBlockId((prev) => (prev === block.id ? null : prev));
|
setActiveEditingBlockId((prev) => (prev === block.id ? null : prev));
|
||||||
@@ -3107,8 +3126,24 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
}}
|
}}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
autoResizeTextarea(e.target);
|
autoResizeTextarea(e.target);
|
||||||
|
const pos = e.target.selectionStart;
|
||||||
|
if (typeof pos === 'number') {
|
||||||
|
activeCursorOffsetRef.current = pos;
|
||||||
|
}
|
||||||
handleContentChange(block, e.target.value);
|
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)}
|
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}`}
|
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}
|
rows={1}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
value={block.content}
|
value={block.content}
|
||||||
onFocus={handleBlockFocus}
|
onFocus={() => handleBlockFocus(block.id)}
|
||||||
onBlur={handleBlockBlur}
|
onBlur={handleBlockBlur}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
autoResizeTextarea(e.target);
|
autoResizeTextarea(e.target);
|
||||||
|
const pos = e.target.selectionStart;
|
||||||
|
if (typeof pos === 'number') activeCursorOffsetRef.current = pos;
|
||||||
handleContentChange(block, e.target.value);
|
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)}
|
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 ${
|
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' : ''
|
isReadOnly ? 'cursor-default' : ''
|
||||||
@@ -4665,12 +4706,18 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
rows={1}
|
rows={1}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
value={block.content}
|
value={block.content}
|
||||||
onFocus={handleBlockFocus}
|
onFocus={() => handleBlockFocus(block.id)}
|
||||||
onBlur={handleBlockBlur}
|
onBlur={handleBlockBlur}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
autoResizeTextarea(e.target);
|
autoResizeTextarea(e.target);
|
||||||
|
const pos = e.target.selectionStart;
|
||||||
|
if (typeof pos === 'number') activeCursorOffsetRef.current = pos;
|
||||||
handleContentChange(block, e.target.value);
|
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)}
|
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 ${
|
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' : ''
|
isReadOnly ? 'cursor-default' : ''
|
||||||
@@ -4688,12 +4735,18 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
rows={1}
|
rows={1}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
value={block.content}
|
value={block.content}
|
||||||
onFocus={handleBlockFocus}
|
onFocus={() => handleBlockFocus(block.id)}
|
||||||
onBlur={handleBlockBlur}
|
onBlur={handleBlockBlur}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
autoResizeTextarea(e.target);
|
autoResizeTextarea(e.target);
|
||||||
|
const pos = e.target.selectionStart;
|
||||||
|
if (typeof pos === 'number') activeCursorOffsetRef.current = pos;
|
||||||
handleContentChange(block, e.target.value);
|
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)}
|
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 ${
|
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' : ''
|
isReadOnly ? 'cursor-default' : ''
|
||||||
|
|||||||
Reference in new issue
Block a user