UNPKG

react-spreadsheet

Version:

Simple, customizable yet performant spreadsheet for React

350 lines (304 loc) 11.7 kB
import _regeneratorRuntime from "@babel/runtime/regenerator"; import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator"; import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2"; import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck"; import _createClass from "@babel/runtime/helpers/esm/createClass"; import _createSuper from "@babel/runtime/helpers/esm/createSuper"; import _inherits from "@babel/runtime/helpers/esm/inherits"; import React, { PureComponent } from "react"; import { connect } from "unistore/react"; // $FlowFixMe import { Parser as FormulaParser, columnIndexToLabel } from "hot-formula-parser"; import * as Types from "./types"; import Table from "./Table"; import Row from "./Row"; import CornerIndicator from "./CornerIndicator"; import { Cell, enhance as enhanceCell } from "./Cell"; import DataViewer from "./DataViewer"; import DataEditor from "./DataEditor"; import ActiveCell from "./ActiveCell"; import Selected from "./Selected"; import Copied from "./Copied"; import { getBindingsForCell } from "./bindings"; import { memoizeOne, range, readTextFromClipboard, writeTextToClipboard } from "./util"; import * as PointSet from "./point-set"; import * as Matrix from "./matrix"; import * as Actions from "./actions"; import "./Spreadsheet.css"; var getValue = function getValue(_ref) { var data = _ref.data; return data ? data.value : null; }; var DefaultColumnIndicator = function DefaultColumnIndicator(_ref2) { var column = _ref2.column, label = _ref2.label; return /*#__PURE__*/React.createElement("th", { className: "Spreadsheet__header" }, label !== undefined ? label : columnIndexToLabel(column)); }; var DefaultRowIndicator = function DefaultRowIndicator(_ref3) { var row = _ref3.row, label = _ref3.label; return /*#__PURE__*/React.createElement("th", { className: "Spreadsheet__header" }, label !== undefined ? label : row + 1); }; var Spreadsheet = /*#__PURE__*/function (_PureComponent) { _inherits(Spreadsheet, _PureComponent); var _super = _createSuper(Spreadsheet); function Spreadsheet() { var _this; _classCallCheck(this, Spreadsheet); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _super.call.apply(_super, [this].concat(args)); _this.formulaParser = _this.props.formulaParser || new FormulaParser(); _this.clip = function (event) { var _this$props = _this.props, store = _this$props.store, getValue = _this$props.getValue; var _store$getState = store.getState(), data = _store$getState.data, selected = _store$getState.selected; var startPoint = PointSet.min(selected); var endPoint = PointSet.max(selected); var slicedMatrix = Matrix.slice(startPoint, endPoint, data); var valueMatrix = Matrix.map(function (value, point) { // Slice makes non-existing cells undefined, empty cells are classically // translated to an empty string in join() if (value === undefined) { return ""; } return getValue(_objectSpread({}, point, { data: value })); }, slicedMatrix); var csv = Matrix.join(valueMatrix); writeTextToClipboard(event, csv); }; _this.handleCopy = function (event) { if (_this.isFocused()) { event.preventDefault(); event.stopPropagation(); _this.clip(event); _this.props.copy(); } }; _this.handlePaste = /*#__PURE__*/function () { var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(event) { var _text; return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: if (_this.props.mode === "view" && _this.isFocused()) { event.preventDefault(); event.stopPropagation(); if (event.clipboardData) { _text = readTextFromClipboard(event); _this.props.paste(_text); } } case 1: case "end": return _context.stop(); } } }, _callee); })); return function (_x) { return _ref4.apply(this, arguments); }; }(); _this.handleCut = function (event) { if (_this.isFocused()) { event.preventDefault(); event.stopPropagation(); _this.clip(event); _this.props.cut(); } }; _this.handleKeyDown = function (event) { var _this$props2 = _this.props, store = _this$props2.store, onKeyDown = _this$props2.onKeyDown, onKeyDownAction = _this$props2.onKeyDownAction; if (onKeyDown) { onKeyDown(event); } // Do not use event in case preventDefault() was called inside onKeyDown if (!event.defaultPrevented) { // Only disable default behavior if an handler exist if (Actions.getKeyDownHandler(store.getState(), event)) { event.nativeEvent.preventDefault(); } onKeyDownAction(event); } }; _this.handleMouseUp = function () { _this.props.onDragEnd(); document.removeEventListener("mouseup", _this.handleMouseUp); }; _this.handleMouseMove = function (event) { if (!_this.props.store.getState().dragging && event.buttons === 1) { _this.props.onDragStart(); document.addEventListener("mouseup", _this.handleMouseUp); } }; _this.handleRoot = function (root) { _this.root = root; }; _this.getCellComponent = memoizeOne(enhanceCell); return _this; } _createClass(Spreadsheet, [{ key: "isFocused", value: function isFocused() { var _document = document, activeElement = _document.activeElement; return this.props.mode === "view" && this.root ? this.root === activeElement || this.root.contains(activeElement) : false; } }, { key: "componentWillUnmount", value: function componentWillUnmount() { document.removeEventListener("cut", this.handleCut); document.removeEventListener("copy", this.handleCopy); document.removeEventListener("paste", this.handlePaste); } }, { key: "componentDidMount", value: function componentDidMount() { var store = this.props.store; document.addEventListener("cut", this.handleCut); document.addEventListener("copy", this.handleCopy); document.addEventListener("paste", this.handlePaste); this.formulaParser.on("callCellValue", function (cellCoord, done) { var value; /** @todo More sound error, or at least document */ try { var cell = Matrix.get(cellCoord.row.index, cellCoord.column.index, store.getState().data); value = getValue({ data: cell }); } catch (error) { console.error(error); } finally { done(value); } }); this.formulaParser.on("callRangeValue", function (startCellCoord, endCellCoord, done) { var startPoint = { row: startCellCoord.row.index, column: startCellCoord.column.index }; var endPoint = { row: endCellCoord.row.index, column: endCellCoord.column.index }; var values = Matrix.toArray(Matrix.slice(startPoint, endPoint, store.getState().data)).map(function (cell) { return getValue({ data: cell }); }); done(values); }); } }, { key: "render", value: function render() { var _this2 = this; var _this$props3 = this.props, Table = _this$props3.Table, Row = _this$props3.Row, CornerIndicator = _this$props3.CornerIndicator, columnLabels = _this$props3.columnLabels, rowLabels = _this$props3.rowLabels, DataViewer = _this$props3.DataViewer, getValue = _this$props3.getValue, rows = _this$props3.rows, columns = _this$props3.columns, onKeyPress = _this$props3.onKeyPress, getBindingsForCell = _this$props3.getBindingsForCell, hideColumnIndicators = _this$props3.hideColumnIndicators, hideRowIndicators = _this$props3.hideRowIndicators; var Cell = this.getCellComponent(this.props.Cell); var ColumnIndicator = this.props.ColumnIndicator || DefaultColumnIndicator; var RowIndicator = this.props.RowIndicator || DefaultRowIndicator; return /*#__PURE__*/React.createElement("div", { ref: this.handleRoot, className: "Spreadsheet", onKeyPress: onKeyPress, onKeyDown: this.handleKeyDown, onMouseMove: this.handleMouseMove }, /*#__PURE__*/React.createElement(Table, { columns: columns, hideColumnIndicators: hideColumnIndicators }, /*#__PURE__*/React.createElement(Row, null, !hideRowIndicators && !hideColumnIndicators && /*#__PURE__*/React.createElement(CornerIndicator, null), !hideColumnIndicators && range(columns).map(function (columnNumber) { return columnLabels ? /*#__PURE__*/React.createElement(ColumnIndicator, { key: columnNumber, column: columnNumber, label: columnNumber in columnLabels ? columnLabels[columnNumber] : null }) : /*#__PURE__*/React.createElement(ColumnIndicator, { key: columnNumber, column: columnNumber }); })), range(rows).map(function (rowNumber) { return /*#__PURE__*/React.createElement(Row, { key: rowNumber }, !hideRowIndicators && (rowLabels ? /*#__PURE__*/React.createElement(RowIndicator, { key: rowNumber, row: rowNumber, label: rowNumber in rowLabels ? rowLabels[rowNumber] : null }) : /*#__PURE__*/React.createElement(RowIndicator, { key: rowNumber, row: rowNumber })), range(columns).map(function (columnNumber) { return /*#__PURE__*/React.createElement(Cell, { key: columnNumber, row: rowNumber, column: columnNumber, DataViewer: DataViewer, getValue: getValue, formulaParser: _this2.formulaParser }); })); })), /*#__PURE__*/React.createElement(ActiveCell, { DataEditor: DataEditor, getValue: getValue, getBindingsForCell: getBindingsForCell }), /*#__PURE__*/React.createElement(Selected, null), /*#__PURE__*/React.createElement(Copied, null)); } }]); return Spreadsheet; }(PureComponent); Spreadsheet.defaultProps = { Table: Table, Row: Row, Cell: Cell, CornerIndicator: CornerIndicator, DataViewer: DataViewer, DataEditor: DataEditor, getValue: getValue, getBindingsForCell: getBindingsForCell }; var mapStateToProps = function mapStateToProps(_ref5, _ref6) { var data = _ref5.data, mode = _ref5.mode; var columnLabels = _ref6.columnLabels; var _Matrix$getSize = Matrix.getSize(data), columns = _Matrix$getSize.columns, rows = _Matrix$getSize.rows; return { mode: mode, rows: rows, columns: columnLabels ? Math.max(columns, columnLabels.length) : columns }; }; export default connect(mapStateToProps, { copy: Actions.copy, cut: Actions.cut, paste: Actions.paste, onKeyDownAction: Actions.keyDown, onKeyPress: Actions.keyPress, onDragStart: Actions.dragStart, onDragEnd: Actions.dragEnd })(Spreadsheet);