mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-13 07:47:05 +08:00
CB-4356 TabPanelValidation handler for switching tabs
This commit is contained in:
@@ -49,12 +49,12 @@ export function useExecutor<T>(options: IUseExecutorOptions<T>): void {
|
||||
}
|
||||
}
|
||||
|
||||
if (props.next) {
|
||||
executor.next(props.next);
|
||||
if (props.before) {
|
||||
executor.before(props.before);
|
||||
}
|
||||
|
||||
if (props.before) {
|
||||
executor.next(props.before);
|
||||
if (props.next) {
|
||||
executor.next(props.next);
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -74,5 +74,9 @@ export function useExecutor<T>(options: IUseExecutorOptions<T>): void {
|
||||
executor.removeBefore(before);
|
||||
}
|
||||
};
|
||||
}, [executor, props.handlers?.length, props.postHandlers?.length]);
|
||||
}, [
|
||||
executor, props.handlers?.length, props.postHandlers?.length,
|
||||
props.next, props.before, props.handlers,
|
||||
props.postHandlers,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -9,44 +9,45 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useEffect, useMemo, useRef } from 'react';
|
||||
import { TabPanel as BaseTabPanel, TabStateReturn } from 'reakit/Tab';
|
||||
|
||||
import { FormContext, getComputed, Loader } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, Loader } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import { TabContext } from './TabContext';
|
||||
import type { TabPanelProps } from './TabPanelProps';
|
||||
import { TabsContext } from './TabsContext';
|
||||
import { TabPanelValidationHandlerContext } from './TabPanelValidationHandlerContext';
|
||||
|
||||
export const TabPanel: React.FC<TabPanelProps> = observer(function TabPanel({ tabId, children, className, lazy }) {
|
||||
const state = useContext(TabsContext);
|
||||
const formContext = useContext(FormContext);
|
||||
const tabContextState = useContext(TabsContext);
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const tabPanelValidationHandlerContext = useContext(TabPanelValidationHandlerContext);
|
||||
|
||||
if (!state) {
|
||||
if (!tabContextState) {
|
||||
throw new Error('Tabs context was not provided');
|
||||
}
|
||||
|
||||
if (!tabPanelValidationHandlerContext) {
|
||||
throw new Error('TabPanelValidationHandlerContext context was not provided');
|
||||
}
|
||||
|
||||
const tabContext = useMemo(() => ({ tabId }), [tabId]);
|
||||
const selected = getComputed(() => state.state.selectedId === tabId);
|
||||
const enabled = getComputed(() => (lazy || state.lazy) && !selected);
|
||||
const selected = getComputed(() => tabContextState.state.selectedId === tabId);
|
||||
const enabled = getComputed(() => (lazy || tabContextState.lazy) && !selected);
|
||||
|
||||
useEffect(() => {
|
||||
if (formContext === null) {
|
||||
if (tabPanelValidationHandlerContext === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
async function trackValidity(event: Event) {
|
||||
if (selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
await state?.open(tabId);
|
||||
formContext?.reportValidity();
|
||||
tabPanelValidationHandlerContext?.addInvalidTab(tabId);
|
||||
}
|
||||
|
||||
panelRef.current?.removeEventListener('invalid', trackValidity, true);
|
||||
panelRef.current?.addEventListener('invalid', trackValidity, true);
|
||||
return () => {
|
||||
panelRef.current?.removeEventListener('invalid', trackValidity, true);
|
||||
};
|
||||
}, [formContext, selected, state, tabId]);
|
||||
}, [tabId]);
|
||||
|
||||
if (enabled) {
|
||||
return null;
|
||||
@@ -55,8 +56,8 @@ export const TabPanel: React.FC<TabPanelProps> = observer(function TabPanel({ ta
|
||||
if (typeof children === 'function') {
|
||||
return (
|
||||
<TabContext.Provider value={tabContext}>
|
||||
<BaseTabPanel ref={panelRef} {...state.state} tabId={tabId} className={className}>
|
||||
<Loader suspense>{(children as (state: TabStateReturn) => React.ReactNode)(state.state)}</Loader>
|
||||
<BaseTabPanel ref={panelRef} {...tabContextState.state} tabId={tabId} className={className}>
|
||||
<Loader suspense>{(children as (state: TabStateReturn) => React.ReactNode)(tabContextState.state)}</Loader>
|
||||
</BaseTabPanel>
|
||||
</TabContext.Provider>
|
||||
);
|
||||
@@ -64,7 +65,7 @@ export const TabPanel: React.FC<TabPanelProps> = observer(function TabPanel({ ta
|
||||
|
||||
return (
|
||||
<TabContext.Provider value={tabContext}>
|
||||
<BaseTabPanel ref={panelRef} {...state.state} tabId={tabId} className={className}>
|
||||
<BaseTabPanel ref={panelRef} {...tabContextState.state} tabId={tabId} className={className}>
|
||||
<Loader suspense>{children}</Loader>
|
||||
</BaseTabPanel>
|
||||
</TabContext.Provider>
|
||||
|
||||
@@ -17,6 +17,7 @@ import { BASE_TAB_STYLES } from './Tab/BASE_TAB_STYLES';
|
||||
import { TabPanel } from './TabPanel';
|
||||
import type { ITabInfo } from './TabsContainer/ITabsContainer';
|
||||
import { TabsContext } from './TabsContext';
|
||||
import { TabPanelValidationHandler } from './TabPanelValidationHandler';
|
||||
|
||||
interface Props extends React.PropsWithChildren {
|
||||
style?: ComponentStyle;
|
||||
@@ -42,7 +43,7 @@ export const TabPanelList = observer<Props>(function TabPanelList({ style, child
|
||||
const displayed = state.container.getDisplayed(state.props);
|
||||
|
||||
return styled(styles)(
|
||||
<>
|
||||
<TabPanelValidationHandler>
|
||||
{displayed
|
||||
.map(
|
||||
generateTabElement(
|
||||
@@ -56,6 +57,6 @@ export const TabPanelList = observer<Props>(function TabPanelList({ style, child
|
||||
)
|
||||
.flat()}
|
||||
{children}
|
||||
</>,
|
||||
</TabPanelValidationHandler>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { TabsContext } from './TabsContext';
|
||||
import { FormContext, useExecutor } from '@cloudbeaver/core-blocks';
|
||||
import { TabPanelValidationHandlerContext } from './TabPanelValidationHandlerContext';
|
||||
import { SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
|
||||
export interface ITabPanelHandlerProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const TabPanelValidationHandler = observer(function TabPanelValidationHandler({ children }: ITabPanelHandlerProps) {
|
||||
const state = useContext(TabsContext);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const formContext = useContext(FormContext);
|
||||
const selectedTab = state?.state.selectedId;
|
||||
const invalidTabs = useRef<Set<string>>(new Set());
|
||||
const [resetExecutor] = useState(() => new SyncExecutor());
|
||||
|
||||
function addInvalidTab(tabId: string) {
|
||||
invalidTabs.current.add(tabId);
|
||||
};
|
||||
|
||||
function resetInvalidTabs() {
|
||||
invalidTabs.current = new Set();
|
||||
}
|
||||
|
||||
useExecutor({
|
||||
executor: resetExecutor,
|
||||
handlers: [resetInvalidTabs],
|
||||
});
|
||||
|
||||
useExecutor({
|
||||
executor: formContext?.onValidate,
|
||||
before: resetExecutor,
|
||||
});
|
||||
|
||||
useExecutor({
|
||||
executor: formContext?.onValidate,
|
||||
postHandlers: [() => formContext?.reportValidity()],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function goNextTab() {
|
||||
const firstInvalidTab = invalidTabs.current.values().next().value;
|
||||
|
||||
if (
|
||||
(selectedTab && invalidTabs.current.has(selectedTab)) ||
|
||||
invalidTabs.current.size === 0 ||
|
||||
!firstInvalidTab
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await state?.open(firstInvalidTab);
|
||||
resetInvalidTabs();
|
||||
}
|
||||
|
||||
formContext?.parent?.ref?.removeEventListener('invalid', goNextTab, true);
|
||||
formContext?.parent?.ref?.addEventListener('invalid', goNextTab, true);
|
||||
|
||||
return () => {
|
||||
formContext?.parent?.ref?.removeEventListener('invalid', goNextTab, true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const value = useMemo(() => ({ addInvalidTab, invalidTabs }), []);
|
||||
|
||||
return (
|
||||
<TabPanelValidationHandlerContext.Provider value={value}>
|
||||
<div ref={containerRef}>
|
||||
{children}
|
||||
</div>
|
||||
</TabPanelValidationHandlerContext.Provider>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { MutableRefObject, createContext } from 'react';
|
||||
|
||||
export interface ITabPanelValidationHandlerContext {
|
||||
invalidTabs: MutableRefObject<Set<string>>;
|
||||
addInvalidTab: (tabId: string) => void;
|
||||
}
|
||||
|
||||
export const TabPanelValidationHandlerContext = createContext<ITabPanelValidationHandlerContext | null>(null);
|
||||
Reference in New Issue
Block a user