@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
176 lines (171 loc) • 8.48 kB
JavaScript
import { closestElement, containsClassName } from '@atlaskit/editor-common/utils';
import { TableCssClassName as ClassName } from '../../types';
export const isCell = node => {
return Boolean(node && (['TH', 'TD'].indexOf(node.tagName) > -1 || !!closestElement(node, `.${ClassName.TABLE_HEADER_CELL}`) || !!closestElement(node, `.${ClassName.TABLE_CELL}`)));
};
export const isCornerButton = node => containsClassName(node, ClassName.CONTROLS_CORNER_BUTTON);
export const isInsertRowButton = node => containsClassName(node, ClassName.CONTROLS_INSERT_ROW) || closestElement(node, `.${ClassName.CONTROLS_INSERT_ROW}`) || containsClassName(node, ClassName.CONTROLS_BUTTON_OVERLAY) && closestElement(node, `.${ClassName.ROW_CONTROLS}`);
export const getColumnOrRowIndex = target => [parseInt(target.getAttribute('data-start-index') || '-1', 10), parseInt(target.getAttribute('data-end-index') || '-1', 10)];
export const isColumnControlsDecorations = node => containsClassName(node, ClassName.COLUMN_CONTROLS_DECORATIONS);
export const isRowControlsButton = node => containsClassName(node, ClassName.ROW_CONTROLS_BUTTON) || containsClassName(node, ClassName.NUMBERED_COLUMN_BUTTON);
export const isResizeHandleDecoration = node => containsClassName(node, ClassName.RESIZE_HANDLE_DECORATION);
export const isTableControlsButton = node => containsClassName(node, ClassName.CONTROLS_BUTTON) || containsClassName(node, ClassName.ROW_CONTROLS_BUTTON_WRAP);
export const isTableContainerOrWrapper = node => containsClassName(node, ClassName.TABLE_CONTAINER) || containsClassName(node, ClassName.TABLE_NODE_WRAPPER);
/** drag-and-drop classes */
export const isDragRowFloatingInsertDot = node => containsClassName(node, ClassName.DRAG_ROW_FLOATING_INSERT_DOT_WRAPPER);
export const isDragColumnFloatingInsertDot = node => containsClassName(node, ClassName.DRAG_COLUMN_FLOATING_INSERT_DOT_WRAPPER);
export const isDragCornerButton = node => containsClassName(node, ClassName.DRAG_CORNER_BUTTON) || containsClassName(node, ClassName.DRAG_CORNER_BUTTON_INNER);
/*
* This function returns which side of a given element the mouse cursor is,
* using as a base the half of the width by default, for example:
*
* legend
* ⌖ = mouse pointer
* ▒ = gap
*
* given this box:
* ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
* ┃ ┊ ┃
* ┃ left ┊ right ┃
* ┃ ┊ ┃
* ┗━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━┛
*
* if the mouse is on the left, it will return `left`,
* if it is on the right it will return `right`.
*
* You can extend this behavior using the parameter `gapInPixels`
* to determinate if the mouse is inside of a gap for each side,
* for example:
*
* given `gapInPixels` is `5`
* and given this box:
* ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
* ┃▒▒▒▒▒ ┊ ▒▒▒▒▒┃
* ┃▒▒▒▒▒ left ┊ right ▒▒▒▒▒┃
* ┃▒▒▒▒▒ ┊ ▒▒▒▒▒┃
* ┗━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━┛
*
* if the mouse cursor is inside of the gap like that:
*
* ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
* ┃▒▒▒▒▒ ┊ ▒▒▒▒▒┃
* ┃▒▒⌖▒▒ left ┊ right ▒▒▒▒▒┃
* ┃▒▒▒▒▒ ┊ ▒▒▒▒▒┃
* ┗━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━┛
*
* the function will return `left` because the mouse is inside of the gap on the left side.
*
* if the mouse cursor is outside of the gap like that:
*
* ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
* ┃▒▒▒▒▒ ┊ ▒▒▒▒▒┃
* ┃▒▒▒▒▒ left ⌖ ┊ right ▒▒▒▒▒┃
* ┃▒▒▒▒▒ ┊ ▒▒▒▒▒┃
* ┗━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━┛
*
* the function will return `null` because the mouse is inside of left
* but is outside of the gap.
*
* the same is valid to the right side.
*/
/**
* This can be used with mouse events to determine the left/right side of the target the pointer is closest too.
*
* WARNING: This metod reads properties which can trigger a reflow; use this wisely.
*
* @param mouseEvent
* @param gapInPixels
* @returns
*/
export const getMousePositionHorizontalRelativeByElement = (mouseEvent, offsetX, gapInPixels) => {
const element = mouseEvent.target;
if (element instanceof HTMLElement) {
const width = element.clientWidth; // reflow
const x = !Number.isNaN(offsetX) ? offsetX : mouseEvent.offsetX; // reflow
if (width <= 0) {
return null;
}
if (!gapInPixels) {
return x / width > 0.5 ? 'right' : 'left';
} else {
if (x <= gapInPixels) {
return 'left';
} else if (x >= width - gapInPixels) {
return 'right';
}
}
}
return null;
};
export const getMousePositionVerticalRelativeByElement = mouseEvent => {
const element = mouseEvent.target;
if (element instanceof HTMLElement) {
const elementRect = element.getBoundingClientRect();
if (elementRect.height <= 0) {
return null;
}
const y = mouseEvent.clientY - elementRect.top;
return y / elementRect.height > 0.5 ? 'bottom' : 'top';
}
return null;
};
export const hasResizeHandler = ({
columnEndIndexTarget,
target
}) => {
const tableElement = closestElement(target, 'table');
if (!tableElement) {
return false;
}
const query = [`.${ClassName.RESIZE_HANDLE_DECORATION}`, `[data-end-index="${columnEndIndexTarget}"]`];
const decorationElement = tableElement.querySelectorAll(query.join(''));
if (!decorationElement || decorationElement.length === 0) {
return false;
}
return true;
};
export const getTree = tr => {
// pm renders into tbody, owned by react
const tbody = tr.parentElement;
if (!tbody) {
return null;
}
// rendered by react
const table = tbody.parentElement;
if (!table) {
return null;
}
// rendered by react
const wrapper = table.parentElement;
if (!wrapper) {
return null;
}
return {
wrapper: wrapper,
table: table
};
};
export const getTop = element => {
var _element$getBoundingC, _element$getBoundingC2, _element$getBoundingC3;
if (!element || element instanceof Window) {
return 0;
}
return (_element$getBoundingC = element === null || element === void 0 ? void 0 : (_element$getBoundingC2 = element.getBoundingClientRect) === null || _element$getBoundingC2 === void 0 ? void 0 : (_element$getBoundingC3 = _element$getBoundingC2.call(element)) === null || _element$getBoundingC3 === void 0 ? void 0 : _element$getBoundingC3.top) !== null && _element$getBoundingC !== void 0 ? _element$getBoundingC : 0;
};
export const findNearestCellIndexToPoint = (x, y) => {
var _cell$parentElement;
const elements = document.elementsFromPoint(x, y);
const cell = elements.find(el => el.nodeName.toUpperCase() === 'TD' || el.nodeName.toUpperCase() === 'TH');
const row = (_cell$parentElement = cell === null || cell === void 0 ? void 0 : cell.parentElement) !== null && _cell$parentElement !== void 0 ? _cell$parentElement : undefined;
if (!Number.isFinite(row === null || row === void 0 ? void 0 : row.rowIndex) || !Number.isFinite(cell === null || cell === void 0 ? void 0 : cell.cellIndex)) {
return undefined;
}
return {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
row: row.rowIndex,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
col: cell.cellIndex
};
};