UNPKG

e-virt-table

Version:

A powerful data table based on canvas. You can use it as data grid、Microsoft Excel or Google sheets. It supports virtual scroll、cell edit etc.

522 lines 21.3 kB
import Cell from './Cell'; import CellHeader from './CellHeader'; export default class EventTable { constructor(ctx) { Object.defineProperty(this, "ctx", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "visibleHoverCell", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "resizeObserver", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "mutationObserver", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.ctx = ctx; this.init(); } init() { // 监听窗口大小变化 this.resizeObserver = new ResizeObserver((entries) => { this.ctx.emit('resetHeader'); this.ctx.emit('resizeObserver', entries); this.ctx.emit('containerResize', this.ctx.containerElement); }); this.resizeObserver.observe(this.ctx.containerElement); this.mutationObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === 'attributes' && mutation.attributeName === 'class') { this.ctx.config.updateCssVar(); this.ctx.emit('draw'); } } }); if (this.ctx.config.ENABLE_AUTO_THEME) { this.mutationObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'], // ✅ 只监听 class 更改 }); } // 按下事件 this.ctx.on('mousedown', (e) => { // 左边点击 if (e.button !== 0) { return; } // 是否忙碌,进行其他操作 if (this.isBusy(e)) { return; } const { offsetY, offsetX } = this.ctx.getOffset(e); const y = offsetY; const x = offsetX; this.handleHeaderEvent(x, y, this.ctx.header.renderCellHeaders, (cell) => { this.ctx.focusCellHeader = cell; this.ctx.focusCell = undefined; this.ctx.emit('cellHeaderMousedown', cell, e); }); this.handleBodyEvent(x, y, this.ctx.body.renderRows, (cell) => { this.ctx.setFocusCell(cell); this.ctx.focusCellHeader = undefined; this.ctx.emit('cellMousedown', cell, e); }); }); // 按上事件 this.ctx.on('mouseup', (e) => { // 左边点击 if (e.button !== 0) { return; } // 是否忙碌,进行其他操作 if (this.isBusy(e)) { return; } const { offsetY, offsetX } = this.ctx.getOffset(e); const y = offsetY; const x = offsetX; this.handleHeaderEvent(x, y, this.ctx.header.renderCellHeaders, (cell) => { this.ctx.focusCellHeader = cell; this.ctx.emit('cellHeaderMouseup', cell, e); }); this.handleBodyEvent(x, y, this.ctx.body.renderRows, (cell) => { this.ctx.setFocusCell(cell); this.ctx.emit('cellMouseup', cell, e); }); }); this.ctx.on('click', (e) => { // 左边点击 if (e.button !== 0) { return; } // 是否忙碌,进行其他操作 if (this.isBusy(e)) { return; } const y = this.ctx.getOffset(e).offsetY; const x = this.ctx.getOffset(e).offsetX; this.handleHeaderEvent(x, y, this.ctx.header.renderCellHeaders, (cell) => { this.ctx.clickCellHeader = cell; this.ctx.emit('cellHeaderClick', cell, e); // selection事件 this.selectionClick(cell, e); this.sortClick(cell, e); }); // 可视区 this.handleBodyEvent(x, y, this.ctx.body.renderRows, (cell) => { this.ctx.clickCell = cell; this.ctx.emit('cellClick', cell, e); this.selectionClick(cell, e); this.treeClick(cell, e); }, true); // 正常 this.handleBodyEvent(x, y, this.ctx.body.renderRows, (cell) => { // hoverIcon事件 this.hoverIconClick(cell); }, false); }); this.ctx.on('dblclick', (e) => { // 左边点击 if (e.button !== 0) { return; } // 是否忙碌,进行其他操作 if (this.isBusy(e)) { return; } const y = this.ctx.getOffset(e).offsetY; const x = this.ctx.getOffset(e).offsetX; this.handleHeaderEvent(x, y, this.ctx.header.renderCellHeaders, (cell) => { this.ctx.emit('cellHeaderDblclick', cell, e); }); this.handleBodyEvent(x, y, this.ctx.body.renderRows, (cell) => { this.ctx.clickCell = cell; this.ctx.emit('cellDblclick', cell, e); }); }); this.ctx.on('contextMenu', (e) => { if (this.isBusy(e)) { return; } const { offsetY, offsetX } = this.ctx.getOffset(e); const y = offsetY; const x = offsetX; this.handleHeaderEvent(x, y, this.ctx.header.renderCellHeaders, (cell) => { this.ctx.emit('cellHeaderContextMenuClick', cell, e); }); this.handleBodyEvent(x, y, this.ctx.body.renderRows, (cell) => { this.ctx.emit('cellContextMenuClick', cell, e); }); }); this.ctx.on('mouseout', (e) => { if (!this.ctx.containerElement.contains(e.relatedTarget) && this.ctx.hoverCell !== undefined) { this.ctx.hoverRow = undefined; this.ctx.hoverCell = undefined; this.ctx.emit('draw'); } }); this.ctx.on('mousemove', (e) => { // 是否忙碌,进行其他操作 if (this.isBusy(e)) { return; } this.ctx.isPointer = false; if (this.ctx.stageElement.style.cursor === 'pointer') { this.ctx.stageElement.style.cursor = 'default'; } const y = this.ctx.getOffset(e).offsetY; const x = this.ctx.getOffset(e).offsetX; this.handleHeaderEvent(x, y, this.ctx.header.renderCellHeaders, (cell) => { this.ctx.emit('cellHeaderMouseenter', cell, e); // 移出事件 if (this.ctx.hoverCellHeader && this.ctx.hoverCellHeader !== cell) { this.ctx.emit('cellHeaderMouseleave', this.ctx.hoverCellHeader, e); } // selection头部事件 this.imageEnterAndLeave(cell, e); if (this.ctx.hoverCellHeader === cell) { return; } this.ctx.hoverCellHeader = cell; this.visibleHoverCell = undefined; // 清除可视区hover this.ctx.emit('cellHeaderHoverChange', cell, e); }); // 可视区 this.handleBodyEvent(x, y, this.ctx.body.renderRows, (cell) => { // selection移入移除事件 // this.ctx.emit("visibleCellHoverChange", cell, e); this.imageEnterAndLeave(cell, e); if (this.visibleHoverCell !== cell) { this.ctx.emit('visibleCellMouseleave', cell, e); this.visibleHoverCell = cell; this.ctx.hoverCellHeader = undefined; // 清除头部hover this.ctx.emit('visibleCellHoverChange', cell, e); } }, true); // 正常 this.handleBodyEvent(x, y, this.ctx.body.renderRows, (cell) => { this.imageEnterAndLeave(cell, e); this.ctx.emit('cellMouseenter', cell, e); // 移出事件 if (this.ctx.hoverCell && this.ctx.hoverCell !== cell) { this.ctx.emit('cellMouseleave', cell, e); } if (this.ctx.hoverCell === cell) return; if (this.ctx.hoverCell?.rowKey !== cell.rowKey) { this.ctx.hoverCell = cell; this.ctx.hoverRow = this.ctx.body.renderRows.find((item) => item.rowKey === cell.rowKey); this.ctx.emit('rowHoverChange', this.ctx.hoverRow, cell, e); this.ctx.emit('draw'); } this.ctx.hoverCell = cell; this.ctx.emit('cellHoverChange', cell, e); }); this.handleFooterEvent(x, y, this.ctx.footer.renderRows, (cell) => { this.ctx.emit('cellFooterMouseenter', cell, e); // 移出事件 if (this.ctx.hoverCell && this.ctx.hoverCell !== cell) { this.ctx.emit('cellFooterMouseleave', cell, e); } this.ctx.emit('cellFooterHoverChange', cell, e); }); }); } hoverIconClick(cell) { // 鼠标移动到图标上会变成pointer,所以这里判断是否是pointer就能判断出是图标点击的 if (cell.hoverIconName && this.ctx.isPointer) { this.ctx.emit('hoverIconClick', cell); } } /** *选中点击 * @param cell */ selectionClick(cell, e) { // 鼠标移动到图标上会变成pointer,所以这里判断是否是pointer就能判断出是图标点击的 const isSelection = ['selection', 'index-selection', 'selection-tree', 'tree-selection'].includes(cell.type) && this.ctx.isPointer; if (!isSelection) { return; } // 判断是否点击checkbox图标 const { offsetY, offsetX } = this.ctx.getOffset(e); const clickY = offsetY; const clickX = offsetX; if (!this.isInsideElement(clickX, clickY, cell.drawSelectionImageX, cell.drawSelectionImageY, cell.drawSelectionImageWidth, cell.drawSelectionImageHeight)) { return; } // 点击头部 if (cell instanceof CellHeader) { if (cell.drawSelectionImageName === 'checkbox-uncheck' || cell.drawSelectionImageName === 'checkbox-indeterminate') { this.ctx.database.toggleAllSelection(); } else if (cell.drawSelectionImageName === 'checkbox-check') { this.ctx.database.clearSelection(true); } } else { // 点击body // 是否可点击 const selectable = this.ctx.database.getRowSelectable(cell.rowKey); if (!selectable) { return; } this.ctx.database.toggleRowSelection(cell.rowKey, cell.type); } } /** * 树点击 * @param cell */ treeClick(cell, e) { // 鼠标移动到图标上会变成pointer,所以这里判断是否是pointer就能判断出是图标点击的 const isTree = ['tree', 'selection-tree', 'tree-selection'].includes(cell.type) && this.ctx.isPointer; if (!isTree) { return; } // 判断是否点击tree图标 const { offsetY, offsetX } = this.ctx.getOffset(e); const clickY = offsetY; const clickX = offsetX; if (!this.isInsideElement(clickX, clickY, cell.drawTreeImageX, cell.drawTreeImageY, cell.drawTreeImageWidth, cell.drawTreeImageHeight)) { return; } const row = this.ctx.database.getRowForRowKey(cell.rowKey); const { expand = false, expandLazy = false } = row || {}; const { EXPAND_LAZY, EXPAND_LAZY_METHOD } = this.ctx.config; // 懒加载且有懒加载方法,不是展开的不是已经加载过的 if (EXPAND_LAZY && EXPAND_LAZY_METHOD && !expand && !expandLazy) { if (typeof EXPAND_LAZY_METHOD === 'function') { this.ctx.database.expandLoading(cell.rowKey, true); const expandLazyMethod = EXPAND_LAZY_METHOD; expandLazyMethod({ row: cell.row, rowIndex: cell.rowIndex, colIndex: cell.colIndex, column: cell.column, value: cell.getValue(), }) .then((res) => { this.ctx.database.setExpandChildren(cell.rowKey, res); this.ctx.database.expandLoading(cell.rowKey, false); this.ctx.emit('expandChange', this.ctx.database.getExpandRowKeys()); }) .catch((err) => { this.ctx.database.expandLoading(cell.rowKey, false); console.error(err); }); } // 懒加载 } else { const isExpand = this.ctx.database.getIsExpand(cell.rowKey); this.ctx.database.expandItem(cell.rowKey, !isExpand); this.ctx.emit('expandChange', this.ctx.database.getExpandRowKeys()); } } sortClick(cellHeader, e) { const { offsetY, offsetX } = this.ctx.getOffset(e); const clickY = offsetY; const clickX = offsetX; if (!this.isInsideElement(clickX, clickY, cellHeader.drawSortImageX, cellHeader.drawSortImageY, cellHeader.drawSortImageWidth, cellHeader.drawSortImageHeight)) { return; } // 前端排序 const currentState = this.ctx.database.getSortState(cellHeader.key); let newDirection; // 按照 不排序->升序->降序->不排序 的顺序循环 if (currentState.direction === 'none') { newDirection = 'asc'; } else if (currentState.direction === 'asc') { newDirection = 'desc'; } else { newDirection = 'none'; } this.ctx.database.setSortState(cellHeader.key, newDirection); } /** * 图标进入和离开事件,包括选中,展开,提示图标等 * @param cell * @param e */ imageEnterAndLeave(cell, e) { const { offsetY, offsetX } = this.ctx.getOffset(e); const y = offsetY; const x = offsetX; // 检查头部图标 if (cell instanceof CellHeader) { // 选中图标 if (cell.drawSelectionImageSource && this.isInsideElement(x, y, cell.drawSelectionImageX, cell.drawSelectionImageY, cell.drawSelectionImageWidth, cell.drawSelectionImageHeight)) { this.ctx.stageElement.style.cursor = 'pointer'; this.ctx.isPointer = true; return; } // 排序图标 if (cell.drawSortImageSource && this.isInsideElement(x, y, cell.drawSortImageX, cell.drawSortImageY, cell.drawSortImageWidth, cell.drawSortImageHeight)) { this.ctx.stageElement.style.cursor = 'pointer'; this.ctx.isPointer = true; return; } } // 检查body图标 if (cell instanceof Cell) { // 选中图标 if (cell.drawSelectionImageSource && this.isInsideElement(x, y, cell.drawSelectionImageX, cell.drawSelectionImageY, cell.drawSelectionImageWidth, cell.drawSelectionImageHeight)) { this.ctx.stageElement.style.cursor = 'pointer'; this.ctx.isPointer = true; // body cell 需要处理是否禁用 const selectable = this.ctx.database.getRowSelectable(cell.rowKey); if (!selectable) { this.ctx.stageElement.style.cursor = 'not-allowed'; } return; } // hover图标 if (cell.drawHoverImageSource && this.isInsideElement(x, y, cell.drawHoverImageX, cell.drawHoverImageY, cell.drawHoverImageWidth, cell.drawHoverImageHeight)) { this.ctx.stageElement.style.cursor = 'pointer'; this.ctx.isPointer = true; return; } // tree图标 if (cell.drawTreeImageSource && this.isInsideElement(x, y, cell.drawTreeImageX, cell.drawTreeImageY, cell.drawTreeImageWidth, cell.drawTreeImageHeight)) { this.ctx.stageElement.style.cursor = 'pointer'; this.ctx.isPointer = true; return; } } } isInsideElement(x, y, xElement, yElement, widthElement, heightElement) { if (x > xElement && x < xElement + widthElement && y > yElement && y < yElement + heightElement) { return true; } return false; } isBusy(e) { const { offsetY, offsetX } = this.ctx.getOffset(e); const y = offsetY; const x = offsetX; if (this.ctx.loading) { return true; } if (!this.ctx.isTarget(e)) { return true; } // 行调整大小中不处理 if (this.ctx.stageElement.style.cursor === 'row-resize') { return true; } // 列调整大小中不处理 if (this.ctx.stageElement.style.cursor === 'col-resize') { return true; } // 列调整大小中不处理 if (this.ctx.columnResizing) { return true; } // 行调整大小中不处理 if (this.ctx.rowResizing) { return true; } const { SCROLLER_TRACK_SIZE } = this.ctx.config; // 滚动条移动不处理 if (this.ctx.scrollerMove) { return true; } // 滚动条悬浮不处理 if (this.ctx.scrollerFocus) { return true; } // 点击滚动条不处理 if (y > this.ctx.stageHeight - SCROLLER_TRACK_SIZE) { return true; } // 点击滚动条不处理 if (x > this.ctx.stageWidth - SCROLLER_TRACK_SIZE) { return true; } return false; } handleBodyEvent(x, y, renderRows, callback, visible = false) { if (!this.isInsideBody(y)) { return; } for (const row of renderRows) { // 优先处理固定列 const cells = row.fixedCells.concat(row.noFixedCells); for (const cell of cells) { const drawX = cell.getDrawX(); const drawY = cell.getDrawY(); if (visible) { if (x > drawX && x < drawX + cell.visibleWidth && y > drawY && y < drawY + cell.visibleHeight) { callback(cell); return; // 找到后直接返回 } } else if (x > drawX && x < drawX + cell.width && y > drawY && y < drawY + cell.height) { callback(cell); return; // 找到后直接返回 } } } } handleHeaderEvent(x, y, renderCellHeaders, callback) { for (const cell of renderCellHeaders) { const drawX = cell.getDrawX(); const drawY = cell.getDrawY(); if (x > drawX && x < drawX + cell.width && y > drawY && y < drawY + cell.height) { callback(cell); return; // 找到后直接返回 } } } handleFooterEvent(x, y, renderRows, callback, visible = false) { for (const row of renderRows) { // 优先处理固定列 const cells = row.fixedCells.concat(row.noFixedCells); for (const cell of cells) { const drawX = cell.getDrawX(); const drawY = cell.getDrawY(); if (visible) { if (x > drawX && x < drawX + cell.visibleWidth && y > drawY && y < drawY + cell.visibleHeight) { callback(cell); return; // 找到后直接返回 } } else if (x > drawX && x < drawX + cell.width && y > drawY && y < drawY + cell.height) { callback(cell); return; // 找到后直接返回 } } } } isInsideBody(y) { return y > this.ctx.body.y && y < this.ctx.body.y + this.ctx.body.visibleHeight; } destroy() { this.resizeObserver.unobserve(this.ctx.stageElement); this.mutationObserver.disconnect(); } } //# sourceMappingURL=EventTable.js.map