forked from angular/components
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(dev-app) Add demo page for FocusTrap
Note that much of this demo page is known to not be working with the current focus trap implementation (ex. elements in the focus trap with tabindex > 0 send focus out of the trap, elements in iframes/shadow DOM get skipped when focus wraps, all focus traps can be escaped by clicking on the URL and then pressing tab). The demo page will be helpful for research into FocusTrap improvements (see angular#13054).
- Loading branch information
1 parent
f4d0c18
commit 6894a3c
Showing
11 changed files
with
368 additions
and
0 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package(default_visibility = ["//visibility:public"]) | ||
|
||
load("//tools:defaults.bzl", "ng_module", "sass_binary") | ||
|
||
ng_module( | ||
name = "focus-trap", | ||
srcs = glob(["**/*.ts"]), | ||
assets = [ | ||
"focus-trap-demo.html", | ||
"focus-trap-dialog-demo.html", | ||
":focus_trap_demo_scss", | ||
":focus_trap_dialog_demo_scss", | ||
], | ||
deps = [ | ||
"//src/cdk/a11y", | ||
"//src/material/button", | ||
"//src/material/card", | ||
"//src/material/dialog", | ||
"//src/material/toolbar", | ||
"@npm//@angular/router", | ||
], | ||
) | ||
|
||
sass_binary( | ||
name = "focus_trap_demo_scss", | ||
src = "focus-trap-demo.scss", | ||
) | ||
|
||
sass_binary( | ||
name = "focus_trap_dialog_demo_scss", | ||
src = "focus-trap-dialog-demo.scss", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {A11yModule} from '@angular/cdk/a11y'; | ||
import {CommonModule} from '@angular/common'; | ||
import {NgModule} from '@angular/core'; | ||
import {MatButtonModule} from '@angular/material/button'; | ||
import {MatCardModule} from '@angular/material/card'; | ||
import {MatDialogModule} from '@angular/material/dialog'; | ||
import {MatToolbarModule} from '@angular/material/toolbar'; | ||
import {RouterModule} from '@angular/router'; | ||
import {FocusTrapDemo, FocusTrapShadowDomDemo, FocusTrapDialogDemo} from './focus-trap-demo'; | ||
|
||
@NgModule({ | ||
imports: [ | ||
A11yModule, | ||
CommonModule, | ||
MatButtonModule, | ||
MatCardModule, | ||
MatDialogModule, | ||
MatToolbarModule, | ||
RouterModule.forChild([{path: '', component: FocusTrapDemo}]), | ||
], | ||
declarations: [FocusTrapDemo, FocusTrapShadowDomDemo, FocusTrapDialogDemo], | ||
entryComponents: [FocusTrapDialogDemo], | ||
}) | ||
export class FocusTrapDemoModule { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
<div> | ||
<mat-card class="demo-mat-card"> | ||
<mat-toolbar color="primary">Basic</mat-toolbar> | ||
<mat-card-content class="demo-mat-card-content"> | ||
<button mat-raised-button (click)="toggleFocus(basicFocusTrap)"> | ||
{{basicFocusTrap && basicFocusTrap.enabled ? "Disable" : "Enable"}} FocusTrap | ||
</button> | ||
<div class="demo-focus-trap-region" #basicDemoRegion | ||
[class.demo-focus-trap-enabled]="(basicFocusTrap && basicFocusTrap.enabled) || false"> | ||
<textarea class="demo-focus-trap-element" placeholder="One"></textarea> | ||
<textarea class="demo-focus-trap-element" placeholder="Two"></textarea> | ||
</div> | ||
</mat-card-content> | ||
</mat-card> | ||
|
||
<mat-card class="demo-mat-card"> | ||
<mat-toolbar color="primary">Nested</mat-toolbar> | ||
<mat-card-content class="demo-mat-card-content"> | ||
<button mat-raised-button (click)="toggleFocus(nestedOuterFocusTrap)"> | ||
{{nestedOuterFocusTrap && nestedOuterFocusTrap.enabled ? "Disable" : "Enable"}} outer FocusTrap | ||
</button> | ||
<div class="demo-focus-trap-region" #nestedOuterDemoRegion | ||
[class.demo-focus-trap-enabled]="(nestedOuterFocusTrap && nestedOuterFocusTrap.enabled) || false"> | ||
<textarea class="demo-focus-trap-element" placeholder="One"></textarea> | ||
<textarea class="demo-focus-trap-element" placeholder="Two"></textarea> | ||
<button mat-raised-button class="demo-focus-trap-element" | ||
(click)="toggleFocus(nestedInnerFocusTrap)"> | ||
{{nestedInnerFocusTrap && nestedInnerFocusTrap.enabled ? "Disable" : "Enable"}} inner FocusTrap | ||
</button> | ||
<div class="demo-focus-trap-region" #nestedInnerDemoRegion | ||
[class.demo-focus-trap-enabled]="(nestedInnerFocusTrap && nestedInnerFocusTrap.enabled) || false"> | ||
<textarea class="demo-focus-trap-element" placeholder="Three"></textarea> | ||
<textarea class="demo-focus-trap-element" placeholder="Four"></textarea> | ||
</div> | ||
</div> | ||
</mat-card-content> | ||
</mat-card> | ||
|
||
<mat-card class="demo-mat-card"> | ||
<mat-toolbar color="primary">Tabindex > 0</mat-toolbar> | ||
<mat-card-content class="demo-mat-card-content"> | ||
<button mat-raised-button (click)="toggleFocus(tabIndexFocusTrap)"> | ||
{{tabIndexFocusTrap && tabIndexFocusTrap.enabled ? "Disable" : "Enable"}} FocusTrap | ||
</button> | ||
<div class="demo-focus-trap-region" #tabIndexDemoRegion | ||
[class.demo-focus-trap-enabled]="(tabIndexFocusTrap && tabIndexFocusTrap.enabled) || false"> | ||
<textarea class="demo-focus-trap-element" tabindex="1" | ||
placeholder="I have tabindex 1"></textarea> | ||
<textarea class="demo-focus-trap-element" placeholder="One"></textarea> | ||
<textarea class="demo-focus-trap-element" placeholder="Two"></textarea> | ||
</div> | ||
<textarea class="demo-focus-trap-element" tabindex="1" | ||
placeholder="I have tabindex 1"></textarea> | ||
</mat-card-content> | ||
</mat-card> | ||
|
||
<mat-card class="demo-mat-card"> | ||
<mat-toolbar color="primary">Shadow DOMs</mat-toolbar> | ||
<mat-card-content class="demo-mat-card-content"> | ||
<button mat-raised-button (click)="toggleFocus(shadowDomFocusTrap)"> | ||
{{shadowDomFocusTrap && shadowDomFocusTrap.enabled ? "Disable" : "Enable"}} FocusTrap | ||
</button> | ||
<div class="demo-focus-trap-region" #shadowDomDemoRegion | ||
[class.demo-focus-trap-enabled]="(shadowDomFocusTrap && shadowDomFocusTrap.enabled) || false"> | ||
<shadow-dom-demo> | ||
<textarea placeholder="I am in a shadow DOM"></textarea> | ||
</shadow-dom-demo> | ||
<textarea class="demo-focus-trap-element" placeholder="One"></textarea> | ||
<textarea class="demo-focus-trap-element" placeholder="Two"></textarea> | ||
</div> | ||
<shadow-dom-demo> | ||
<textarea class="demo-focus-trap-element" placeholder="I am in a shadow DOM"></textarea> | ||
</shadow-dom-demo> | ||
</mat-card-content> | ||
</mat-card> | ||
|
||
<mat-card class="demo-mat-card"> | ||
<mat-toolbar color="primary">iframes</mat-toolbar> | ||
<mat-card-content class="demo-mat-card-content"> | ||
<button mat-raised-button (click)="toggleFocus(iframeFocusTrap)"> | ||
{{iframeFocusTrap && iframeFocusTrap.enabled ? "Disable" : "Enable"}} FocusTrap | ||
</button> | ||
<div class="demo-focus-trap-region" #iframeDemoRegion | ||
[class.demo-focus-trap-enabled]="(iframeFocusTrap && iframeFocusTrap.enabled) || false"> | ||
<iframe class="demo-focus-trap-element" | ||
srcdoc="<textarea placeholder='I am in an iframe'></textarea>"> | ||
</iframe> | ||
<textarea class="demo-focus-trap-element" placeholder="One"></textarea> | ||
<textarea class="demo-focus-trap-element" placeholder="Two"></textarea> | ||
</div> | ||
<iframe srcdoc="<textarea placeholder='I am in an iframe'></textarea>"></iframe> | ||
</mat-card-content> | ||
</mat-card> | ||
|
||
<mat-card class="demo-mat-card"> | ||
<mat-toolbar color="primary">Dynamic page content</mat-toolbar> | ||
<mat-card-content class="demo-mat-card-content"> | ||
<button mat-raised-button (click)="toggleFocus(dynamicFocusTrap)"> | ||
{{dynamicFocusTrap && dynamicFocusTrap.enabled ? "Disable" : "Enable"}} FocusTrap | ||
</button> | ||
<div class="demo-focus-trap-region" #dynamicDemoRegion | ||
[class.demo-focus-trap-enabled]="(dynamicFocusTrap && dynamicFocusTrap.enabled) || false"> | ||
<textarea class="demo-focus-trap-element" placeholder="One"></textarea> | ||
<textarea class="demo-focus-trap-element" placeholder="Two"></textarea> | ||
<button mat-raised-button class="demo-focus-trap-element" (click)="addNewElement()"> | ||
Click to add more focusable elements to the page | ||
</button> | ||
</div> | ||
<div #newElements></div> | ||
</mat-card-content> | ||
</mat-card> | ||
|
||
<mat-card class="demo-mat-card"> | ||
<mat-toolbar color="primary">Dialog-on-dialog</mat-toolbar> | ||
<mat-card-content class="demo-mat-card-content"> | ||
<button mat-raised-button (click)="openDialog()">Open dialog</button> | ||
</mat-card-content> | ||
</mat-card> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
.demo-focus-trap-region { | ||
outline: 2px dashed lightgray; | ||
padding: 4px; | ||
margin: 12px 0; | ||
|
||
|
||
&.demo-focus-trap-enabled { | ||
outline: 2px solid red; | ||
} | ||
|
||
.demo-focus-trap-element, .demo-focus-trap-shadow-root { | ||
display: block; | ||
margin: 4px; | ||
} | ||
|
||
.demo-focus-trap-region { | ||
margin: 12px 4px; | ||
} | ||
} | ||
|
||
.demo-focus-trap-shadow-root { | ||
display: block; | ||
padding: 4px; | ||
background-color: lightgrey; | ||
} | ||
|
||
.demo-mat-card { | ||
padding: 0; | ||
margin: 16px; | ||
|
||
& .demo-mat-card-content { | ||
padding: 24px; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {FocusTrap, FocusTrapFactory} from '@angular/cdk/a11y'; | ||
import { | ||
AfterViewInit, | ||
Component, | ||
ElementRef, | ||
ViewChild, | ||
ViewEncapsulation} from '@angular/core'; | ||
import {MatDialog} from '@angular/material/dialog'; | ||
|
||
@Component({ | ||
selector: 'shadow-dom-demo', | ||
template: '<ng-content></ng-content>', | ||
host: {'class': 'demo-focus-trap-shadow-root'}, | ||
encapsulation: ViewEncapsulation.ShadowDom | ||
}) | ||
export class FocusTrapShadowDomDemo {} | ||
|
||
@Component({ | ||
selector: 'focus-trap-demo', | ||
templateUrl: 'focus-trap-demo.html', | ||
styleUrls: ['focus-trap-demo.css'], | ||
}) | ||
export class FocusTrapDemo implements AfterViewInit { | ||
|
||
basicFocusTrap: FocusTrap; | ||
@ViewChild('basicDemoRegion', {static: false}) private readonly _basicDemoRegion!: ElementRef; | ||
|
||
nestedOuterFocusTrap: FocusTrap; | ||
@ViewChild('nestedOuterDemoRegion', {static: false}) | ||
private readonly _nestedOuterDemoRegion!: ElementRef; | ||
nestedInnerFocusTrap: FocusTrap; | ||
@ViewChild('nestedInnerDemoRegion', {static: false}) | ||
private readonly _nestedInnerDemoRegion!: ElementRef; | ||
|
||
tabIndexFocusTrap: FocusTrap; | ||
@ViewChild('tabIndexDemoRegion', {static: false}) | ||
private readonly _tabIndexDemoRegion!: ElementRef; | ||
|
||
shadowDomFocusTrap: FocusTrap; | ||
@ViewChild('shadowDomDemoRegion', {static: false}) | ||
private readonly _shadowDomDemoRegion!: ElementRef; | ||
|
||
iframeFocusTrap: FocusTrap; | ||
@ViewChild('iframeDemoRegion', {static: false}) | ||
private readonly _iframeDemoRegion!: ElementRef; | ||
|
||
dynamicFocusTrap: FocusTrap; | ||
@ViewChild('dynamicDemoRegion', {static: false}) | ||
private readonly _dynamicDemoRegion!: ElementRef; | ||
@ViewChild('newElements', {static: false}) private readonly _newElements!: ElementRef; | ||
|
||
constructor( | ||
public dialog: MatDialog, | ||
private _focusTrapFactory: FocusTrapFactory) {} | ||
|
||
ngAfterViewInit() { | ||
this.basicFocusTrap = this._focusTrapFactory.create(this._basicDemoRegion.nativeElement); | ||
this.basicFocusTrap.enabled = false; | ||
|
||
this.nestedOuterFocusTrap = this._focusTrapFactory.create( | ||
this._nestedOuterDemoRegion.nativeElement); | ||
this.nestedOuterFocusTrap.enabled = false; | ||
|
||
this.nestedInnerFocusTrap = this._focusTrapFactory.create( | ||
this._nestedInnerDemoRegion.nativeElement); | ||
this.nestedInnerFocusTrap.enabled = false; | ||
|
||
this.tabIndexFocusTrap = this._focusTrapFactory.create( | ||
this._tabIndexDemoRegion.nativeElement); | ||
this.tabIndexFocusTrap.enabled = false; | ||
|
||
this.shadowDomFocusTrap = this._focusTrapFactory.create( | ||
this._shadowDomDemoRegion.nativeElement); | ||
this.shadowDomFocusTrap.enabled = false; | ||
|
||
this.iframeFocusTrap = this._focusTrapFactory.create(this._iframeDemoRegion.nativeElement); | ||
this.iframeFocusTrap.enabled = false; | ||
|
||
this.dynamicFocusTrap = this._focusTrapFactory.create(this._dynamicDemoRegion.nativeElement); | ||
this.dynamicFocusTrap.enabled = false; | ||
} | ||
|
||
toggleFocus(focusTrap: FocusTrap) { | ||
focusTrap.enabled = !focusTrap.enabled; | ||
if (focusTrap.enabled) { | ||
focusTrap.focusInitialElementWhenReady(); | ||
} | ||
} | ||
|
||
addNewElement() { | ||
const textarea = document.createElement('textarea'); | ||
textarea.setAttribute('placeholder', 'I am a new element!'); | ||
this._newElements.nativeElement.appendChild(textarea); | ||
} | ||
|
||
openDialog() { | ||
this.dialog.open(FocusTrapDialogDemo); | ||
} | ||
} | ||
|
||
let dialogCount = 0; | ||
|
||
@Component({ | ||
selector: 'focus-trap-dialog-demo', | ||
styleUrls: ['focus-trap-dialog-demo.css'], | ||
templateUrl: 'focus-trap-dialog-demo.html', | ||
}) | ||
export class FocusTrapDialogDemo { | ||
id = dialogCount++; | ||
constructor(public dialog: MatDialog) {} | ||
|
||
openAnotherDialog() { | ||
this.dialog.open(FocusTrapDialogDemo); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<h2 mat-dialog-title>Dialog {{id}}</h2> | ||
|
||
<mat-dialog-content> | ||
<textarea class="demo-dialog-textarea" placeholder="One"></textarea> | ||
<textarea class="demo-dialog-textarea" placeholder="Two"></textarea> | ||
</mat-dialog-content> | ||
|
||
<mat-dialog-actions> | ||
<button mat-raised-button mat-dialog-close> | ||
Close | ||
</button> | ||
|
||
<button mat-raised-button (click)="openAnotherDialog()"> | ||
Open another dialog | ||
</button> | ||
</mat-dialog-actions> |
Oops, something went wrong.