mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
router refactored to use custom events instead of MutationObserver
This commit is contained in:
parent
2cfe9071d4
commit
7f1106a2dd
9 changed files with 85 additions and 58 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
45
src/router/Route/Route.js
Normal 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;
|
||||
3
src/router/Route/index.js
Normal file
3
src/router/Route/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
const Route = require('./Route');
|
||||
|
||||
module.exports = Route;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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(() => ({
|
||||
|
|
|
|||
Loading…
Reference in a new issue