react-spreadsheet
Version:
Simple, customizable yet performant spreadsheet for React
350 lines (304 loc) • 11.7 kB
JavaScript
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);