UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

300 lines (295 loc) 11.6 kB
import _extends from "@babel/runtime/helpers/extends"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; import React, { Component } from 'react'; import { createPortal } from 'react-dom'; import { INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { tableMessages as messages } from '@atlaskit/editor-common/messages'; import { Popup } from '@atlaskit/editor-common/ui'; import { closestElement } from '@atlaskit/editor-common/utils'; import { akEditorTableNumberColumnWidth } from '@atlaskit/editor-shared-styles'; import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; import { getSelectionRect, isTableSelected } from '@atlaskit/editor-tables/utils'; import { clearHoverSelection, hoverColumns, hoverRows } from '../../pm-plugins/commands'; import { deleteColumnsWithAnalytics, deleteRowsWithAnalytics } from '../../pm-plugins/commands/commands-with-analytics'; import { getPluginState as getTablePluginState } from '../../pm-plugins/plugin-factory'; import { getColumnDeleteButtonParams, getColumnsWidths } from '../../pm-plugins/utils/column-controls'; import { getRowDeleteButtonParams, getRowHeights } from '../../pm-plugins/utils/row-controls'; import { TableCssClassName as ClassName } from '../../types'; import { stickyRowZIndex } from '../consts'; import DeleteButton from './DeleteButton'; import getPopupOptions from './getPopUpOptions'; function getSelectionType(selection) { if (!isTableSelected(selection) && selection instanceof CellSelection) { if (selection.isRowSelection()) { return 'row'; } if (selection.isColSelection()) { return 'column'; } } return; } // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/react/no-class-components class FloatingDeleteButton extends Component { constructor(props) { super(props); _defineProperty(this, "wrapper", null); _defineProperty(this, "updateWrapper", () => { const tableWrapper = closestElement(this.props.tableRef, `.${ClassName.TABLE_NODE_WRAPPER}`); if (tableWrapper) { this.wrapper = tableWrapper; // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners this.wrapper.addEventListener('scroll', this.onWrapperScrolled); this.setState({ scrollLeft: tableWrapper.scrollLeft }); } else { if (this.wrapper) { // unsubscribe if we previously had one and it just went away // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners this.wrapper.removeEventListener('scroll', this.onWrapperScrolled); // and reset scroll position this.setState({ scrollLeft: 0 }); } this.wrapper = null; } }); _defineProperty(this, "onWrapperScrolled", e => { // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting const wrapper = e.target; this.setState({ scrollLeft: wrapper.scrollLeft }); }); _defineProperty(this, "handleMouseEnter", () => { const { state, dispatch } = this.props.editorView; switch (this.state.selectionType) { case 'row': { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return hoverRows(this.state.indexes, true)(state, dispatch, this.props.editorView); } case 'column': { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return hoverColumns(this.state.indexes, true)(state, dispatch, this.props.editorView); } } return false; }); _defineProperty(this, "handleMouseLeave", () => { const { state, dispatch } = this.props.editorView; return clearHoverSelection()(state, dispatch); }); /** * * * @private * @memberof FloatingDeleteButton */ _defineProperty(this, "handleClick", event => { event.preventDefault(); const { editorAnalyticsAPI } = this.props; let { state, dispatch } = this.props.editorView; const { pluginConfig: { isHeaderRowRequired } } = getTablePluginState(state); const rect = getSelectionRect(state.selection); if (rect) { switch (this.state.selectionType) { case 'column': { deleteColumnsWithAnalytics(editorAnalyticsAPI, this.props.api)(INPUT_METHOD.BUTTON, rect)(state, dispatch, this.props.editorView); return; } case 'row': { deleteRowsWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.BUTTON, rect, !!isHeaderRowRequired)(state, dispatch); return; } } } ({ state, dispatch } = this.props.editorView); clearHoverSelection()(state, dispatch); }); this.state = { selectionType: undefined, top: 0, left: 0, indexes: [], scrollLeft: 0 }; } shouldComponentUpdate(_, nextState) { return this.state.selectionType !== nextState.selectionType || this.state.left !== nextState.left || this.state.top !== nextState.top || this.state.scrollLeft !== nextState.scrollLeft; } componentDidMount() { this.updateWrapper(); } componentDidUpdate() { this.updateWrapper(); } componentWillUnmount() { if (this.wrapper) { // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners this.wrapper.removeEventListener('scroll', this.onWrapperScrolled); } } /** * We derivate the button state from the properties passed. * We do this in here because we need this information in different places * and this prevent to do multiple width calculations in the same component. */ static getDerivedStateFromProps(nextProps, prevState) { const selectionType = getSelectionType(nextProps.selection); const inStickyMode = nextProps.stickyHeaders && nextProps.stickyHeaders.sticky; const rect = getSelectionRect(nextProps.selection); // only tie row delete to sticky header if it's the only thing // in the selection, otherwise the row delete will hover around // the rest of the selection const firstRowInSelection = rect && rect.top === 0 && rect.bottom === 1; const shouldStickyButton = inStickyMode && firstRowInSelection; const stickyTop = nextProps.stickyHeaders ? nextProps.stickyHeaders.top + nextProps.stickyHeaders.padding : 0; if (selectionType) { switch (selectionType) { case 'column': { // Calculate the button position and indexes for columns const columnsWidths = getColumnsWidths(nextProps.editorView); const deleteBtnParams = getColumnDeleteButtonParams(columnsWidths, nextProps.editorView.state.selection); if (deleteBtnParams) { return { ...deleteBtnParams, // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion top: inStickyMode ? nextProps.stickyHeaders.top : 0, position: inStickyMode ? 'sticky' : undefined, selectionType }; } return null; } case 'row': { // Calculate the button position and indexes for rows if (nextProps.tableRef) { const rowHeights = getRowHeights(nextProps.tableRef); const offsetTop = inStickyMode ? -rowHeights[0] : 0; const deleteBtnParams = getRowDeleteButtonParams(rowHeights, nextProps.editorView.state.selection, shouldStickyButton ? stickyTop : offsetTop); if (deleteBtnParams) { return { ...deleteBtnParams, position: shouldStickyButton ? 'sticky' : undefined, left: 0, selectionType: selectionType }; } } return null; } } } // Clean state if no type if (prevState.selectionType !== selectionType) { return { selectionType: undefined, top: 0, left: 0, indexes: [] }; } // Do nothing if doesn't change anything return null; } render() { const { mountPoint, boundariesElement, tableRef } = this.props; const { selectionType } = this.state; if (!selectionType || !tableRef) { return null; } const tableContainerWrapper = closestElement(tableRef, `.${ClassName.TABLE_CONTAINER}`); const button = /*#__PURE__*/React.createElement(DeleteButton, { removeLabel: selectionType === 'column' ? messages.removeColumns : messages.removeRows, onClick: this.handleClick, onMouseEnter: this.handleMouseEnter, onMouseLeave: this.handleMouseLeave }); const popupOpts = getPopupOptions({ left: this.state.left, top: this.state.top, selectionType: this.state.selectionType, tableWrapper: this.wrapper }); const mountTo = tableContainerWrapper || mountPoint; if (this.state.position === 'sticky' && mountTo) { const headerRow = tableRef.querySelector('tr.sticky'); if (headerRow) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const rect = headerRow.getBoundingClientRect(); const calculatePosition = popupOpts.onPositionCalculated || (pos => pos); const pos = calculatePosition({ left: this.state.left, top: this.state.top }); return /*#__PURE__*/createPortal( /*#__PURE__*/React.createElement("div", { style: { // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 position: 'fixed', // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage/preview top: pos.top, // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766 zIndex: stickyRowZIndex, // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage/preview left: rect.left + (pos.left || 0) - (this.state.selectionType === 'column' ? this.state.scrollLeft : 0) - ( // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766 this.props.isNumberColumnEnabled ? akEditorTableNumberColumnWidth : 0) } }, button), mountTo); } } return /*#__PURE__*/React.createElement(Popup, _extends({ target: tableRef, mountTo: mountTo, boundariesElement: tableContainerWrapper || boundariesElement, scrollableElement: this.wrapper || undefined, forcePlacement: true, allowOutOfBounds: true // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading }, popupOpts), button); } } _defineProperty(FloatingDeleteButton, "displayName", 'FloatingDeleteButton'); export default FloatingDeleteButton;