fx115: fix tooltips for tabs, tabs menu and virtualized table (#3877)

Fix and improve tooltip logic

- fx115: move main html-tooltip outside of the deck, otherwise it only
shows up in the library tab.
- Zotero_Tooltip is not required in the tabs bar and the button of the
tag selector. Setting the tooltip attribute on the closest XUL parent
and adding title attribute makes the tooltip properly appear.
- Remove manual handling of the hover effect from tabs manu. The
hover effect doesn't stick around after drag-drop with fx115 anymore,
so it's not required. And then the usual title attribute works for the
tooltip.
- Set title instead of tooltiptext attribute in tagSelectorList
so that the tooltip appears

After zotero@8e2790e,
the pointer effects don't fire on the actual cells of the table
(only on the parent row), so the tooltips do not appear for the cells.
This is a (hopefully) temporary solution to handle mousemove events over the row
of the table, find the right cell that the mouse is over, and
use Zotero_Tooltip to manually display the tooltip.

Tweaked Zotero_Tooltip to create the fake tooltip and place it into
the DOM. Zotero_Tooltip is also imported by virtualized-table as a module
because one shouldn't have to load it in .xhtml file for every new window
where the virtualized-table is used.
This commit is contained in:
abaevbog 2024-03-29 03:57:36 -04:00 committed by Dan Stillman
parent a92358bdd1
commit 2f4a232c41
9 changed files with 79 additions and 65 deletions

View file

@ -89,22 +89,9 @@ class Button extends PureComponent {
if (!this.props.isDisabled) {
attr.onMouseDown = (event) => {
// Hide tooltip on mousedown
if (this.title) {
window.Zotero_Tooltip.stop();
}
return this.handleMouseDown(event);
};
attr.onClick = this.handleClick
// Fake tooltip behavior as long as 'title' doesn't work for HTML-in-XUL elements
if (this.title) {
attr.onMouseOver = () => {
window.Zotero_Tooltip.start(this.title);
};
attr.onMouseOut = () => {
window.Zotero_Tooltip.stop();
};
}
}
return attr

View file

@ -225,18 +225,6 @@ const TabBar = forwardRef(function (props, ref) {
event.stopPropagation();
}
function handleTabMouseMove(title) {
// Fix `title` not working for HTML-in-XUL. Using `mousemove` ensures we restart the tooltip
// after just a small movement even when the active tab has changed under the cursor, which
// matches behavior in Firefox.
window.Zotero_Tooltip.start(title);
}
function handleTabBarMouseOut() {
// Hide any possibly open `title` tooltips when mousing out of any tab or the tab bar as a
// whole. `mouseout` bubbles up from element you moved out of, so it covers both cases.
window.Zotero_Tooltip.stop();
}
function handleWheel(event) {
// Normalize wheel speed
@ -280,7 +268,6 @@ const TabBar = forwardRef(function (props, ref) {
data-id={id}
className={cx('tab', { selected, dragging: dragging && id === dragIDRef.current })}
draggable={true}
onMouseMove={() => handleTabMouseMove(title)}
onMouseDown={(event) => handleTabMouseDown(event, id)}
onClick={(event) => handleTabClick(event, id)}
onAuxClick={(event) => handleTabClick(event, id)}
@ -289,7 +276,7 @@ const TabBar = forwardRef(function (props, ref) {
tabIndex="-1"
>
{icon}
<div className="tab-name">{title}</div>
<div className="tab-name" title={title}>{title}</div>
<div
className="tab-close"
onClick={(event) => handleTabClose(event, id)}
@ -310,7 +297,6 @@ const TabBar = forwardRef(function (props, ref) {
<div className="pinned-tabs">
<div
className="tabs"
onMouseOut={handleTabBarMouseOut}
>
{tabs.length ? renderTab(tabs[0], 0) : null}
</div>
@ -332,7 +318,6 @@ const TabBar = forwardRef(function (props, ref) {
ref={tabsRef}
className="tabs"
onDragOver={handleTabBarDragOver}
onMouseOut={handleTabBarMouseOut}
onScroll={updateScrollArrows}
dir={Zotero.dir}
>

View file

@ -196,10 +196,8 @@ class TagList extends React.PureComponent {
delete props.style.width;
}
else {
// Setting this via props doesn't seem to work in XUL, but setting it on hover does.
// Hopefully in an HTML window we'll be able to just set 'title'.
props.onMouseOver = function (event) {
event.target.setAttribute('tooltiptext', tag.name);
event.target.setAttribute('title', tag.name);
};
}

View file

@ -67,12 +67,24 @@ var Zotero_Tooltip = new function () {
function handleMouseStop() {
var tooltipElem = document.getElementById('fake-tooltip');
// Create the fake tooltip if it does not exist
if (!tooltipElem) {
tooltipElem = document.createXULElement("tooltip");
tooltipElem.id = "fake-tooltip";
// The tooltip location is important. If the tooltip is placed
// within a lower level component, it may be not visible
document.documentElement.appendChild(tooltipElem);
}
tooltipElem.setAttribute('label', text);
tooltipElem.openPopupAtScreen(x, y, false, null);
}
function hidePopup() {
var tooltipElem = document.getElementById('fake-tooltip');
tooltipElem.hidePopup();
if (tooltipElem) {
tooltipElem.hidePopup();
}
}
};
export { Zotero_Tooltip };

View file

@ -31,6 +31,7 @@ const cx = require('classnames');
const WindowedList = require('./windowed-list');
const Draggable = require('./draggable');
const { CSSIcon, getDOMElement } = require('components/icons');
const { Zotero_Tooltip } = require('./tooltip');
const TYPING_TIMEOUT = 1000;
const MINIMUM_ROW_HEIGHT = 20; // px
@ -909,6 +910,9 @@ class VirtualizedTable extends React.Component {
* @param event
*/
_handleMouseOver = (event) => {
// On scroll, mouse position does not change, so _handleMouseMove does not fire
// to close the fake tooltip. Make sure it is closed here.
Zotero_Tooltip.stop();
let elem = event.target;
if (!elem.classList.contains('cell') || elem.classList.contains('cell-icon')) return;
let textElem = elem.querySelector('.label, .cell-text');
@ -929,6 +933,59 @@ class VirtualizedTable extends React.Component {
}
}
/**
* Manually handle tooltip setting for table cells with overflowing values.
* Temporary, after
* https://github.com/zotero/zotero/commit/8e2790e2d2a1d8b15efbf84935f0a80d58db4e44.
* @param event
*/
_handleMouseMove = (event) => {
let tgt = event.target;
// Mouse left the previous cell - close the tooltip
if (!tgt.classList.contains("row")
|| event.clientX < parseInt(tgt.dataset.mouseLeft)
|| event.clientX > parseInt(tgt.dataset.mouseRight)) {
delete tgt.dataset.mouseLeft;
delete tgt.dataset.mouseRight;
Zotero_Tooltip.stop();
}
if (!tgt.classList.contains("row")) return;
let cells = tgt.querySelectorAll(".cell");
let targetCell;
// Find the cell the mouse is over
for (let cell of cells) {
let rect = cell.getBoundingClientRect();
if (event.clientX >= rect.left && event.clientX <= rect.right) {
targetCell = cell;
tgt.dataset.mouseLeft = rect.left;
tgt.dataset.mouseRight = rect.right;
break;
}
}
if (!targetCell) return;
// Primary cell will .cell-text child node
let textCell = targetCell.querySelector(".cell-text") || targetCell;
// If the cell has overflowing content, display the fake tooltip
if (textCell.offsetWidth < textCell.scrollWidth) {
Zotero_Tooltip.stop();
Zotero_Tooltip.start(textCell.textContent);
}
};
/**
* Remove manually added fake tooltip from _handleMouseMove when the
* mouse leaves the row completely.
*/
_handleMouseLeave = (_) => {
Zotero_Tooltip.stop();
let lastRow = document.querySelector("[mouseLeft][mouseRight]");
if (lastRow) {
delete lastRow.dataset.mouseLeft;
delete lastRow.dataset.mouseRight;
}
};
_handleResizerDragStop = (event) => {
event.stopPropagation();
const result = this._getResizeColumns();
@ -1193,6 +1250,8 @@ class VirtualizedTable extends React.Component {
onDrop: e => this.props.onDrop && this.props.onDrop(e),
onFocus: e => this.props.onFocus && this.props.onFocus(e),
onMouseOver: e => this._handleMouseOver(e),
onMouseMove: e => this._handleMouseMove(e),
onMouseLeave: e => this._handleMouseLeave(e),
className: cx(["virtualized-table", {
resizing: this.state.resizing,
'multi-select': this.props.multiSelect

View file

@ -128,8 +128,6 @@ var Zotero_Tabs = new function () {
return;
}
document.title = (tab.title.length ? tab.title + ' - ' : '') + Zotero.appName;
// Hide any tab `title` tooltips that might be open
window.Zotero_Tooltip.stop();
if (this.isTabsMenuVisible()) {
this.refreshTabsMenuList();
if (document.activeElement.id !== "zotero-tabs-menu-filter") {
@ -639,7 +637,6 @@ var Zotero_Tabs = new function () {
this._openMenu = function (x, y, id) {
var { tab, tabIndex } = this._getTab(id);
window.Zotero_Tooltip.stop();
let menuitem;
let popup = document.createXULElement('menupopup');
document.querySelector('popupset').appendChild(popup);
@ -851,7 +848,7 @@ var Zotero_Tabs = new function () {
tabName.setAttribute('class', 'zotero-tabs-menu-entry title');
tabName.setAttribute('tabindex', `${index++}`);
tabName.setAttribute('aria-label', tab.title);
tabName.setAttribute('tooltiptext', tab.title);
tabName.setAttribute('title', tab.title);
// Cross button to close a tab
let closeButton = document.createElement('div');
@ -907,25 +904,6 @@ var Zotero_Tabs = new function () {
this.tabsMenuPanel.hidePopup();
this.select(tab.id);
});
// Manually handle hover effects as a workaround for a likely mozilla bug that
// keeps :hover at the location of dragstart after drop.
for (let node of [tabName, closeButton]) {
node.addEventListener('mouseenter', (_) => {
if (this._tabsMenuIgnoreMouseover) {
return;
}
node.classList.add('hover');
// If the mouse moves over a tab, send focus back to the panel
// to avoid having two fields that appear greyed out.
if (document.activeElement.id !== "zotero-tabs-menu-filter") {
this._tabsMenuFocusedIndex = -1;
this.tabsMenuPanel.focus();
}
});
node.addEventListener('mouseleave', (_) => {
node.classList.remove('hover');
});
}
row.appendChild(tabName);
row.appendChild(closeButton);

View file

@ -85,7 +85,6 @@
Services.scriptloader.loadSubScript("chrome://zotero/content/lookup.js", this);
Services.scriptloader.loadSubScript("chrome://zotero/content/locateMenu.js", this);
Services.scriptloader.loadSubScript("chrome://zotero/content/menuAccessKey.js", this);
Services.scriptloader.loadSubScript("chrome://zotero/content/tooltip.js", this);
Services.scriptloader.loadSubScript("chrome://zotero/content/containers/tagSelectorContainer.js", this);
</script>
@ -837,7 +836,7 @@
tabindex="0"
data-l10n-id="zotero-tabs-menu-filter"
/>
<vbox id="zotero-tabs-menu-list"></vbox>
<vbox id="zotero-tabs-menu-list" tooltip="html-tooltip"></vbox>
</vbox>
</panel>
@ -876,6 +875,7 @@
<stack id="zotero-pane-stack" flex="1">
<!-- A placeholder to persist pdf-reader sidebar collapse state to avoid introducing another pref -->
<box id="zotero-reader-sidebar-pane" hidden="true" collapsed="true" zotero-persist="collapsed width"/>
<tooltip id="html-tooltip" page="true"/>
<hbox>
<div id="zotero-tab-cover" class="hidden" xmlns="http://www.w3.org/1999/xhtml">
<label>&zotero.general.loading;</label>
@ -907,7 +907,6 @@
</vbox>
<popupset>
<tooltip id="html-tooltip" page="true"/>
<menupopup id="zotero-collectionmenu"
oncommand="ZoteroPane.onCollectionContextMenuSelect(event)">
<!-- Keep order in sync with buildCollectionContextMenu, which adds additional attributes -->
@ -1000,7 +999,6 @@
<menuitem class="menuitem-iconic zotero-menuitem-reindex" oncommand="ZoteroPane_Local.reindexItem();"/>
</menupopup>
<tooltip id="fake-tooltip"/>
</popupset>
<hbox id="zotero-trees" flex="1">

View file

@ -52,7 +52,7 @@ zotero-collections-search-btn =
zotero-tabs-menu-filter =
.placeholder = Search Tabs
zotero-tabs-menu-close-button =
.tooltiptext = Close Tab
.title = Close Tab
toolbar-add-attachment =
.tooltiptext = { add-attachment }

View file

@ -78,10 +78,7 @@
}
}
// Using .hover class instead of pseudo element to avoid the
// hover effect from sticking around after drag-and-drop
// https://bugs.webkit.org/show_bug.cgi?id=134555
&.hover {
&:hover {
background-color: var(--fill-quinary) !important;
}
&:active {