"use strict"; const React = require("react"); const classnames = require("classnames"); const asExpression = require("as-expression"); const AutosizeTextarea = require("../autoresize-textarea"); const style = require("./table.css"); const MOUSE_PRIMARY = 0; function generateColumnTemplate(columnCount) { return (new Array(columnCount)) .fill("auto") .join(" "); } function StaticCellContents({ dummy, children }) { return (
{children}
); } function EditableCellContents({ onChange, children }) { let ref = React.useRef(); React.useEffect(() => { ref.current.focus(); ref.current.select(); }, [ ref ]); return ( { // Workaround for Chromium, which will try to select the underlying cell when attempting to click the editable text event.stopPropagation(); }} /> ); } function Cell({ rowIndex, columnIndex, children }) { let table = React.useContext(TableContext); let isSingleSelection = ( (table.selection.rowStart != null || table.selection.columnStart != null) && rowIndex === table.selection.rowStart && rowIndex === table.selection.rowEnd && columnIndex === table.selection.columnStart && columnIndex === table.selection.columnEnd ); let isPartOfSelection = ( (table.selection.rowStart != null || table.selection.columnStart != null) && rowIndex >= table.selection.rowStart && rowIndex <= table.selection.rowEnd && columnIndex >= table.selection.columnStart && columnIndex <= table.selection.columnEnd ); let isLeftEdge = (columnIndex === table.selection.columnStart); let isRightEdge = (columnIndex === table.selection.columnEnd); let isTopEdge = (rowIndex === table.selection.rowStart); let isBottomEdge = (rowIndex === table.selection.rowEnd); let isEditable = (table.isEditing && isPartOfSelection); let contents = (isEditable) ? table.onCellContentsChange(rowIndex, columnIndex, contents)} /> : ; return
table.onCellDown(rowIndex, columnIndex, event)} onMouseUp={(event) => table.onCellUp(rowIndex, columnIndex, event)} onMouseEnter={(event) => table.onCellEnter(rowIndex, columnIndex, event)} onMouseLeave={(event) => table.onCellLeave(rowIndex, columnIndex, event)} onDoubleClick={(event) => table.onCellDoubleClick(rowIndex, columnIndex, event)} onKeyDown={(event) => { if (event.key === "Enter" && !event.shiftKey) { table.onCellCompleteEditing(rowIndex, columnIndex); } }} />; } function Row({ columns, index }) { return columns.map((cell, columnIndex) => { return {cell} }); } let TableContext = React.createContext(); module.exports = function Table({ sheet }) { let [ selectedRowStart, setSelectedRowStart ] = React.useState(); let [ selectedRowEnd, setSelectedRowEnd ] = React.useState(); let [ selectedColumnStart, setSelectedColumnStart ] = React.useState(); let [ selectedColumnEnd, setSelectedColumnEnd ] = React.useState(); let [ isSelecting, setIsSelecting ] = React.useState(false); let [ isEditing, setIsEditing ] = React.useState(false); // FIXME: Ensure that in editing mode, there's always just *one* cell selected, including when editing mode is activated by eg. pressing Enter (at least until we have multi-cell editing implemented!) React.useEffect(() => { function mouseupListener(event) { if (event.button === MOUSE_PRIMARY) { setIsSelecting(false); } }; function mousedownListener(event) { if (event.button === MOUSE_PRIMARY) { stopEditing(); deselect(); } } document.addEventListener("mouseup", mouseupListener); document.addEventListener("mousedown", mousedownListener); return function () { document.removeEventListener("mouseup", mouseupListener); document.removeEventListener("mousedown", mousedownListener); } }, []); function deselect() { setSelectedRowStart(null); setSelectedRowEnd(null); setSelectedColumnStart(null); setSelectedColumnEnd(null); } function select(rowIndex, columnIndex) { setSelectedRowStart(rowIndex); setSelectedRowEnd(rowIndex); setSelectedColumnStart(columnIndex); setSelectedColumnEnd(columnIndex); } function selectCellStart(rowIndex, columnIndex) { setSelectedRowStart(rowIndex); setSelectedColumnStart(columnIndex); } // FIXME: Swap when end < start function selectCellEnd(rowIndex, columnIndex) { setSelectedRowEnd(rowIndex); setSelectedColumnEnd(columnIndex); } function startEditing() { setIsEditing(true); } function stopEditing() { setIsEditing(false); } let flippedSelection = asExpression(() => { let [ rowStart, rowEnd ] = (selectedRowStart < selectedRowEnd) ? [ selectedRowStart, selectedRowEnd ] : [ selectedRowEnd, selectedRowStart ]; let [ columnStart, columnEnd ] = (selectedColumnStart < selectedColumnEnd) ? [ selectedColumnStart, selectedColumnEnd ] : [ selectedColumnEnd, selectedColumnStart ]; return { rowStart, rowEnd, columnStart, columnEnd }; }); // TODO: Also stop selecting on focus blur and document mouseleave? Or is this not necessary? let tableAPI = { selection: flippedSelection, isEditing: isEditing, onCellDown: function (rowIndex, columnIndex, event) { if (event.button === MOUSE_PRIMARY) { stopEditing(); setIsSelecting(true); selectCellStart(rowIndex, columnIndex); selectCellEnd(rowIndex, columnIndex); event.stopPropagation(); } }, onCellUp: function (rowIndex, columnIndex, event) { if (event.button === MOUSE_PRIMARY) { setIsSelecting(false); event.stopPropagation(); } }, onCellEnter: function (rowIndex, columnIndex, event) { if (isSelecting) { selectCellEnd(rowIndex, columnIndex); } }, onCellLeave: function (rowIndex, columnIndex, event) { }, onCellDoubleClick: function (rowIndex, columnIndex, event) { if (event.button === MOUSE_PRIMARY) { startEditing(); select(rowIndex, columnIndex); event.stopPropagation(); } }, onCellContentsChange: function (rowIndex, columnIndex, contents) { // FIXME: Properly run this through a state management cycle? sheet.cells[rowIndex][columnIndex] = contents; }, onCellCompleteEditing: function (rowIndex, columnIndex) { stopEditing(); } }; return (
{sheet.cells.map((columns, rowIndex) => { return ; })}
); }