Skip to content

Commit 10fbe36

Browse files
committed
fix(cdk/a11y): resolve hydration error in focus trap
Disables focus traps on the server, because they insert DOM nodes that can throw off hydration.
1 parent a8b8e62 commit 10fbe36

File tree

3 files changed

+19
-11
lines changed

3 files changed

+19
-11
lines changed

src/cdk/a11y/focus-trap/focus-trap.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {_getFocusedElementPierceShadowDom} from '@angular/cdk/platform';
9+
import {Platform, _getFocusedElementPierceShadowDom} from '@angular/cdk/platform';
1010
import {DOCUMENT} from '@angular/common';
1111
import {
1212
AfterContentInit,
@@ -21,6 +21,7 @@ import {
2121
SimpleChanges,
2222
OnChanges,
2323
booleanAttribute,
24+
inject,
2425
} from '@angular/core';
2526
import {take} from 'rxjs/operators';
2627
import {InteractivityChecker} from '../interactivity-checker/interactivity-checker';
@@ -416,10 +417,12 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoC
416417
/** Whether the focus trap is active. */
417418
@Input({alias: 'cdkTrapFocus', transform: booleanAttribute})
418419
get enabled(): boolean {
419-
return this.focusTrap.enabled;
420+
return this.focusTrap?.enabled || false;
420421
}
421422
set enabled(value: boolean) {
422-
this.focusTrap.enabled = value;
423+
if (this.focusTrap) {
424+
this.focusTrap.enabled = value;
425+
}
423426
}
424427

425428
/**
@@ -437,11 +440,15 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoC
437440
*/
438441
@Inject(DOCUMENT) _document: any,
439442
) {
440-
this.focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement, true);
443+
const platform = inject(Platform);
444+
445+
if (platform.isBrowser) {
446+
this.focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement, true);
447+
}
441448
}
442449

443450
ngOnDestroy() {
444-
this.focusTrap.destroy();
451+
this.focusTrap?.destroy();
445452

446453
// If we stored a previously focused element when using autoCapture, return focus to that
447454
// element now that the trapped region is being destroyed.
@@ -452,15 +459,15 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoC
452459
}
453460

454461
ngAfterContentInit() {
455-
this.focusTrap.attachAnchors();
462+
this.focusTrap?.attachAnchors();
456463

457464
if (this.autoCapture) {
458465
this._captureFocus();
459466
}
460467
}
461468

462469
ngDoCheck() {
463-
if (!this.focusTrap.hasAttached()) {
470+
if (this.focusTrap && !this.focusTrap.hasAttached()) {
464471
this.focusTrap.attachAnchors();
465472
}
466473
}
@@ -472,14 +479,14 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoC
472479
autoCaptureChange &&
473480
!autoCaptureChange.firstChange &&
474481
this.autoCapture &&
475-
this.focusTrap.hasAttached()
482+
this.focusTrap?.hasAttached()
476483
) {
477484
this._captureFocus();
478485
}
479486
}
480487

481488
private _captureFocus() {
482489
this._previouslyFocusedElement = _getFocusedElementPierceShadowDom();
483-
this.focusTrap.focusInitialElementWhenReady();
490+
this.focusTrap?.focusInitialElementWhenReady();
484491
}
485492
}

src/universal-app/kitchen-sink/kitchen-sink.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ <h2>Horizontal Stepper</h2>
449449

450450
<h2>Focus trap</h2>
451451

452-
<div cdkTrapFocus cdkTrapFocusAutoCapture>
452+
<div cdkTrapFocus [cdkTrapFocusAutoCapture]="isAutomated">
453453
<button>Oh no, I'm trapped!</button>
454454
</div>
455455

src/universal-app/kitchen-sink/kitchen-sink.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {FocusMonitor} from '@angular/cdk/a11y';
1+
import {A11yModule, FocusMonitor} from '@angular/cdk/a11y';
22
import {DragDropModule} from '@angular/cdk/drag-drop';
33
import {ScrollingModule, ViewportRuler} from '@angular/cdk/scrolling';
44
import {CdkTableModule, DataSource} from '@angular/cdk/table';
@@ -122,6 +122,7 @@ export class TestEntryComponent {}
122122
// CDK Modules
123123
CdkTableModule,
124124
DragDropModule,
125+
A11yModule,
125126

126127
// Other modules
127128
YouTubePlayer,

0 commit comments

Comments
 (0)