UNPKG

@antv/s2

Version:

effective spreadsheet render core lib

548 lines 25.3 kB
import { Canvas, DisplayObject, } from '@antv/g'; import { each, get, hasIn, isEmpty, isFunction, isNil } from 'lodash'; import { GuiIcon, InteractionName } from '../common'; import { CellType, InteractionKeyboardKey, InterceptType, OriginEventType, S2Event, SHAPE_STYLE_MAP, } from '../common/constant'; import { CustomImage } from '../engine'; import { getSelectedData } from '../utils/export/copy'; import { keyEqualTo } from '../utils/export/method'; import { getAppendInfo } from '../utils/interaction/common'; import { isMobile } from '../utils/is-mobile'; import { verifyTheElementInTooltip } from '../utils/tooltip'; export class EventController { constructor(spreadsheet) { this.canvasEventHandlers = []; this.s2EventHandlers = []; this.domEventListeners = []; this.isCanvasEffect = false; // 不能单独判断是否 Image Shape, 用户如果自定义单元格绘制图片, 会导致判断错误 this.isGuiIconShape = (target) => { return target instanceof CustomImage && target.imgType === GuiIcon.type; }; this.isConditionIconShape = (target, cell) => { return cell .getConditionIconShapes() .some((shape) => { var _a; return (shape === null || shape === void 0 ? void 0 : shape.name) === ((_a = target === null || target === void 0 ? void 0 : target.attributes) === null || _a === void 0 ? void 0 : _a.name); }); }; this.onCanvasMousedown = (event) => { this.target = event.target; // 点击时清除 hover focus 状态 this.spreadsheet.interaction.clearHoverTimer(); if (this.isResizeArea(event)) { this.spreadsheet.emit(S2Event.LAYOUT_RESIZE_MOUSE_DOWN, event); // 仅捕获在 Canvas 之外触发的事件 https://github.com/antvis/S2/issues/1592 const resizeMouseMoveCapture = (pointerEvent) => { if (!this.spreadsheet.getCanvasElement()) { return false; } if (this.spreadsheet.getCanvasElement() !== pointerEvent.target) { this.spreadsheet.emit(S2Event.LAYOUT_RESIZE_MOUSE_MOVE, pointerEvent); } }; window.addEventListener(OriginEventType.POINTER_MOVE, resizeMouseMoveCapture); window.addEventListener(OriginEventType.POINTER_UP, () => { window.removeEventListener(OriginEventType.POINTER_MOVE, resizeMouseMoveCapture); }, { once: true }); return; } const cellType = this.spreadsheet.getCellType(event.target); switch (cellType) { case CellType.DATA_CELL: this.spreadsheet.emit(S2Event.DATA_CELL_MOUSE_DOWN, event); break; case CellType.ROW_CELL: this.spreadsheet.emit(S2Event.ROW_CELL_MOUSE_DOWN, event); break; case CellType.COL_CELL: this.spreadsheet.emit(S2Event.COL_CELL_MOUSE_DOWN, event); break; case CellType.CORNER_CELL: this.spreadsheet.emit(S2Event.CORNER_CELL_MOUSE_DOWN, event); break; case CellType.MERGED_CELL: this.spreadsheet.emit(S2Event.MERGED_CELLS_MOUSE_DOWN, event); break; default: break; } }; this.onCanvasMousemove = (event) => { this.canvasMousemoveEvent = event; if (this.isResizeArea(event)) { this.activeResizeArea(event); this.spreadsheet.emit(S2Event.LAYOUT_RESIZE_MOUSE_MOVE, event.nativeEvent); return; } this.resetResizeArea(); const cell = this.spreadsheet.getCell(event.target); if (cell) { const cellType = cell.cellType; switch (cellType) { case CellType.DATA_CELL: this.spreadsheet.emit(S2Event.DATA_CELL_MOUSE_MOVE, event); break; case CellType.ROW_CELL: this.spreadsheet.emit(S2Event.ROW_CELL_MOUSE_MOVE, event); break; case CellType.COL_CELL: this.spreadsheet.emit(S2Event.COL_CELL_MOUSE_MOVE, event); break; case CellType.CORNER_CELL: this.spreadsheet.emit(S2Event.CORNER_CELL_MOUSE_MOVE, event); break; case CellType.MERGED_CELL: this.spreadsheet.emit(S2Event.MERGED_CELLS_MOUSE_MOVE, event); break; default: break; } if (!this.hasBrushSelectionIntercepts()) { this.spreadsheet.emit(S2Event.GLOBAL_HOVER, event); switch (cellType) { case CellType.DATA_CELL: this.spreadsheet.emit(S2Event.DATA_CELL_HOVER, event); break; case CellType.ROW_CELL: this.spreadsheet.emit(S2Event.ROW_CELL_HOVER, event); break; case CellType.COL_CELL: this.spreadsheet.emit(S2Event.COL_CELL_HOVER, event); break; case CellType.CORNER_CELL: this.spreadsheet.emit(S2Event.CORNER_CELL_HOVER, event); break; case CellType.MERGED_CELL: this.spreadsheet.emit(S2Event.MERGED_CELLS_HOVER, event); break; default: break; } } } }; this.onCanvasMouseup = (event) => { if (this.isResizeArea(event)) { this.spreadsheet.emit(S2Event.LAYOUT_RESIZE_MOUSE_UP, event); return; } const cell = this.spreadsheet.getCell(event.target); if (cell) { const cellType = cell.cellType; // 通用的 mouseup 事件 switch (cellType) { case CellType.DATA_CELL: this.spreadsheet.emit(S2Event.DATA_CELL_MOUSE_UP, event); break; case CellType.ROW_CELL: this.spreadsheet.emit(S2Event.ROW_CELL_MOUSE_UP, event); break; case CellType.COL_CELL: this.spreadsheet.emit(S2Event.COL_CELL_MOUSE_UP, event); break; case CellType.CORNER_CELL: this.spreadsheet.emit(S2Event.CORNER_CELL_MOUSE_UP, event); break; case CellType.MERGED_CELL: this.spreadsheet.emit(S2Event.MERGED_CELLS_MOUSE_UP, event); break; default: break; } if (isMobile()) { // Mobile场景下单击走这里 this.onCanvasSingleClick(event); } } }; this.onCanvasClick = (event) => { const spreadsheet = this.spreadsheet; if (event.detail === 1 && !isMobile()) { // PC场景下单击走这里 this.onCanvasSingleClick(event); return; } if (event.detail !== 2) { return; } if (this.isResizeArea(event)) { spreadsheet.emit(S2Event.LAYOUT_RESIZE_MOUSE_UP, event); return; } spreadsheet.emit(S2Event.GLOBAL_DOUBLE_CLICK, event); const cell = spreadsheet.getCell(event.target); if (cell) { const cellType = cell.cellType; if (this.target === event.target) { switch (cellType) { case CellType.DATA_CELL: spreadsheet.emit(S2Event.DATA_CELL_DOUBLE_CLICK, event); break; case CellType.ROW_CELL: spreadsheet.emit(S2Event.ROW_CELL_DOUBLE_CLICK, event); break; case CellType.COL_CELL: spreadsheet.emit(S2Event.COL_CELL_DOUBLE_CLICK, event); break; case CellType.CORNER_CELL: spreadsheet.emit(S2Event.CORNER_CELL_DOUBLE_CLICK, event); break; case CellType.MERGED_CELL: spreadsheet.emit(S2Event.MERGED_CELLS_DOUBLE_CLICK, event); break; default: break; } } } }; this.onCanvasMouseout = (event) => { if (!this.isAutoResetSheetStyle(event) || this.isMouseOnTheCanvasContainer(event)) { return; } const { interaction } = this.spreadsheet; // 两种情况不能重置 1. 选中单元格 2. 有 intercepts 时(重置会清空 intercepts) if (!interaction.isSelectedState() && !(interaction.intercepts.size > 0)) { interaction.reset(); } }; this.onCanvasContextMenu = (event) => { const spreadsheet = this.spreadsheet; if (this.isResizeArea(event)) { spreadsheet.emit(S2Event.LAYOUT_RESIZE_MOUSE_UP, event); return; } spreadsheet.emit(S2Event.GLOBAL_CONTEXT_MENU, event); const cellType = this.spreadsheet.getCellType(event.target); switch (cellType) { case CellType.DATA_CELL: this.spreadsheet.emit(S2Event.DATA_CELL_CONTEXT_MENU, event); break; case CellType.ROW_CELL: this.spreadsheet.emit(S2Event.ROW_CELL_CONTEXT_MENU, event); break; case CellType.COL_CELL: this.spreadsheet.emit(S2Event.COL_CELL_CONTEXT_MENU, event); break; case CellType.CORNER_CELL: this.spreadsheet.emit(S2Event.CORNER_CELL_CONTEXT_MENU, event); break; case CellType.MERGED_CELL: this.spreadsheet.emit(S2Event.MERGED_CELLS_CONTEXT_MENU, event); break; default: break; } }; this.spreadsheet = spreadsheet; this.bindEvents(); } get canvasContainer() { return this.spreadsheet.container; } isAutoResetSheetStyle(event) { var _a; const { interaction } = this.spreadsheet.options; return isFunction(interaction === null || interaction === void 0 ? void 0 : interaction.autoResetSheetStyle) ? (_a = interaction === null || interaction === void 0 ? void 0 : interaction.autoResetSheetStyle) === null || _a === void 0 ? void 0 : _a.call(interaction, event, this.spreadsheet) : interaction === null || interaction === void 0 ? void 0 : interaction.autoResetSheetStyle; } bindEvents() { this.clearAllEvents(); // canvas events this.addCanvasEvent(OriginEventType.MOUSE_DOWN, this.onCanvasMousedown); this.addCanvasEvent(OriginEventType.TOUCH_START, (event) => { this.target = event.target; }); const realClickEvent = isMobile() ? OriginEventType.TOUCH_END : OriginEventType.POINTER_UP; this.addCanvasEvent(OriginEventType.POINTER_MOVE, this.onCanvasMousemove); this.addCanvasEvent(OriginEventType.MOUSE_OUT, this.onCanvasMouseout); this.addCanvasEvent(realClickEvent, this.onCanvasMouseup); this.addCanvasEvent(OriginEventType.CLICK, this.onCanvasClick); /** * 如果监听 G Canvas, 右键对应的是 rightup/rightdown 事件, 如需禁用右键菜单 (preventDefault), 需要监听 DOM * https://g.antv.antgroup.com/api/event/faq#%E7%A6%81%E7%94%A8%E5%8F%B3%E9%94%AE%E8%8F%9C%E5%8D%95 */ this.addCanvasEvent(OriginEventType.RIGHT_DOWN, this.onCanvasContextMenu); // spreadsheet events this.addS2Event(S2Event.GLOBAL_ACTION_ICON_CLICK, () => { this.spreadsheet.interaction.addIntercepts([InterceptType.HOVER]); this.spreadsheet.interaction.clearState(); }); // dom events this.addDomEventListener(window, OriginEventType.CLICK, (event) => { this.resetSheetStyle(event); this.isCanvasEffect = this.isMouseOnTheCanvasContainer(event); }); this.addDomEventListener(window, OriginEventType.KEY_DOWN, (event) => { this.onKeyboardCopy(event); this.onKeyboardEsc(event); this.spreadsheet.emit(S2Event.GLOBAL_KEYBOARD_DOWN, event); }); this.addDomEventListener(window, OriginEventType.KEY_UP, (event) => { this.spreadsheet.emit(S2Event.GLOBAL_KEYBOARD_UP, event); }); this.addDomEventListener(window, OriginEventType.POINTER_UP, (event) => { this.spreadsheet.emit(S2Event.GLOBAL_MOUSE_UP, event); }); this.addDomEventListener(window, OriginEventType.POINTER_MOVE, (event) => { this.spreadsheet.emit(S2Event.GLOBAL_MOUSE_MOVE, event); }); } // Windows and Mac OS onKeyboardCopy(event) { const { copy } = this.spreadsheet.options.interaction; if (this.isCanvasEffect && (copy === null || copy === void 0 ? void 0 : copy.enable) && keyEqualTo(event.key, InteractionKeyboardKey.COPY) && (event.metaKey || event.ctrlKey)) { const copyData = getSelectedData(this.spreadsheet); if (!isNil(copyData)) { this.spreadsheet.emit(S2Event.GLOBAL_COPIED, copyData); } } } onKeyboardEsc(event) { if (this.isCanvasEffect && keyEqualTo(event.key, InteractionKeyboardKey.ESC)) { this.resetSheetStyle(event); } } resetSheetStyle(event) { var _a, _b; if (!this.isAutoResetSheetStyle(event) || !this.spreadsheet) { return; } /** * 全局有 mouseUp 和 click 事件, 当刷选完成后会同时触发, 当选中单元格后, 会同时触发 click 对应的 reset 事件 * 所以如果是 刷选过程中 引起的 click(mousedown + mouseup) 事件, 则不需要重置 */ const { interaction } = this.spreadsheet; if ((_a = interaction === null || interaction === void 0 ? void 0 : interaction.hasIntercepts) === null || _a === void 0 ? void 0 : _a.call(interaction, [ InterceptType.DATA_CELL_BRUSH_SELECTION, InterceptType.COL_CELL_BRUSH_SELECTION, InterceptType.ROW_CELL_BRUSH_SELECTION, ])) { (_b = interaction === null || interaction === void 0 ? void 0 : interaction.removeIntercepts) === null || _b === void 0 ? void 0 : _b.call(interaction, [ InterceptType.DATA_CELL_BRUSH_SELECTION, InterceptType.ROW_CELL_BRUSH_SELECTION, InterceptType.COL_CELL_BRUSH_SELECTION, ]); return; } if (this.isMouseOnTheTooltip(event) || this.isMouseOnTheCanvasContainer(event)) { return; } interaction.reset(); this.spreadsheet.emit(S2Event.GLOBAL_RESET, event); this.spreadsheet.emit(S2Event.GLOBAL_SELECTED, interaction.getActiveCells(), { event, targetCell: null, interactionName: InteractionName.GLOBAL_RESET, }); } isMouseEvent(event) { // 通过 MouseEvent 特有属性判断,避免 instanceof 失效的问题 return hasIn(event, 'clientX') && hasIn(event, 'clientY'); } isMatchElement(event) { const canvas = this.spreadsheet.getCanvasElement(); const { target } = event; return (target === canvas || target instanceof DisplayObject || target instanceof Canvas); } isMatchPoint(event) { /** * 这里不能使用 bounding rect 的 width 和 height, 高清适配后 canvas 实际宽高会变 * 比如实际 400 * 300 => hd (800 * 600) * 从视觉来看, 虽然点击了空白处, 但其实还是处于 放大后的 canvas 区域, 所以还需要额外判断一下坐标 */ const canvas = this.spreadsheet.getCanvasElement(); const { width, height } = this.getContainerRect(); const { x, y } = canvas.getBoundingClientRect() || {}; const { clientX, clientY } = event; return (clientX <= x + width && clientX >= x && clientY <= y + height && clientY >= y); } isMouseOnTheCanvasContainer(event) { if (this.isMouseEvent(event)) { const canvas = this.spreadsheet.getCanvasElement(); if (!canvas) { return false; } // 开启 CSS transform 时, 降级处理, 不做 canvas 内的空白检测: https://github.com/antvis/S2/issues/2879 if (this.spreadsheet.getCanvasConfig().supportsCSSTransform) { return this.isMatchElement(event); } return this.isMatchElement(event) && this.isMatchPoint(event); } return false; } getContainerRect() { var _a; const { facet, options } = this.spreadsheet; const scrollBar = (facet === null || facet === void 0 ? void 0 : facet.hRowScrollBar) || (facet === null || facet === void 0 ? void 0 : facet.hScrollBar); const { maxX, maxY } = (facet === null || facet === void 0 ? void 0 : facet.panelBBox) || {}; const { width = 0, height = 0 } = options; /** * https://github.com/antvis/S2/issues/2376 * 横向的滚动条在表格外 (Canvas 内), 点击滚动条(含滑道区域) 不应该重置交互 */ const trackHeight = ((_a = scrollBar === null || scrollBar === void 0 ? void 0 : scrollBar.theme) === null || _a === void 0 ? void 0 : _a.size) || 0; return { width: Math.min(width, maxX), height: Math.min(height, maxY + trackHeight), }; } isMouseOnTheTooltip(event) { var _a, _b, _c, _d; const { tooltip } = this.spreadsheet; if (!(tooltip === null || tooltip === void 0 ? void 0 : tooltip.visible)) { return false; } const { x, y, width, height } = ((_c = (_b = (_a = this.spreadsheet.tooltip) === null || _a === void 0 ? void 0 : _a.container) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect) === null || _c === void 0 ? void 0 : _c.call(_b)) || {}; if (event.target instanceof Node) { return verifyTheElementInTooltip((_d = this.spreadsheet.tooltip) === null || _d === void 0 ? void 0 : _d.container, event.target); } if (this.isMouseEvent(event)) { return (event.clientX >= x && event.clientX <= x + width && event.clientY >= y && event.clientY <= y + height); } return false; } isResizeArea(event) { const appendInfo = getAppendInfo(event.target); return appendInfo === null || appendInfo === void 0 ? void 0 : appendInfo.isResizeArea; } activeResizeArea(event) { var _a, _b, _c; const appendInfo = get(event.target, 'attrs.appendInfo'); if (appendInfo === null || appendInfo === void 0 ? void 0 : appendInfo.isResizeMask) { return; } this.resetResizeArea(); const resizeArea = event.target; this.spreadsheet.store.set('activeResizeArea', resizeArea); resizeArea.attr(SHAPE_STYLE_MAP.backgroundOpacity, (_c = (_b = (_a = this.spreadsheet.theme.resizeArea) === null || _a === void 0 ? void 0 : _a.interactionState) === null || _b === void 0 ? void 0 : _b.hover) === null || _c === void 0 ? void 0 : _c.backgroundOpacity); } resetResizeArea() { var _a; const resizeArea = this.spreadsheet.store.get('activeResizeArea'); if (!isEmpty(resizeArea)) { resizeArea.attr(SHAPE_STYLE_MAP.backgroundOpacity, (_a = this.spreadsheet.theme.resizeArea) === null || _a === void 0 ? void 0 : _a.backgroundOpacity); } this.spreadsheet.store.set('activeResizeArea', resizeArea); } hasBrushSelectionIntercepts() { return this.spreadsheet.interaction.hasIntercepts([ InterceptType.HOVER, InterceptType.DATA_CELL_BRUSH_SELECTION, InterceptType.ROW_CELL_BRUSH_SELECTION, InterceptType.COL_CELL_BRUSH_SELECTION, ]); } onCanvasSingleClick(event) { const cell = this.spreadsheet.getCell(event.target); // target 相同,说明是一个 cell 内的 click 事件 if (this.target === event.target && cell) { // 屏蔽 actionIcons 的点击,字段标记增加的 icon 除外. if (this.isGuiIconShape(event.target) && !this.isConditionIconShape(event.target, cell)) { return; } this.spreadsheet.emit(S2Event.GLOBAL_CLICK, event); switch (cell.cellType) { case CellType.DATA_CELL: this.spreadsheet.emit(S2Event.DATA_CELL_CLICK, event); break; case CellType.ROW_CELL: this.spreadsheet.emit(S2Event.ROW_CELL_CLICK, event); break; case CellType.COL_CELL: this.spreadsheet.emit(S2Event.COL_CELL_CLICK, event); break; case CellType.CORNER_CELL: this.spreadsheet.emit(S2Event.CORNER_CELL_CLICK, event); break; case CellType.MERGED_CELL: this.spreadsheet.emit(S2Event.MERGED_CELLS_CLICK, event); break; default: break; } } } clear() { this.unbindEvents(); } unbindEvents() { this.clearAllEvents(); } addCanvasEvent(eventType, handler) { var _a; (_a = this.canvasContainer) === null || _a === void 0 ? void 0 : _a.on(eventType, handler); this.canvasEventHandlers.push({ type: eventType, handler }); } addS2Event(eventType, handler) { this.spreadsheet.on(eventType, handler); this.s2EventHandlers.push({ type: eventType, handler, }); } addDomEventListener(target, type, handler) { if (target.addEventListener) { const { eventListenerOptions } = this.spreadsheet.options.interaction; target.addEventListener(type, handler, eventListenerOptions); this.domEventListeners.push({ target, type, handler: handler, options: eventListenerOptions, }); } else { // eslint-disable-next-line no-console console.error(`Please make sure ${target} has addEventListener function`); } } clearAllEvents() { each(this.canvasEventHandlers, ({ type, handler }) => { var _a; (_a = this.canvasContainer) === null || _a === void 0 ? void 0 : _a.removeEventListener(type, handler); }); each(this.s2EventHandlers, ({ type, handler }) => { this.spreadsheet.off(type, handler); }); each(this.domEventListeners, (listener) => { listener.target.removeEventListener(listener.type, listener.handler, listener.options); }); this.canvasEventHandlers = []; this.s2EventHandlers = []; this.domEventListeners = []; } getViewportPoint(event) { const config = this.spreadsheet.getCanvasConfig(); // https://github.com/antvis/G/blob/a43c19a662684945d0bf9dc1876af43ac26b1243/packages/g-lite/src/plugins/EventPlugin.ts#L216 if (config.supportsCSSTransform && !isNil(event.offsetX) && !isNil(event.offsetY)) { return { x: event.offsetX, y: event.offsetY, }; } return this.spreadsheet.container.client2Viewport({ x: event.clientX, y: event.clientY, }); } } //# sourceMappingURL=event-controller.js.map