Skip to content

Commit bbb7e94

Browse files
artem-harbourArtem Nistuley
andauthored
fix: place cursor at clicked pos inside tracked change (#2855)
Co-authored-by: Artem Nistuley <artem@superdoc.dev>
1 parent e272425 commit bbb7e94

2 files changed

Lines changed: 40 additions & 13 deletions

File tree

packages/super-editor/src/editors/v1/core/presentation-editor/pointer-events/EditorInputManager.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ function isDirectSingleCommentHighlightHit(target: EventTarget | null): boolean
101101
return getCommentHighlightThreadIds(target).length === 1;
102102
}
103103

104+
function isDirectTrackedChangeHit(target: EventTarget | null): boolean {
105+
if (!(target instanceof Element)) return false;
106+
return target.closest(TRACK_CHANGE_SELECTOR) != null;
107+
}
108+
104109
function resolveTrackChangeThreadId(target: EventTarget | null): string | null {
105110
if (!(target instanceof Element)) {
106111
return null;
@@ -220,11 +225,11 @@ function shouldIgnoreRepeatClickOnActiveComment(
220225
return false;
221226
}
222227

223-
// Direct clicks on commented text should place a caret at the clicked
224-
// position and let the comments plugin infer the active thread from the
225-
// resulting selection. Only preserve the pointerdown short-circuit for
226-
// nearby non-text surfaces, such as split-run gaps.
227-
if (isDirectSingleCommentHighlightHit(target)) {
228+
// Direct clicks on single-thread comment text or tracked-change text should
229+
// place a caret at the clicked position and let comment/thread activation be
230+
// inferred from the resulting selection. Only preserve the pointerdown
231+
// short-circuit for nearby non-text surfaces, such as split-run gaps.
232+
if (isDirectSingleCommentHighlightHit(target) || isDirectTrackedChangeHit(target)) {
228233
return false;
229234
}
230235

@@ -2292,7 +2297,9 @@ export class EditorInputManager {
22922297
}
22932298

22942299
#handleSingleCommentHighlightClick(event: PointerEvent, target: HTMLElement | null, editor: Editor): boolean {
2295-
if (isDirectSingleCommentHighlightHit(target)) {
2300+
// Direct hits on inline annotated text should not be intercepted here.
2301+
// Let generic click-to-position place the caret at the clicked pixel.
2302+
if (isDirectSingleCommentHighlightHit(target) || isDirectTrackedChangeHit(target)) {
22962303
return false;
22972304
}
22982305

packages/super-editor/src/editors/v1/core/presentation-editor/tests/EditorInputManager.activeCommentClick.test.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ describe('EditorInputManager - single-thread comment highlight clicks', () => {
238238
expect(viewportHost.setPointerCapture).toHaveBeenCalled();
239239
});
240240

241-
it('activates a tracked-change decoration when it owns the clicked visual surface', () => {
241+
it('lets direct clicks on a tracked-change decoration fall through to generic caret placement', () => {
242242
mockEditor.state.comments$.activeThreadId = 'comment-2';
243243

244244
const trackedChange = document.createElement('span');
@@ -248,12 +248,32 @@ describe('EditorInputManager - single-thread comment highlight clicks', () => {
248248

249249
dispatchPointerDown(trackedChange);
250250

251-
expect(mockEditor.commands.setCursorById).toHaveBeenCalledWith('change-1', {
252-
activeCommentId: 'change-1',
253-
});
254-
expect(resolvePointerPositionHit).not.toHaveBeenCalled();
255-
expect(mockEditor.state.tr.setSelection).not.toHaveBeenCalled();
256-
expect(viewportHost.setPointerCapture).not.toHaveBeenCalled();
251+
expect(mockEditor.commands.setCursorById).not.toHaveBeenCalled();
252+
expect(resolvePointerPositionHit).toHaveBeenCalled();
253+
expect(TextSelection.create as unknown as Mock).toHaveBeenCalled();
254+
expect(mockEditor.state.tr.setSelection).toHaveBeenCalled();
255+
expect(viewportHost.setPointerCapture).toHaveBeenCalled();
256+
});
257+
258+
it('lets repeat direct clicks on the active tracked-change decoration reposition caret', () => {
259+
mockEditor.state.comments$.activeThreadId = 'change-1';
260+
261+
const trackedChange = document.createElement('span');
262+
trackedChange.className = 'track-delete-dec highlighted';
263+
trackedChange.setAttribute('data-track-change-id', 'change-1');
264+
viewportHost.appendChild(trackedChange);
265+
266+
dispatchPointerDown(trackedChange);
267+
268+
expect(mockEditor.emit).not.toHaveBeenCalledWith(
269+
'commentsUpdate',
270+
expect.objectContaining({ activeCommentId: 'change-1' }),
271+
);
272+
expect(mockEditor.commands.setCursorById).not.toHaveBeenCalled();
273+
expect(resolvePointerPositionHit).toHaveBeenCalled();
274+
expect(TextSelection.create as unknown as Mock).toHaveBeenCalled();
275+
expect(mockEditor.state.tr.setSelection).toHaveBeenCalled();
276+
expect(viewportHost.setPointerCapture).toHaveBeenCalled();
257277
});
258278

259279
it('activates a nearby single-thread highlight when a split-run gap receives the pointer event', () => {

0 commit comments

Comments
 (0)