mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) Add overflowTooltip() tool, and use for long tables in widget picker, and long page names.
Summary: Usage is simply to call `overflowTooltip()` with no arguments, as an argument to an element whose text may overflow. On 'mouseenter', it'll check for overflow and show the element's .textContent in a tooltip. - Added for long table names in the widget picker (Add Page, Add Widget to Page). - Added for long page names in the left-panel list of pages. Test Plan: Added test cases for the new overflow tooltips Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3814
This commit is contained in:
		
							parent
							
								
									b312b3b08b
								
							
						
					
					
						commit
						86681de595
					
				@ -8,6 +8,7 @@ import { linkId, NoLink } from 'app/client/ui/selectBy';
 | 
			
		||||
import { withInfoTooltip } from 'app/client/ui/tooltips';
 | 
			
		||||
import { getWidgetTypes, IWidgetType } from 'app/client/ui/widgetTypes';
 | 
			
		||||
import { bigPrimaryButton } from "app/client/ui2018/buttons";
 | 
			
		||||
import { overflowTooltip } from "app/client/ui/tooltips";
 | 
			
		||||
import { theme, vars } from "app/client/ui2018/cssVars";
 | 
			
		||||
import { icon } from "app/client/ui2018/icons";
 | 
			
		||||
import { spinnerModal } from 'app/client/ui2018/modals';
 | 
			
		||||
@ -321,7 +322,7 @@ export class PageWidgetSelect extends Disposable {
 | 
			
		||||
          dom.forEach(this._tables, (table) => dom('div',
 | 
			
		||||
            cssEntryWrapper(
 | 
			
		||||
              cssEntry(cssIcon('TypeTable'),
 | 
			
		||||
                       cssLabel(dom.text(use => use(table.tableNameDef) || use(table.tableId))),
 | 
			
		||||
                       cssLabel(dom.text(table.tableNameDef), overflowTooltip()),
 | 
			
		||||
                       dom.on('click', () => this._selectTable(table.id())),
 | 
			
		||||
                       cssEntry.cls('-selected', (use) => use(this._value.table) === table.id()),
 | 
			
		||||
                       testId('table-label')
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@ import {menuCssClass} from 'app/client/ui2018/menus';
 | 
			
		||||
import {dom, DomContents, DomElementArg, DomElementMethod, styled} from 'grainjs';
 | 
			
		||||
import Popper from 'popper.js';
 | 
			
		||||
import {cssMenu, defaultMenuOptions, IMenuOptions, setPopupToCreateDom} from 'popweasel';
 | 
			
		||||
import merge = require('lodash/merge');
 | 
			
		||||
 | 
			
		||||
export interface ITipOptions {
 | 
			
		||||
  /**
 | 
			
		||||
@ -25,6 +26,12 @@ export interface ITipOptions {
 | 
			
		||||
 | 
			
		||||
  /** When set, a tooltip will replace any previous tooltip with the same key. */
 | 
			
		||||
  key?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Optionally, popper modifiers (e.g. {offset: {offset: 8}}),
 | 
			
		||||
   * See https://popper.js.org/docs/v1/#modifiers.
 | 
			
		||||
   */
 | 
			
		||||
  modifiers?: Popper.Modifiers;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ITransientTipOptions extends ITipOptions {
 | 
			
		||||
@ -66,6 +73,9 @@ export interface IHoverTipOptions extends ITransientTipOptions {
 | 
			
		||||
   * Should only be set to true if `openOnClick` is false.
 | 
			
		||||
   */
 | 
			
		||||
  closeOnClick?: boolean;
 | 
			
		||||
 | 
			
		||||
  /** Whether to show the tooltip only when the ref element overflows horizontally. */
 | 
			
		||||
  overflowOnly?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type ITooltipContent = ITooltipContentFunc | DomContents;
 | 
			
		||||
@ -132,9 +142,13 @@ export function showTooltip(
 | 
			
		||||
 | 
			
		||||
  // Create a popper for positioning the tooltip content relative to refElem.
 | 
			
		||||
  const popperOptions: Popper.PopperOptions = {
 | 
			
		||||
    modifiers: {preventOverflow: {boundariesElement: 'viewport'}},
 | 
			
		||||
    modifiers: merge(
 | 
			
		||||
      { preventOverflow: {boundariesElement: 'viewport'} },
 | 
			
		||||
      options.modifiers
 | 
			
		||||
    ),
 | 
			
		||||
    placement,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const popper = new Popper(refElem, content, popperOptions);
 | 
			
		||||
 | 
			
		||||
  // If refElem is disposed we close the tooltip.
 | 
			
		||||
@ -159,6 +173,20 @@ export function hoverTooltip(tipContent: ITooltipContent, options?: IHoverTipOpt
 | 
			
		||||
  return (elem) => setHoverTooltip(elem, tipContent, {...defaultOptions, ...options});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * On hover, show the full text of this element when it overflows horizontally. It is intended
 | 
			
		||||
 * mainly for styled with "text-overflow: ellipsis".
 | 
			
		||||
 * E.g. dom('label', 'Long text...', overflowTooltip()).
 | 
			
		||||
 */
 | 
			
		||||
export function overflowTooltip(options?: IHoverTipOptions): DomElementMethod {
 | 
			
		||||
  const defaultOptions: IHoverTipOptions = {
 | 
			
		||||
    placement: 'bottom-start',
 | 
			
		||||
    overflowOnly: true,
 | 
			
		||||
    modifiers: {offset: {offset: '40, 0'}},
 | 
			
		||||
  };
 | 
			
		||||
  return (elem) => setHoverTooltip(elem, () => elem.textContent,  {...defaultOptions, ...options});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Attach a tooltip to the given element, to be rendered on hover.
 | 
			
		||||
 */
 | 
			
		||||
@ -167,7 +195,8 @@ export function setHoverTooltip(
 | 
			
		||||
  tipContent: ITooltipContent,
 | 
			
		||||
  options: IHoverTipOptions = {}
 | 
			
		||||
) {
 | 
			
		||||
  const {key, openDelay = 200, timeoutMs, closeDelay = 100, openOnClick, closeOnClick = true} = options;
 | 
			
		||||
  const {key, openDelay = 200, timeoutMs, closeDelay = 100, openOnClick, closeOnClick = true,
 | 
			
		||||
    overflowOnly = false} = options;
 | 
			
		||||
 | 
			
		||||
  const tipContentFunc = typeof tipContent === 'function' ? tipContent : () => tipContent;
 | 
			
		||||
 | 
			
		||||
@ -204,6 +233,9 @@ export function setHoverTooltip(
 | 
			
		||||
 | 
			
		||||
  // We simulate hover effect by handling mouseenter/mouseleave.
 | 
			
		||||
  dom.onElem(refElem, 'mouseenter', () => {
 | 
			
		||||
    if (overflowOnly && (refElem as HTMLElement).offsetWidth >= refElem.scrollWidth) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (!tipControl && !timer) {
 | 
			
		||||
      // If we're replacing a tooltip, open without delay.
 | 
			
		||||
      const delay = key && openTooltips.has(key) ? 0 : openDelay;
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import { cssEditorInput } from "app/client/ui/HomeLeftPane";
 | 
			
		||||
import { itemHeader, itemHeaderWrapper, treeViewContainer } from "app/client/ui/TreeViewComponentCss";
 | 
			
		||||
import { theme } from "app/client/ui2018/cssVars";
 | 
			
		||||
import { icon } from "app/client/ui2018/icons";
 | 
			
		||||
import { hoverTooltip } from 'app/client/ui/tooltips';
 | 
			
		||||
import { hoverTooltip, overflowTooltip } from 'app/client/ui/tooltips';
 | 
			
		||||
import { menu, menuItem, menuText } from "app/client/ui2018/menus";
 | 
			
		||||
import { dom, domComputed, DomElementArg, makeTestId, observable, Observable, styled } from "grainjs";
 | 
			
		||||
 | 
			
		||||
@ -88,6 +88,7 @@ export function buildPageDom(name: Observable<string>, actions: PageActions, ...
 | 
			
		||||
              dom.text(name),
 | 
			
		||||
              testId('label'),
 | 
			
		||||
              dom.on('click', (ev) => isTargetSelected(ev.target as HTMLElement) && isRenaming.set(true)),
 | 
			
		||||
              overflowTooltip(),
 | 
			
		||||
            ),
 | 
			
		||||
            cssPageMenuTrigger(
 | 
			
		||||
              cssPageMenuIcon('Dots'),
 | 
			
		||||
 | 
			
		||||
@ -259,6 +259,26 @@ describe('Pages', function() {
 | 
			
		||||
    assert.include(await gu.getPageNames(), 'People');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should show tooltip for long page names on hover', async () => {
 | 
			
		||||
    await gu.openPageMenu('People');
 | 
			
		||||
    await driver.find('.test-docpage-rename').doClick();
 | 
			
		||||
    await driver.find('.test-docpage-editor')
 | 
			
		||||
      .sendKeys('People, Persons, Humans, Ladies & Gentlemen', Key.ENTER);
 | 
			
		||||
    await gu.waitForServer();
 | 
			
		||||
 | 
			
		||||
    await driver.findContent('.test-treeview-label', /People, Persons, Humans, Ladies & Gentlemen/).mouseMove();
 | 
			
		||||
    await driver.wait(() => driver.findWait('.test-tooltip', 1000).isDisplayed(), 3000);
 | 
			
		||||
    assert.equal(await driver.find('.test-tooltip').getText(),
 | 
			
		||||
      'People, Persons, Humans, Ladies & Gentlemen');
 | 
			
		||||
 | 
			
		||||
    await gu.undo();
 | 
			
		||||
    assert.deepEqual(await gu.getPageNames(), ['Interactions', 'Documents', 'People', 'User & Leads', 'Overview']);
 | 
			
		||||
 | 
			
		||||
    await driver.findContent('.test-treeview-label', /People/).mouseMove();
 | 
			
		||||
    await driver.sleep(500);
 | 
			
		||||
    assert.equal(await driver.find('.test-tooltip').isPresent(), false);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should not change page when clicking the input while renaming page', async () => {
 | 
			
		||||
    // check that initially People is selected
 | 
			
		||||
    assert.match(await driver.find('.test-treeview-itemHeader.selected').getText(), /People/);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user