Merge pull request #12090 from ToolJet/fix/auto-scroll-component

Fix: Scroll and bring the component in view port
This commit is contained in:
Johnson Cherian 2025-03-10 12:11:45 +05:30 committed by GitHub
commit 21fd97e290
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 114 additions and 22 deletions

View file

@ -9,6 +9,7 @@ const useCallbackActions = () => {
const currentPageComponents = useStore((state) => state?.getCurrentPageComponents(), shallow);
const shouldFreeze = useStore((state) => state.getShouldFreeze());
const runQuery = useStore((state) => state.queryPanel.runQuery);
const getComponentIdToAutoScroll = useStore((state) => state.getComponentIdToAutoScroll);
const handleRemoveComponent = (component) => {
deleteComponents([component.id]);
@ -30,30 +31,22 @@ const useCallbackActions = () => {
return toast.success('Copied to the clipboard', { position: 'top-center' });
};
const autoScrollTo = (id) => {
setSelectedComponents([id]);
const target = document.getElementById(id);
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
};
const handleAutoScrollToComponent = (data) => {
const currentPageComponents = useStore.getState().getCurrentPageComponents();
const component = currentPageComponents?.[data.id];
let parentId = component?.component?.parent;
if (parentId) {
const regex = /-\d+$/;
if (regex.test(parentId)) {
parentId = parentId.replace(regex, ''); // To get parentId without tab index if parent type is Tab
}
const parentType = currentPageComponents?.[parentId]?.component?.component;
if (parentType && (parentType === 'Modal' || parentType === 'Tabs')) {
autoScrollTo(parentId); // To scroll to parent component if parent type is Modal or Tabs
return;
}
const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(data.id);
if (!isAccessible) {
if (isOnCanvas) {
toast.success(
`This component can't be opened because it's on the main canvas. Close ${computedComponentId} and click "Go to component" to view it there`
);
} else
toast.success(
`This component can't be opened because it's inside ${computedComponentId}. Open ${computedComponentId} and click "Go to component"to view it.`
);
return;
}
autoScrollTo(data.id);
setSelectedComponents([computedComponentId]);
const target = document.getElementById(computedComponentId);
target.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
};
const callbackActions = [

View file

@ -56,6 +56,7 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) {
const [containers, setContainers] = useState([]);
const [showModal, setShowModal] = useState(false);
const setModalOpenOnCanvas = useStore((state) => state.setModalOpenOnCanvas);
const [activeId, setActiveId] = useState(null);
const cardMovementRef = useRef(null);
const shouldUpdateData = useRef(false);
@ -117,6 +118,7 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) {
}
/**** End - Logic to reduce the zIndex of modal control box ****/
}
setModalOpenOnCanvas(`${id}-modal`, showModal);
}, [showModal]);
useEffect(() => {

View file

@ -49,6 +49,7 @@ export const Modal = function Modal({
const size = properties.size ?? 'lg';
const [modalWidth, setModalWidth] = useState();
const mode = useStore((state) => state.currentMode, shallow);
const setModalOpenOnCanvas = useStore((state) => state.setModalOpenOnCanvas);
/**** Start - Logic to reset the zIndex of modal control box ****/
useEffect(() => {
@ -63,6 +64,7 @@ export const Modal = function Modal({
useGridStore.getState().actions.setOpenModalWidgetId(null);
}
}
setModalOpenOnCanvas(id, showModal);
}, [showModal, id, mode]);
/**** End - Logic to reset the zIndex of modal control box ****/

View file

@ -44,6 +44,7 @@ const initialState = {
currentPageHandle: null,
showWidgetDeleteConfirmation: false,
focusedParentId: null,
modalsOpenOnCanvas: [],
};
export const createComponentsSlice = (set, get) => ({
@ -1867,4 +1868,17 @@ export const createComponentsSlice = (set, get) => ({
const currentPage = getCurrentPage(moduleId);
return currentPage?.autoComputeLayout;
},
setModalOpenOnCanvas: (modalId, isOpen) => {
const { modalsOpenOnCanvas } = get();
let newModalOpenOnCanvas = [];
if (isOpen) {
newModalOpenOnCanvas = [...modalsOpenOnCanvas, modalId];
} else {
newModalOpenOnCanvas = modalsOpenOnCanvas.filter((id) => id !== modalId);
}
set((state) => {
state.modalsOpenOnCanvas = newModalOpenOnCanvas;
});
},
});

View file

@ -37,4 +37,85 @@ export const createLeftSideBarSlice = (set, get) => ({
toggleLeftSidebar(true);
}
},
getComponentIdToAutoScroll: (componentId) => {
const { getCurrentPageComponents, getAllExposedValues, modalsOpenOnCanvas } = get();
const currentPageComponents = getCurrentPageComponents();
let targetComponentId = componentId;
let current = componentId;
const visited = new Set();
let isInsideOpenModal = false;
// Bubble up to the outermost parent to find the target component
// eslint-disable-next-line no-constant-condition
while (true) {
if (visited.has(current)) break;
visited.add(current);
const parentId = currentPageComponents?.[current]?.component?.parent;
if (!parentId) break;
let isComponentVisibleInParent = true;
let nextPossibleCandidate = parentId;
// If the component exists inside a tab component
const regForTabs = /-(?!\d{12}$)\d+$/; // Parent id for tabs follow the format 'id-index' and index is not UUIDv4 id segment
if (regForTabs.test(parentId)) {
const reg = /-(\d+)$/;
const tabIndex = Number(parentId.match(reg)[1]); // Tab index inside which the component exists
const tabId = parentId.replace(regForTabs, ''); // Extract tab id from parent id
const { currentTab } = getAllExposedValues().components?.[tabId] || {};
const activeTabIndex = Number(currentTab);
nextPossibleCandidate = tabId;
if (tabIndex !== activeTabIndex) {
isComponentVisibleInParent = false;
}
}
// If the component exists inside a modal component
if (currentPageComponents?.[parentId]?.component?.component === 'Modal') {
nextPossibleCandidate = parentId;
if (!modalsOpenOnCanvas.includes(parentId)) {
isComponentVisibleInParent = false;
}
}
// If the component exists inside the kanban component's modal
if (parentId.endsWith('-modal')) {
nextPossibleCandidate = parentId.replace(/-modal$/, ''); // Extract kanban id from parent id
if (!modalsOpenOnCanvas.includes(parentId)) {
isComponentVisibleInParent = false;
}
}
// If the open modal contains the component
if (modalsOpenOnCanvas[modalsOpenOnCanvas.length - 1] === parentId) {
isInsideOpenModal = true;
}
if (!isComponentVisibleInParent) {
targetComponentId = nextPossibleCandidate;
}
current = nextPossibleCandidate;
}
if (modalsOpenOnCanvas.length > 0 && !isInsideOpenModal) {
const targetId = visited.size === 1 ? modalsOpenOnCanvas[modalsOpenOnCanvas.length - 1] : current;
const componentName = currentPageComponents?.[targetId]?.component?.name;
return {
isAccessible: false,
computedComponentId: componentName,
isOnCanvas: visited.size === 1,
};
}
return {
isAccessible: true,
computedComponentId: targetComponentId,
};
},
});