@@ -24,6 +24,9 @@ import 'package:angular_components/utils/disposer/disposer.dart';
2424class GlobalModalStack {
2525 final List <Modal > _stack = List <Modal >();
2626
27+ /// Size of the stack.
28+ int get length => _stack.length;
29+
2730 /// Should be triggered when [modal] is closed.
2831 void onModalClosed (Modal modal) {
2932 assert (_stack.last == modal);
@@ -152,6 +155,7 @@ class ModalComponent
152155 final Element _element;
153156 final Modal _parentModal;
154157 final GlobalModalStack _stack;
158+ final NgZone _ngZone;
155159
156160 @override
157161 Stream <AsyncAction > get onOpen => _onOpen.stream;
@@ -171,11 +175,19 @@ class ModalComponent
171175 bool _isHidden = false ;
172176 bool _isVisible = false ;
173177 OverlayRef _resolvedOverlayRef;
178+ Element _lastFocusedElement;
179+
180+ /// Whether to return focus to the last focused element before the modal
181+ /// opened.
182+ ///
183+ /// Defaults to true.
184+ @Input ()
185+ bool restoreFocus = true ;
174186
175187 Future <bool > _pendingOpen;
176188 Future <bool > _pendingClose;
177189
178- ModalComponent (OverlayService overlayService, this ._element,
190+ ModalComponent (OverlayService overlayService, this ._element, this ._ngZone,
179191 @Optional () @SkipSelf () this ._parentModal, @Optional () this ._stack) {
180192 _createdOverlayRef (
181193 overlayService.createOverlayRefSync (OverlayState .Dialog ));
@@ -239,6 +251,7 @@ class ModalComponent
239251 //
240252 // If it has a parent, we should temporarily hide it.
241253 void _showModalOverlay ({bool temporary = false }) {
254+ _saveFocus ();
242255 if (! temporary) {
243256 if (_stack != null ) {
244257 _stack.onModalOpened (this );
@@ -253,6 +266,7 @@ class ModalComponent
253266 //
254267 // If it has a parent, we should re-show it.
255268 void _hideModalOverlay ({bool temporary = false }) {
269+ _restoreFocus ();
256270 if (! temporary) {
257271 if (_stack != null ) {
258272 _stack.onModalClosed (this );
@@ -263,6 +277,28 @@ class ModalComponent
263277 _resolvedOverlayRef.setVisible (false );
264278 }
265279
280+ void _saveFocus () {
281+ _lastFocusedElement = restoreFocus ? document.activeElement : null ;
282+ }
283+
284+ void _restoreFocus () {
285+ if (_lastFocusedElement == null ) return ;
286+ if (_stack != null && _stack.length > 1 || _parentModal != null ) return ;
287+ // Only restore focus if the current active element is inside this overlay.
288+ // Note in a browser activeElement is never null and the null check below is
289+ // only for testing.
290+ if (document.activeElement == null ||
291+ ! _resolvedOverlayRef.overlayElement.contains (document.activeElement)) {
292+ return ;
293+ }
294+ final elementToFocus = _lastFocusedElement;
295+ scheduleMicrotask (() {
296+ // Note that if the [elementToFocus] is no longer in the document,
297+ // the body element will be focused instead.
298+ elementToFocus.focus ();
299+ });
300+ }
301+
266302 @override
267303 Future <bool > open () {
268304 if (_pendingOpen == null ) {
0 commit comments