Skip to content

Commit 42484de

Browse files
committed
fix press and hold cursor lag when lynx enabled
1 parent 8c2f89b commit 42484de

File tree

3 files changed

+74
-12
lines changed

3 files changed

+74
-12
lines changed

src/SIL.XForge.Scripture/ClientApp/src/app/shared/text/text.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
rtl: isRtl,
2222
'mark-invalid': markInvalid,
2323
'selectable-verses': selectableVerses,
24-
'custom-local-cursor': showInsights
24+
'custom-local-cursor': showInsights && !isCursorMoveKeyDown
2525
}"
2626
[dir]="$any(textDirection)"
2727
[lang]="lang"

src/SIL.XForge.Scripture/ClientApp/src/app/shared/text/text.component.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ quill-editor {
1313
}
1414

1515
.ql-cursor-caret {
16-
width: 1px;
16+
width: 1.5px;
1717
background-color: $local-cursor-color;
1818
}
1919

src/SIL.XForge.Scripture/ClientApp/src/app/shared/text/text.component.ts

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { SFProjectRole } from 'realtime-server/lib/esm/scriptureforge/models/sf-
2020
import { TextAnchor } from 'realtime-server/lib/esm/scriptureforge/models/text-anchor';
2121
import { StringMap } from 'rich-text';
2222
import { fromEvent, Subject, Subscription, timer } from 'rxjs';
23-
import { takeUntil } from 'rxjs/operators';
23+
import { takeUntil, tap } from 'rxjs/operators';
2424
import { LocalPresence, Presence } from 'sharedb/lib/sharedb';
2525
import tinyColor from 'tinycolor2';
2626
import { WINDOW } from 'xforge-common/browser-globals';
@@ -113,12 +113,39 @@ export class TextComponent implements AfterViewInit, OnDestroy {
113113
@Output() editorCreated = new EventEmitter<void>();
114114

115115
lang: string = '';
116+
117+
/**
118+
* Flag activated when user presses and holds keys that cause the cursor to move.
119+
* A true value will cause the system cursor to be used instead of
120+
* Quill custom local cursor in order to avoid cursor lag.
121+
*/
122+
isCursorMoveKeyDown = false;
123+
116124
// only use USX formats and not default Quill formats
117125
readonly allowedFormats: string[] = this.quillFormatRegistry.getRegisteredFormats();
118126
// allow for different CSS based on the browser engine
119127
readonly browserEngine: string = getBrowserEngine();
120128
readonly cursorColor: string;
121129

130+
/** Set of currently pressed keys that move the cursor. */
131+
private readonly pressedCursorMoveKeys = new Set<string>();
132+
133+
/** Set of non-printable keys that move the cursor. */
134+
private readonly nonPrintableCursorMoveKeys = new Set<string>([
135+
'ArrowLeft',
136+
'ArrowRight',
137+
'ArrowUp',
138+
'ArrowDown',
139+
'Home',
140+
'End',
141+
'PageUp',
142+
'PageDown',
143+
'Tab'
144+
]);
145+
146+
private cursorMoveKeyHoldTimeout?: any;
147+
private cursorMoveKeyHoldDelay: number = 500; // Press and hold ms delay before switching to system cursor
148+
122149
private clickSubs: Map<string, Subscription[]> = new Map<string, Subscription[]>();
123150
private _isReadOnly: boolean = true;
124151
private _editorStyles: any = { fontSize: '1rem' };
@@ -533,21 +560,56 @@ export class TextComponent implements AfterViewInit, OnDestroy {
533560
});
534561

535562
fromEvent<KeyboardEvent>(this.document, 'keydown')
536-
.pipe(quietTakeUntilDestroyed(this.destroyRef))
563+
.pipe(
564+
quietTakeUntilDestroyed(this.destroyRef),
565+
tap(event => (this.isShiftDown = event.shiftKey))
566+
)
537567
.subscribe(event => {
538-
this.isShiftDown = event.shiftKey;
568+
// Set flag to use system cursor when any key is down that would move the cursor (avoids cursor lag issue)
569+
if (this.nonPrintableCursorMoveKeys.has(event.key) || event.key.length === 1) {
570+
this.pressedCursorMoveKeys.add(event.key);
571+
572+
// Only set the flag when the user presses and holds (detect with a short timeout delay)
573+
if (!this.isCursorMoveKeyDown && this.cursorMoveKeyHoldTimeout == null) {
574+
this.cursorMoveKeyHoldTimeout = setTimeout(() => {
575+
if (this.pressedCursorMoveKeys.size > 0) {
576+
this.isCursorMoveKeyDown = true;
577+
}
578+
579+
this.cursorMoveKeyHoldTimeout = undefined;
580+
}, this.cursorMoveKeyHoldDelay);
581+
}
582+
}
539583
});
540584

541585
fromEvent<KeyboardEvent>(this.document, 'keyup')
542-
.pipe(quietTakeUntilDestroyed(this.destroyRef))
586+
.pipe(
587+
quietTakeUntilDestroyed(this.destroyRef),
588+
tap(event => {
589+
// Call 'update()' when shift key is released, as update is disabled while shift is down
590+
// to prevent incorrect cursor position updates while selecting text.
591+
if (this.isShiftDown && !event.shiftKey) {
592+
this.update();
593+
}
594+
595+
this.isShiftDown = event.shiftKey;
596+
})
597+
)
543598
.subscribe(event => {
544-
// Call 'update()' when shift key is released, as update is disabled while shift is down
545-
// to prevent incorrect cursor position updates while selecting text.
546-
if (this.isShiftDown && !event.shiftKey) {
547-
this.update();
548-
}
599+
this.pressedCursorMoveKeys.delete(event.key);
549600

550-
this.isShiftDown = event.shiftKey;
601+
// If set is empty, all cursor movement keys are released
602+
if (this.pressedCursorMoveKeys.size === 0) {
603+
if (this.cursorMoveKeyHoldTimeout) {
604+
clearTimeout(this.cursorMoveKeyHoldTimeout);
605+
this.cursorMoveKeyHoldTimeout = undefined;
606+
}
607+
608+
// Helps to not yet show custom local cursor until it has caught up to system cursor that was just visible
609+
requestAnimationFrame(() => {
610+
this.isCursorMoveKeyDown = false;
611+
});
612+
}
551613
});
552614

553615
fromEvent<FocusEvent>(this.window, 'blur')

0 commit comments

Comments
 (0)