cheetah-grid
Version:
Cheetah Grid is a high performance grid engine that works on canvas
1,908 lines (1,831 loc) • 108 kB
text/typescript
import * as calc from "../internal/calc";
import * as hiDPI from "../internal/hiDPI";
import * as style from "../internal/style";
import type {
AfterSelectedCellEvent,
AnyFunction,
BeforeSelectedCellEvent,
CellAddress,
CellContext,
CellRange,
DrawGridAPI,
DrawGridEventHandlersEventMap,
DrawGridEventHandlersReturnMap,
DrawGridKeyboardOptions,
EventListenerId,
KeyboardEventListener,
KeydownEvent,
MousePointerCellEvent,
PasteCellEvent,
PasteRangeBoxValues,
} from "../ts-types";
import {
array,
browser,
event,
isDescendantElement,
isPromise,
} from "../internal/utils";
import {
normalizePasteValue,
parsePasteRangeBoxValues,
} from "../internal/paste-utils";
import { DG_EVENT_TYPE } from "./DG_EVENT_TYPE";
import { EventHandler } from "../internal/EventHandler";
import { EventTarget } from "./EventTarget";
import { NumberMap } from "../internal/NumberMap";
import { Rect } from "../internal/Rect";
import { Scrollable } from "../internal/Scrollable";
import { getFontSize } from "../internal/canvases";
//protected symbol
import { getProtectedSymbol } from "../internal/symbolManager";
const {
/** @private */
isTouchEvent,
/** @private */
getMouseButtons,
/** @private */
getKeyCode,
/** @private */
cancel: cancelEvent,
} = event;
/** @private */
const _ = getProtectedSymbol();
/** @private */
function createRootElement(): HTMLElement {
const element = document.createElement("div");
element.classList.add("cheetah-grid");
return element;
}
/** @private */
const KEY_BS = 8;
/** @private */
const KEY_TAB = 9;
/** @private */
const KEY_ENTER = 13;
/** @private */
const KEY_END = 35;
/** @private */
const KEY_HOME = 36;
/** @private */
const KEY_LEFT = 37;
/** @private */
const KEY_UP = 38;
/** @private */
const KEY_RIGHT = 39;
/** @private */
const KEY_DOWN = 40;
/** @private */
const KEY_DEL = 46;
/** @private */
const KEY_ALPHA_A = 65;
/** @private */
const KEY_ALPHA_C = 67;
/** @private */
const KEY_ALPHA_V = 86;
//private methods
/** @private */
function _vibrate(e: TouchEvent | MouseEvent): void {
if (navigator.vibrate && isTouchEvent(e)) {
navigator.vibrate(50);
}
}
/** @private */
function _getTargetRowAt(
this: DrawGrid,
absoluteY: number
): { row: number; top: number } | null {
const internal = this.getTargetRowAtInternal(absoluteY);
if (internal != null) {
return internal;
}
const findBefore = (
startRow: number,
startBottom: number
): {
top: number;
row: number;
} | null => {
let bottom = startBottom;
for (let row = startRow; row >= 0; row--) {
const height = _getRowHeight.call(this, row);
const top = bottom - height;
if (top <= absoluteY && absoluteY < bottom) {
return {
top,
row,
};
}
bottom = top;
}
return null;
};
const findAfter = (
startRow: number,
startBottom: number
): {
top: number;
row: number;
} | null => {
let top = startBottom - _getRowHeight.call(this, startRow);
const { rowCount } = this[_];
for (let row = startRow; row < rowCount; row++) {
const height = _getRowHeight.call(this, row);
const bottom = top + height;
if (top <= absoluteY && absoluteY < bottom) {
return {
top,
row,
};
}
top = bottom;
}
return null;
};
const candidateRow = Math.min(
Math.ceil(absoluteY / this[_].defaultRowHeight),
this.rowCount - 1
);
const bottom = _getRowsHeight.call(this, 0, candidateRow);
if (absoluteY >= bottom) {
return findAfter(candidateRow, bottom);
} else {
return findBefore(candidateRow, bottom);
}
}
/** @private */
function _getTargetColAt(
grid: DrawGrid,
absoluteX: number
): {
left: number;
col: number;
} | null {
let left = 0;
const { colCount } = grid[_];
for (let col = 0; col < colCount; col++) {
const width = _getColWidth(grid, col);
const right = left + width;
if (right > absoluteX) {
return {
left,
col,
};
}
left = right;
}
return null;
}
/** @private */
function _getTargetFrozenRowAt(
grid: DrawGrid,
absoluteY: number
): {
top: number;
row: number;
} | null {
if (!grid[_].frozenRowCount) {
return null;
}
let { top } = grid[_].scroll;
const rowCount = grid[_].frozenRowCount;
for (let row = 0; row < rowCount; row++) {
const height = _getRowHeight.call(grid, row);
const bottom = top + height;
if (bottom > absoluteY) {
return {
top,
row,
};
}
top = bottom;
}
return null;
}
/** @private */
function _getTargetFrozenColAt(
grid: DrawGrid,
absoluteX: number
): {
left: number;
col: number;
} | null {
if (!grid[_].frozenColCount) {
return null;
}
let { left } = grid[_].scroll;
const colCount = grid[_].frozenColCount;
for (let col = 0; col < colCount; col++) {
const width = _getColWidth(grid, col);
const right = left + width;
if (right > absoluteX) {
return {
left,
col,
};
}
left = right;
}
return null;
}
/** @private */
function _getFrozenRowsRect(grid: DrawGrid): Rect | null {
if (!grid[_].frozenRowCount) {
return null;
}
const { top } = grid[_].scroll;
let height = 0;
const rowCount = grid[_].frozenRowCount;
for (let row = 0; row < rowCount; row++) {
height += _getRowHeight.call(grid, row);
}
return new Rect(grid[_].scroll.left, top, grid[_].canvas.width, height);
}
/** @private */
function _getFrozenColsRect(grid: DrawGrid): Rect | null {
if (!grid[_].frozenColCount) {
return null;
}
const { left } = grid[_].scroll;
let width = 0;
const colCount = grid[_].frozenColCount;
for (let col = 0; col < colCount; col++) {
width += _getColWidth(grid, col);
}
return new Rect(left, grid[_].scroll.top, width, grid[_].canvas.height);
}
/** @private */
function _getCellDrawing(
grid: DrawGrid,
col: number,
row: number
): DrawCellContext | null {
if (!grid[_].drawCells[row]) {
return null;
}
return grid[_].drawCells[row][col];
}
/** @private */
function _putCellDrawing(
grid: DrawGrid,
col: number,
row: number,
context: DrawCellContext
): void {
if (!grid[_].drawCells[row]) {
grid[_].drawCells[row] = {};
}
grid[_].drawCells[row][col] = context;
}
/** @private */
function _removeCellDrawing(grid: DrawGrid, col: number, row: number): void {
if (!grid[_].drawCells[row]) {
return;
}
delete grid[_].drawCells[row][col];
if (Object.keys(grid[_].drawCells[row]).length === 0) {
delete grid[_].drawCells[row];
}
}
/** @private */
function _drawCell(
this: DrawGrid,
ctx: CanvasRenderingContext2D,
col: number,
absoluteLeft: number,
width: number,
row: number,
absoluteTop: number,
height: number,
visibleRect: Rect,
skipAbsoluteTop: number,
skipAbsoluteLeft: number,
drawLayers: DrawLayers
): void {
const rect = new Rect(
absoluteLeft - visibleRect.left,
absoluteTop - visibleRect.top,
width,
height
);
const drawRect = Rect.bounds(
Math.max(absoluteLeft, skipAbsoluteLeft) - visibleRect.left,
Math.max(absoluteTop, skipAbsoluteTop) - visibleRect.top,
rect.right,
rect.bottom
);
if (drawRect.height > 0 && drawRect.width > 0) {
ctx.save();
try {
const cellDrawing = _getCellDrawing(this, col, row);
if (cellDrawing) {
cellDrawing.cancel();
}
const dcContext = new DrawCellContext(
col,
row,
ctx,
rect,
drawRect,
!!cellDrawing,
this[_].selection,
drawLayers
);
const p = this.onDrawCell(col, row, dcContext);
if (isPromise(p)) {
//遅延描画
_putCellDrawing(this, col, row, dcContext);
const pCol = col;
dcContext._delayMode(this, () => {
_removeCellDrawing(this, pCol, row);
});
p.then(() => {
dcContext.terminate();
});
}
} finally {
ctx.restore();
}
}
}
/** @private */
function _drawRow(
grid: DrawGrid,
ctx: CanvasRenderingContext2D,
initFrozenCol: { left: number; col: number } | null,
initCol: { left: number; col: number },
drawRight: number,
row: number,
absoluteTop: number,
height: number,
visibleRect: Rect,
skipAbsoluteTop: number,
drawLayers: DrawLayers
): void {
const { colCount } = grid[_];
const drawOuter = (col: number, absoluteLeft: number): void => {
//データ範囲外の描画
if (
col >= colCount - 1 &&
grid[_].canvas.width > absoluteLeft - visibleRect.left
) {
const outerLeft = absoluteLeft - visibleRect.left;
ctx.save();
ctx.beginPath();
ctx.fillStyle = grid.underlayBackgroundColor || "#F6F6F6";
ctx.rect(
outerLeft,
absoluteTop - visibleRect.top,
grid[_].canvas.width - outerLeft,
height
);
ctx.fill();
ctx.restore();
}
};
let skipAbsoluteLeft = 0;
if (initFrozenCol) {
let absoluteLeft = initFrozenCol.left;
const count = grid[_].frozenColCount;
for (let { col } = initFrozenCol; col < count; col++) {
const width = _getColWidth(grid, col);
_drawCell.call(
grid,
ctx,
col,
absoluteLeft,
width,
row,
absoluteTop,
height,
visibleRect,
skipAbsoluteTop,
0,
drawLayers
);
absoluteLeft += width;
if (drawRight <= absoluteLeft) {
//描画範囲外(終了)
drawOuter(col, absoluteLeft);
return;
}
}
skipAbsoluteLeft = absoluteLeft;
}
let absoluteLeft = initCol.left;
for (let { col } = initCol; col < colCount; col++) {
const width = _getColWidth(grid, col);
_drawCell.call(
grid,
ctx,
col,
absoluteLeft,
width,
row,
absoluteTop,
height,
visibleRect,
skipAbsoluteTop,
skipAbsoluteLeft,
drawLayers
);
absoluteLeft += width;
if (drawRight <= absoluteLeft) {
//描画範囲外(終了)
drawOuter(col, absoluteLeft);
return;
}
}
drawOuter(colCount - 1, absoluteLeft);
}
/** @private */
function _getInitContext(this: DrawGrid): CanvasRenderingContext2D {
return this._getInitContext();
}
/** @private */
function _invalidateRect(grid: DrawGrid, drawRect: Rect): void {
const visibleRect = _getVisibleRect(grid);
const { rowCount } = grid[_];
const ctx = _getInitContext.call(grid);
const initRow = _getTargetRowAt.call(
grid,
Math.max(visibleRect.top, drawRect.top)
) || {
top: _getRowsHeight.call(grid, 0, rowCount - 1),
row: rowCount,
};
const initCol = _getTargetColAt(
grid,
Math.max(visibleRect.left, drawRect.left)
) || {
left: _getColsWidth(grid, 0, grid[_].colCount - 1),
col: grid[_].colCount,
};
const drawBottom = Math.min(visibleRect.bottom, drawRect.bottom);
const drawRight = Math.min(visibleRect.right, drawRect.right);
const initFrozenRow = _getTargetFrozenRowAt(
grid,
Math.max(visibleRect.top, drawRect.top)
);
const initFrozenCol = _getTargetFrozenColAt(
grid,
Math.max(visibleRect.left, drawRect.left)
);
const drawLayers = new DrawLayers();
const drawOuter = (row: number, absoluteTop: number): void => {
//データ範囲外の描画
if (
row >= rowCount - 1 &&
grid[_].canvas.height > absoluteTop - visibleRect.top
) {
const outerTop = absoluteTop - visibleRect.top;
ctx.save();
ctx.beginPath();
ctx.fillStyle = grid.underlayBackgroundColor || "#F6F6F6";
ctx.rect(
0,
outerTop,
grid[_].canvas.width,
grid[_].canvas.height - outerTop
);
ctx.fill();
ctx.restore();
}
};
let skipAbsoluteTop = 0;
if (initFrozenRow) {
let absoluteTop = initFrozenRow.top;
const count = grid[_].frozenRowCount;
for (let { row } = initFrozenRow; row < count; row++) {
const height = _getRowHeight.call(grid, row);
_drawRow(
grid,
ctx,
initFrozenCol,
initCol,
drawRight,
row,
absoluteTop,
height,
visibleRect,
0,
drawLayers
);
absoluteTop += height;
if (drawBottom <= absoluteTop) {
//描画範囲外(終了)
drawOuter(row, absoluteTop);
drawLayers.draw(ctx);
return;
}
}
skipAbsoluteTop = absoluteTop;
}
let absoluteTop = initRow.top;
for (let { row } = initRow; row < rowCount; row++) {
const height = _getRowHeight.call(grid, row);
//行の描画
_drawRow(
grid,
ctx,
initFrozenCol,
initCol,
drawRight,
row,
absoluteTop,
height,
visibleRect,
skipAbsoluteTop,
drawLayers
);
absoluteTop += height;
if (drawBottom <= absoluteTop) {
//描画範囲外(終了)
drawOuter(row, absoluteTop);
drawLayers.draw(ctx);
return;
}
}
drawOuter(rowCount - 1, absoluteTop);
drawLayers.draw(ctx);
}
/** @private */
function _toPxWidth(grid: DrawGrid, width: string | number): number {
return Math.round(calc.toPx(width, grid[_].calcWidthContext));
}
/** @private */
function _adjustColWidth(
grid: DrawGrid,
col: number,
orgWidth: number
): number {
const limits = _getColWidthLimits(grid, col);
return Math.max(_applyColWidthLimits(limits, orgWidth), 0);
}
/** @private */
function _applyColWidthLimits(
limits: { min?: number; max?: number } | void | null,
orgWidth: number
): number {
if (!limits) {
return orgWidth;
}
if (limits.min) {
if (limits.min > orgWidth) {
return limits.min;
}
}
if (limits.max) {
if (limits.max < orgWidth) {
return limits.max;
}
}
return orgWidth;
}
/**
* Gets the definition of the column width.
* @param {DrawGrid} grid grid instance
* @param {number} col number of column
* @returns {string|number} width definition
* @private
*/
function _getColWidthDefine(grid: DrawGrid, col: number): string | number {
const width = grid[_].colWidthsMap.get(col);
if (width) {
return width;
}
return grid.defaultColWidth;
}
/**
* Gets the column width limits.
* @param {DrawGrid} grid grid instance
* @param {number} col number of column
* @returns {object|null} the column width limits
* @private
*/
function _getColWidthLimits(
grid: DrawGrid,
col: number
):
| {
min?: undefined;
minDef?: undefined;
max?: undefined;
maxDef?: undefined;
}
| {
min: number;
minDef: string | number;
max?: undefined;
maxDef?: undefined;
}
| {
min?: undefined;
minDef?: undefined;
max: number;
maxDef: string | number;
}
| null {
const limit = grid[_].colWidthsLimit[col];
if (!limit) {
return null;
}
const result: {
min?: number;
max?: number;
minDef?: string | number;
maxDef?: string | number;
} = {};
if (limit.min) {
result.min = _toPxWidth(grid, limit.min);
result.minDef = limit.min;
}
if (limit.max) {
result.max = _toPxWidth(grid, limit.max);
result.maxDef = limit.max;
}
return result as never;
}
/**
* Checks if the given width definition is `auto`.
* @param {string|number} width width definition to check
* @returns {boolean} `true ` if the given width definition is `auto`
* @private
*/
function isAutoDefine(width: string | number): width is "auto" {
return Boolean(
width && typeof width === "string" && width.toLowerCase() === "auto"
);
}
/**
* Creates a formula to calculate the auto width.
* @param {DrawGrid} grid grid instance
* @returns {string} formula
* @private
*/
function _calcAutoColWidthExpr(grid: DrawGrid, shortCircuit = true): string {
const fullWidth = grid[_].calcWidthContext.full;
let sumMin = 0;
const others: (string | number)[] = [];
let autoCount = 0;
const hasLimitsOnAuto = [];
for (let col = 0; col < grid[_].colCount; col++) {
const def = _getColWidthDefine(grid, col);
const limits = _getColWidthLimits(grid, col);
if (isAutoDefine(def)) {
if (limits) {
hasLimitsOnAuto.push(limits);
if (limits.min) {
sumMin += limits.min;
}
}
autoCount++;
} else {
let expr = def;
if (limits) {
const orgWidth = _toPxWidth(grid, expr);
const newWidth = _applyColWidthLimits(limits, orgWidth);
if (orgWidth !== newWidth) {
expr = `${newWidth}px`;
}
sumMin += newWidth;
}
others.push(expr);
}
if (shortCircuit && sumMin > fullWidth) {
// Returns 0px because it has consumed the full width.
return "0px";
}
}
if (hasLimitsOnAuto.length && others.length) {
const autoPx =
(fullWidth -
_toPxWidth(
grid,
`calc(${others
.map((c) => (typeof c === "number" ? `${c}px` : c))
.join(" + ")})`
)) /
autoCount;
hasLimitsOnAuto.forEach((limits) => {
if (limits.min && autoPx < limits.min) {
others.push(limits.minDef);
autoCount--;
} else if (limits.max && limits.max < autoPx) {
others.push(limits.maxDef);
autoCount--;
}
});
if (shortCircuit && autoCount <= 0) {
return `${autoPx}px`;
}
}
if (others.length) {
const strDefs: string[] = [];
let num = 0;
others.forEach((c) => {
if (typeof c === "number") {
num += c;
} else {
strDefs.push(c);
}
});
strDefs.push(`${num}px`);
return `calc((100% - (${strDefs.join(" + ")})) / ${autoCount})`;
} else {
return `${100 / autoCount}%`;
}
}
/**
* Calculate the pixels of width from the definition of width.
* @param {DrawGrid} grid grid instance
* @param {string|number} width width definition
* @returns {number} the pixels of width
* @private
*/
function _colWidthDefineToPxWidth(
grid: DrawGrid,
width: string | number
): number {
if (isAutoDefine(width)) {
return _toPxWidth(grid, _calcAutoColWidthExpr(grid));
}
return _toPxWidth(grid, width);
}
/** @private */
function _getColWidth(grid: DrawGrid, col: number): number {
const width = _getColWidthDefine(grid, col);
return _adjustColWidth(grid, col, _colWidthDefineToPxWidth(grid, width));
}
/** @private */
function _setColWidth(
grid: DrawGrid,
col: number,
width: string | number | null
): void {
if (width != null) {
grid[_].colWidthsMap.put(col, width);
} else {
grid[_].colWidthsMap.remove(col);
}
}
/**
* Overwrites the definition of a column whose width is set to `auto` with the current auto width formula.
* @param {DrawGrid} grid grid instance
* @returns {void}
* @private
*/
function _storeAutoColWidthExprs(grid: DrawGrid): void {
let expr: string | null = null;
for (let col = 0; col < grid[_].colCount; col++) {
const def = _getColWidthDefine(grid, col);
if (isAutoDefine(def)) {
_setColWidth(
grid,
col,
expr || (expr = _calcAutoColWidthExpr(grid, false))
);
}
}
}
/** @private */
function _getColsWidth(
grid: DrawGrid,
startCol: number,
endCol: number
): number {
const defaultColPxWidth = _colWidthDefineToPxWidth(
grid,
grid.defaultColWidth
);
const colCount = endCol - startCol + 1;
let w = defaultColPxWidth * colCount;
grid[_].colWidthsMap.each(startCol, endCol, (width, col) => {
w +=
_adjustColWidth(grid, col, _colWidthDefineToPxWidth(grid, width)) -
defaultColPxWidth;
});
for (let col = startCol; col <= endCol; col++) {
if (grid[_].colWidthsMap.has(col)) {
continue;
}
const adj = _adjustColWidth(grid, col, defaultColPxWidth);
if (adj !== defaultColPxWidth) {
w += adj - defaultColPxWidth;
}
}
return w;
}
/** @private */
function _getRowHeight(this: DrawGrid, row: number): number {
const internal = this.getRowHeightInternal(row);
if (internal != null) {
return internal;
}
const height = this[_].rowHeightsMap.get(row);
if (height) {
return height;
}
return this[_].defaultRowHeight;
}
/** @private */
function _setRowHeight(
grid: DrawGrid,
row: number,
height: number | null
): void {
if (height != null) {
grid[_].rowHeightsMap.put(row, height);
} else {
grid[_].rowHeightsMap.remove(row);
}
}
/** @private */
function _getRowsHeight(
this: DrawGrid,
startRow: number,
endRow: number
): number {
const internal = this.getRowsHeightInternal(startRow, endRow);
if (internal != null) {
return internal;
}
const rowCount = endRow - startRow + 1;
let h = this[_].defaultRowHeight * rowCount;
this[_].rowHeightsMap.each(startRow, endRow, (height: number): void => {
h += height - this[_].defaultRowHeight;
});
return h;
}
/** @private */
function _getScrollWidth(grid: DrawGrid): number {
return _getColsWidth(grid, 0, grid[_].colCount - 1);
}
/** @private */
function _getScrollHeight(this: DrawGrid, row?: number): number {
const internal = this.getScrollHeightInternal(row);
if (internal != null) {
return internal;
}
let h = this[_].defaultRowHeight * this[_].rowCount;
this[_].rowHeightsMap.each(0, this[_].rowCount - 1, (height) => {
h += height - this[_].defaultRowHeight;
});
return h;
}
/** @private */
function _onScroll(grid: DrawGrid, _e: Event): void {
const lastLeft = grid[_].scroll.left;
const lastTop = grid[_].scroll.top;
const moveX = grid[_].scrollable.scrollLeft - lastLeft;
const moveY = grid[_].scrollable.scrollTop - lastTop;
//次回計算用情報を保持
grid[_].scroll = {
left: grid[_].scrollable.scrollLeft,
top: grid[_].scrollable.scrollTop,
};
// If the focus is on the header, recalculate and move the focus position.
const { focus } = grid[_].selection;
const isFrozenCell = grid.isFrozenCell(focus.col, focus.row);
if (
isFrozenCell &&
((isFrozenCell?.col && moveX) || (isFrozenCell?.row && moveY))
) {
grid.setFocusCursor(focus.col, focus.row);
}
const visibleRect = _getVisibleRect(grid);
if (
Math.abs(moveX) >= visibleRect.width ||
Math.abs(moveY) >= visibleRect.height
) {
//全再描画
_invalidateRect(grid, visibleRect);
} else {
//差分再描画
grid[_].context.drawImage(grid[_].canvas, -moveX, -moveY);
if (moveX !== 0) {
//横移動の再描画範囲を計算
const redrawRect = visibleRect.copy();
if (moveX < 0) {
redrawRect.width = -moveX;
if (grid[_].frozenColCount > 0) {
//固定列がある場合固定列分描画
const frozenRect = _getFrozenColsRect(grid)!;
redrawRect.width += frozenRect.width;
}
} else if (moveX > 0) {
redrawRect.left = redrawRect.right - moveX;
}
//再描画
_invalidateRect(grid, redrawRect);
if (moveX > 0) {
if (grid[_].frozenColCount > 0) {
//固定列がある場合固定列描画
_invalidateRect(grid, _getFrozenColsRect(grid)!);
}
}
}
if (moveY !== 0) {
//縦移動の再描画範囲を計算
const redrawRect = visibleRect.copy();
if (moveY < 0) {
redrawRect.height = -moveY;
if (grid[_].frozenRowCount > 0) {
//固定行がある場合固定行分描画
const frozenRect = _getFrozenRowsRect(grid)!;
redrawRect.height += frozenRect.height;
}
} else if (moveY > 0) {
redrawRect.top = redrawRect.bottom - moveY;
}
//再描画
_invalidateRect(grid, redrawRect);
if (moveY > 0) {
if (grid[_].frozenRowCount > 0) {
//固定行がある場合固定行描画
_invalidateRect(grid, _getFrozenRowsRect(grid)!);
}
}
}
}
}
/** @private */
// eslint-disable-next-line complexity
function _onKeyDownMove(this: DrawGrid, e: KeyboardEvent): void {
const keyCode = getKeyCode(e);
const focusCell = e.shiftKey ? this.selection.focus : this.selection.select;
const ctrlOrMeta = e.ctrlKey || e.metaKey;
if (keyCode === KEY_LEFT) {
if (e.altKey) return; // unknown modifier key
if (ctrlOrMeta) {
move(this, null, "W", e.shiftKey);
} else {
if (!hMove.call(this, "W", e.shiftKey)) {
return;
}
}
cancelEvent(e);
} else if (keyCode === KEY_UP) {
if (e.altKey) return; // unknown modifier key
if (ctrlOrMeta) {
move(this, "N", null, e.shiftKey);
} else {
if (!vMove.call(this, "N", e.shiftKey)) {
return;
}
}
cancelEvent(e);
} else if (keyCode === KEY_RIGHT) {
if (e.altKey) return; // unknown modifier key
if (ctrlOrMeta) {
move(this, null, "E", e.shiftKey);
} else {
if (!hMove.call(this, "E", e.shiftKey)) {
return;
}
}
cancelEvent(e);
} else if (keyCode === KEY_DOWN) {
if (e.altKey) return; // unknown modifier key
if (ctrlOrMeta) {
move(this, "S", null, e.shiftKey);
} else {
if (!vMove.call(this, "S", e.shiftKey)) {
return;
}
}
cancelEvent(e);
} else if (keyCode === KEY_HOME) {
if (e.altKey) return; // unknown modifier key
if (ctrlOrMeta) {
move(this, "N", "W", e.shiftKey);
} else {
move(this, null, "W", e.shiftKey);
}
cancelEvent(e);
} else if (keyCode === KEY_END) {
if (e.altKey) return; // unknown modifier key
if (ctrlOrMeta) {
move(this, "S", "E", e.shiftKey);
} else {
move(this, null, "E", e.shiftKey);
}
cancelEvent(e);
} else if (this.keyboardOptions?.moveCellOnTab && keyCode === KEY_TAB) {
if (e.altKey || ctrlOrMeta) return; // unknown modifier key
let newCell: CellAddress | null = null;
if (typeof this.keyboardOptions.moveCellOnTab === "function") {
newCell = this.keyboardOptions.moveCellOnTab({
cell: focusCell,
grid: this,
event: e,
});
}
if (newCell) {
_moveFocusCell.call(this, newCell.col, newCell.row, false);
} else if (e.shiftKey) {
if (!hMove.call(this, "W", false)) {
const row = this.getMoveUpRowByKeyDownInternal(focusCell);
if (0 > row) {
return;
}
_moveFocusCell.call(this, this.colCount - 1, row, false);
}
} else {
if (!hMove.call(this, "E", false)) {
const row = this.getMoveDownRowByKeyDownInternal(focusCell);
if (this.rowCount <= row) {
return;
}
_moveFocusCell.call(this, 0, row, false);
}
}
cancelEvent(e);
} else if (this.keyboardOptions?.moveCellOnEnter && keyCode === KEY_ENTER) {
if (e.altKey || ctrlOrMeta) return; // unknown modifier key
let newCell: CellAddress | null = null;
if (typeof this.keyboardOptions.moveCellOnEnter === "function") {
newCell = this.keyboardOptions.moveCellOnEnter({
cell: focusCell,
grid: this,
event: e,
});
}
if (newCell) {
_moveFocusCell.call(this, newCell.col, newCell.row, false);
} else if (e.shiftKey) {
if (!vMove.call(this, "N", false)) {
const col = this.getMoveLeftColByKeyDownInternal(focusCell);
if (0 > col) {
return;
}
_moveFocusCell.call(this, col, this.rowCount - 1, false);
}
} else {
if (!vMove.call(this, "S", false)) {
const col = this.getMoveRightColByKeyDownInternal(focusCell);
if (this.colCount <= col) {
return;
}
_moveFocusCell.call(
this,
col,
Math.min(this.frozenRowCount, this.rowCount - 1),
false
);
}
}
cancelEvent(e);
} else if (
this.keyboardOptions?.selectAllOnCtrlA &&
keyCode === KEY_ALPHA_A
) {
if (e.altKey || e.shiftKey) return; // unknown modifier key
if (!ctrlOrMeta) return;
this.selection.range = {
start: { col: 0, row: 0 },
end: { col: this.colCount - 1, row: this.rowCount - 1 },
};
this.invalidate();
cancelEvent(e);
}
function move(
grid: DrawGrid,
vDir: "N" | "S" | null,
hDir: "W" | "E" | null,
shiftKeyFlg: boolean
): void {
const row =
vDir === "S" ? grid.rowCount - 1 : vDir === "N" ? 0 : focusCell.row;
const col =
hDir === "E" ? grid.colCount - 1 : hDir === "W" ? 0 : focusCell.col;
_moveFocusCell.call(grid, col, row, shiftKeyFlg);
}
function vMove(
this: DrawGrid,
vDir: "N" | "S",
shiftKeyFlg: boolean
): boolean {
const { col } = focusCell;
let row: number;
if (vDir === "S") {
row = this.getMoveDownRowByKeyDownInternal(focusCell);
if (this.rowCount <= row) {
// Avoids the problem of the scroll position breaking due to a delayed scrolling event if user hold down the arrow keys.
this.makeVisibleCell(col, this.rowCount - 1);
return false;
}
} else {
row = this.getMoveUpRowByKeyDownInternal(focusCell);
if (row < 0) {
// Avoids the problem of the scroll position breaking due to a delayed scrolling event if user hold down the arrow keys.
this.makeVisibleCell(col, 0);
return false;
}
}
_moveFocusCell.call(this, col, row, shiftKeyFlg);
return true;
}
function hMove(
this: DrawGrid,
hDir: "W" | "E",
shiftKeyFlg: boolean
): boolean {
const { row } = focusCell;
let col: number;
if (hDir === "E") {
col = this.getMoveRightColByKeyDownInternal(focusCell);
if (this.colCount <= col) {
// Avoids the problem of the scroll position breaking due to a delayed scrolling event if user hold down the arrow keys.
this.makeVisibleCell(this.colCount - 1, row);
return false;
}
} else {
col = this.getMoveLeftColByKeyDownInternal(focusCell);
if (col < 0) {
// Avoids the problem of the scroll position breaking due to a delayed scrolling event if user hold down the arrow keys.
this.makeVisibleCell(0, row);
return false;
}
}
_moveFocusCell.call(this, col, row, shiftKeyFlg);
return true;
}
}
/** @private */
function _moveFocusCell(
this: DrawGrid,
col: number,
row: number,
shiftKey: boolean
): void {
const offset = this.getOffsetInvalidateCells();
function extendRange(range: CellRange): CellRange {
if (offset > 0) {
range.start.col -= offset;
range.start.row -= offset;
range.end.col += offset;
range.end.row += offset;
}
return range;
}
const beforeRange = extendRange(this.selection.range);
const beforeRect = this.getCellRangeRect(beforeRange);
this.selection._setFocusCell(col, row, shiftKey);
this.makeVisibleCell(col, row);
this.focusCell(col, row);
const afterRange = extendRange(this.selection.range);
const afterRect = this.getCellRangeRect(afterRange);
if (afterRect.intersection(beforeRect)) {
const invalidateRect = Rect.max(afterRect, beforeRect);
_invalidateRect(this, invalidateRect);
} else {
_invalidateRect(this, beforeRect);
_invalidateRect(this, afterRect);
}
}
/** @private */
function _updatedSelection(this: DrawGrid): void {
const { focusControl } = this[_];
const { col: selCol, row: selRow } = this[_].selection.select;
const results = this.fireListeners(DG_EVENT_TYPE.EDITABLEINPUT_CELL, {
col: selCol,
row: selRow,
});
const editMode = array.findIndex(results, (v) => !!v) >= 0;
focusControl.editMode = editMode;
if (editMode) {
focusControl.storeInputStatus();
focusControl.setDefaultInputStatus();
this.fireListeners(DG_EVENT_TYPE.MODIFY_STATUS_EDITABLEINPUT_CELL, {
col: selCol,
row: selRow,
input: focusControl.input,
});
}
}
/** @private */
function _getMouseAbstractPoint(
grid: DrawGrid,
evt: TouchEvent | MouseEvent
): { x: number; y: number } | null {
let e: MouseEvent | Touch;
if (isTouchEvent(evt)) {
e = evt.changedTouches[0];
} else {
e = evt;
}
const clientX = e.clientX || e.pageX + window.scrollX;
const clientY = e.clientY || e.pageY + window.scrollY;
const rect = grid[_].canvas.getBoundingClientRect();
if (rect.right <= clientX) {
return null;
}
if (rect.bottom <= clientY) {
return null;
}
const x = clientX - rect.left + grid[_].scroll.left;
const y = clientY - rect.top + grid[_].scroll.top;
return { x, y };
}
/** @private */
function _bindEvents(this: DrawGrid): void {
// eslint-disable-next-line consistent-this, @typescript-eslint/no-this-alias
const grid = this;
const { handler, element, scrollable } = grid[_];
const getCellEventArgsSet = <EVT extends TouchEvent | MouseEvent>(
e: EVT
): {
abstractPos?: { x: number; y: number };
cell?: CellAddress;
eventArgs?: CellAddress & { event: EVT };
} => {
const abstractPos = _getMouseAbstractPoint(grid, e);
if (!abstractPos) {
return {};
}
const cell = grid.getCellAt(abstractPos.x, abstractPos.y);
if (cell.col < 0 || cell.row < 0) {
return {
abstractPos,
cell,
};
}
const eventArgs = {
col: cell.col,
row: cell.row,
event: e,
};
return {
abstractPos,
cell,
eventArgs,
};
};
const canResizeColumn = (col: number): boolean => {
if (grid[_].disableColumnResize) {
return false;
}
const limit = grid[_].colWidthsLimit[col];
if (!limit || !limit.min || !limit.max) {
return true;
}
return limit.max !== limit.min;
};
handler.on(element, "mousedown", (e) => {
const eventArgsSet = getCellEventArgsSet(e);
const { abstractPos, eventArgs } = eventArgsSet;
if (!abstractPos) {
return;
}
if (eventArgs) {
const results = grid.fireListeners(
DG_EVENT_TYPE.MOUSEDOWN_CELL,
eventArgs
);
if (array.findIndex(results, (v) => !v) >= 0) {
return;
}
}
if (
getMouseButtons(e) !== 1 &&
// For mobile safari. If we do not post-process here, the keyboard will not start in Mobile Safari.
e.buttons !== 0
) {
return;
}
const resizeCol = _getResizeColAt(grid, abstractPos.x, abstractPos.y);
if (resizeCol >= 0 && canResizeColumn(resizeCol)) {
//幅変更
grid[_].columnResizer.start(resizeCol, e);
} else {
//選択
grid[_].cellSelector.start(e);
}
});
handler.on(element, "mouseup", (e) => {
if (!grid.hasListeners(DG_EVENT_TYPE.MOUSEUP_CELL)) {
return;
}
const { eventArgs } = getCellEventArgsSet(e);
if (eventArgs) {
grid.fireListeners(DG_EVENT_TYPE.MOUSEUP_CELL, eventArgs);
}
});
let doubleTapBefore:
| (CellAddress & { event: TouchEvent | MouseEvent })
| null
| undefined = null;
let longTouchId: NodeJS.Timeout | null = null;
let useTouch: { timeoutId?: NodeJS.Timeout } | null = null;
function useTouchStart() {
if (useTouch?.timeoutId != null) clearTimeout(useTouch.timeoutId);
useTouch = {};
}
function useTouchEnd() {
if (useTouch) {
if (useTouch.timeoutId != null) clearTimeout(useTouch.timeoutId);
useTouch.timeoutId = setTimeout(() => {
useTouch = null;
}, 400);
}
}
handler.on(element, "touchstart", (e) => {
// Since it is an environment where touch start can be used, it blocks mousemove that occurs after this.
useTouchStart();
const { eventArgs } = getCellEventArgsSet(e);
if (eventArgs) {
grid.fireListeners(DG_EVENT_TYPE.TOUCHSTART_CELL, eventArgs);
}
if (!doubleTapBefore) {
doubleTapBefore = eventArgs;
setTimeout(() => {
doubleTapBefore = null;
}, 350);
} else {
if (
eventArgs &&
eventArgs.col === doubleTapBefore.col &&
eventArgs.row === doubleTapBefore.row
) {
grid.fireListeners(DG_EVENT_TYPE.DBLTAP_CELL, eventArgs);
}
doubleTapBefore = null;
if (e.defaultPrevented) {
return;
}
}
if (e.targetTouches.length > 1) {
// If touchstart with multiple fingers,
// it is not considered as an operation event.
return;
}
longTouchId = setTimeout(() => {
//長押しした場合選択モード
longTouchId = null;
const abstractPos = _getMouseAbstractPoint(grid, e);
if (!abstractPos) {
return;
}
const resizeCol = _getResizeColAt(grid, abstractPos.x, abstractPos.y, 15);
if (resizeCol >= 0 && canResizeColumn(resizeCol)) {
//幅変更
grid[_].columnResizer.start(resizeCol, e);
} else {
//選択
grid[_].cellSelector.start(e);
}
}, 500);
});
function cancel(_e: Event): void {
if (longTouchId) {
clearTimeout(longTouchId);
longTouchId = null;
}
}
handler.on(element, "touchcancel", (e) => {
cancel(e);
useTouchEnd();
});
handler.on(element, "touchmove", cancel);
handler.on(element, "touchend", (e) => {
useTouchEnd();
if (longTouchId) {
clearTimeout(longTouchId);
grid[_].cellSelector.select(e);
longTouchId = null;
}
});
let isMouseover = false;
let mouseEnterCell: CellAddress | null = null;
let mouseOverCell: CellAddress | null = null;
type MousePointerCellEventInfoProps = Pick<
MousePointerCellEvent,
"related" | "event"
>;
function onMouseenterCell(
cell: CellAddress,
props: MousePointerCellEventInfoProps
): void {
grid.fireListeners(DG_EVENT_TYPE.MOUSEENTER_CELL, {
...props,
col: cell.col,
row: cell.row,
});
mouseEnterCell = cell;
}
function onMouseleaveCell(
props: MousePointerCellEventInfoProps
): CellAddress | undefined {
const beforeMouseCell = mouseEnterCell;
mouseEnterCell = null;
if (beforeMouseCell) {
grid.fireListeners(DG_EVENT_TYPE.MOUSELEAVE_CELL, {
...props,
col: beforeMouseCell.col,
row: beforeMouseCell.row,
});
}
return beforeMouseCell || undefined;
}
function onMouseoverCell(
cell: CellAddress,
props: MousePointerCellEventInfoProps
): void {
grid.fireListeners(DG_EVENT_TYPE.MOUSEOVER_CELL, {
...props,
col: cell.col,
row: cell.row,
});
mouseOverCell = cell;
}
function onMouseoutCell(
props: MousePointerCellEventInfoProps
): CellAddress | undefined {
const beforeMouseCell = mouseOverCell;
mouseOverCell = null;
if (beforeMouseCell) {
grid.fireListeners(DG_EVENT_TYPE.MOUSEOUT_CELL, {
...props,
col: beforeMouseCell.col,
row: beforeMouseCell.row,
});
}
return beforeMouseCell || undefined;
}
const scrollElement = scrollable.getElement();
handler.on(scrollElement, "mouseover", (_e: MouseEvent): void => {
isMouseover = true;
});
handler.on(scrollElement, "mouseout", (event: MouseEvent): void => {
isMouseover = false;
onMouseoutCell({ event });
});
handler.on(element, "mouseleave", (event: MouseEvent): void => {
onMouseleaveCell({ event });
});
handler.on(element, "mousemove", (e) => {
if (useTouch) {
// Probably a mousemove event triggered by a touchstart. Therefore, this event is blocked.
return;
}
const eventArgsSet = getCellEventArgsSet(e);
const { abstractPos, eventArgs } = eventArgsSet;
if (eventArgs) {
const beforeMouseCell = mouseEnterCell;
if (beforeMouseCell) {
grid.fireListeners(DG_EVENT_TYPE.MOUSEMOVE_CELL, eventArgs);
if (
beforeMouseCell.col !== eventArgs.col ||
beforeMouseCell.row !== eventArgs.row
) {
const enterCell = {
col: eventArgs.col,
row: eventArgs.row,
};
const outCell = onMouseoutCell({ related: enterCell, event: e });
const leaveCell = onMouseleaveCell({ related: enterCell, event: e });
onMouseenterCell(enterCell, { related: leaveCell, event: e });
if (isMouseover) {
onMouseoverCell(enterCell, { related: outCell, event: e });
}
} else if (isMouseover && !mouseOverCell) {
onMouseoverCell(
{
col: eventArgs.col,
row: eventArgs.row,
},
{
event: e,
}
);
}
} else {
const enterCell = {
col: eventArgs.col,
row: eventArgs.row,
};
onMouseenterCell(enterCell, {
event: e,
});
if (isMouseover) {
onMouseoverCell(enterCell, {
event: e,
});
}
grid.fireListeners(DG_EVENT_TYPE.MOUSEMOVE_CELL, eventArgs);
}
} else {
onMouseoutCell({
event: e,
});
onMouseleaveCell({
event: e,
});
}
if (grid[_].columnResizer.moving(e) || grid[_].cellSelector.moving(e)) {
return;
}
const { style } = element;
if (!abstractPos) {
if (style.cursor === "col-resize") {
style.cursor = "";
}
return;
}
const resizeCol = _getResizeColAt(grid, abstractPos.x, abstractPos.y);
if (resizeCol >= 0 && canResizeColumn(resizeCol)) {
style.cursor = "col-resize";
} else {
if (style.cursor === "col-resize") {
style.cursor = "";
}
}
});
handler.on(element, "click", (e) => {
if (
grid[_].columnResizer.lastMoving(e) ||
grid[_].cellSelector.lastMoving(e)
) {
return;
}
if (!grid.hasListeners(DG_EVENT_TYPE.CLICK_CELL)) {
return;
}
const { eventArgs } = getCellEventArgsSet(e);
if (!eventArgs) {
return;
}
grid.fireListeners(DG_EVENT_TYPE.CLICK_CELL, eventArgs);
});
handler.on(element, "contextmenu", (e) => {
if (!grid.hasListeners(DG_EVENT_TYPE.CONTEXTMENU_CELL)) {
return;
}
const { eventArgs } = getCellEventArgsSet(e);
if (!eventArgs) {
return;
}
grid.fireListeners(DG_EVENT_TYPE.CONTEXTMENU_CELL, eventArgs);
});
handler.on(element, "dblclick", (e) => {
if (!grid.hasListeners(DG_EVENT_TYPE.DBLCLICK_CELL)) {
return;
}
const { eventArgs } = getCellEventArgsSet(e);
if (!eventArgs) {
return;
}
grid.fireListeners(DG_EVENT_TYPE.DBLCLICK_CELL, eventArgs);
});
grid[_].focusControl.onKeyDown((evt: KeydownEvent) => {
grid.fireListeners(DG_EVENT_TYPE.KEYDOWN, evt);
});
grid[_].selection.listen(DG_EVENT_TYPE.SELECTED_CELL, (data) => {
grid.fireListeners(DG_EVENT_TYPE.SELECTED_CELL, data, data.selected);
});
scrollable.onScroll((e) => {
_onScroll(grid, e);
grid.fireListeners(DG_EVENT_TYPE.SCROLL, { event: e });
});
grid[_].focusControl.onKeyDownMove((e) => {
_onKeyDownMove.call(grid, e);
});
grid.listen("copydata", (range) => {
const copyRange = grid.getCopyRangeInternal(range);
const copyLines: string[] = [];
for (let { row } = copyRange.start; row <= copyRange.end.row; row++) {
let copyLine = "";
for (let { col } = copyRange.start; col <= copyRange.end.col; col++) {
const copyCellValue = grid.getCopyCellValue(col, row, copyRange);
let strCellValue: string;
if (typeof copyCellValue === "string") {
strCellValue = copyCellValue;
} else if (
copyCellValue == null ||
// Asynchronous data is treated as empty.
(typeof Promise !== "undefined" && copyCellValue instanceof Promise)
) {
strCellValue = "";
} else {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
strCellValue = `${copyCellValue}`;
if (/^\[object .*\]$/.exec(strCellValue)) {
// Ignore maybe object
strCellValue = "";
}
}
copyLine += /[\t\n]/.test(strCellValue)
? // Need quote
`"${strCellValue.replace(/"/g, '""')}"`
: strCellValue;
if (col < copyRange.end.col) {
copyLine += "\t";
}
}
copyLines.push(copyLine);
}
return copyLines.join("\n");
});
grid[_].focusControl.onCopy((_e: ClipboardEvent): string | void =>
array.find(
grid.fireListeners("copydata", grid[_].selection.range),
(r) => r != null
)
);
grid[_].focusControl.onPaste(
({ value, event }: { value: string; event: ClipboardEvent }) => {
const { trimOnPaste } = grid;
const normalizedValue = normalizePasteValue(value);
const { col, row } = grid[_].selection.select;
const multi = /[\r\n\u2028\u2029\t]/.test(normalizedValue); // is multi cell values
let rangeBoxValues: PasteRangeBoxValues | null = null;
const pasteCellEvent: PasteCellEvent = {
col,
row,
value,
normalizeValue: trimOnPaste ? normalizedValue.trim() : normalizedValue,
multi,
get rangeBoxValues(): PasteRangeBoxValues {
return (
rangeBoxValues ??
(rangeBoxValues = parsePasteRangeBoxValues(normalizedValue, {
trimOnPaste,
}))
);
},
event,
};
grid.fireListeners(DG_EVENT_TYPE.PASTE_CELL, pasteCellEvent);
}
);
grid[_].focusControl.onInput((value) => {
const { col, row } = grid[_].selection.select;
grid.fireListeners(DG_EVENT_TYPE.INPUT_CELL, { col, row, value });
});
grid[_].focusControl.onDelete((event) => {
const { col, row } = grid[_].selection.select;
grid.fireListeners(DG_EVENT_TYPE.DELETE_CELL, { col, row, event });
});
grid[_].focusControl.onFocus((e: FocusEvent) => {
grid.fireListeners(DG_EVENT_TYPE.FOCUS_GRID, e);
grid[_].focusedGrid = true;
const { col, row } = grid[_].selection.select;
grid.invalidateCell(col, row);
});
grid[_].focusControl.onBlur((e) => {
grid.fireListeners(DG_EVENT_TYPE.BLUR_GRID, e);
grid[_].focusedGrid = false;
const { col, row } = grid[_].selection.select;
grid.invalidateCell(col, row);
});
}
/** @private */
function _getResizeColAt(
grid: DrawGrid,
abstractX: number,
abstractY: number,
offset = 5
): number {
if (grid[_].frozenRowCount <= 0) {
return -1;
}
const frozenRect = _getFrozenRowsRect(grid)!;
if (!frozenRect.inPoint(abstractX, abstractY)) {
return -1;
}
const cell = grid.getCellAt(abstractX, abstractY);
const cellRect = grid.getCellRect(cell.col, cell.row);
if (abstractX < cellRect.left + offset) {
return cell.col - 1;
}
if (cellRect.right - offset < abstractX) {
return cell.col;
}
return -1;
}
/** @private */
function _getVisibleRect(grid: DrawGrid): Rect {
const {
scroll: { left, top },
canvas: { width, height },
} = grid[_];
return new Rect(left, top, width, height);
}
/** @private */
function _getScrollableVisibleRect(grid: DrawGrid): Rect {
let frozenColsWidth = 0;
if (grid[_].frozenColCount > 0) {
//固定列がある場合固定列分描画
const frozenRect = _getFrozenColsRect(grid)!;
frozenColsWidth = frozenRect.width;
}
let frozenRowsHeight = 0;
if (grid[_].frozenRowCount > 0) {
//固定列がある場合固定列分描画
const frozenRect = _getFrozenRowsRect(grid)!;
frozenRowsHeight = frozenRect.height;
}
return new Rect(
grid[_].scrollable.scrollLeft + frozenColsWidth,
grid[_].scrollable.scrollTop + frozenRowsHeight,
grid[_].canvas.width - frozenColsWidth,
grid[_].canvas.height - frozenRowsHeight
);
}
/** @private */
function _toRelativeRect(grid: DrawGrid, absoluteRect: Rect): Rect {
const rect = absoluteRect.copy();
const visibleRect = _getVisibleRect(grid);
rect.offsetLeft(-visibleRect.left);
rect.offsetTop(-visibleRect.top);
return rect;
}
//end private methods
//
//
//
//
/**
* managing mouse down moving
* @private
*/
class BaseMouseDownMover {
protected _grid: DrawGrid;
private _handler: EventHandler;
private _events: {
mousemove?: EventListenerId;
mouseup?: EventListenerId;
touchmove?: EventListenerId;
touchend?: EventListenerId;
touchcancel?: EventListenerId;
};
private _started: boolean;
private _moved: boolean;
private _mouseEndPoint?: { x: number; y: number } | null;
constructor(grid: DrawGrid) {
this._grid = grid;
this._handler = new EventHandler();
this._events = {};
this._started = false;
this._moved = false;
}
moving(_e: MouseEvent | TouchEvent): boolean {
return !!this._started;
}
lastMoving(e: MouseEvent | TouchEvent): boolean {
// mouseup後すぐに、clickイベントを反応しないようにする制御要
if (this.moving(e)) {
return true;
}
const last = this._mouseEndPoint;
if (!last) {
return false;
}
const pt = _getMouseAbstractPoint(this._grid, e);
return pt != null && pt.x === last.x && pt.y === last.y;
}
protected _bindMoveAndUp(e: MouseEvent | TouchEvent): void {
const events = this._events;
const handler = this._handler;
if (!isTouchEvent(e)) {
events.mousemove = handler.on(document.body, "mousemove", (e) =>
this._mouseMove(e)
);
events.mouseup = handler.on(document.body, "mouseup", (e) =>
this._mouseUp(e)
);
} else {
events.touchmove = handler.on(
document.body,
"touchmove",
(e) => this._mouseMove(e),
{ passive: false }
);
events.touchend = handler.on(document.body, "touchend", (e) =>
this._mouseUp(e)
);
events.touchcancel = handler.on(document.body, "touchcancel", (e) =>
this._mouseUp(e)
);
}
this._started = true;
this._moved = false;
}
private _mouseMove(e: MouseEvent | TouchEvent): void {
if (!isTouchEvent(e)) {
if (getMouseButtons(e) !== 1) {
this._mouseUp(e);
return;
}
}
this._moved = this._moveInternal(e) || this._moved /*calculation on after*/;
cancelEvent(e);
}
protected _moveInternal(_e: MouseEvent | TouchEvent): boolean {
//protected
return false;
}
private _mouseUp(e: MouseEvent | TouchEvent): void {
const events = this._events;
const handler =