import * as React from "react"; import * as ReactDOM from "react-dom"; import { OptionProps } from "../typings/Option"; import { OptionGroupProps, OptionGroupState } from "../typings/OptionGroup"; import scrollIntoView from "scroll-into-view-if-needed"; import { cx } from "emotion"; import Search from "../Search"; import { searchBoxScrolledStyle, searchBoxWrapper, optionsWrapper, searchBoxHeight } from "../styles/OptionGroup.styles"; import { rowWrapper, advancedActionsWrapper } from "../styles/Options.styles"; class OptionGroup extends React.PureComponent< OptionGroupProps, OptionGroupState > { optionRef: React.RefObject = React.createRef(); optionsRefsSet = new Map>(); observer?: IntersectionObserver; state: Readonly = { highlighted: -1, isScrolled: false }; private handleKeyPress = (e: React.KeyboardEvent) => { const { handleChange, isSelected } = this.props; const children = React.Children.toArray(this.props.children); const { highlighted } = this.state; const { which } = e; if (which === 13 && children && children[highlighted]) { // Enter key // @ts-ignore const { value } = // @ts-ignore children && children[highlighted] && children[highlighted].props; handleChange( { value, checked: !isSelected(value) }, e ); } this.setState( () => { let _highlighted = highlighted; if (which === 40) { _highlighted = Math.min( _highlighted + 1, React.Children.count(children) - 1 ); } if (which === 38) { _highlighted = Math.max(_highlighted - 1, 0); } return { highlighted: _highlighted }; }, () => { const currentRef = this.optionsRefsSet.get(highlighted); if ( this.optionRef.current && (which === 40 || which === 38) && currentRef && currentRef.current ) { const element = ReactDOM.findDOMNode(currentRef.current) as Element; if (element) { scrollIntoView(element, { behavior: "smooth", boundary: this.optionRef.current }); } } } ); }; componentDidMount() { this.observer = new IntersectionObserver( entries => { this.setState({ isScrolled: entries[0].intersectionRatio < 0.9 }); }, { root: this.optionRef.current, threshold: 0.9 } ); if ( this.optionRef.current && this.optionRef.current.childNodes && this.optionRef.current.childNodes.length ) { this.observer.observe(this.optionRef.current.childNodes[0] as Element); } } componentWillUnmount() { if (this.observer) { this.observer.disconnect(); } } render() { const { searchBox, advancedOptions, advancedOptionsProps, children, multiSelect, className, isSelected, handleChange, searchBoxProps } = this.props; const { isScrolled, highlighted } = this.state; const _children = React.Children.map(children, (_option, i) => { // `_option as React.ReactElement` is a hack // Because React does not allow us to specify what sort of elements // you can allow as children and leaves it on you to figure out // all various types of children provided. const option = _option as React.ReactElement>; let ref = this.optionsRefsSet.get(i); if (!ref) { ref = React.createRef(); this.optionsRefsSet.set(i, ref); } return React.cloneElement(option, { onChange: handleChange, isActive: highlighted === i, isSelected: isSelected(option.props.value), multiSelect, // @ts-ignore ref }); }); const searchBoxClassName = cx(searchBoxWrapper, { [searchBoxScrolledStyle]: isScrolled }); const _class = cx( rowWrapper, advancedActionsWrapper, advancedOptionsProps && advancedOptionsProps.className ); return ( {searchBox && searchBoxProps && (
)} {!!React.Children.count(children) && (
{advancedOptions && advancedOptionsProps && (
Select Visible
Clear Visible
)} {_children}
)}
); } } export default OptionGroup;