mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-20 19:02:15 +00:00
FocusableProvider determine focusable based on routescontainer and modalscontainer childlist
This commit is contained in:
parent
15b135bc2f
commit
349e6112ab
4 changed files with 58 additions and 90 deletions
|
|
@ -1,90 +1,55 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const { withModalsContainer } = require('../ModalsContainerContext');
|
||||
const { useRoutesContainer } = require('../RoutesContainerContext');
|
||||
const { useModalsContainer } = require('../ModalsContainerContext');
|
||||
const FocusableContext = require('./FocusableContext');
|
||||
|
||||
class FocusableProvider extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.contentRef = React.createRef();
|
||||
this.modalsContainerDomTreeObserver = new MutationObserver(this.onModalsContainerDomTreeChange);
|
||||
this.state = {
|
||||
focusable: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.findFocusInContentContainer();
|
||||
this.onModalsContainerDomTreeChange();
|
||||
this.modalsContainerDomTreeObserver.observe(this.props.modalsContainer, {
|
||||
childList: true
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.modalsContainerDomTreeObserver.disconnect();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextState.focusable !== this.state.focusable ||
|
||||
nextProps.modalsContainer !== this.props.modalsContainer ||
|
||||
nextProps.children !== this.props.children;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.focusable !== this.state.focusable) {
|
||||
this.findFocusInContentContainer();
|
||||
}
|
||||
|
||||
if (prevProps.modalsContainer !== this.props.modalsContainer) {
|
||||
this.onModalsContainerDomTreeChange();
|
||||
this.modalsContainerDomTreeObserver.disconnect();
|
||||
this.modalsContainerDomTreeObserver.observe(this.props.modalsContainer, {
|
||||
childList: true
|
||||
const FocusableProvider = ({ children, ...props }) => {
|
||||
const routesContainer = useRoutesContainer();
|
||||
const modalsContainer = useModalsContainer();
|
||||
const contentContainerRef = React.useRef();
|
||||
const [focusable, setFocusable] = React.useState(false);
|
||||
const onFocusableChange = React.useCallback(() => {
|
||||
const focusable =
|
||||
props.onModalsContainerDomTreeChange({
|
||||
modalsContainer: modalsContainer,
|
||||
contentContainer: contentContainerRef.current
|
||||
})
|
||||
&&
|
||||
props.onRoutesContainerDomTreeChange({
|
||||
routesContainer: routesContainer,
|
||||
contentContainer: contentContainerRef.current
|
||||
});
|
||||
setFocusable(focusable);
|
||||
}, []);
|
||||
const [modalsContainerDomTreeObserver, routesContainerDomTreeObserver] = React.useMemo(() => {
|
||||
return [new MutationObserver(onFocusableChange), new MutationObserver(onFocusableChange)];
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
onFocusableChange();
|
||||
modalsContainerDomTreeObserver.observe(modalsContainer, { childList: true });
|
||||
routesContainerDomTreeObserver.observe(routesContainer, { childList: true });
|
||||
return () => {
|
||||
modalsContainerDomTreeObserver.disconnect();
|
||||
routesContainerDomTreeObserver.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
findFocusInContentContainer = () => {
|
||||
if (!this.state.focusable || this.contentRef.current.hidden) {
|
||||
return;
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
if (focusable) {
|
||||
contentContainerRef.current.focus();
|
||||
}
|
||||
|
||||
const focusableChild = Array.from(this.contentRef.current.querySelectorAll('[tabIndex="0"]'))
|
||||
.find((element) => !element.hidden);
|
||||
if (focusableChild) {
|
||||
focusableChild.focus();
|
||||
} else {
|
||||
this.contentRef.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onModalsContainerDomTreeChange = () => {
|
||||
const focusable = this.props.onModalsContainerDomTreeChange({
|
||||
modalsContainerElement: this.props.modalsContainer,
|
||||
contentElement: this.contentRef.current
|
||||
});
|
||||
this.setState({ focusable });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FocusableContext.Provider value={this.state.focusable}>
|
||||
{React.cloneElement(React.Children.only(this.props.children), { ref: this.contentRef, tabIndex: -1 })}
|
||||
</FocusableContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FocusableProvider.propTypes = {
|
||||
modalsContainer: PropTypes.instanceOf(HTMLElement).isRequired,
|
||||
onModalsContainerDomTreeChange: PropTypes.func.isRequired,
|
||||
children: PropTypes.node.isRequired
|
||||
}, [focusable]);
|
||||
return (
|
||||
<FocusableContext.Provider value={focusable}>
|
||||
{React.cloneElement(React.Children.only(children), { ref: contentContainerRef, tabIndex: -1 })}
|
||||
</FocusableContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const FocusableProviderWithModalsContainer = withModalsContainer(FocusableProvider);
|
||||
FocusableProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
onModalsContainerDomTreeChange: PropTypes.func.isRequired,
|
||||
onRoutesContainerDomTreeChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
FocusableProviderWithModalsContainer.displayName = 'FocusableProviderWithModalsContainer';
|
||||
|
||||
module.exports = FocusableProviderWithModalsContainer;
|
||||
module.exports = FocusableProvider;
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@ const { useModalsContainer } = require('../ModalsContainerContext');
|
|||
|
||||
const Modal = ({ children }) => {
|
||||
const modalsContainer = useModalsContainer();
|
||||
const onModalsContainerDomTreeChange = React.useCallback(({ modalsContainerElement, contentElement }) => {
|
||||
return modalsContainerElement.lastElementChild === contentElement;
|
||||
const onRoutesContainerDomTreeChange = React.useCallback(({ routesContainer, contentContainer }) => {
|
||||
return routesContainer.lastElementChild === contentContainer.parentElement.parentElement;
|
||||
}, []);
|
||||
const onModalsContainerDomTreeChange = React.useCallback(({ modalsContainer, contentContainer }) => {
|
||||
return modalsContainer.lastElementChild === contentContainer;
|
||||
}, []);
|
||||
return ReactDOM.createPortal(
|
||||
<FocusableProvider onModalsContainerDomTreeChange={onModalsContainerDomTreeChange}>
|
||||
<FocusableProvider onRoutesContainerDomTreeChange={onRoutesContainerDomTreeChange} onModalsContainerDomTreeChange={onModalsContainerDomTreeChange}>
|
||||
<div>{children}</div>
|
||||
</FocusableProvider>,
|
||||
modalsContainer
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@ const { ModalsContainerProvider } = require('../ModalsContainerContext');
|
|||
const styles = require('./styles');
|
||||
|
||||
const Route = ({ children }) => {
|
||||
const onModalsContainerDomTreeChange = React.useCallback(({ modalsContainerElement }) => {
|
||||
return modalsContainerElement.childElementCount === 0;
|
||||
const onRoutesContainerDomTreeChange = React.useCallback(({ routesContainer, contentContainer }) => {
|
||||
return routesContainer.lastElementChild === contentContainer.parentElement;
|
||||
}, []);
|
||||
const onModalsContainerDomTreeChange = React.useCallback(({ modalsContainer }) => {
|
||||
return modalsContainer.childElementCount === 0;
|
||||
}, []);
|
||||
return (
|
||||
<div className={styles['route-container']}>
|
||||
<ModalsContainerProvider containerClassName={styles['modals-container']}>
|
||||
<FocusableProvider onModalsContainerDomTreeChange={onModalsContainerDomTreeChange}>
|
||||
<div className={styles['route-content']}>
|
||||
{children}
|
||||
</div>
|
||||
<FocusableProvider onRoutesContainerDomTreeChange={onRoutesContainerDomTreeChange} onModalsContainerDomTreeChange={onModalsContainerDomTreeChange}>
|
||||
<div className={styles['route-content']}>{children}</div>
|
||||
</FocusableProvider>
|
||||
</ModalsContainerProvider>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ const Router = ({ className, homePath, ...props }) => {
|
|||
window.removeEventListener('hashchange', onLocationHashChanged);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<RoutesContainerProvider containerClassName={classnames(className, styles['router-container'])}>
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue