Skip to content
Snippets Groups Projects
Unverified Commit bae0aedf authored by Ivan Križnar's avatar Ivan Križnar Committed by GitHub
Browse files

Fix scrollbar interaction in the custom expression suggestion list (#43995)


* Fix dismissing popup

* Undo revert

* Merge refs

* Update frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield/ExpressionEditorTextfield.tsx

Co-authored-by: default avatarKamil Mielnik <kamil@kamilmielnik.com>

* Update frontend/src/metabase/query_builder/components/expressions/ExpressionEditorSuggestions/ExpressionEditorSuggestions.tsx

Co-authored-by: default avatarRomeo Van Snick <romeo@romeovansnick.be>

* Update frontend/src/metabase/query_builder/components/expressions/ExpressionEditorSuggestions/ExpressionEditorSuggestions.tsx

Co-authored-by: default avatarRomeo Van Snick <romeo@romeovansnick.be>

* Use semantically correct HTML

* Fix popmenuTarget type

---------

Co-authored-by: default avatarKamil Mielnik <kamil@kamilmielnik.com>
Co-authored-by: default avatarRomeo Van Snick <romeo@romeovansnick.be>
parent ae2c9609
No related branches found
No related tags found
No related merge requests found
import { useMergedRef } from "@mantine/hooks";
import {
useEffect,
useRef,
useCallback,
forwardRef,
type ReactNode,
type MouseEvent,
} from "react";
......@@ -48,27 +50,33 @@ type WithIndex<T> = T & {
index: number;
};
export function ExpressionEditorSuggestions({
query,
stageIndex,
suggestions = [],
onSuggestionMouseDown,
open,
highlightedIndex,
onHighlightSuggestion,
children,
}: {
query: Lib.Query;
stageIndex: number;
suggestions?: (Suggestion | SuggestionFooter | SuggestionShortcut)[];
onSuggestionMouseDown: (index: number) => void;
open: boolean;
highlightedIndex: number;
onHighlightSuggestion: (index: number) => void;
children: ReactNode;
}) {
const ref = useRef(null);
export const ExpressionEditorSuggestions = forwardRef<
HTMLUListElement,
{
query: Lib.Query;
stageIndex: number;
suggestions?: (Suggestion | SuggestionFooter | SuggestionShortcut)[];
onSuggestionMouseDown: (index: number) => void;
open: boolean;
highlightedIndex: number;
onHighlightSuggestion: (index: number) => void;
children: ReactNode;
}
>(function ExpressionEditorSuggestions(
{
query,
stageIndex,
suggestions = [],
onSuggestionMouseDown,
open,
highlightedIndex,
onHighlightSuggestion,
children,
},
ref,
) {
const listRef = useRef(null);
const mergedRef = useMergedRef(ref, listRef);
const withIndex = suggestions.map((suggestion, index) => ({
...suggestion,
index,
......@@ -87,7 +95,7 @@ export function ExpressionEditorSuggestions({
const groups = group(items);
function handleMouseDown(evt: MouseEvent) {
if (evt.target === ref.current) {
if (evt.target === listRef.current) {
evt.preventDefault();
evt.stopPropagation();
}
......@@ -111,7 +119,7 @@ export function ExpressionEditorSuggestions({
<DelayGroup>
<ExpressionList
data-testid="expression-suggestions-list"
ref={ref}
ref={mergedRef}
onMouseDownCapture={handleMouseDown}
>
<ExpressionEditorSuggestionsListGroup
......@@ -162,7 +170,7 @@ export function ExpressionEditorSuggestions({
</Popover.Dropdown>
</Popover>
);
}
});
function ExpressionEditorSuggestionsListGroup({
name,
......@@ -251,8 +259,9 @@ function ExpressionEditorSuggestionsListItem({
}, [index, onHighlightSuggestion]);
return (
<HoverParent>
<HoverParent as="li">
<ExpressionListItem
as="div"
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
ref={ref}
......@@ -356,7 +365,7 @@ function group(suggestions: Suggestion[]): Groups {
shortcuts: [],
};
suggestions.forEach(function (suggestion) {
suggestions.forEach(suggestion => {
if (suggestion.group) {
groups[suggestion.group].push(suggestion);
} else {
......
......@@ -217,6 +217,7 @@ class ExpressionEditorTextfield extends React.Component<
input = React.createRef<AceEditor>();
suggestionTarget = React.createRef<HTMLDivElement>();
helpTextTarget = React.createRef<HTMLDivElement>();
popupMenuTarget = React.createRef<HTMLUListElement>();
static defaultProps = {
expression: "",
......@@ -446,6 +447,15 @@ class ExpressionEditorTextfield extends React.Component<
};
handleInputBlur = (e: React.FocusEvent) => {
// Ensure there is no active popup menu before we blur or
// that user didn't interact with the popup menu
if (
this.popupMenuTarget.current &&
e.relatedTarget?.contains(this.popupMenuTarget.current)
) {
return;
}
this.setState({ isFocused: false });
// Switching to another window also triggers the blur event.
......@@ -721,6 +731,7 @@ class ExpressionEditorTextfield extends React.Component<
highlightedIndex={highlightedSuggestionIndex}
onHighlightSuggestion={this.handleHighlightSuggestion}
open={isFocused}
ref={this.popupMenuTarget}
>
<EditorContainer
isFocused={isFocused}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment