Browse Source

Fixed sync highlight overlay scroll on Safari in string-pattern-autocomplete (#15754)

* Fixed sync highlight overlay scroll on Safari in string-pattern-autocomplete

* Minor adjustment

* Code improvements
pull/15796/head
Maksym Tsymbarov 4 months ago
committed by GitHub
parent
commit
9c610c4707
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      ui-ngx/src/app/shared/components/string-pattern-autocomplete.component.html
  2. 49
      ui-ngx/src/app/shared/components/string-pattern-autocomplete.component.ts

2
ui-ngx/src/app/shared/components/string-pattern-autocomplete.component.html

@ -24,7 +24,7 @@
[formControl]="selectionFormControl"
[placeholder]="placeholderText"
(focusin)="onFocus()"
(scroll)="onInputScroll($event)">
(focusout)="onBlur()">
@if (predefinedValuesButton) {
<button type="button"
matSuffix

49
ui-ngx/src/app/shared/components/string-pattern-autocomplete.component.ts

@ -45,6 +45,7 @@ import {
PositionStrategy
} from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
@Component({
selector: 'tb-string-pattern-autocomplete',
@ -123,6 +124,8 @@ export class StringPatternAutocompleteComponent implements ControlValueAccessor,
private predefinedValuesButtonMode = false;
private scrollSyncCancel: CancelAnimationFrame | null = null;
private propagateChange = (_val: any) => {
};
@ -131,7 +134,8 @@ export class StringPatternAutocompleteComponent implements ControlValueAccessor,
private translate: TranslateService,
private viewContainerRef: ViewContainerRef,
private destroyRef: DestroyRef,
private cd: ChangeDetectorRef) {
private cd: ChangeDetectorRef,
private raf: RafService) {
}
ngOnInit() {
@ -150,6 +154,8 @@ export class StringPatternAutocompleteComponent implements ControlValueAccessor,
fromEvent<KeyboardEvent>(this.inputRef.nativeElement, 'keydown')
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(event => this.handleKeydown(event));
this.destroyRef.onDestroy(() => this.stopScrollSync());
}
ngOnChanges(changes: SimpleChanges) {
@ -180,6 +186,11 @@ export class StringPatternAutocompleteComponent implements ControlValueAccessor,
onFocus() {
this.onSelectionChange();
this.startScrollSync();
}
onBlur() {
this.stopScrollSync();
}
registerOnChange(fn: any): void {
@ -199,12 +210,36 @@ export class StringPatternAutocompleteComponent implements ControlValueAccessor,
}
}
onInputScroll(event: Event) {
const scrollLeft = (event.target as HTMLInputElement).scrollLeft;
const scrollTop = (event.target as HTMLInputElement).scrollTop;
if (this.highlightTextRef && this.highlightTextRef.nativeElement) {
this.highlightTextRef.nativeElement.scrollLeft = scrollLeft;
this.highlightTextRef.nativeElement.scrollTop = scrollTop;
// Continuous per-frame poll instead of a 'scroll' event handler: Safari does not reliably
// fire 'scroll' on this input, so the highlight overlay would drift out of sync. The poll
// also keeps the overlay synced frame-by-frame during drag-selection / auto-scroll, where an
// event-driven sync stutters. The loop runs only while the input is focused (one at a time),
// so its cost is bounded - do not revert this to a 'scroll' listener.
private startScrollSync() {
if (this.scrollSyncCancel !== null) {
return;
}
let lastLeft = -1;
let lastTop = -1;
const tick = () => {
const input = this.inputRef?.nativeElement as HTMLInputElement;
const overlay = this.highlightTextRef?.nativeElement as HTMLElement;
if (input && overlay && (input.scrollLeft !== lastLeft || input.scrollTop !== lastTop)) {
lastLeft = input.scrollLeft;
lastTop = input.scrollTop;
overlay.scrollLeft = lastLeft;
overlay.scrollTop = lastTop;
}
this.scrollSyncCancel = this.raf.raf(tick);
};
this.scrollSyncCancel = this.raf.raf(tick);
}
private stopScrollSync() {
if (this.scrollSyncCancel !== null) {
this.scrollSyncCancel();
this.scrollSyncCancel = null;
}
}

Loading…
Cancel
Save