diff --git a/package.json b/package.json index 369c297e..1b49f406 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "localized", "material", "bootstrap", - "i18n" + "i18n", + "modal" ], "files": [ "lang", diff --git a/src/index.js b/src/index.js index 07988e0f..a1ed5ef8 100644 --- a/src/index.js +++ b/src/index.js @@ -239,12 +239,15 @@ class PhoneInput extends React.Component { if(this.props.onMount){ this.props.onMount(this.state.formattedNumber.replace(/[^0-9]+/g,''), this.getCountryData(), this.state.formattedNumber) } + window.addEventListener('resize', this.handleWindowResize); } componentWillUnmount() { if (document.removeEventListener && this.props.enableClickOutside) { document.removeEventListener('mousedown', this.handleClickOutside); } + window.removeEventListener('resize', this.handleWindowResize); + this.handleOverflowDisposal(); } componentDidUpdate(prevProps, prevState, snapshot) { @@ -489,12 +492,102 @@ class PhoneInput extends React.Component { } } + handleWindowResize = debounce(() => { + if (this.state.showDropdown) { + const newModalStyle = this.calculateModalPosition(); + this.setState({ modalStyle: newModalStyle }); + } + }, 100); + + calculateModalPosition = () => { + if (!this.rootRef) return {}; + + const root = this.rootRef; + const { x, y, height } = root.getBoundingClientRect(); + const { innerHeight, innerWidth } = window; + + let dropdownHeight = 200; + let mTop = 0; + let dropdownWidth = 300; + + if (this.dropdownRef) { + const dropdownRect = this.dropdownRef.getBoundingClientRect(); + const { marginTop } = window.getComputedStyle(this.dropdownRef); + dropdownHeight = dropdownRect.height; + dropdownWidth = dropdownRect.width; + mTop = parseInt(marginTop) + } + + const spaceBelow = innerHeight - (y + height); + const spaceAbove = y; + + let modalTop = y + height; + let modalLeft = x + 1; + + if (spaceBelow < dropdownHeight + dropdownHeight * 0.1 && spaceAbove >= dropdownHeight + dropdownHeight * 0.1) { + modalTop = y - dropdownHeight - mTop * 2; + } + + if (modalLeft + dropdownWidth > innerWidth) { + modalLeft = innerWidth - dropdownWidth - 10; + } + if (modalLeft < 0) { + modalLeft = 10; + } + + return { + position: 'absolute', + left: modalLeft, + top: modalTop + }; + } + + /** + * ### Create Modal + * + * this function will render list insite fixed modal + * + * this way it will avoid various issues + * that occure on overflows, dialogs and many more + */ + handleCreateModal() { + const root = this.rootRef; + if(!root) return; + + document.documentElement.style.overflow = 'hidden'; + + const initialModalStyle = this.calculateModalPosition(); + const modalStyle = this.state.modalStyle.position ? this.state.modalStyle : initialModalStyle; + + requestAnimationFrame(() => { + if (this.dropdownRef) { + const refinedStyle = this.calculateModalPosition(); + this.setState({ modalStyle: refinedStyle }); + } + }); + + return ( +
+ {this.getCountryDropdownList()} +
+ ); + } + /** + * ### Dispose Overflow + * + * this function will dispose overflow hidden on HTML + */ + handleOverflowDisposal() { + document.documentElement.removeAttribute('style'); + this.setState({ modalStyle: {} }); + } handleFlagDropdownClick = (e) => { e.preventDefault(); if (!this.state.showDropdown && this.props.disabled) return; const { preferredCountries, onlyCountries, selectedCountry } = this.state + const allCountries = this.concatPreferredCountries(preferredCountries, onlyCountries); const highlightCountryIndex = allCountries.findIndex(o => @@ -503,6 +596,7 @@ class PhoneInput extends React.Component { this.setState({ showDropdown: !this.state.showDropdown, highlightCountryIndex, + modalStyle: this.state.showDropdown ? {} : this.calculateModalPosition() }, () => { if (this.state.showDropdown) { this.scrollTo(this.getElement(this.state.highlightCountryIndex)); @@ -628,6 +722,7 @@ class PhoneInput extends React.Component { formattedNumber, searchValue: '' }, () => { + this.handleOverflowDisposal(); this.cursorToEnd(); if (this.props.onChange) this.props.onChange(formattedNumber.replace(/[^0-9]+/g,''), this.getCountryData(), e, formattedNumber); }); @@ -728,9 +823,10 @@ class PhoneInput extends React.Component { break; case keys.ESC: case keys.TAB: - this.setState({ - showDropdown: false - }, this.cursorToEnd); + this.setState({ showDropdown: false }, () => { + this.handleOverflowDisposal(); + this.cursorToEnd(); + }); break; default: if ((e.which >= keys.A && e.which <= keys.Z) || e.which === keys.SPACE) { @@ -751,7 +847,11 @@ class PhoneInput extends React.Component { handleClickOutside = (e) => { if (this.dropdownRef && !this.dropdownContainerRef.contains(e.target)) { - this.state.showDropdown && this.setState({ showDropdown: false }); + if (this.state.showDropdown) { + this.setState({ showDropdown: false }, () => { + this.handleOverflowDisposal(); + }); + } } } @@ -953,6 +1053,7 @@ class PhoneInput extends React.Component { return (
this.rootRef = el} className={`${containerClasses} ${this.props.className}`} style={this.props.style || this.props.containerStyle} onKeyDown={this.handleKeydown}> @@ -999,13 +1100,18 @@ class PhoneInput extends React.Component { role='button' aria-haspopup="listbox" aria-expanded={showDropdown ? true : undefined} + aria-controls='modal-root' >
{!disableDropdown &&
}
} - {showDropdown && this.getCountryDropdownList()} + {showDropdown && ( + + )} ); diff --git a/src/style/bootstrap.less b/src/style/bootstrap.less index 01317a38..cfe14013 100644 --- a/src/style/bootstrap.less +++ b/src/style/bootstrap.less @@ -183,4 +183,9 @@ padding: 0 5px; white-space: nowrap; } + .modal-root { + position: fixed; + z-index: 99999; + inset: 0px; + } } diff --git a/src/style/high-res.less b/src/style/high-res.less index 21d2b2b6..08fdb0b3 100644 --- a/src/style/high-res.less +++ b/src/style/high-res.less @@ -168,4 +168,9 @@ padding: 0 2px; white-space: nowrap; } + .modal-root { + position: fixed; + z-index: 99999; + inset: 0px; + } } diff --git a/src/style/material.less b/src/style/material.less index 913d9941..730061ea 100644 --- a/src/style/material.less +++ b/src/style/material.less @@ -186,4 +186,9 @@ font-size: 13px; white-space: nowrap; } + .modal-root { + position: fixed; + z-index: 99999; + inset: 0px; + } } diff --git a/src/style/plain.less b/src/style/plain.less index 707efabe..98acb1c8 100644 --- a/src/style/plain.less +++ b/src/style/plain.less @@ -169,4 +169,9 @@ padding: 0 2px; white-space: nowrap; } + .modal-root { + position: fixed; + z-index: 99999; + inset: 0px; + } } diff --git a/src/style/semantic-ui.less b/src/style/semantic-ui.less index 70d77352..9afee1ff 100644 --- a/src/style/semantic-ui.less +++ b/src/style/semantic-ui.less @@ -190,4 +190,9 @@ padding: 0 2px; white-space: nowrap; } + .modal-root { + position: fixed; + z-index: 99999; + inset: 0px; + } } diff --git a/src/style/style.less b/src/style/style.less index 298a1046..5f96b89c 100644 --- a/src/style/style.less +++ b/src/style/style.less @@ -176,4 +176,9 @@ padding: 0 2px; white-space: nowrap; } + .modal-root { + position: fixed; + z-index: 99999; + inset: 0px; + } }