diff --git a/src/Spreadsheet.tsx b/src/Spreadsheet.tsx index 4b2f06fb..4102de44 100644 --- a/src/Spreadsheet.tsx +++ b/src/Spreadsheet.tsx @@ -36,6 +36,7 @@ import { getCellRangeValue, getCellValue, shouldHandleClipboardEvent, + isFocusedWithin, } from "./util"; import reducer, { INITIAL_STATE, hasKeyDownHandler } from "./reducer"; import context from "./context"; @@ -105,6 +106,8 @@ export type Props = { onSelect?: (selected: Point.Point[]) => void; /** Callback called when Spreadsheet's active cell changes. */ onActivate?: (active: Point.Point) => void; + /** Callback called when the Spreadsheet loses focus */ + onBlur?: () => void; onCellCommit?: ( prevCell: null | CellType, nextCell: null | CellType, @@ -137,6 +140,7 @@ const Spreadsheet = ( onModeChange = () => {}, onSelect = () => {}, onActivate = () => {}, + onBlur = () => {}, onCellCommit = () => {}, } = props; const initialState = React.useMemo( @@ -195,6 +199,7 @@ const Spreadsheet = ( (data) => dispatch(Actions.setData(data)), [dispatch] ); + const blur = React.useCallback(() => dispatch(Actions.blur()), [dispatch]); React.useEffect(() => { const prevState = prevStateRef.current; @@ -222,8 +227,16 @@ const Spreadsheet = ( onSelect(points); } - if (state.active !== prevState.active && state.active) { - onActivate(state.active); + if (state.active !== prevState.active) { + if (state.active) { + onActivate(state.active); + } else { + const root = rootRef.current; + if (root && isFocusedWithin(root) && document.activeElement) { + (document.activeElement as HTMLElement).blur(); + } + onBlur(); + } } prevStateRef.current = state; @@ -231,6 +244,7 @@ const Spreadsheet = ( props.data, state, onActivate, + onBlur, onCellCommit, onChange, onModeChange, @@ -325,6 +339,18 @@ const Spreadsheet = ( [state, onDragStart, handleMouseUp] ); + const handleBlur = React.useCallback( + (event) => { + const { currentTarget } = event; + setTimeout(() => { + if (!isFocusedWithin(currentTarget)) { + blur(); + } + }, 0); + }, + [blur] + ); + const formulaParser = React.useMemo(() => { return props.formulaParser || new FormulaParser(); }, [props.formulaParser]); @@ -460,6 +486,7 @@ const Spreadsheet = ( onKeyPress={onKeyPress} onKeyDown={handleKeyDown} onMouseMove={handleMouseMove} + onBlur={handleBlur} > {tableNode} {activeCellNode} @@ -472,6 +499,7 @@ const Spreadsheet = ( onKeyPress, handleKeyDown, handleMouseMove, + handleBlur, tableNode, activeCellNode, ] diff --git a/src/reducer.test.ts b/src/reducer.test.ts index ef4649dc..70186fae 100644 --- a/src/reducer.test.ts +++ b/src/reducer.test.ts @@ -101,7 +101,11 @@ describe("reducer", () => { ["view", EDIT_STATE, Actions.view(), INITIAL_STATE], [ "blur", - { ...INITIAL_STATE, active: Point.ORIGIN }, + { + ...INITIAL_STATE, + active: Point.ORIGIN, + selected: PointRange.create(Point.ORIGIN, Point.ORIGIN), + }, Actions.blur(), INITIAL_STATE, ], diff --git a/src/reducer.ts b/src/reducer.ts index 185f40b1..bdaf6b3e 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -267,7 +267,7 @@ function clear(state: Types.StoreState): Types.StoreState | void { } function blur(state: Types.StoreState): Types.StoreState { - return { ...state, active: null }; + return { ...state, active: null, selected: null }; } function view(state: Types.StoreState): Types.StoreState {