Item box: Save text selection along with focus (#4165)
This commit is contained in:
parent
3176516b79
commit
81954d74d1
1 changed files with 74 additions and 26 deletions
|
@ -50,6 +50,7 @@
|
||||||
this._initialVisibleCreators = 5;
|
this._initialVisibleCreators = 5;
|
||||||
this._draggedCreator = false;
|
this._draggedCreator = false;
|
||||||
this._selectField = null;
|
this._selectField = null;
|
||||||
|
this._selectFieldSelection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get content() {
|
get content() {
|
||||||
|
@ -221,29 +222,11 @@
|
||||||
() => Zotero.Utilities.Internal.copyTextToClipboard(this._linkMenu.dataset.link)
|
() => Zotero.Utilities.Internal.copyTextToClipboard(this._linkMenu.dataset.link)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Save the last focused element, so that the focus can go back to it if
|
|
||||||
// the table is refreshed
|
|
||||||
this._infoTable.addEventListener("focusin", (e) => {
|
|
||||||
let target = e.target.closest("[fieldname], [tabindex], [focusable]");
|
|
||||||
// Special treatment for unsaved creator rows. When they are just added, their ids
|
|
||||||
// do not correspond to their positioning to avoid shifting all creators in case new row is not saved.
|
|
||||||
// So, use the index that this row will occupy after saving.
|
|
||||||
let maybeRow = target.closest(".meta-row");
|
|
||||||
if (maybeRow?.querySelector(".creator-type-value[unsaved=true]")) {
|
|
||||||
let { unsavedIndex } = this.getCreatorFields(maybeRow);
|
|
||||||
this._selectField = (target?.id || "").replace(/\d+/g, unsavedIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (target?.id) {
|
|
||||||
this._selectField = target.id;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the focus leaves the itemBox, clear the last focused element
|
// If the focus leaves the itemBox, clear the last focused element
|
||||||
this._infoTable.addEventListener("focusout", (e) => {
|
this._infoTable.addEventListener("focusout", (e) => {
|
||||||
let destination = e.relatedTarget;
|
let destination = e.relatedTarget;
|
||||||
if (!(destination && this._infoTable.contains(destination))) {
|
if (!(destination && this._infoTable.contains(destination))) {
|
||||||
this._selectField = null;
|
this._clearSavedFieldFocus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -495,6 +478,8 @@
|
||||||
this.updateRetracted();
|
this.updateRetracted();
|
||||||
|
|
||||||
if (this._isAlreadyRendered()) return;
|
if (this._isAlreadyRendered()) return;
|
||||||
|
|
||||||
|
this._saveFieldFocus();
|
||||||
|
|
||||||
delete this._linkMenu.dataset.link;
|
delete this._linkMenu.dataset.link;
|
||||||
|
|
||||||
|
@ -851,12 +836,7 @@
|
||||||
this._ensureButtonsFocusable();
|
this._ensureButtonsFocusable();
|
||||||
|
|
||||||
// Set focus on the last focused field
|
// Set focus on the last focused field
|
||||||
if (this._selectField) {
|
this._restoreFieldFocus();
|
||||||
let refocusField = this.querySelector(`#${this._selectField}`);
|
|
||||||
if (refocusField) {
|
|
||||||
refocusField.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Make sure that any opened popup closes
|
// Make sure that any opened popup closes
|
||||||
this.querySelectorAll("menupopup").forEach((popup) => {
|
this.querySelectorAll("menupopup").forEach((popup) => {
|
||||||
popup.hidePopup();
|
popup.hidePopup();
|
||||||
|
@ -1515,7 +1495,7 @@
|
||||||
creatorRow.remove();
|
creatorRow.remove();
|
||||||
|
|
||||||
this._creatorCount--;
|
this._creatorCount--;
|
||||||
this.querySelector(`#${this._selectField}`)?.focus();
|
this._restoreFieldFocus();
|
||||||
this._updateCreatorButtonsStatus();
|
this._updateCreatorButtonsStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2188,6 +2168,74 @@
|
||||||
this.querySelector(`editable-text[fieldname="${fieldName}"]`)?.focus();
|
this.querySelector(`editable-text[fieldname="${fieldName}"]`)?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_saveFieldFocus() {
|
||||||
|
// If we've already set _selectField, abort
|
||||||
|
if (this._selectField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let activeElement = document.activeElement;
|
||||||
|
if (!this._infoTable.contains(activeElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let field = activeElement.closest("[fieldname], [tabindex], [focusable]");
|
||||||
|
let fieldID;
|
||||||
|
// Special treatment for unsaved creator rows. When they are just added, their ids
|
||||||
|
// do not correspond to their positioning to avoid shifting all creators in case new row is not saved.
|
||||||
|
// So, use the index that this row will occupy after saving.
|
||||||
|
let maybeRow = field.closest(".meta-row");
|
||||||
|
if (maybeRow?.querySelector(".creator-type-value[unsaved=true]")) {
|
||||||
|
let { unsavedIndex } = this.getCreatorFields(maybeRow);
|
||||||
|
fieldID = (field?.id || "").replace(/\d+/g, unsavedIndex);
|
||||||
|
}
|
||||||
|
else if (field?.id) {
|
||||||
|
fieldID = field.id;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the field ID
|
||||||
|
this._selectField = fieldID;
|
||||||
|
|
||||||
|
// Save selection inside inputs
|
||||||
|
let targetInput = activeElement.closest("input, textarea");
|
||||||
|
if (targetInput) {
|
||||||
|
this._selectFieldSelection = [
|
||||||
|
targetInput.selectionStart,
|
||||||
|
targetInput.selectionEnd,
|
||||||
|
targetInput.selectionDirection,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearSavedFieldFocus() {
|
||||||
|
this._selectField = null;
|
||||||
|
this._selectFieldSelection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_restoreFieldFocus() {
|
||||||
|
if (!this._selectField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let refocusField = this.querySelector(`#${CSS.escape(this._selectField)}`);
|
||||||
|
if (!refocusField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refocusField.focus();
|
||||||
|
|
||||||
|
if (this._selectFieldSelection) {
|
||||||
|
let input = refocusField.querySelector("input, textarea");
|
||||||
|
if (input) {
|
||||||
|
input.setSelectionRange(...this._selectFieldSelection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._clearSavedFieldFocus();
|
||||||
|
}
|
||||||
|
|
||||||
getTitleField() {
|
getTitleField() {
|
||||||
var titleFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(this.item.itemTypeID, 'title');
|
var titleFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(this.item.itemTypeID, 'title');
|
||||||
return this._infoTable.querySelector(`editable-text[fieldname="${Zotero.ItemFields.getName(titleFieldID)}"]`);
|
return this._infoTable.querySelector(`editable-text[fieldname="${Zotero.ItemFields.getName(titleFieldID)}"]`);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue