router refactored to use custom events instead of MutationObserver

This commit is contained in:
NikolaBorislavovHristov 2019-08-30 11:06:09 +03:00
parent 2cfe9071d4
commit 7f1106a2dd
9 changed files with 85 additions and 58 deletions

View file

@ -1,38 +1,36 @@
const React = require('react');
const PropTypes = require('prop-types');
const { useRoutesContainer } = require('../RoutesContainerContext');
const { useModalsContainer } = require('../ModalsContainerContext');
const { useRoutesContainer } = require('../RoutesContainerContext');
const FocusableContext = require('./FocusableContext');
const FocusableProvider = ({ children, onRoutesContainerDomTreeChange, onModalsContainerDomTreeChange }) => {
const FocusableProvider = ({ children, onRoutesContainerChildrenChange, onModalsContainerChildrenChange }) => {
const routesContainer = useRoutesContainer();
const modalsContainer = useModalsContainer();
const contentContainerRef = React.useRef(null);
const [focusable, setFocusable] = React.useState(false);
React.useEffect(() => {
const onDomTreeChange = () => {
const focusable =
onRoutesContainerDomTreeChange({
const onContainerChildrenChange = () => {
setFocusable(
onRoutesContainerChildrenChange({
routesContainer: routesContainer,
contentContainer: contentContainerRef.current
})
&&
onModalsContainerDomTreeChange({
onModalsContainerChildrenChange({
modalsContainer: modalsContainer,
contentContainer: contentContainerRef.current
});
setFocusable(focusable);
})
);
};
const routesContainerDomTreeObserver = new MutationObserver(onDomTreeChange);
const modalsContainerDomTreeObserver = new MutationObserver(onDomTreeChange);
routesContainerDomTreeObserver.observe(routesContainer, { childList: true });
modalsContainerDomTreeObserver.observe(modalsContainer, { childList: true });
onDomTreeChange();
routesContainer.addEventListener('childrenchange', onContainerChildrenChange);
modalsContainer.addEventListener('childrenchange', onContainerChildrenChange);
onContainerChildrenChange();
return () => {
routesContainerDomTreeObserver.disconnect();
modalsContainerDomTreeObserver.disconnect();
routesContainer.removeEventListener('childrenchange', onContainerChildrenChange);
modalsContainer.removeEventListener('childrenchange', onContainerChildrenChange);
};
}, [routesContainer, modalsContainer, onRoutesContainerDomTreeChange, onModalsContainerDomTreeChange]);
}, [routesContainer, modalsContainer, onRoutesContainerChildrenChange, onModalsContainerChildrenChange]);
React.useEffect(() => {
if (focusable && !contentContainerRef.current.contains(document.activeElement)) {
contentContainerRef.current.focus();
@ -50,8 +48,8 @@ const FocusableProvider = ({ children, onRoutesContainerDomTreeChange, onModalsC
FocusableProvider.propTypes = {
children: PropTypes.node.isRequired,
onModalsContainerDomTreeChange: PropTypes.func.isRequired,
onRoutesContainerDomTreeChange: PropTypes.func.isRequired
onRoutesContainerChildrenChange: PropTypes.func.isRequired,
onModalsContainerChildrenChange: PropTypes.func.isRequired
};
module.exports = FocusableProvider;

View file

@ -7,14 +7,26 @@ const { useModalsContainer } = require('../ModalsContainerContext');
const Modal = ({ className, children }) => {
const modalsContainer = useModalsContainer();
const onRoutesContainerDomTreeChange = React.useCallback(({ routesContainer, contentContainer }) => {
return routesContainer.lastElementChild === contentContainer.parentElement.parentElement;
const onRoutesContainerChildrenChange = React.useCallback(({ routesContainer, contentContainer }) => {
return routesContainer.lastElementChild.contains(contentContainer);
}, []);
const onModalsContainerDomTreeChange = React.useCallback(({ modalsContainer, contentContainer }) => {
const onModalsContainerChildrenChange = React.useCallback(({ modalsContainer, contentContainer }) => {
return modalsContainer.lastElementChild === contentContainer;
}, []);
React.useEffect(() => {
modalsContainer.dispatchEvent(new CustomEvent('childrenchange', {
bubbles: false,
cancelable: false
}));
return () => {
modalsContainer.dispatchEvent(new CustomEvent('childrenchange', {
bubbles: false,
cancelable: false
}));
};
}, [modalsContainer]);
return ReactDOM.createPortal(
<FocusableProvider onRoutesContainerDomTreeChange={onRoutesContainerDomTreeChange} onModalsContainerDomTreeChange={onModalsContainerDomTreeChange}>
<FocusableProvider onRoutesContainerChildrenChange={onRoutesContainerChildrenChange} onModalsContainerChildrenChange={onModalsContainerChildrenChange}>
<div className={classnames(className, 'modal-container')}>{children}</div>
</FocusableProvider>,
modalsContainer

45
src/router/Route/Route.js Normal file
View file

@ -0,0 +1,45 @@
const React = require('react');
const PropTypes = require('prop-types');
const { FocusableProvider } = require('../FocusableContext');
const { ModalsContainerProvider } = require('../ModalsContainerContext');
const { useRoutesContainer } = require('../RoutesContainerContext');
const Route = ({ children }) => {
const routesContainer = useRoutesContainer();
const onRoutesContainerChildrenChange = React.useCallback(({ routesContainer, contentContainer }) => {
return routesContainer.lastElementChild.contains(contentContainer);
}, []);
const onModalsContainerChildrenChange = React.useCallback(({ modalsContainer }) => {
return modalsContainer.childElementCount === 0;
}, []);
React.useEffect(() => {
routesContainer.dispatchEvent(new CustomEvent('childrenchange', {
bubbles: false,
cancelable: false
}));
return () => {
routesContainer.dispatchEvent(new CustomEvent('childrenchange', {
bubbles: false,
cancelable: false
}));
};
}, [routesContainer]);
return (
<div className={'route-container'}>
<ModalsContainerProvider>
<FocusableProvider onRoutesContainerChildrenChange={onRoutesContainerChildrenChange} onModalsContainerChildrenChange={onModalsContainerChildrenChange}>
<div className={'route-content'}>{children}</div>
</FocusableProvider>
</ModalsContainerProvider>
</div>
);
};
Route.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
])
};
module.exports = Route;

View file

@ -0,0 +1,3 @@
const Route = require('./Route');
module.exports = Route;

View file

@ -1,31 +0,0 @@
const React = require('react');
const PropTypes = require('prop-types');
const { FocusableProvider } = require('../FocusableContext');
const { ModalsContainerProvider } = require('../ModalsContainerContext');
const Route = ({ children }) => {
const onRoutesContainerDomTreeChange = React.useCallback(({ routesContainer, contentContainer }) => {
return routesContainer.lastElementChild === contentContainer.parentElement;
}, []);
const onModalsContainerDomTreeChange = React.useCallback(({ modalsContainer }) => {
return modalsContainer.childElementCount === 0;
}, []);
return (
<div className={'route-container'}>
<ModalsContainerProvider>
<FocusableProvider onRoutesContainerDomTreeChange={onRoutesContainerDomTreeChange} onModalsContainerDomTreeChange={onModalsContainerDomTreeChange}>
<div className={'route-content'}>{children}</div>
</FocusableProvider>
</ModalsContainerProvider>
</div>
);
};
Route.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
])
};
module.exports = Route;

View file

@ -1,12 +1,12 @@
const React = require('react');
const PropTypes = require('prop-types');
const ReactIs = require('react-is');
const PropTypes = require('prop-types');
const UrlUtils = require('url');
const Route = require('../Route');
const { RoutesContainerProvider } = require('../RoutesContainerContext');
const queryParamsForQuery = require('../queryParamsForQuery');
const routeConfigForPath = require('../routeConfigForPath');
const urlParamsForPath = require('../urlParamsForPath');
const Route = require('./Route');
const queryParamsForQuery = require('./queryParamsForQuery');
const routeConfigForPath = require('./routeConfigForPath');
const urlParamsForPath = require('./urlParamsForPath');
const Router = ({ onPathNotMatch, ...props }) => {
const [{ homePath, viewsConfig }] = React.useState(() => ({