properly refocus tag from tag selector on re-enter (#4035)
- do not erase the last recorded tag on blur of the tag selector - on tab from collectionTree or shift-tab from tag selector input, try to refocus that last focused tag. If one does not exist, try to focus the first non-disabled tag. Fixes: #4008
This commit is contained in:
parent
d5194f234d
commit
638efad3eb
3 changed files with 55 additions and 19 deletions
|
@ -224,6 +224,36 @@ class TagList extends React.PureComponent {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tagSelectorList() {
|
||||||
|
return document.querySelector('.tag-selector-list');
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty() {
|
||||||
|
return !this.tagSelectorList().querySelector('.tag-selector-item:not(.disabled)');
|
||||||
|
}
|
||||||
|
|
||||||
|
clearRecordedFocusedTag() {
|
||||||
|
this.lastFocusedTagIndex = null;
|
||||||
|
this.focusedTagIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus the last focused tag from the list. If there is none, focus the first
|
||||||
|
// non-disabled tag.
|
||||||
|
async focus() {
|
||||||
|
if (this.focusedTagIndex === null) {
|
||||||
|
let enabledTag = this.tagSelectorList().querySelector('.tag-selector-item:not(.disabled)');
|
||||||
|
if (!enabledTag) return;
|
||||||
|
enabledTag.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let tagRefocused = this.refocusTag();
|
||||||
|
if (tagRefocused) return;
|
||||||
|
// If the tag could not be refocused, it means it was removed due to windowing,
|
||||||
|
// so we need to scroll to it.
|
||||||
|
this.setState({ scrollToCell: this.focusedTagIndex });
|
||||||
|
await this.waitForSectionRender();
|
||||||
|
}
|
||||||
|
|
||||||
// Try to refocus a focused tag that was removed due to windowing
|
// Try to refocus a focused tag that was removed due to windowing
|
||||||
refocusTag() {
|
refocusTag() {
|
||||||
let tagsList = document.querySelector('.tag-selector-list');
|
let tagsList = document.querySelector('.tag-selector-list');
|
||||||
|
@ -232,7 +262,9 @@ class TagList extends React.PureComponent {
|
||||||
let nodeToFocus = tagsNodes.find(node => node.textContent == tagToFocus.name);
|
let nodeToFocus = tagsNodes.find(node => node.textContent == tagToFocus.name);
|
||||||
if (nodeToFocus) {
|
if (nodeToFocus) {
|
||||||
nodeToFocus.focus();
|
nodeToFocus.focus();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForSectionRender() {
|
waitForSectionRender() {
|
||||||
|
@ -266,15 +298,6 @@ class TagList extends React.PureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleBlur = (event) => {
|
|
||||||
// If the focus leaves the tags list, clear the last focused tag index
|
|
||||||
let tagsList = document.querySelector('.tag-selector-list');
|
|
||||||
if (!tagsList.contains(event.relatedTarget)) {
|
|
||||||
this.focusedTagIndex = null;
|
|
||||||
this.lastFocusedTagIndex = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async handleKeyDown(e) {
|
async handleKeyDown(e) {
|
||||||
if (!["ArrowRight", "ArrowLeft"].includes(e.key)) return;
|
if (!["ArrowRight", "ArrowLeft"].includes(e.key)) return;
|
||||||
// If the windowing kicks in, the node of the initially-focused tag may not
|
// If the windowing kicks in, the node of the initially-focused tag may not
|
||||||
|
@ -346,7 +369,7 @@ class TagList extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tag-selector-list-container" onBlur={this.handleBlur} onKeyDown={this.handleKeyDown.bind(this)}>
|
<div className="tag-selector-list-container" onKeyDown={this.handleKeyDown.bind(this)}>
|
||||||
{tagList}
|
{tagList}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -81,6 +81,14 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
||||||
this.searchBoxRef.current.focus();
|
this.searchBoxRef.current.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focusTagList() {
|
||||||
|
this.tagListRef.current.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
isTagListEmpty() {
|
||||||
|
return this.tagListRef.current.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
componentDidCatch(error, info) {
|
componentDidCatch(error, info) {
|
||||||
// Async operations might attempt to update the react components
|
// Async operations might attempt to update the react components
|
||||||
// after window close in tests, which will cause unnecessary crashing.
|
// after window close in tests, which will cause unnecessary crashing.
|
||||||
|
@ -100,6 +108,12 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
||||||
this.prevTreeViewID = this.collectionTreeRow.id;
|
this.prevTreeViewID = this.collectionTreeRow.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSnapshotBeforeUpdate(_) {
|
||||||
|
// Clear the focused tag's record if the props change
|
||||||
|
this.tagListRef.current.clearRecordedFocusedTag();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Update trigger #1 (triggered by ZoteroPane)
|
// Update trigger #1 (triggered by ZoteroPane)
|
||||||
async onItemViewChanged({ collectionTreeRow, libraryID }) {
|
async onItemViewChanged({ collectionTreeRow, libraryID }) {
|
||||||
|
@ -528,7 +542,6 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
|
||||||
searchString={this.state.searchString}
|
searchString={this.state.searchString}
|
||||||
dragObserver={this.dragObserver}
|
dragObserver={this.dragObserver}
|
||||||
onSelect={this.handleTagSelected}
|
onSelect={this.handleTagSelected}
|
||||||
onKeyDown={this.handleKeyDown}
|
|
||||||
onTagContext={this.handleTagContext}
|
onTagContext={this.handleTagContext}
|
||||||
onSearch={this.handleSearch}
|
onSearch={this.handleSearch}
|
||||||
onSettings={this.handleSettings.bind(this)}
|
onSettings={this.handleSettings.bind(this)}
|
||||||
|
|
|
@ -420,11 +420,11 @@ var ZoteroPane = new function()
|
||||||
return document.getElementById('zotero-tb-add');
|
return document.getElementById('zotero-tb-add');
|
||||||
}
|
}
|
||||||
// If tag selector is collapsed, go to "New item" button, otherwise focus tag selector
|
// If tag selector is collapsed, go to "New item" button, otherwise focus tag selector
|
||||||
let firstNonDisabledTag = tagSelector.querySelector('.tag-selector-item:not(.disabled)');
|
if (ZoteroPane.tagSelector.isTagListEmpty()) {
|
||||||
if (firstNonDisabledTag) {
|
return tagSelector.querySelector(".search-input");
|
||||||
return firstNonDisabledTag;
|
|
||||||
}
|
}
|
||||||
return tagSelector.querySelector(".search-input");
|
ZoteroPane.tagSelector.focusTagList();
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
Escape: clearCollectionSearch
|
Escape: clearCollectionSearch
|
||||||
}
|
}
|
||||||
|
@ -446,11 +446,11 @@ var ZoteroPane = new function()
|
||||||
'search-input': {
|
'search-input': {
|
||||||
Tab: () => tagSelector.querySelector('.tag-selector-actions'),
|
Tab: () => tagSelector.querySelector('.tag-selector-actions'),
|
||||||
ShiftTab: () => {
|
ShiftTab: () => {
|
||||||
let firstNonDisabledTag = tagSelector.querySelector('.tag-selector-item:not(.disabled)');
|
if (ZoteroPane.tagSelector.isTagListEmpty()) {
|
||||||
if (firstNonDisabledTag) {
|
return document.getElementById("collection-tree");
|
||||||
return firstNonDisabledTag;
|
|
||||||
}
|
}
|
||||||
return document.getElementById("collection-tree");
|
ZoteroPane.tagSelector.focusTagList();
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'tag-selector-item': {
|
'tag-selector-item': {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue