Skip to content

Commit

Permalink
feat: 表格滚动后触发hover (#2235)
Browse files Browse the repository at this point in the history
* feat: trigger hover after scroll

* chore: rename property and add note

* test: should trigger mousemove after scroll

* chore: ci pass

* feat: add option to control hover after scroll

* test: set hover after scroll option to true

* docs: hover after scroll

- description in interaction chapter
- one demo

* chore: config button in react playground

* chore: update screenshot link

---------

Co-authored-by: 刘嘉一 <[email protected]>
  • Loading branch information
d2FuZ3h1ZG9uZw and lcx-seima authored Jun 2, 2023
1 parent 87b7867 commit f9f97b0
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 39 deletions.
56 changes: 56 additions & 0 deletions packages/s2-core/__tests__/spreadsheet/scroll-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,62 @@ describe('Scroll Tests', () => {
},
);

// https://github.com/antvis/S2/issues/2222
test.each([
{
type: 'horizontal',
offset: {
scrollX: 20,
scrollY: 0,
},
},
{
type: 'vertical',
offset: {
scrollX: 0,
scrollY: 20,
},
},
])(
'should trigger hover cells when hover cells after scroll by %o',
async ({ offset }) => {
s2.setOptions({
interaction: {
hoverAfterScroll: true,
},
});

s2.facet.cornerBBox.maxY = -9999;
s2.facet.panelBBox.minX = -9999;
s2.facet.panelBBox.minY = -9999;

const bbox = s2.getCanvasElement().getBoundingClientRect();
const mousemoveEvent = new MouseEvent('mousemove', {
clientX: bbox.left + 100,
clientY: bbox.top + 100,
});

canvas.dispatchEvent(mousemoveEvent);

s2.container.emit = jest.fn();

const wheelEvent = new WheelEvent('wheel', {
deltaX: offset.scrollX,
deltaY: offset.scrollY,
});

canvas.dispatchEvent(wheelEvent);

// wait requestAnimationFrame and debounce
await sleep(1000);

expect(s2.container.emit).toHaveBeenCalledWith(
OriginEventType.MOUSE_MOVE,
expect.any(Object),
);
},
);

test('should not trigger scroll event on passive renders', () => {
const sheet = new PivotSheet(getContainer(), mockDataConfig, {
...s2Options,
Expand Down
20 changes: 11 additions & 9 deletions packages/s2-core/src/common/interface/interaction.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import type { SimpleBBox } from '@antv/g-canvas';
import type {
InteractionStateName,
CellTypes,
InterceptType,
ScrollbarPositionType,
InteractionCellSelectedHighlightType,
} from '../constant';
import type {
BaseCell,
ColCell,
Expand All @@ -16,11 +9,18 @@ import type {
} from '../../cell';
import type { HeaderCell } from '../../cell/header-cell';
import type { Node } from '../../facet/layout/node';
import type { RootInteraction } from '../../interaction';
import type { BaseEvent } from '../../interaction/base-event';
import type { SpreadSheet } from '../../sheet-type';
import type { RootInteraction } from '../../interaction';
import type { ResizeInteractionOptions } from './resize';
import type {
CellTypes,
InteractionCellSelectedHighlightType,
InteractionStateName,
InterceptType,
ScrollbarPositionType,
} from '../constant';
import type { ViewMeta } from './basic';
import type { ResizeInteractionOptions } from './resize';

export type S2CellType<T extends SimpleBBox = ViewMeta> =
| DataCell
Expand Down Expand Up @@ -174,6 +174,8 @@ export interface InteractionOptions {
selectedCellHighlight?: boolean | InteractionCellSelectedHighlightType;
// https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior
overscrollBehavior?: 'auto' | 'none' | 'contain';
// trigger hover after scroll
hoverAfterScroll?: boolean;
/** ***********CUSTOM INTERACTION HOOKS**************** */
// register custom interactions
customInteractions?: CustomInteraction[];
Expand Down
35 changes: 27 additions & 8 deletions packages/s2-core/src/facet/base-facet.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IElement, IGroup } from '@antv/g-canvas';
import type { Event as GraphEvent } from '@antv/g-base';
import type { IElement, IGroup } from '@antv/g-canvas';
import { Group } from '@antv/g-canvas';
import { type GestureEvent, Wheel } from '@antv/g-gesture';
import { Wheel, type GestureEvent } from '@antv/g-gesture';
import { interpolateArray } from 'd3-interpolate';
import { timer, type Timer } from 'd3-timer';
import {
Expand All @@ -23,13 +23,15 @@ import {
KEY_GROUP_CORNER_RESIZE_AREA,
KEY_GROUP_ROW_INDEX_RESIZE_AREA,
KEY_GROUP_ROW_RESIZE_AREA,
OriginEventType,
S2Event,
ScrollbarPositionType,
} from '../common/constant';
import { DEFAULT_PAGE_INDEX } from '../common/constant/pagination';
import {
DebuggerUtil,
DEBUG_HEADER_LAYOUT,
DEBUG_VIEW_RENDER,
DebuggerUtil,
} from '../common/debug';
import type {
CellCustomWidth,
Expand All @@ -43,9 +45,9 @@ import type {
ViewMeta,
} from '../common/interface';
import type {
ScrollOffset,
CellScrollPosition,
CellScrollOffset,
CellScrollPosition,
ScrollOffset,
} from '../common/interface/scroll';
import type { SpreadSheet } from '../sheet-type';
import { ScrollBar, ScrollType } from '../ui/scrollbar';
Expand All @@ -54,7 +56,6 @@ import { getAllChildCells } from '../utils/get-all-child-cells';
import { getColsForGrid, getRowsForGrid } from '../utils/grid';
import { diffPanelIndexes, type PanelIndexes } from '../utils/indexes';
import { isMobile, isWindows } from '../utils/is-mobile';
import { DEFAULT_PAGE_INDEX } from '../common/constant/pagination';
import { CornerBBox } from './bbox/cornerBBox';
import { PanelBBox } from './bbox/panelBBox';
import {
Expand Down Expand Up @@ -1346,10 +1347,28 @@ export abstract class BaseFacet {
}

protected onAfterScroll = debounce(() => {
const { interaction } = this.spreadsheet;
const { interaction, container } = this.spreadsheet;
// 如果是选中单元格状态, 则继续保留 hover 拦截, 避免滚动后 hover 清空已选单元格
if (!interaction.isSelectedState()) {
this.spreadsheet.interaction.removeIntercepts([InterceptType.HOVER]);
interaction.removeIntercepts([InterceptType.HOVER]);

if (interaction.getHoverAfterScroll()) {
// https://github.com/antvis/S2/issues/2222
const canvasMousemoveEvent =
interaction.eventController.canvasMousemoveEvent;
if (canvasMousemoveEvent) {
const { x, y } = canvasMousemoveEvent;
const shape = container.getShape(x, y);
if (shape) {
container.emit(OriginEventType.MOUSE_MOVE, {
...canvasMousemoveEvent,
shape,
target: shape,
timestamp: performance.now(),
});
}
}
}
}
}, 300);

Expand Down
6 changes: 5 additions & 1 deletion packages/s2-core/src/interaction/event-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import type { EmitterType, ResizeInfo } from '../common/interface';
import type { SpreadSheet } from '../sheet-type';
import { getSelectedData, keyEqualTo } from '../utils/export/copy';
import { getTooltipOptions, verifyTheElementInTooltip } from '../utils/tooltip';
import { verifyTheElementInTooltip } from '../utils/tooltip';

interface EventListener {
target: EventTarget;
Expand Down Expand Up @@ -51,6 +51,8 @@ export class EventController {

public isCanvasEffect = false;

public canvasMousemoveEvent: CanvasEvent;

constructor(spreadsheet: SpreadSheet) {
this.spreadsheet = spreadsheet;
this.bindEvents();
Expand Down Expand Up @@ -333,6 +335,8 @@ export class EventController {
};

private onCanvasMousemove = (event: CanvasEvent) => {
this.canvasMousemoveEvent = event;

if (this.isResizeArea(event)) {
this.activeResizeArea(event);
this.spreadsheet.emit(S2Event.LAYOUT_RESIZE_MOUSE_MOVE, event);
Expand Down
12 changes: 8 additions & 4 deletions packages/s2-core/src/interaction/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ import {
} from './base-interaction/click';
import { CornerCellClick } from './base-interaction/click/corner-cell-click';
import { HoverEvent } from './base-interaction/hover';
import { EventController } from './event-controller';
import { RangeSelection } from './range-selection';
import { SelectedCellMove } from './selected-cell-move';
import { DataCellBrushSelection } from './brush-selection/data-cell-brush-selection';
import { ColBrushSelection } from './brush-selection/col-brush-selection';
import { DataCellBrushSelection } from './brush-selection/data-cell-brush-selection';
import { RowBrushSelection } from './brush-selection/row-brush-selection';
import { DataCellMultiSelection } from './data-cell-multi-selection';
import { EventController } from './event-controller';
import { RangeSelection } from './range-selection';
import { RowColumnResize } from './row-column-resize';
import { SelectedCellMove } from './selected-cell-move';

export class RootInteraction {
public spreadsheet: SpreadSheet;
Expand Down Expand Up @@ -595,4 +595,8 @@ export class RootInteraction {
currentCol,
};
}

public getHoverAfterScroll(): boolean {
return this.spreadsheet.options.interaction.hoverAfterScroll;
}
}
46 changes: 30 additions & 16 deletions packages/s2-react/playground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable no-console */
import {
BaseTooltip,
DEFAULT_STYLE,
Node,
SpreadSheet,
customMerge,
type DataType,
generatePalette,
getLang,
getPalette,
type DataType,
type HeaderActionIconProps,
Node,
type InteractionCellSelectedHighlightType,
type InteractionOptions,
type S2DataConfig,
SpreadSheet,
type TargetCellInfo,
type ThemeCfg,
type TooltipAutoAdjustBoundary,
getLang,
type InteractionOptions,
DEFAULT_STYLE,
type InteractionCellSelectedHighlightType,
BaseTooltip,
} from '@antv/s2';
import type { Adaptive, SheetType } from '@antv/s2-shared';
import corePkg from '@antv/s2/package.json';
Expand All @@ -29,34 +29,34 @@ import {
Input,
Popover,
Radio,
type RadioChangeEvent,
Select,
Slider,
Space,
Switch,
Tabs,
Tag,
Tooltip,
type RadioChangeEvent,
} from 'antd';
import 'antd/dist/antd.min.css';
import { debounce, forEach, isBoolean, random } from 'lodash';
import React from 'react';
import { ChromePicker } from 'react-color';
import ReactDOM from 'react-dom';
import { customTreeFields } from '../__tests__/data/custom-tree-fields';
import { dataCustomTrees } from '../__tests__/data/data-custom-trees';
import { mockGridAnalysisDataCfg } from '../__tests__/data/grid-analysis-data';
import {
StrategyOptions,
StrategySheetDataConfig,
} from '../__tests__/data/strategy-data';
import reactPkg from '../package.json';
import type {
PartDrillDown,
PartDrillDownInfo,
SheetComponentOptions,
} from '../src';
import { SheetComponent } from '../src';
import { customTreeFields } from '../__tests__/data/custom-tree-fields';
import { dataCustomTrees } from '../__tests__/data/data-custom-trees';
import { mockGridAnalysisDataCfg } from '../__tests__/data/grid-analysis-data';
import {
StrategySheetDataConfig,
StrategyOptions,
} from '../__tests__/data/strategy-data';
import {
defaultOptions,
mockGridAnalysisOptions,
Expand Down Expand Up @@ -978,6 +978,20 @@ function MainLayout() {
}}
/>
</Tooltip>
<Tooltip title="滚动后自动触发悬停状态">
<Switch
checkedChildren="滚动悬停开"
unCheckedChildren="滚动悬停关"
checked={mergedOptions.interaction.hoverAfterScroll}
onChange={(checked) => {
updateOptions({
interaction: {
hoverAfterScroll: checked,
},
});
}}
/>
</Tooltip>
<Tooltip title="开启后,点击空白处,按下ESC键, 取消高亮, 清空选中单元格, 等交互样式">
<Switch
checkedChildren="自动重置交互样式开"
Expand Down
3 changes: 2 additions & 1 deletion s2-site/docs/common/interaction.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ order: 5
| scrollbarPosition | Used to control whether the scroll bar is displayed on the edge of the content area or the edge of the canvas | `content` \\ | `canvas` | `content` |
| eventListenerOptions | [Optional configuration](https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener) of the event listening function `addEventListener` , which can control whether the event is triggered from the bubbling phase or the capturing phase | `false` | | |
| selectedCellHighlight | Whether to highlight the column header where the grid is located | `boolean` | `false` | |
| overscrollBehavior | Controls the behavior of scrolling to bounds, which disables the browser's default scrolling behavior. [details](/docs/manual/advanced/interaction/basic/#%E4%BF%AE%E6%94%B9%E6%BB%9A%E5%8A%A8%E8%87%B3%E8%BE%B9%E7%95%8C%E8%A1%8C%E4%B8%BA) | `auto` \\ | `contain` \\ | `none` \\ |
| overscrollBehavior | Controls the behavior of scrolling to bounds, which disables the browser's default scrolling behavior. [details](/docs/manual/advanced/interaction/basic/#%E4%BF%AE%E6%94%B9%E6%BB%9A%E5%8A%A8%E8%87%B3%E8%BE%B9%E7%95%8C%E8%A1%8C%E4%B8%BA) | `auto \| contain \| none \| null` | `auto` |
| hoverAfterScroll | Whether to automatically trigger hover performance in the cell where the mouse is currently located after scrolling | `boolean` | `false` |

### CustomInteraction

Expand Down
1 change: 1 addition & 0 deletions s2-site/docs/common/interaction.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ order: 5
| eventListenerOptions | 事件监听函数 `addEventListener`[可选项配置](https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener), 可控制事件从冒泡阶段还是捕获阶段触发 | `false` | |
| selectedCellHighlight | 选中格子后的高亮行为<br/>rowHeader:是否高亮选中格子所在行头<br/>colHeader:是否高亮选中格子所在列头<br/>currentRow:是否高亮选中格子所在行<br/>currentCol:是否高亮选中格子所在列<br/>true:同{rowHeader: true, colHeader: true} | `boolean \| { rowHeader?: boolean, colHeader?: boolean, currentRow?: boolean, currentCol?: boolean }` | `false` | |
| overscrollBehavior | 控制滚动至边界的行为,可禁用浏览器的默认滚动行为。[详情](/docs/manual/advanced/interaction/basic/#修改滚动至边界行为) | `auto \| contain \| none \| null` | `auto` |
| hoverAfterScroll | 滚动结束后是否在当前鼠标所处单元格自动触发悬停表现 | `boolean` | `false` |

### CustomInteraction

Expand Down
31 changes: 31 additions & 0 deletions s2-site/examples/interaction/basic/demo/hover-after-scroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { PivotSheet, S2Options } from '@antv/s2';

fetch(
'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json',
)
.then((res) => res.json())
.then((dataCfg) => {
const container = document.getElementById('container');

const s2Options: S2Options = {
width: 600,
height: 480,
style: {
cellCfg: {
height: 100,
},
},
interaction: {
// 悬停高亮
hoverHighlight: true,
// 滚动后自动触发悬停状态
hoverAfterScroll: true,
},
tooltip: {
showTooltip: true,
},
};
const s2 = new PivotSheet(container, dataCfg, s2Options);

s2.render();
});
8 changes: 8 additions & 0 deletions s2-site/examples/interaction/basic/demo/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@
"en": "Disable auto reset sheet style"
},
"screenshot": "https://gw.alipayobjects.com/zos/antfincdn/Lb0%26u6LtAu/reset.gif"
},
{
"filename": "hover-after-scroll.ts",
"title": {
"zh": "滚动后自动触发悬停状态",
"en": "Trigger hover after scroll"
},
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*NbG7SakRzXUAAAAAAAAAAAAADmJ7AQ/original.gif"
}
]
}

0 comments on commit f9f97b0

Please sign in to comment.