From 2c4ec461c731fb8876c2738f2dfee7d9cd7d166e Mon Sep 17 00:00:00 2001 From: Vigen Abrahamyan Date: Thu, 9 Mar 2023 11:51:39 +0400 Subject: [PATCH] feat: custom outside click predicate --- .../click-outside-container.tsx | 6 ++++- packages/core/src/data-editor/data-editor.tsx | 14 +++++++++-- .../data-grid-overlay-editor.tsx | 8 +++++- .../test/click-outside-container.test.tsx | 25 +++++++++++++++++++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/packages/core/src/click-outside-container/click-outside-container.tsx b/packages/core/src/click-outside-container/click-outside-container.tsx index b75a023a0..3d603cbc6 100644 --- a/packages/core/src/click-outside-container/click-outside-container.tsx +++ b/packages/core/src/click-outside-container/click-outside-container.tsx @@ -1,6 +1,7 @@ import * as React from "react"; interface Props extends React.HTMLAttributes { onClickOutside: () => void; + isOutsideClick?: (event: MouseEvent) => boolean; } export default class ClickOutsideContainer extends React.PureComponent { @@ -17,6 +18,9 @@ export default class ClickOutsideContainer extends React.PureComponent { } private clickOutside = (event: MouseEvent) => { + if (this.props.isOutsideClick && !this.props.isOutsideClick(event)) { + return; + } if (this.wrapperRef.current !== null && !this.wrapperRef.current.contains(event.target as Node | null)) { let node = event.target as Element | null; while (node !== null) { @@ -31,7 +35,7 @@ export default class ClickOutsideContainer extends React.PureComponent { }; public render(): React.ReactNode { - const { onClickOutside, ...rest } = this.props; + const { onClickOutside, isOutsideClick, ...rest } = this.props; return (
{this.props.children} diff --git a/packages/core/src/data-editor/data-editor.tsx b/packages/core/src/data-editor/data-editor.tsx index 2d81fe286..6574cb32c 100644 --- a/packages/core/src/data-editor/data-editor.tsx +++ b/packages/core/src/data-editor/data-editor.tsx @@ -525,8 +525,8 @@ export interface DataEditorProps extends Props { * Determins which keybindings are enabled. * @group Editing * @defaultValue is - - { + + { selectAll: true, selectRow: true, selectColumn: true, @@ -608,6 +608,14 @@ export interface DataEditorProps extends Props { readonly customRenderers?: readonly CustomRenderer>[]; readonly scaleToRem?: boolean; + + /** + * Custom predicate function to decide whether the click event occurred outside the grid + * Especially used when custom editor is opened with the portal and is outside the grid, but there is no possibility + * to add a class "click-outside-ignore" + * If this function is supplied and returns false, the click event is ignored + */ + readonly isOutsideClick?: (e: MouseEvent) => boolean; } type ScrollToFn = ( @@ -783,6 +791,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction )} diff --git a/packages/core/src/data-grid-overlay-editor/data-grid-overlay-editor.tsx b/packages/core/src/data-grid-overlay-editor/data-grid-overlay-editor.tsx index 00e93573c..5db45c681 100644 --- a/packages/core/src/data-grid-overlay-editor/data-grid-overlay-editor.tsx +++ b/packages/core/src/data-grid-overlay-editor/data-grid-overlay-editor.tsx @@ -42,6 +42,7 @@ interface DataGridOverlayEditorProps { newValue: EditableGridCell, prevValue: GridCell ) => boolean | ValidatedGridCell; + readonly isOutsideClick?: (e: MouseEvent) => boolean; } const DataGridOverlayEditor: React.FunctionComponent = p => { @@ -61,6 +62,7 @@ const DataGridOverlayEditor: React.FunctionComponent validateCell, getCellRenderer, provideEditor, + isOutsideClick, } = p; const [tempValue, setTempValueRaw] = React.useState(forceEditMode ? content : undefined); @@ -206,7 +208,11 @@ const DataGridOverlayEditor: React.FunctionComponent return createPortal( - + { await userEvent.click(outsideElement); expect(onClickOutside).toHaveBeenCalledTimes(1); }); + + it(`Does not trigger onClose when clicking outside but 'isOutsideClick' returns false`, async () => { + const onClickOutside = jest.fn(); + const isOutsideClick = jest.fn(); + + const result = render( +
+
+

I am outside

+
+ +
+ ); + + const outsideElement = await result.findByText("I am outside"); + + isOutsideClick.mockReturnValueOnce(true); + + expect(onClickOutside).not.toHaveBeenCalled(); + await userEvent.click(outsideElement); + expect(onClickOutside).toHaveBeenCalledTimes(1); + isOutsideClick.mockReturnValueOnce(false); + await userEvent.click(outsideElement); + expect(onClickOutside).toHaveBeenCalledTimes(1); + }); });