Skip to content
This repository was archived by the owner on May 20, 2023. It is now read-only.

Commit da83b47

Browse files
cissyshinshahan
authored andcommitted
Try to restore focus when modal closes.
This will be the default behavior in modal component and can be turned off by setting `restoreFocus` to false. PiperOrigin-RevId: 220857845
1 parent 9312f16 commit da83b47

File tree

1 file changed

+37
-1
lines changed
  • angular_components/lib/laminate/components/modal

1 file changed

+37
-1
lines changed

angular_components/lib/laminate/components/modal/modal.dart

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import 'package:angular_components/utils/disposer/disposer.dart';
2424
class 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

Comments
 (0)