UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

176 lines (171 loc) 8.48 kB
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 }; };