mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 00:48:25 +00:00
Merge pull request #12842 from ToolJet/fix/query-panel-overflow
Fix: Dropdown options go out of scope when scrolling through Query Manager
This commit is contained in:
commit
ecf1d824ec
7 changed files with 116 additions and 8 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 518f3334b12a83785fd37dd53b0245d72848211a
|
||||
Subproject commit 52e3f8b488ddc7701c44f2cb73c4cef3b5ddd9e1
|
||||
|
|
@ -1,8 +1,20 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import Select from '@/_ui/Select';
|
||||
import { decodeEntities } from '@/_helpers/utils';
|
||||
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
|
||||
|
||||
export const ChangeDataSource = ({ dataSources, onChange, value, isVersionReleased }) => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
usePopoverObserver(
|
||||
document.getElementsByClassName('query-details')[0],
|
||||
document.querySelector('.change-data-source-select.react-select__control'),
|
||||
document.querySelector('.change-data-source-select.react-select__menu'),
|
||||
isMenuOpen,
|
||||
() => (document.querySelector('.change-data-source-select.react-select__menu').style.display = 'block'),
|
||||
() => (document.querySelector('.change-data-source-select.react-select__menu').style.display = 'none')
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
className="w-100"
|
||||
|
|
@ -14,6 +26,13 @@ export const ChangeDataSource = ({ dataSources, onChange, value, isVersionReleas
|
|||
}}
|
||||
useMenuPortal={true}
|
||||
isDisabled={isVersionReleased}
|
||||
customClassPrefix="change-data-source-select"
|
||||
onMenuOpen={() => {
|
||||
setIsMenuOpen(true);
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
setIsMenuOpen(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ class Restapi extends React.Component {
|
|||
codeHinterHeight: 32, // Default height
|
||||
};
|
||||
this.codeHinterRef = React.createRef();
|
||||
this.isMenuOpenRef = React.createRef();
|
||||
this.prevIsMenuOpenRef = React.createRef(false);
|
||||
this.intersectionObserver = null;
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +50,9 @@ class Restapi extends React.Component {
|
|||
if (this.codeHinterRef.current && !this.resizeObserver) {
|
||||
this.setupResizeObserver();
|
||||
}
|
||||
if (!this.intersectionObserver) {
|
||||
this.setupIntersectionObserver();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -75,6 +81,7 @@ class Restapi extends React.Component {
|
|||
}, 1000);
|
||||
|
||||
this.setupResizeObserver();
|
||||
this.setupIntersectionObserver();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
|
@ -84,6 +91,9 @@ class Restapi extends React.Component {
|
|||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
}
|
||||
if (this.intersectionObserver) {
|
||||
this.intersectionObserver.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
setupResizeObserver() {
|
||||
|
|
@ -132,6 +142,33 @@ class Restapi extends React.Component {
|
|||
this.resizeObserver.observe(element);
|
||||
}
|
||||
|
||||
setupIntersectionObserver() {
|
||||
const container = document.getElementsByClassName('query-details')[0];
|
||||
const trigger = document.querySelector('.restapi-method-select.react-select__control');
|
||||
|
||||
if (this.intersectionObserver) {
|
||||
this.intersectionObserver.disconnect();
|
||||
}
|
||||
|
||||
this.intersectionObserver = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
const popover = document.querySelector('.restapi-method-select.react-select__menu');
|
||||
if (entry.isIntersecting) {
|
||||
if (this.prevIsMenuOpenRef.current) {
|
||||
popover.style.display = 'block';
|
||||
this.prevIsMenuOpenRef.current = false;
|
||||
}
|
||||
} else if (this.isMenuOpenRef.current) {
|
||||
popover.style.display = 'none';
|
||||
this.prevIsMenuOpenRef.current = true;
|
||||
}
|
||||
},
|
||||
{ root: container, threshold: [0.5] }
|
||||
);
|
||||
|
||||
this.intersectionObserver.observe(trigger);
|
||||
}
|
||||
|
||||
initizalizeRetryNetworkErrorsToggle = () => {
|
||||
const isRetryNetworkErrorToggleUnused = this.props.options.retry_network_errors === null;
|
||||
if (isRetryNetworkErrorToggleUnused) {
|
||||
|
|
@ -287,6 +324,13 @@ class Restapi extends React.Component {
|
|||
height={32}
|
||||
styles={this.customSelectStyles(this.props.darkMode, 91)}
|
||||
useCustomStyles={true}
|
||||
customClassPrefix="restapi-method-select"
|
||||
onMenuOpen={() => {
|
||||
this.isMenuOpenRef.current = true;
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
this.isMenuOpenRef.current = false;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -5,14 +5,25 @@ import CodeHinter from '@/AppBuilder/CodeEditor';
|
|||
import './workflows-query.scss';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
|
||||
|
||||
export function Workflows({ options, optionsChanged, currentState }) {
|
||||
const [workflowOptions, setWorkflowOptions] = useState([]);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [_selectedWorkflowId, setSelectedWorkflowId] = useState(undefined);
|
||||
const [params, setParams] = useState([...(options.params ?? [{ key: '', value: '' }])]);
|
||||
|
||||
const appId = useStore((state) => state.app.appId);
|
||||
|
||||
usePopoverObserver(
|
||||
document.getElementsByClassName('query-details')[0],
|
||||
document.querySelector('.workflow-select.react-select__control'),
|
||||
document.querySelector('.workflow-select.react-select__menu'),
|
||||
isMenuOpen,
|
||||
() => (document.querySelector('.workflow-select.react-select__menu').style.display = 'block'),
|
||||
() => (document.querySelector('.workflow-select.react-select__menu').style.display = 'none')
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
appsService.getWorkflows(appId).then(({ workflows }) => {
|
||||
setWorkflowOptions(
|
||||
|
|
@ -50,6 +61,13 @@ export function Workflows({ options, optionsChanged, currentState }) {
|
|||
customWrap={true}
|
||||
width="300px"
|
||||
menuPlacement="bottom"
|
||||
customClassPrefix="workflow-select"
|
||||
onMenuOpen={() => {
|
||||
setIsMenuOpen(true);
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
setIsMenuOpen(false);
|
||||
}}
|
||||
/>
|
||||
<label className="my-2">Params</label>
|
||||
<div className="grid"></div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import React, { useState, useEffect, useContext, useRef } from 'react';
|
||||
|
||||
import { ActionTypes } from '@/Editor/ActionTypes';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
|
|
@ -32,6 +32,7 @@ import useStore from '@/AppBuilder/_stores/store';
|
|||
import { useEventActions, useEvents } from '@/AppBuilder/_stores/slices/eventsSlice';
|
||||
import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
|
||||
import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
|
||||
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import { components as selectComponents } from 'react-select';
|
||||
|
||||
|
|
@ -84,6 +85,8 @@ export const EventManager = ({
|
|||
|
||||
const [events, setEvents] = useState([]);
|
||||
const [focusedEventIndex, setFocusedEventIndex] = useState(null);
|
||||
const lastFocusedEventIndex = useRef(null);
|
||||
const shouldSkipOnToggle = useRef(null);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -1090,10 +1093,21 @@ export const EventManager = ({
|
|||
placement={popoverPlacement || 'left'}
|
||||
rootClose={true}
|
||||
overlay={eventPopover(event.event, index)}
|
||||
onHide={() => setFocusedEventIndex(null)}
|
||||
onToggle={(showing) => {
|
||||
// If the toggle action should be skipped (e.g., due to a previous state change), reset the flag and exit early.
|
||||
if (shouldSkipOnToggle.current) {
|
||||
shouldSkipOnToggle.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is already a focused event, set the skip flag to prevent unnecessary state updates.
|
||||
if (focusedEventIndex !== null && showing) {
|
||||
shouldSkipOnToggle.current = true;
|
||||
}
|
||||
|
||||
if (showing) {
|
||||
setFocusedEventIndex(index);
|
||||
lastFocusedEventIndex.current = index;
|
||||
} else {
|
||||
setFocusedEventIndex(null);
|
||||
}
|
||||
|
|
@ -1102,6 +1116,7 @@ export const EventManager = ({
|
|||
>
|
||||
<div
|
||||
key={index}
|
||||
id={`${sourceId}-${index}`}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
|
|
@ -1145,6 +1160,17 @@ export const EventManager = ({
|
|||
);
|
||||
};
|
||||
|
||||
const shouldUsePopoverObserver = events.length !== 0 && eventSourceType === 'data_query';
|
||||
|
||||
usePopoverObserver(
|
||||
shouldUsePopoverObserver ? document.getElementsByClassName('query-details')[0] : null,
|
||||
document.getElementById(`${sourceId}-${lastFocusedEventIndex.current}`),
|
||||
document.getElementById('popover-basic'),
|
||||
focusedEventIndex !== null,
|
||||
() => (document.getElementById('popover-basic').style.display = 'block'),
|
||||
() => (document.getElementById('popover-basic').style.display = 'none')
|
||||
);
|
||||
|
||||
if (events.length === 0) {
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ function usePopoverObserver(containerRef, triggerRef, popoverRef, show, onShow,
|
|||
const prevShow = useRef(false);
|
||||
|
||||
// Check if it is a ref or a DOM element
|
||||
const container = containerRef?.current ? containerRef.current : containerRef;
|
||||
const trigger = triggerRef?.current ? triggerRef.current : triggerRef;
|
||||
const popover = popoverRef?.current ? popoverRef.current : popoverRef;
|
||||
const container = containerRef?.current !== undefined ? containerRef.current : containerRef;
|
||||
const trigger = triggerRef?.current !== undefined ? triggerRef.current : triggerRef;
|
||||
const popover = popoverRef?.current !== undefined ? popoverRef.current : popoverRef;
|
||||
|
||||
useEffect(() => {
|
||||
if (!container || !trigger) return;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele
|
|||
isDisabled = false,
|
||||
borderRadius,
|
||||
openMenuOnFocus = false,
|
||||
customClassPrefix = '',
|
||||
} = restProps;
|
||||
|
||||
const customStyles = useCustomStyles ? styles : defaultStyles(isDarkMode, width, height, styles, borderRadius);
|
||||
|
|
@ -74,7 +75,7 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele
|
|||
maxMenuHeight={maxMenuHeight}
|
||||
menuPortalTarget={useMenuPortal ? document.body : menuPortalTarget}
|
||||
closeMenuOnSelect={closeMenuOnSelect ?? true}
|
||||
classNamePrefix={`${isDarkMode && 'dark-theme'} ${'react-select'}`}
|
||||
classNamePrefix={`${customClassPrefix} ${isDarkMode && 'dark-theme'} ${'react-select'}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue