Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide better click handling for Mesa DataCells #1339

Open
bobular opened this issue Mar 2, 2025 · 2 comments
Open

Provide better click handling for Mesa DataCells #1339

bobular opened this issue Mar 2, 2025 · 2 comments
Labels
enhancement New feature or request

Comments

@bobular
Copy link
Member

bobular commented Mar 2, 2025

Background - in the new AI Expression Summary, we are adding a click handler to all cells in the parent row to expand the child row. This seems like much better UX than having users click on the fiddly triangle alone. It works fine but if the user wants to double-click or click-drag to select some text, the child row will expand and/or collapse unexpectedly and distractingly.

Because it's a class component we can't pass a functional component containing the relevant state hooks (to handle drag and double-click properly) as the render function. We probably need to update the RenderCell component to make use of a new column.onSingleClick prop. I don't think this should be part of options.eventHandlers because we'll want it to be column-specific, I think.

An untested implementation idea will be posted in a comment below.

Note: it would also be good for the thumbnail column in the Transcript Expression section table to expand the row on click. So there is wider utility than just the AI Expression thing (which is only a prototype).

Note 2: it might also be good for the ortho group table - allow row selection by clicking anywhere in the row?

@bobular bobular added the enhancement New feature or request label Mar 2, 2025
@bobular
Copy link
Member Author

bobular commented Mar 2, 2025

Here's a suggestion. It adds a fair amount of active stuff to the component even if column.onSingleClick hasn't been provided. Maybe we can improve on that.

import React from 'react';
import PropTypes from 'prop-types';

import Templates from '../Templates';
import { makeClassifier } from '../Utils/Utils';

import { Tooltip } from '../../../components/info/Tooltip';

const dataCellClass = makeClassifier('DataCell');

class DataCell extends React.PureComponent {
  constructor(props) {
    super(props);
    this.isDragging = false; // Track if user is dragging
    this.clickTimer = null; // Track click timing

    this.handleMouseDown = this.handleMouseDown.bind(this);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.handleClick = this.handleClick.bind(this);
    this.renderContent = this.renderContent.bind(this);
  }

  handleMouseDown() {
    this.isDragging = false; // Reset drag state
  }

  handleMouseMove() {
    this.isDragging = true; // Detect dragging
  }

  handleClick(event) {
    if (this.isDragging) return; // Ignore clicks if dragging

    const { row, column: { onSingleClick }, options: { getRowId } } = this.props;

    if (this.clickTimer) {
      clearTimeout(this.clickTimer);
      this.clickTimer = null;
      return; // Ignore because it's part of a double-click
    }

    this.clickTimer = setTimeout(() => {
      if (onSingleClick && getRowId && row) {
        const rowId = getRowId(row);
        onSingleClick(rowId);
      }
      this.clickTimer = null;
    }, 200); // Adjust timeout if needed
  }

  renderContent() {
    const { row, column, rowIndex, columnIndex, inline, options, isChildRow } =
      this.props;
    const { key, getValue } = column;
    const value =
      typeof getValue === 'function' ? getValue({ row, key }) : row[key];
    const cellProps = { key, value, row, column, rowIndex, columnIndex };
    const { childRow } = options;
    if (isChildRow && childRow != null) {
      return childRow(rowIndex, row);
    }
    if ('renderCell' in column) {
      return column.renderCell(cellProps);
    }

    if (!column.type) return Templates.textCell(cellProps);
    if (!cellProps.value) return Templates.textCell(cellProps);

    switch (column.type.toLowerCase()) {
      case 'wdklink':
        return Templates.wdkLinkCell(cellProps);
      case 'link':
        return Templates.linkCell(cellProps);
      case 'number':
        return Templates.numberCell(cellProps);
      case 'html': {
        const Component = Templates[inline ? 'textCell' : 'htmlCell'];
        return Component(cellProps);
      }
      case 'text':
      default:
        return Templates.textCell(cellProps);
    }
  }

  setTitle(el) {
    if (el == null) return;
    el.title = el.scrollWidth <= el.clientWidth ? '' : el.innerText;
  }

  render() {
    let { column, inline, options, isChildRow, childRowColSpan, rowIndex } =
      this.props;
    let { style, width, className, key } = column;

    let whiteSpace = !inline
      ? {}
      : {
          textOverflow: 'ellipsis',
          overflow: 'hidden',
          maxWidth: options.inlineMaxWidth ? options.inlineMaxWidth : '20vw',
          maxHeight: options.inlineMaxHeight ? options.inlineMaxHeight : '2em',
        };

    width = typeof width === 'number' ? width + 'px' : width;
    width = width ? { width, maxWidth: width, minWidth: width } : {};
    style = Object.assign({}, style, width, whiteSpace);
    className = dataCellClass() + (className ? ' ' + className : '');

    const content = this.renderContent();

    const props = {
      style,
      children: content,
      className,
      ...(isChildRow ? { colSpan: childRowColSpan } : null),
    };

    return column.hidden ? null : (
      <td
        onMouseEnter={(e) => this.setTitle(e.target)}
        onMouseLeave={() => this.setTitle()}
        onMouseDown={this.handleMouseDown}
        onMouseMove={this.handleMouseMove}
        onClick={this.handleClick}
        key={key + '_' + rowIndex}
        {...props}
      />
    );
  }
}

// type stuff may/may not need updating

@bobular
Copy link
Member Author

bobular commented Mar 2, 2025

Yeah, we should add the handlers like this

const props = {
  style,
  children: content,
  className,
  ...(isChildRow
    ? { colSpan: childRowColSpan }
    : onSingleClick && getRowId && row
      ? {
          onMouseDown: this.handleMouseDown,
          onMouseMove: this.handleMouseMove,
          onClick: this.handleClick,
        }
      : {}
  ),
};

to the props passed to the <td> only when onSingleClick is defined and not for child rows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant