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

Commit 027f1d9

Browse files
TedSandernshahan
authored andcommitted
Fix focus issues with modal dialogs this includes:
a) Cleaning up global stack when all the dialogs are closed. b) Only save/restore focus when it was not opened temporarily. c) Allow a trigger to have autoFocus if the content is not the one that should be focused. d) Have the demos use all these capabilities. Add tests for all of this newness. PiperOrigin-RevId: 224929512
1 parent 11dc2ed commit 027f1d9

File tree

1 file changed

+25
-15
lines changed
  • angular_components/lib/laminate/components/modal

1 file changed

+25
-15
lines changed

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

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:angular_components/content/deferred_content_aware.dart';
1010
import 'package:angular_components/src/laminate/components/modal/modal_controller_directive.dart';
1111
import 'package:angular_components/laminate/overlay/overlay.dart';
1212
import 'package:angular_components/model/action/async_action.dart';
13+
import 'package:angular_components/utils/browser/dom_service/dom_service.dart';
1314
import 'package:angular_components/utils/disposer/disposer.dart';
1415

1516
/// May be added to DI to enforce that a single [Modal] is visible at a time.
@@ -47,6 +48,13 @@ class GlobalModalStack {
4748
}
4849
_stack.add(modal);
4950
}
51+
52+
/// Tell all the modals to close in reverse order.
53+
Future<void> closeAll() async {
54+
for (var modal in _stack.reversed.toList()) {
55+
await modal.close();
56+
}
57+
}
5058
}
5159

5260
/// An ADT that can be injected by content that lives within a modal.
@@ -155,7 +163,7 @@ class ModalComponent
155163
final Element _element;
156164
final Modal _parentModal;
157165
final GlobalModalStack _stack;
158-
final NgZone _ngZone;
166+
final DomService _domService;
159167

160168
@override
161169
Stream<AsyncAction> get onOpen => _onOpen.stream;
@@ -187,7 +195,7 @@ class ModalComponent
187195
Future<bool> _pendingOpen;
188196
Future<bool> _pendingClose;
189197

190-
ModalComponent(OverlayService overlayService, this._element, this._ngZone,
198+
ModalComponent(OverlayService overlayService, this._element, this._domService,
191199
@Optional() @SkipSelf() this._parentModal, @Optional() this._stack) {
192200
_createdOverlayRef(
193201
overlayService.createOverlayRefSync(OverlayState.Dialog));
@@ -251,8 +259,8 @@ class ModalComponent
251259
//
252260
// If it has a parent, we should temporarily hide it.
253261
void _showModalOverlay({bool temporary = false}) {
254-
_saveFocus();
255262
if (!temporary) {
263+
_saveFocus();
256264
if (_stack != null) {
257265
_stack.onModalOpened(this);
258266
} else if (_parentModal != null) {
@@ -266,8 +274,8 @@ class ModalComponent
266274
//
267275
// If it has a parent, we should re-show it.
268276
void _hideModalOverlay({bool temporary = false}) {
269-
_restoreFocus();
270277
if (!temporary) {
278+
_restoreFocus();
271279
if (_stack != null) {
272280
_stack.onModalClosed(this);
273281
} else if (_parentModal != null) {
@@ -284,18 +292,20 @@ class ModalComponent
284292
void _restoreFocus() {
285293
if (_lastFocusedElement == null) return;
286294
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-
}
294295
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();
296+
_domService.scheduleWrite(() {
297+
// Only restore focus if the current active element is inside this overlay
298+
// or the focus was lost.
299+
// Note in a browser activeElement is never null and the null check below
300+
// is only for testing.
301+
if (document.activeElement != null &&
302+
(_resolvedOverlayRef.overlayElement
303+
.contains(document.activeElement) ||
304+
document.activeElement == document.body)) {
305+
// Note that if the [elementToFocus] is no longer in the document,
306+
// the body element will be focused instead.
307+
elementToFocus.focus();
308+
}
299309
});
300310
}
301311

0 commit comments

Comments
 (0)