Я работаю над проектом веб-приложения Angular 8 для использования несколькими платформами (iOS, Android, Windows и др. c.). Я заметил, что между использованием Safari и Chrome существует несколько различий. Например, при размещении ngIf
внутри <div>
с ложным условием, оно не будет отображаться в представлении Safari, когда оно станет истинным, но оно будет отображаться в Chrome (размещение ngIf
в <ng-container>
обертка исправляет эту проблему).
Моя текущая проблема касается использования <textarea>
. При использовании следующего textarea
в Safari возникает проблема возврата на задний план, которая продолжает возникать. Я могу написать первую строку текста, и, прежде чем возврат каретки, Backspace работает как задумано (убрать один символ сразу слева от курсора). После возврата каретки, если используется возврат на одну позицию, все символы перед курсором удаляются, а все символы после курсора, если таковые имеются, остаются. Это происходит, когда курсор находится в любой позиции на любой строке, кроме первой, где я все еще могу использовать клавишу Backspace, как и ожидалось. На первом снимке экрана ниже показано, что содержится в каждой строке, прежде чем поместить курсор в конец второй строки. Второй снимок экрана показывает, что происходит, когда я нажимаю кнопку возврата.
Это поведение не наблюдается при использовании Chrome, Firefox или Edge, а ведет себя как ожидалось. Я работаю на Macbook Pro Mojave с самыми последними версиями браузера на момент публикации. Такое поведение происходит как локально, так и в производстве для разработчиков и пользователей (не ограничиваясь только моей установкой).
Любые идеи о том, что вызывает это поведение и как его смягчить?
Вот код, который используется (из-за проприетарных проблем я делюсь только ссылочными элементами / функциями, не все файлы):
от ребенка HTML
<textarea class="textbox form-control hammerContainer" id="{{id}}" name="textArea" placeholder="{{placeholder}}" rows="{{rowNumbers}}"
#textArea value="{{value}}" [attr.minLength]="minlength" [attr.maxLength]="maxlength" (keyup)="writeValue($event.target.value)"
(blur)="onTouched()" (paste)="onPaste($event)" (click)="getCursorPos(textArea)" (keydown)="getCursorPos(textArea)" (contextmenu)="getCursorPos(textArea)"
(select)="getCursorPos(textArea)" (press)="getCursorPos(textArea)" (tap)="getCursorPos(textArea)">
</textarea>
от ребенка .ts
import { Component, OnInit, Output [...more imports...], EventEmitter, ChangeDetectorRef } from '@angular/core';
@Output() valueChange = new EventEmitter<string>();
private caretPosStart: number = 0;
private caretPosEnd: number = 0;
public onChange: Function = (val: string) => { };
public onTouched: Function = () => { };
constructor(private changeDetectorRef: ChangeDetectorRef) { }
public writeValue(value: any): void {
if (value !== null && value !== undefined) {
this.value = value.substring(0,this.maxlength);
this.onChange(this.value);
this.valueChange.emit(this.value);
}
}
public getCursorPos(element: any): void {
if (element.selectionEnd || element.selectionEnd === 0) {
this.caretPosStart = element.selectionStart;
this.caretPosEnd = element.selectionEnd;
}
}
onPaste(event: ClipboardEvent): void {
try {
event.preventDefault(); // Prevent default action in order to not duplicate pasted value
const clipboardData = event ? event.clipboardData : undefined;
let pastedText = clipboardData ? clipboardData.getData('text') : '';
if (pastedText) {
const selectedText = window.getSelection().toString(); // Get selected text, if any
// If selectedText has data, then this.value should exist with data as well, hence why there is no additional checks for
// this.value before setting currentTextArr
if (selectedText) {
// Split selectedText and this.value into arrays in order to compare the string values.
// If any string values match, and based on caret position in case of multiples of same word(s), filter/remove them
const selectedTextArr = selectedText.split(' ');
const currentTextArr = this.value.split(' ');
let firstMatchIndex;
let currentStrCount = 0;
for (let x = 0; x < currentTextArr.length; x++) {
currentStrCount += (currentTextArr[x].length + 1);
for (let i = 0; i < selectedTextArr.length; i++) {
if (currentTextArr[x] === selectedTextArr[i] && ((this.caretPosStart < currentStrCount) && ((currentStrCount - 2) <= this.caretPosEnd))) {
if (!firstMatchIndex) {
firstMatchIndex = x; // setting index based on the first word match to know where to insert pasted text
}
currentTextArr.splice(x, 1);
}
}
}
// If there was a match, insert the pasted text based on the index of the first matched word, otherwise the pasted text will be placed at the end
// of the current data. Then format the array back into a string and write the value.
let finalText;
if (firstMatchIndex) {
currentTextArr.splice(firstMatchIndex, 0, pastedText);
finalText = currentTextArr.join(' ');
} else {
finalText = currentTextArr.join(' ') + ' ' + pastedText;
}
this.writeValue(finalText);
this.changeDetectorRef.detectChanges();
// Update caret position after paste
this.caretPosStart = finalText.length;
this.caretPosEnd = finalText.length;
} else {
// Check to see if there is existing data
if (this.value) {
// If the carotPos is less than the current strings length, add the
// pasted text where the cursor is
if (this.caretPosStart < this.value.length) {
pastedText = this.value.slice(0, this.caretPosStart) + pastedText + this.value.slice(this.caretPosStart);
} else { // Otherwise add pasted text after current data
pastedText = this.value + ' ' + pastedText;
}
}
this.writeValue(pastedText);
this.changeDetectorRef.detectChanges();
// Update caret position after paste
this.caretPosStart = pastedText.length;
this.caretPosEnd = pastedText.length;
}
}
} catch (e) {
// Do nothing if error occurs. This just prevents the app from breaking if there is an issue handling the pasting of data.
// However, will still be able to enter additional narrative text manually.
}
}
От родителя HTML
<app-textarea (valueChange)='setSpecialInstructions($event)'>
</app-textarea>
От родителя .ts
setSpecialInstructions(value: string) {
this.specialInstructions = value;
this.someService.setSpecialInstructions(this.specialInstructions);
}