Skip to content

Commit 9567adf

Browse files
committed
fix(user-input): track raw selection in ref and sync mention menus on ranged selections
1 parent 2e0aacd commit 9567adf

1 file changed

Lines changed: 27 additions & 29 deletions

File tree

  • apps/sim/app/workspace/[workspaceId]/home/components/user-input

apps/sim/app/workspace/[workspaceId]/home/components/user-input/user-input.tsx

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,7 +1030,7 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
10301030
]
10311031
)
10321032

1033-
/** Last observed selection; tells which edge of a range moved, and which way. */
1033+
/** Last selection reported by the DOM; tells which edge of a range moved, and which way. */
10341034
const lastSelectionRef = useRef<{ start: number; end: number }>({ start: 0, end: 0 })
10351035

10361036
/**
@@ -1045,55 +1045,53 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
10451045
const start = textarea.selectionStart ?? 0
10461046
const end = textarea.selectionEnd ?? 0
10471047
const prev = lastSelectionRef.current
1048+
// Always track the raw observed selection — never an intended write that
1049+
// may get superseded — so edge-movement inference stays true to the DOM.
1050+
lastSelectionRef.current = { start, end }
10481051

1049-
// Deferred so in-flight click/drag processing can't override the write;
1050-
// bails if the selection moved again first (a newer event supersedes it).
1051-
const applySelection = (nextStart: number, nextEnd: number) => {
1052-
const direction = textarea.selectionDirection ?? undefined
1053-
setTimeout(() => {
1054-
if (textarea.selectionStart !== start || textarea.selectionEnd !== end) return
1055-
textarea.setSelectionRange(nextStart, nextEnd, direction)
1056-
}, 0)
1057-
}
1058-
1052+
let newStart = start
1053+
let newEnd = end
10591054
if (start !== end) {
10601055
const startRange = mentionTokensWithContext.findRangeContaining(start)
10611056
const endRange = mentionTokensWithContext.findRangeContaining(end)
10621057
// A lone moved edge (keyboard extend/shrink, drag) snaps in its direction
10631058
// of travel: growing absorbs the chip, shrinking releases it. Fresh
10641059
// selections (double-click, select-all) expand outward.
10651060
const singleEdgeMoved = (start !== prev.start) !== (end !== prev.end)
1066-
let newStart = startRange
1061+
newStart = startRange
10671062
? singleEdgeMoved && start > prev.start
10681063
? startRange.end
10691064
: startRange.start
10701065
: start
1071-
const newEnd = endRange
1072-
? singleEdgeMoved && end < prev.end
1073-
? endRange.start
1074-
: endRange.end
1075-
: end
1066+
newEnd = endRange ? (singleEdgeMoved && end < prev.end ? endRange.start : endRange.end) : end
10761067
// A selection contained in one chip snaps both edges; don't let it invert.
10771068
if (newStart > newEnd) {
10781069
newStart = newEnd
10791070
}
1080-
lastSelectionRef.current = { start: newStart, end: newEnd }
1081-
if (newStart !== start || newEnd !== end) {
1082-
applySelection(newStart, newEnd)
1071+
} else {
1072+
const r = mentionTokensWithContext.findRangeContaining(start)
1073+
if (r) {
1074+
const snapPos = start - r.start < r.end - start ? r.start : r.end
1075+
newStart = snapPos
1076+
newEnd = snapPos
10831077
}
1084-
return
10851078
}
10861079

1087-
const r = mentionTokensWithContext.findRangeContaining(start)
1088-
if (r) {
1089-
const snapPos = start - r.start < r.end - start ? r.start : r.end
1090-
lastSelectionRef.current = { start: snapPos, end: snapPos }
1091-
applySelection(snapPos, snapPos)
1080+
if (newStart !== start || newEnd !== end) {
1081+
// Deferred so in-flight click/drag processing can't override the write;
1082+
// bails if the selection moved again first (a newer event supersedes it).
1083+
// The write re-fires this handler, which then syncs the menus below.
1084+
const direction = textarea.selectionDirection ?? undefined
1085+
setTimeout(() => {
1086+
if (textarea.selectionStart !== start || textarea.selectionEnd !== end) return
1087+
textarea.setSelectionRange(newStart, newEnd, direction)
1088+
}, 0)
10921089
return
10931090
}
1094-
lastSelectionRef.current = { start, end }
1095-
syncMentionState(textarea, textarea.value, start)
1096-
syncSlashState(textarea, textarea.value, start)
1091+
1092+
const focusPos = textarea.selectionDirection === 'backward' ? start : end
1093+
syncMentionState(textarea, textarea.value, focusPos)
1094+
syncSlashState(textarea, textarea.value, focusPos)
10971095
}, [textareaRef, mentionTokensWithContext, syncMentionState, syncSlashState])
10981096

10991097
const handleInput = useCallback(

0 commit comments

Comments
 (0)