diff --git a/src/Modal.tsx b/src/Modal.tsx index 1555c04..5500003 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -4,6 +4,7 @@ import activeElement from 'dom-helpers/activeElement'; import contains from 'dom-helpers/contains'; import canUseDOM from 'dom-helpers/canUseDOM'; import listen from 'dom-helpers/listen'; + import { useState, useRef, @@ -299,9 +300,7 @@ const Modal: React.ForwardRefExoticComponent< removeFocusListenerRef.current = listen( document as any, 'focus', - // the timeout is necessary b/c this will run before the new modal is mounted - // and so steals focus from it - () => setTimeout(handleEnforceFocus), + handleEnforceFocus, true, ); @@ -363,7 +362,7 @@ const Modal: React.ForwardRefExoticComponent< // -------------------------------- - const handleEnforceFocus = useEventCallback(() => { + const handleEnforceFocus = useEventCallback((event: FocusEvent) => { if (!enforceFocus || !isMounted() || !modal.isTopModal()) { return; } @@ -375,7 +374,9 @@ const Modal: React.ForwardRefExoticComponent< currentActiveElement && !contains(modal.dialog, currentActiveElement) ) { + event.preventDefault(); modal.dialog.focus(); + manager.maybeResetScrollPosition(); } }); @@ -417,7 +418,8 @@ const Modal: React.ForwardRefExoticComponent< role, ref: modal.setDialogRef, // apparently only works on the dialog role element - 'aria-modal': role === 'dialog' ? true : undefined, + 'aria-modal': + role === 'dialog' || role === 'alertdialog' ? true : undefined, ...rest, style, className, diff --git a/src/ModalManager.ts b/src/ModalManager.ts index fbab930..3b83819 100644 --- a/src/ModalManager.ts +++ b/src/ModalManager.ts @@ -1,4 +1,8 @@ import css from 'dom-helpers/css'; +import getSetScrollTop from 'dom-helpers/scrollTop'; +import getSetScrollLeft from 'dom-helpers/scrollLeft'; +import getScrollParent from 'dom-helpers/scrollParent'; +import isDocument from 'dom-helpers/isDocument'; import { dataAttr } from './DataKey'; import getBodyScrollbarWidth from './getScrollbarWidth'; @@ -51,6 +55,11 @@ class ModalManager { return getBodyScrollbarWidth(this.ownerDocument); } + protected getScollingElement() { + const element = getScrollParent(this.getElement()); + return isDocument(element) ? element.defaultView! : element; + } + getElement() { return (this.ownerDocument || document).body; } @@ -113,8 +122,12 @@ class ModalManager { return modalIdx; } + const scrollElement = this.getScollingElement() as Element; + this.state = { scrollBarWidth: this.getScrollbarWidth(), + scrollTop: getSetScrollTop(scrollElement), + scrollLeft: getSetScrollLeft(scrollElement), style: {}, }; @@ -125,6 +138,19 @@ class ModalManager { return modalIdx; } + maybeResetScrollPosition() { + const scrollElement = this.getScollingElement() as Element; + + const { scrollTop, scrollLeft } = this.state; + + if (getSetScrollTop(scrollElement) !== scrollTop) { + getSetScrollTop(scrollElement, scrollTop); + } + if (getSetScrollLeft(scrollElement) !== scrollLeft) { + getSetScrollLeft(scrollElement, scrollLeft); + } + } + remove(modal: ModalInstance) { const modalIdx = this.modals.indexOf(modal);