FIX: Cell editor correct behaviour
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
f26cdd3a4b
commit
42c1a39131
@@ -19,16 +19,32 @@
|
|||||||
// 2. Move the cursor to the right
|
// 2. Move the cursor to the right
|
||||||
// 3. Insert in the formula the cell name on the right
|
// 3. Insert in the formula the cell name on the right
|
||||||
|
|
||||||
|
// You can either be editing a formula or content.
|
||||||
|
// When editing content (behaviour is common to Excel and Google Sheets):
|
||||||
|
// * If you start editing by typing you are in *accept* mode
|
||||||
|
// * If you start editing by F2 you are in *cruise* mode
|
||||||
|
// * If you start editing by double click you are in *cruise* mode
|
||||||
|
// In Google Sheets "Enter" starts editing and puts you in *cruise* mode. We do not do that
|
||||||
|
// Once you are in cruise mode it is not possible to switch to accept mode
|
||||||
|
// The only way to go from accept mode to cruise mode is clicking in the content somewhere
|
||||||
|
|
||||||
|
// When editing a formula.
|
||||||
|
// In Google Sheets you are either in insert mode or cruise mode.
|
||||||
|
// You can get back to accept mode if you delete the whole formula
|
||||||
|
// In Excel you can be either in insert or accept but if you click in the formula body
|
||||||
|
// you switch to cruise mode. Once in cruise mode you can go to insert mode by selecting a range.
|
||||||
|
// Then you are back in accept/insert modes
|
||||||
|
|
||||||
import type { Model } from "@ironcalc/wasm";
|
import type { Model } from "@ironcalc/wasm";
|
||||||
import {
|
import {
|
||||||
type CSSProperties,
|
type CSSProperties,
|
||||||
type KeyboardEvent,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import type { WorkbookState } from "../workbookState";
|
import type { WorkbookState } from "../workbookState";
|
||||||
|
import useKeyDown from "./useKeyDown";
|
||||||
import getFormulaHTML from "./util";
|
import getFormulaHTML from "./util";
|
||||||
|
|
||||||
const commonCSS: CSSProperties = {
|
const commonCSS: CSSProperties = {
|
||||||
@@ -92,113 +108,16 @@ const Editor = (options: EditorOptions) => {
|
|||||||
}
|
}
|
||||||
}, [originalText, model]);
|
}, [originalText, model]);
|
||||||
|
|
||||||
const onKeyDown = useCallback(
|
const { onKeyDown } = useKeyDown({
|
||||||
(event: KeyboardEvent) => {
|
model,
|
||||||
const { key, shiftKey, altKey } = event;
|
text,
|
||||||
const textarea = textareaRef.current;
|
onEditEnd,
|
||||||
if (!textarea) {
|
onTextUpdated,
|
||||||
return;
|
workbookState,
|
||||||
}
|
textareaRef,
|
||||||
switch (key) {
|
setStyledFormula,
|
||||||
case "Enter": {
|
setText,
|
||||||
if (altKey) {
|
});
|
||||||
// new line
|
|
||||||
const start = textarea.selectionStart;
|
|
||||||
const end = textarea.selectionEnd;
|
|
||||||
const newText = `${text.slice(0, start)}\n${text.slice(end)}`;
|
|
||||||
setText(newText);
|
|
||||||
setTimeout(() => {
|
|
||||||
textarea.setSelectionRange(start + 1, start + 1);
|
|
||||||
}, 0);
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// end edit and select cell bellow
|
|
||||||
setTimeout(() => {
|
|
||||||
const cell = workbookState.getEditingCell();
|
|
||||||
if (cell) {
|
|
||||||
model.setUserInput(
|
|
||||||
cell.sheet,
|
|
||||||
cell.row,
|
|
||||||
cell.column,
|
|
||||||
cell.text + (cell.referencedRange?.str || ""),
|
|
||||||
);
|
|
||||||
const sign = shiftKey ? -1 : 1;
|
|
||||||
model.setSelectedSheet(cell.sheet);
|
|
||||||
model.setSelectedCell(cell.row + sign, cell.column);
|
|
||||||
workbookState.clearEditingCell();
|
|
||||||
}
|
|
||||||
onEditEnd();
|
|
||||||
}, 0);
|
|
||||||
// event bubbles up
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case "Tab": {
|
|
||||||
// end edit and select cell to the right
|
|
||||||
const cell = workbookState.getEditingCell();
|
|
||||||
if (cell) {
|
|
||||||
workbookState.clearEditingCell();
|
|
||||||
model.setUserInput(
|
|
||||||
cell.sheet,
|
|
||||||
cell.row,
|
|
||||||
cell.column,
|
|
||||||
cell.text + (cell.referencedRange?.str || ""),
|
|
||||||
);
|
|
||||||
const sign = shiftKey ? -1 : 1;
|
|
||||||
model.setSelectedSheet(cell.sheet);
|
|
||||||
model.setSelectedCell(cell.row, cell.column + sign);
|
|
||||||
if (textareaRef.current) {
|
|
||||||
textareaRef.current.value = "";
|
|
||||||
setStyledFormula(getFormulaHTML(model, "", "").html);
|
|
||||||
}
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
onEditEnd();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case "Escape": {
|
|
||||||
// quit editing without modifying the cell
|
|
||||||
const cell = workbookState.getEditingCell();
|
|
||||||
if (cell) {
|
|
||||||
model.setSelectedSheet(cell.sheet);
|
|
||||||
}
|
|
||||||
workbookState.clearEditingCell();
|
|
||||||
onEditEnd();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: Arrow keys navigate in Excel
|
|
||||||
case "ArrowRight": {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
// We run this in a timeout because the value is not yet in the textarea
|
|
||||||
// since we are capturing the keydown event
|
|
||||||
setTimeout(() => {
|
|
||||||
const cell = workbookState.getEditingCell();
|
|
||||||
if (cell) {
|
|
||||||
// accept whatever is in the referenced range
|
|
||||||
const value = textarea.value;
|
|
||||||
const styledFormula = getFormulaHTML(model, value, "");
|
|
||||||
|
|
||||||
cell.text = value;
|
|
||||||
cell.referencedRange = null;
|
|
||||||
cell.cursorStart = textarea.selectionStart;
|
|
||||||
cell.cursorEnd = textarea.selectionEnd;
|
|
||||||
workbookState.setEditingCell(cell);
|
|
||||||
|
|
||||||
workbookState.setActiveRanges(styledFormula.activeRanges);
|
|
||||||
setStyledFormula(styledFormula.html);
|
|
||||||
|
|
||||||
onTextUpdated();
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[model, text, onEditEnd, onTextUpdated, workbookState],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (display) {
|
if (display) {
|
||||||
@@ -207,6 +126,35 @@ const Editor = (options: EditorOptions) => {
|
|||||||
}, [display]);
|
}, [display]);
|
||||||
|
|
||||||
const onChange = useCallback(() => {
|
const onChange = useCallback(() => {
|
||||||
|
const textarea = textareaRef.current;
|
||||||
|
const cell = workbookState.getEditingCell();
|
||||||
|
if (!textarea || !cell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const value = textarea.value;
|
||||||
|
cell.text = value;
|
||||||
|
cell.referencedRange = null;
|
||||||
|
cell.cursorStart = textarea.selectionStart;
|
||||||
|
cell.cursorEnd = textarea.selectionEnd;
|
||||||
|
const styledFormula = getFormulaHTML(model, cell.text, "");
|
||||||
|
if (value === "") {
|
||||||
|
// When we delete the content of a cell we jump to accept mode
|
||||||
|
cell.mode = "accept";
|
||||||
|
}
|
||||||
|
workbookState.setEditingCell(cell);
|
||||||
|
|
||||||
|
workbookState.setActiveRanges(styledFormula.activeRanges);
|
||||||
|
setText(cell.text);
|
||||||
|
setStyledFormula(styledFormula.html);
|
||||||
|
|
||||||
|
onTextUpdated();
|
||||||
|
|
||||||
|
// Should we stop propagations?
|
||||||
|
// event.stopPropagation();
|
||||||
|
// event.preventDefault();
|
||||||
|
}, [workbookState, model, onTextUpdated]);
|
||||||
|
|
||||||
|
const onBlur = useCallback(() => {
|
||||||
if (textareaRef.current) {
|
if (textareaRef.current) {
|
||||||
textareaRef.current.value = "";
|
textareaRef.current.value = "";
|
||||||
setStyledFormula(getFormulaHTML(model, "", "").html);
|
setStyledFormula(getFormulaHTML(model, "", "").html);
|
||||||
@@ -272,10 +220,16 @@ const Editor = (options: EditorOptions) => {
|
|||||||
defaultValue={text}
|
defaultValue={text}
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
onBlur={onChange}
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
// Prevents this from bubbling up and focusing on the spreadsheet
|
// Prevents this from bubbling up and focusing on the spreadsheet
|
||||||
if (isCellEditing && type === "cell") {
|
if (isCellEditing && type === "cell") {
|
||||||
|
const cell = workbookState.getEditingCell();
|
||||||
|
if (cell) {
|
||||||
|
cell.mode = "edit";
|
||||||
|
workbookState.setEditingCell(cell);
|
||||||
|
}
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
423
webapp/src/components/editor/useKeyDown.ts
Normal file
423
webapp/src/components/editor/useKeyDown.ts
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
import type { Model } from "@ironcalc/wasm";
|
||||||
|
import { type KeyboardEvent, type RefObject, useCallback } from "react";
|
||||||
|
import { rangeToStr } from "../util";
|
||||||
|
import type { WorkbookState } from "../workbookState";
|
||||||
|
import getFormulaHTML, { isInReferenceMode } from "./util";
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
model: Model;
|
||||||
|
text: string;
|
||||||
|
onEditEnd: () => void;
|
||||||
|
onTextUpdated: () => void;
|
||||||
|
workbookState: WorkbookState;
|
||||||
|
textareaRef: RefObject<HTMLTextAreaElement>;
|
||||||
|
setText: (s: string) => void;
|
||||||
|
setStyledFormula: (html: JSX.Element[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useKeyDown = (
|
||||||
|
options: Options,
|
||||||
|
): { onKeyDown: (event: KeyboardEvent) => void } => {
|
||||||
|
const {
|
||||||
|
model,
|
||||||
|
text,
|
||||||
|
onEditEnd,
|
||||||
|
onTextUpdated,
|
||||||
|
workbookState,
|
||||||
|
textareaRef,
|
||||||
|
setText,
|
||||||
|
setStyledFormula,
|
||||||
|
} = options;
|
||||||
|
const onKeyDown = useCallback(
|
||||||
|
(event: KeyboardEvent) => {
|
||||||
|
const { key, shiftKey, altKey } = event;
|
||||||
|
const textarea = textareaRef.current;
|
||||||
|
const cell = workbookState.getEditingCell();
|
||||||
|
if (!textarea || !cell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (key) {
|
||||||
|
case "Enter": {
|
||||||
|
if (altKey) {
|
||||||
|
// new line
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const newText = `${text.slice(0, start)}\n${text.slice(end)}`;
|
||||||
|
setText(newText);
|
||||||
|
setTimeout(() => {
|
||||||
|
textarea.setSelectionRange(start + 1, start + 1);
|
||||||
|
}, 0);
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
// end edit and select cell bellow (or above if shiftKey)
|
||||||
|
model.setUserInput(
|
||||||
|
cell.sheet,
|
||||||
|
cell.row,
|
||||||
|
cell.column,
|
||||||
|
cell.text + (cell.referencedRange?.str || ""),
|
||||||
|
);
|
||||||
|
const sign = shiftKey ? -1 : 1;
|
||||||
|
model.setSelectedSheet(cell.sheet);
|
||||||
|
model.setSelectedCell(cell.row + sign, cell.column);
|
||||||
|
workbookState.clearEditingCell();
|
||||||
|
onEditEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "Tab": {
|
||||||
|
// end edit and select cell to the right (or left if ShiftKey)
|
||||||
|
workbookState.clearEditingCell();
|
||||||
|
model.setUserInput(
|
||||||
|
cell.sheet,
|
||||||
|
cell.row,
|
||||||
|
cell.column,
|
||||||
|
cell.text + (cell.referencedRange?.str || ""),
|
||||||
|
);
|
||||||
|
const sign = shiftKey ? -1 : 1;
|
||||||
|
model.setSelectedSheet(cell.sheet);
|
||||||
|
model.setSelectedCell(cell.row, cell.column + sign);
|
||||||
|
if (textareaRef.current) {
|
||||||
|
textareaRef.current.value = "";
|
||||||
|
setStyledFormula(getFormulaHTML(model, "", "").html);
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
onEditEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "Escape": {
|
||||||
|
// quit editing without modifying the cell
|
||||||
|
const cell = workbookState.getEditingCell();
|
||||||
|
if (cell) {
|
||||||
|
model.setSelectedSheet(cell.sheet);
|
||||||
|
}
|
||||||
|
workbookState.clearEditingCell();
|
||||||
|
onEditEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: Arrow keys navigate in Excel
|
||||||
|
case "ArrowRight": {
|
||||||
|
if (cell.mode === "edit") {
|
||||||
|
// just edit
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (cell.referencedRange) {
|
||||||
|
// There is already a reference range we move it to the right
|
||||||
|
// (or expand if shift is pressed)
|
||||||
|
const sheetNames = model
|
||||||
|
.getWorksheetsProperties()
|
||||||
|
.map((s) => s.name);
|
||||||
|
const range = cell.referencedRange.range;
|
||||||
|
if (shiftKey) {
|
||||||
|
range.columnEnd += 1;
|
||||||
|
} else {
|
||||||
|
const column = range.columnStart + 1;
|
||||||
|
const row = range.rowStart;
|
||||||
|
range.columnStart = column;
|
||||||
|
range.columnEnd = column;
|
||||||
|
range.rowEnd = row;
|
||||||
|
}
|
||||||
|
cell.referencedRange = {
|
||||||
|
range,
|
||||||
|
str: rangeToStr(range, cell.sheet, sheetNames[range.sheet]),
|
||||||
|
};
|
||||||
|
workbookState.setEditingCell(cell);
|
||||||
|
onTextUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isInReferenceMode(cell.text, cell.cursorStart)) {
|
||||||
|
// there is not a referenced Range but we are in reference mode
|
||||||
|
// we select the next cell
|
||||||
|
const sheetNames = model
|
||||||
|
.getWorksheetsProperties()
|
||||||
|
.map((s) => s.name);
|
||||||
|
const range = {
|
||||||
|
sheet: cell.sheet,
|
||||||
|
rowStart: cell.row,
|
||||||
|
rowEnd: cell.row,
|
||||||
|
columnStart: cell.column + 1,
|
||||||
|
columnEnd: cell.column + 1,
|
||||||
|
};
|
||||||
|
cell.referencedRange = {
|
||||||
|
range,
|
||||||
|
str: rangeToStr(range, cell.sheet, sheetNames[range.sheet]),
|
||||||
|
};
|
||||||
|
workbookState.setEditingCell(cell);
|
||||||
|
onTextUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// at this point we finish editing and select the cell to the right
|
||||||
|
// (or left if ShiftKey is pressed)
|
||||||
|
workbookState.clearEditingCell();
|
||||||
|
model.setUserInput(cell.sheet, cell.row, cell.column, cell.text);
|
||||||
|
model.setSelectedSheet(cell.sheet);
|
||||||
|
if (shiftKey) {
|
||||||
|
// TODO: ShiftKey
|
||||||
|
} else {
|
||||||
|
model.setSelectedCell(cell.row, cell.column + 1);
|
||||||
|
}
|
||||||
|
if (textareaRef.current) {
|
||||||
|
textareaRef.current.value = "";
|
||||||
|
setStyledFormula(getFormulaHTML(model, "", "").html);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "ArrowLeft": {
|
||||||
|
if (cell.mode === "edit") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
if (cell.referencedRange) {
|
||||||
|
// There is already a reference range we move it to the right
|
||||||
|
// (or expand if shift is pressed)
|
||||||
|
const sheetNames = model
|
||||||
|
.getWorksheetsProperties()
|
||||||
|
.map((s) => s.name);
|
||||||
|
const range = cell.referencedRange.range;
|
||||||
|
if (shiftKey) {
|
||||||
|
range.columnEnd -= 1;
|
||||||
|
} else {
|
||||||
|
const column = range.columnStart - 1;
|
||||||
|
const row = range.rowStart;
|
||||||
|
range.columnStart = column;
|
||||||
|
range.columnEnd = column;
|
||||||
|
range.rowEnd = row;
|
||||||
|
}
|
||||||
|
cell.referencedRange = {
|
||||||
|
range,
|
||||||
|
str: rangeToStr(range, cell.sheet, sheetNames[range.sheet]),
|
||||||
|
};
|
||||||
|
workbookState.setEditingCell(cell);
|
||||||
|
onTextUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isInReferenceMode(cell.text, cell.cursorStart)) {
|
||||||
|
// there is not a referenced Range but we are in reference mode
|
||||||
|
// we select the next cell
|
||||||
|
const sheetNames = model
|
||||||
|
.getWorksheetsProperties()
|
||||||
|
.map((s) => s.name);
|
||||||
|
const range = {
|
||||||
|
sheet: cell.sheet,
|
||||||
|
rowStart: cell.row,
|
||||||
|
rowEnd: cell.row,
|
||||||
|
columnStart: cell.column - 1,
|
||||||
|
columnEnd: cell.column - 1,
|
||||||
|
};
|
||||||
|
cell.referencedRange = {
|
||||||
|
range,
|
||||||
|
str: rangeToStr(range, cell.sheet, sheetNames[range.sheet]),
|
||||||
|
};
|
||||||
|
workbookState.setEditingCell(cell);
|
||||||
|
onTextUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// at this point we finish editing and select the cell to the right
|
||||||
|
// (or left if ShiftKey is pressed)
|
||||||
|
workbookState.clearEditingCell();
|
||||||
|
model.setUserInput(cell.sheet, cell.row, cell.column, cell.text);
|
||||||
|
model.setSelectedSheet(cell.sheet);
|
||||||
|
if (shiftKey) {
|
||||||
|
// TODO: ShiftKey
|
||||||
|
} else {
|
||||||
|
model.setSelectedCell(cell.row, cell.column - 1);
|
||||||
|
}
|
||||||
|
if (textareaRef.current) {
|
||||||
|
textareaRef.current.value = "";
|
||||||
|
setStyledFormula(getFormulaHTML(model, "", "").html);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "ArrowUp": {
|
||||||
|
if (cell.mode === "edit") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
if (cell.referencedRange) {
|
||||||
|
// There is already a reference range we move it to the right
|
||||||
|
// (or expand if shift is pressed)
|
||||||
|
const sheetNames = model
|
||||||
|
.getWorksheetsProperties()
|
||||||
|
.map((s) => s.name);
|
||||||
|
const range = cell.referencedRange.range;
|
||||||
|
if (shiftKey) {
|
||||||
|
if (range.rowEnd > range.rowStart) {
|
||||||
|
range.rowEnd -= 1;
|
||||||
|
} else {
|
||||||
|
range.rowStart -= 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const column = range.columnStart;
|
||||||
|
const row = range.rowStart - 1;
|
||||||
|
range.columnStart = column;
|
||||||
|
range.columnEnd = column;
|
||||||
|
range.rowStart = row;
|
||||||
|
range.rowEnd = row;
|
||||||
|
}
|
||||||
|
cell.referencedRange = {
|
||||||
|
range,
|
||||||
|
str: rangeToStr(range, cell.sheet, sheetNames[range.sheet]),
|
||||||
|
};
|
||||||
|
workbookState.setEditingCell(cell);
|
||||||
|
onTextUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isInReferenceMode(cell.text, cell.cursorStart)) {
|
||||||
|
// there is not a referenced Range but we are in reference mode
|
||||||
|
// we select the next cell
|
||||||
|
const sheetNames = model
|
||||||
|
.getWorksheetsProperties()
|
||||||
|
.map((s) => s.name);
|
||||||
|
const range = {
|
||||||
|
sheet: cell.sheet,
|
||||||
|
rowStart: cell.row - 1,
|
||||||
|
rowEnd: cell.row - 1,
|
||||||
|
columnStart: cell.column,
|
||||||
|
columnEnd: cell.column,
|
||||||
|
};
|
||||||
|
cell.referencedRange = {
|
||||||
|
range,
|
||||||
|
str: rangeToStr(range, cell.sheet, sheetNames[range.sheet]),
|
||||||
|
};
|
||||||
|
workbookState.setEditingCell(cell);
|
||||||
|
onTextUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// at this point we finish editing and select the cell to the right
|
||||||
|
// (or left if ShiftKey is pressed)
|
||||||
|
workbookState.clearEditingCell();
|
||||||
|
model.setUserInput(cell.sheet, cell.row, cell.column, cell.text);
|
||||||
|
model.setSelectedSheet(cell.sheet);
|
||||||
|
if (shiftKey) {
|
||||||
|
// TODO: ShiftKey
|
||||||
|
} else {
|
||||||
|
model.setSelectedCell(cell.row - 1, cell.column);
|
||||||
|
}
|
||||||
|
if (textareaRef.current) {
|
||||||
|
textareaRef.current.value = "";
|
||||||
|
setStyledFormula(getFormulaHTML(model, "", "").html);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "ArrowDown": {
|
||||||
|
if (cell.mode === "edit") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
if (cell.referencedRange) {
|
||||||
|
// There is already a reference range we move it to the right
|
||||||
|
// (or expand if shift is pressed)
|
||||||
|
const sheetNames = model
|
||||||
|
.getWorksheetsProperties()
|
||||||
|
.map((s) => s.name);
|
||||||
|
const range = cell.referencedRange.range;
|
||||||
|
if (shiftKey) {
|
||||||
|
range.rowEnd += 1;
|
||||||
|
} else {
|
||||||
|
const column = range.columnStart;
|
||||||
|
const row = range.rowStart + 1;
|
||||||
|
range.columnStart = column;
|
||||||
|
range.columnEnd = column;
|
||||||
|
range.rowStart = row;
|
||||||
|
range.rowEnd = row;
|
||||||
|
}
|
||||||
|
cell.referencedRange = {
|
||||||
|
range,
|
||||||
|
str: rangeToStr(range, cell.sheet, sheetNames[range.sheet]),
|
||||||
|
};
|
||||||
|
workbookState.setEditingCell(cell);
|
||||||
|
onTextUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isInReferenceMode(cell.text, cell.cursorStart)) {
|
||||||
|
// there is not a referenced Range but we are in reference mode
|
||||||
|
// we select the next cell
|
||||||
|
const sheetNames = model
|
||||||
|
.getWorksheetsProperties()
|
||||||
|
.map((s) => s.name);
|
||||||
|
const range = {
|
||||||
|
sheet: cell.sheet,
|
||||||
|
rowStart: cell.row + 1,
|
||||||
|
rowEnd: cell.row + 1,
|
||||||
|
columnStart: cell.column,
|
||||||
|
columnEnd: cell.column,
|
||||||
|
};
|
||||||
|
cell.referencedRange = {
|
||||||
|
range,
|
||||||
|
str: rangeToStr(range, cell.sheet, sheetNames[range.sheet]),
|
||||||
|
};
|
||||||
|
workbookState.setEditingCell(cell);
|
||||||
|
onTextUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// at this point we finish editing and select the cell to the right
|
||||||
|
// (or left if ShiftKey is pressed)
|
||||||
|
workbookState.clearEditingCell();
|
||||||
|
model.setUserInput(cell.sheet, cell.row, cell.column, cell.text);
|
||||||
|
model.setSelectedSheet(cell.sheet);
|
||||||
|
if (shiftKey) {
|
||||||
|
// TODO: ShiftKey
|
||||||
|
} else {
|
||||||
|
model.setSelectedCell(cell.row + 1, cell.column);
|
||||||
|
}
|
||||||
|
if (textareaRef.current) {
|
||||||
|
textareaRef.current.value = "";
|
||||||
|
setStyledFormula(getFormulaHTML(model, "", "").html);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "Shift": {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "PageDown":
|
||||||
|
case "PageUp": {
|
||||||
|
// TODO: We can do something similar to what we do with navigation keys
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "End":
|
||||||
|
case "Home": {
|
||||||
|
// Excel does something similar to what we do with navigation keys
|
||||||
|
cell.mode = "edit";
|
||||||
|
workbookState.setEditingCell(cell);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
model,
|
||||||
|
text,
|
||||||
|
setText,
|
||||||
|
setStyledFormula,
|
||||||
|
onEditEnd,
|
||||||
|
onTextUpdated,
|
||||||
|
workbookState,
|
||||||
|
textareaRef.current,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
return { onKeyDown };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useKeyDown;
|
||||||
@@ -52,6 +52,7 @@ function FormulaBar(properties: FormulaBarProps) {
|
|||||||
cursorEnd: formulaValue.length,
|
cursorEnd: formulaValue.length,
|
||||||
focus: "formula-bar",
|
focus: "formula-bar",
|
||||||
activeRanges: [],
|
activeRanges: [],
|
||||||
|
mode: "accept",
|
||||||
});
|
});
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
@@ -154,10 +154,12 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
|
|||||||
focus: "cell",
|
focus: "cell",
|
||||||
referencedRange: null,
|
referencedRange: null,
|
||||||
activeRanges: [],
|
activeRanges: [],
|
||||||
|
mode: "accept",
|
||||||
});
|
});
|
||||||
setRedrawId((id) => id + 1);
|
setRedrawId((id) => id + 1);
|
||||||
},
|
},
|
||||||
onCellEditStart: (): void => {
|
onCellEditStart: (): void => {
|
||||||
|
// User presses F2, we start editing at the edn of the text
|
||||||
const { sheet, row, column } = model.getSelectedView();
|
const { sheet, row, column } = model.getSelectedView();
|
||||||
const text = model.getCellContent(sheet, row, column);
|
const text = model.getCellContent(sheet, row, column);
|
||||||
workbookState.setEditingCell({
|
workbookState.setEditingCell({
|
||||||
@@ -170,6 +172,7 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
|
|||||||
referencedRange: null,
|
referencedRange: null,
|
||||||
focus: "cell",
|
focus: "cell",
|
||||||
activeRanges: [],
|
activeRanges: [],
|
||||||
|
mode: "edit",
|
||||||
});
|
});
|
||||||
setRedrawId((id) => id + 1);
|
setRedrawId((id) => id + 1);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ export interface ReferencedRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Focus = "cell" | "formula-bar";
|
type Focus = "cell" | "formula-bar";
|
||||||
|
type EditorMode = "accept" | "edit";
|
||||||
|
|
||||||
|
// In "edit" mode arrow keys will move you around the text in the editor
|
||||||
|
// In "accept" mode arrow keys will accept the content and move to the next cell or select another cell
|
||||||
|
|
||||||
// The cell that we are editing
|
// The cell that we are editing
|
||||||
export interface EditingCell {
|
export interface EditingCell {
|
||||||
@@ -67,6 +71,7 @@ export interface EditingCell {
|
|||||||
referencedRange: ReferencedRange | null;
|
referencedRange: ReferencedRange | null;
|
||||||
focus: Focus;
|
focus: Focus;
|
||||||
activeRanges: ActiveRange[];
|
activeRanges: ActiveRange[];
|
||||||
|
mode: EditorMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Those are styles that are copied
|
// Those are styles that are copied
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ function Worksheet(props: {
|
|||||||
focus: "cell",
|
focus: "cell",
|
||||||
referencedRange: null,
|
referencedRange: null,
|
||||||
activeRanges: [],
|
activeRanges: [],
|
||||||
|
mode: "accept",
|
||||||
});
|
});
|
||||||
setOriginalText(text);
|
setOriginalText(text);
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import InsertColumnRightIcon from "./insert-column-right.svg?react";
|
|||||||
import InsertRowAboveIcon from "./insert-row-above.svg?react";
|
import InsertRowAboveIcon from "./insert-row-above.svg?react";
|
||||||
import InsertRowBelow from "./insert-row-below.svg?react";
|
import InsertRowBelow from "./insert-row-below.svg?react";
|
||||||
|
|
||||||
import IronCalcLogo from "./orange+black.svg?react";
|
|
||||||
import IronCalcIcon from "./ironcalc_icon.svg?react";
|
import IronCalcIcon from "./ironcalc_icon.svg?react";
|
||||||
|
import IronCalcLogo from "./orange+black.svg?react";
|
||||||
|
|
||||||
import Fx from "./fx.svg?react";
|
import Fx from "./fx.svg?react";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user