diff --git a/CHANGELOG.md b/CHANGELOG.md index a31ad31947a..42ce0671f6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,12 +20,24 @@ All notable changes for each version of this project will be documented in this - For more information, check out the [README](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/stepper/README.md), [specification](https://github.com/IgniteUI/igniteui-angular/wiki/Stepper-Specification) and [official documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/stepper). +- Added `IgxFocusTrap` directive, which traps the Tab key focus within an element. + + ```html +
+ + + +
+ ``` + - `IgxCsvExporterService`, `IgxExcelExporterService` - Exporter services are no longer required to be provided in the application since they are now injected on a root level. - `IgxGridToolbarPinningComponent`, `IgxGridToolbarHidingComponent` - Exposed new input `buttonText` which sets the text that is displayed inside the dropdown button in the toolbar. - `IgxCombo` - Added `groupSortingDirection` input, which allows you to set groups sorting order. +- `IgxDialog` + - Added `focusTrap` input to set whether the Tab key focus is trapped within the dialog when opened. Defaults to `true`. ### General diff --git a/README.md b/README.md index 5bca238553b..b3573c30b03 100644 --- a/README.md +++ b/README.md @@ -41,25 +41,25 @@ Some of the Angular chart types included are: [Polar chart](https://www.infragis |bottom navigation|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/tabs/bottom-nav/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tabbar)|divider|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/button/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/button)| |button group|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/buttonGroup/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/button-group)|dragdrop|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/directives/divider/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/divider)| |calendar|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/calendar/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/calendar)|filter|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/filter/README-FILTER.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/list)| -|card|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/card/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/card)|forOf|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/for-of/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/for-of)| -|carousel|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/carousel/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/carousel)|hint|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/input-group/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input-group)| -|checkbox|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/checkbox/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/checkbox)|input|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/input/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input-group)| -|chips|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/chips/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/chip)|label|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/label/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/label-input)| -|circular progress|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/progressbar/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/circular-progress)|layout|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/layout/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/layout)| -|combo|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/combo/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/combo)|mask|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/mask/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/mask)| -|date picker|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/date-picker/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/date-picker)|prefix|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/input-group/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input-group)| -|date range picker|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/date-range-picker/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/date-range-picker)|radio-group|:white_check_mark:||[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/radio-button)| -|dialog|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/dialog/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/dialog)|ripple|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/ripple/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/ripple)| -|drop down|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/drop-down/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/drop-down)|suffix|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/input-group/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input-group)| -|expansion panel|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/expansion-panel/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/expansion-panel)|text-highlight|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/directives/text-highlight/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/texthighlight)| -|grid|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/grids/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/grid/grid)|toggle|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/toggle/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/toggle)| -|hierarchical grid|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/grids/hierarchical-grid/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/hierarchicalgrid/hierarchical-grid)|tooltip|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/tooltip/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tooltip)| -|icon|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/icon/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/icon)|Others|Status|Docs|| -|input group|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/input-group/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input-group)|Animations|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/animations/README.md)|| -|linear progress|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/progressbar)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/linear-progress)|dataUtil|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/data-operations/README-DATAUTIL.md)|| -|list|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/list/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/list)|dataContainer|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/data-operations/README-DATACONTAINER.md)|| -|month picker|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/calendar/month-picker/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/month-picker)|IgxGridState|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/grids/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/grid/state-persistence)|| -|navbar|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/navbar/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/navbar)||||| +|card|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/card/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/card)|focus-trap|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/focus-trap/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/dialog)| +|carousel|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/carousel/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/carousel)|forOf|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/for-of/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/for-of)| +|checkbox|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/checkbox/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/checkbox)|hint|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/input-group/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input-group)| +|chips|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/chips/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/chip)|input|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/input/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input-group)| +|circular progress|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/progressbar/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/circular-progress)|label|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/label/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/label-input)| +|combo|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/combo/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/combo)|layout|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/layout/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/layout)| +|date picker|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/date-picker/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/date-picker)|mask|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/mask/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/mask)| +|date range picker|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/date-range-picker/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/date-range-picker)|prefix|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/input-group/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input-group)| +|dialog|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/dialog/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/dialog)|radio-group|:white_check_mark:||[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/radio-button)| +|drop down|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/drop-down/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/drop-down)|ripple|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/ripple/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/ripple)| +|expansion panel|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/expansion-panel/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/expansion-panel)|suffix|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/input-group/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input-group)| +|grid|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/grids/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/grid/grid)|text-highlight|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/directives/text-highlight/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/texthighlight)| +|hierarchical grid|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/grids/hierarchical-grid/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/hierarchicalgrid/hierarchical-grid)|toggle|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/toggle/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/toggle)| +|icon|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/icon/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/icon)|tooltip|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/tooltip/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tooltip)| +|input group|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/input-group/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input-group)|Others|Status|Docs|| +|linear progress|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/progressbar)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/linear-progress)|Animations|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/animations/README.md)|| +|list|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/list/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/list)|dataUtil|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/data-operations/README-DATAUTIL.md)|| +|month picker|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/calendar/month-picker/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/month-picker)|dataContainer|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/data-operations/README-DATACONTAINER.md)|| +|navbar|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/navbar/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/navbar)|IgxGridState|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/grids/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/grid/state-persistence)|| |navigation drawer|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/navigation-drawer/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/navdrawer)||||| |radio|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/radio/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/radio-button)||||| |select|:white_check_mark:|[Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/select/README.md)|[Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/select)||||| diff --git a/projects/igniteui-angular/src/lib/dialog/dialog-content.component.html b/projects/igniteui-angular/src/lib/dialog/dialog-content.component.html index 8dbe26d8b24..af5fdf171fb 100644 --- a/projects/igniteui-angular/src/lib/dialog/dialog-content.component.html +++ b/projects/igniteui-angular/src/lib/dialog/dialog-content.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/projects/igniteui-angular/src/lib/dialog/dialog.component.spec.ts b/projects/igniteui-angular/src/lib/dialog/dialog.component.spec.ts index 7e393815e81..1353481f406 100644 --- a/projects/igniteui-angular/src/lib/dialog/dialog.component.spec.ts +++ b/projects/igniteui-angular/src/lib/dialog/dialog.component.spec.ts @@ -8,6 +8,7 @@ import { configureTestSuite } from '../test-utils/configure-suite'; import { useAnimation } from '@angular/animations'; import { PositionSettings, HorizontalAlignment, VerticalAlignment } from '../services/overlay/utilities'; import { slideOutBottom, slideInTop } from '../animations/main'; +import { IgxToggleDirective } from '../directives/toggle/toggle.directive'; const OVERLAY_MAIN_CLASS = 'igx-overlay'; const OVERLAY_WRAPPER_CLASS = `${OVERLAY_MAIN_CLASS}__wrapper`; @@ -81,6 +82,68 @@ describe('Dialog', () => { expect(messageDebugElement.nativeElement.textContent.trim()).toEqual(expectedMessage); }); + it('Should focus focusable elements in dialog on Tab key pressed', () => { + const fix = TestBed.createComponent(DialogComponent); + fix.detectChanges(); + + const dialog = fix.componentInstance.dialog; + dialog.open(); + fix.detectChanges(); + + const buttons = fix.debugElement.queryAll(By.css('button')); + const toggle = fix.debugElement.query(By.directive(IgxToggleDirective)); + + UIInteractions.triggerEventHandlerKeyDown('Tab', toggle); + fix.detectChanges(); + expect(document.activeElement).toEqual(buttons[0].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', toggle); + fix.detectChanges(); + expect(document.activeElement).toEqual(buttons[1].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', toggle); + fix.detectChanges(); + expect(document.activeElement).toEqual(buttons[0].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', toggle, false, true); + fix.detectChanges(); + expect(document.activeElement).toEqual(buttons[1].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', toggle, false, true); + fix.detectChanges(); + expect(document.activeElement).toEqual(buttons[0].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', toggle, false, true); + fix.detectChanges(); + expect(document.activeElement).toEqual(buttons[1].nativeElement); + }); + + it('should trap focus on dialog modal with non-focusable elements', () => { + const fix = TestBed.createComponent(AlertComponent); + fix.detectChanges(); + + const dialog = fix.componentInstance.dialog; + dialog.leftButtonLabel = ''; + fix.detectChanges(); + + dialog.open(); + fix.detectChanges(); + + const toggle = fix.debugElement.query(By.directive(IgxToggleDirective)); + + UIInteractions.triggerEventHandlerKeyDown('Tab', toggle); + fix.detectChanges(); + expect(document.activeElement).toEqual(toggle.nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', toggle, false, true); + fix.detectChanges(); + expect(document.activeElement).toEqual(toggle.nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', toggle); + fix.detectChanges(); + expect(document.activeElement).toEqual(toggle.nativeElement); + }); + it('Should open and close dialog when set values to IsOpen', fakeAsync(() => { const fixture = TestBed.createComponent(AlertComponent); const dialog = fixture.componentInstance.dialog; diff --git a/projects/igniteui-angular/src/lib/dialog/dialog.component.ts b/projects/igniteui-angular/src/lib/dialog/dialog.component.ts index b2d4d201291..e557b46657a 100644 --- a/projects/igniteui-angular/src/lib/dialog/dialog.component.ts +++ b/projects/igniteui-angular/src/lib/dialog/dialog.component.ts @@ -23,6 +23,7 @@ import { IgxToggleModule, IgxToggleDirective } from '../directives/toggle/toggle import { OverlaySettings, GlobalPositionStrategy, NoOpScrollStrategy, PositionSettings } from '../services/public_api'; import {fadeIn, fadeOut} from '../animations/fade/index'; import { IgxFocusModule } from '../directives/focus/focus.directive'; +import { IgxFocusTrapModule } from '../directives/focus-trap/focus-trap.directive'; import { CancelableEventArgs, IBaseEventArgs } from '../core/utils'; let DIALOG_ID = 0; @@ -110,6 +111,16 @@ export class IgxDialogComponent implements IToggleView, OnInit, OnDestroy, After this._closeOnEscape = val; } + /** + * An @Input property to set whether the Tab key focus is trapped within the dialog when opened. + * Defaults to `true`. + * ```html + * + * ``` + */ + @Input() + public focusTrap = true; + /** * An @Input property controlling the `title` of the dialog. * ```html @@ -619,6 +630,6 @@ export interface IDialogCancellableEventArgs extends IDialogEventArgs, Cancelabl @NgModule({ declarations: [IgxDialogComponent, IgxDialogTitleDirective, IgxDialogActionsDirective], exports: [IgxDialogComponent, IgxDialogTitleDirective, IgxDialogActionsDirective], - imports: [CommonModule, IgxToggleModule, IgxButtonModule, IgxRippleModule, IgxFocusModule] + imports: [CommonModule, IgxToggleModule, IgxButtonModule, IgxRippleModule, IgxFocusModule, IgxFocusTrapModule] }) export class IgxDialogModule { } diff --git a/projects/igniteui-angular/src/lib/directives/focus-trap/README.md b/projects/igniteui-angular/src/lib/directives/focus-trap/README.md new file mode 100644 index 00000000000..76744cbdac0 --- /dev/null +++ b/projects/igniteui-angular/src/lib/directives/focus-trap/README.md @@ -0,0 +1,17 @@ +# IgxFocusTrap Directive + +The **IgxFocusTrap** directive provides functionality to trap the focus within an element. The focus should not leave the element when the user keeps tabbing through the focusable elements. Typically, when the focus leaves the last element, it should move to the first element. And vice versa, when SHIFT + TAB is pressed, when the focus leaves the first element, the last element should be focused. In case the element does not contain any focusable elements, the focus will be trapped on the element itself. + +#Usage +```typescript +import { IgxFocusTrapModule } from "igniteui-angular"; +``` + +Basic initialization +```html +
+ + + +
+``` diff --git a/projects/igniteui-angular/src/lib/directives/focus-trap/focus-trap.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/focus-trap/focus-trap.directive.spec.ts new file mode 100644 index 00000000000..17577d62b82 --- /dev/null +++ b/projects/igniteui-angular/src/lib/directives/focus-trap/focus-trap.directive.spec.ts @@ -0,0 +1,186 @@ +import { Component } from '@angular/core'; +import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { IgxFocusTrapDirective, IgxFocusTrapModule } from './focus-trap.directive'; + +import { configureTestSuite } from '../../test-utils/configure-suite'; +import { IgxCheckboxModule } from '../../checkbox/checkbox.component'; +import { IgxDatePickerModule } from '../../date-picker/public_api'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { UIInteractions } from '../../test-utils/ui-interactions.spec'; + +describe('igxFocusTrap', () => { + configureTestSuite(); + beforeAll(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ + TrapFocusTestComponent + ], + imports: [IgxFocusTrapModule, IgxCheckboxModule, IgxDatePickerModule, NoopAnimationsModule] + }).compileComponents(); + })); + + afterEach(() => { + UIInteractions.clearOverlay(); + }); + + it('should focus focusable elements on Tab key pressed', () => { + const fix = TestBed.createComponent(TrapFocusTestComponent); + fix.detectChanges(); + + const focusTrap = fix.debugElement.query(By.directive(IgxFocusTrapDirective)); + const button = fix.debugElement.query(By.css('button')); + const inputs = fix.debugElement.queryAll(By.css('input')); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + fix.detectChanges(); + expect(document.activeElement).toEqual(inputs[0].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + fix.detectChanges(); + expect(document.activeElement).toEqual(inputs[1].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + fix.detectChanges(); + expect(document.activeElement).toEqual(button.nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + fix.detectChanges(); + expect(document.activeElement).toEqual(inputs[0].nativeElement); + }); + + it('should focus focusable elements in reversed order on Shift + Tab key pressed', () => { + const fix = TestBed.createComponent(TrapFocusTestComponent); + fix.detectChanges(); + + const focusTrap = fix.debugElement.query(By.directive(IgxFocusTrapDirective)); + const button = fix.debugElement.query(By.css('button')); + const inputs = fix.debugElement.queryAll(By.css('input')); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap, false, true); + fix.detectChanges(); + expect(document.activeElement).toEqual(button.nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap, false, true); + fix.detectChanges(); + expect(document.activeElement).toEqual(inputs[1].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap, false, true); + fix.detectChanges(); + expect(document.activeElement).toEqual(inputs[0].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap, false, true); + fix.detectChanges(); + expect(document.activeElement).toEqual(button.nativeElement); + }); + + it('should trap focus on element when there is only one focusable element', () => { + const fix = TestBed.createComponent(TrapFocusTestComponent); + fix.detectChanges(); + + fix.componentInstance.showInput = false; + fix.detectChanges(); + + const focusTrap = fix.debugElement.query(By.directive(IgxFocusTrapDirective)); + const button = fix.debugElement.query(By.css('button')); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + fix.detectChanges(); + expect(document.activeElement).toEqual(button.nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap, false, true); + fix.detectChanges(); + expect(document.activeElement).toEqual(button.nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + fix.detectChanges(); + expect(document.activeElement).toEqual(button.nativeElement); + }); + + it('should trap focus on element with non-focusable elements', fakeAsync(() => { + const fix = TestBed.createComponent(TrapFocusTestComponent); + fix.detectChanges(); + + fix.componentInstance.showInput = false; + fix.componentInstance.showButton = false; + fix.detectChanges(); + + const focusTrap = fix.debugElement.query(By.directive(IgxFocusTrapDirective)); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + tick(); + fix.detectChanges(); + expect(document.activeElement).toEqual(focusTrap.nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap, false, true); + tick(); + fix.detectChanges(); + expect(document.activeElement).toEqual(focusTrap.nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + tick(); + fix.detectChanges(); + expect(document.activeElement).toEqual(focusTrap.nativeElement); + })); + + it('should be able to set focusTrap dynamically', fakeAsync(() => { + const fix = TestBed.createComponent(TrapFocusTestComponent); + fix.detectChanges(); + + const focusTrap = fix.debugElement.query(By.directive(IgxFocusTrapDirective)); + const button = fix.debugElement.query(By.css('button')); + const inputs = fix.debugElement.queryAll(By.css('input')); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + fix.detectChanges(); + expect(document.activeElement).toEqual(inputs[0].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + fix.detectChanges(); + expect(document.activeElement).toEqual(inputs[1].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + fix.detectChanges(); + expect(document.activeElement).toEqual(button.nativeElement); + + button.nativeElement.blur(); + fix.detectChanges(); + + fix.componentInstance.focusTrap = false; + fix.detectChanges(); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + fix.detectChanges(); + expect(document.activeElement).not.toEqual(inputs[0].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap, false, true); + fix.detectChanges(); + expect(document.activeElement).not.toEqual(inputs[1].nativeElement); + + fix.componentInstance.focusTrap = true; + fix.detectChanges(); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap); + fix.detectChanges(); + expect(document.activeElement).toEqual(inputs[0].nativeElement); + + UIInteractions.triggerEventHandlerKeyDown('Tab', focusTrap, false, true); + fix.detectChanges(); + expect(document.activeElement).toEqual(button.nativeElement); + })); +}); + + +@Component({ + template: `
+
+
+
+
+ +
` }) +class TrapFocusTestComponent { + public showInput = true; + public showButton = true; + public focusTrap = true; +} diff --git a/projects/igniteui-angular/src/lib/directives/focus-trap/focus-trap.directive.ts b/projects/igniteui-angular/src/lib/directives/focus-trap/focus-trap.directive.ts new file mode 100644 index 00000000000..b3c7dfbeac0 --- /dev/null +++ b/projects/igniteui-angular/src/lib/directives/focus-trap/focus-trap.directive.ts @@ -0,0 +1,111 @@ +import { AfterViewInit, Directive, ElementRef, Input, NgModule, OnDestroy } from '@angular/core'; +import { fromEvent, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { PlatformUtil } from '../../core/utils'; + +@Directive({ + selector: '[igxFocusTrap]' +}) +export class IgxFocusTrapDirective implements AfterViewInit, OnDestroy { + /** @hidden */ + public get element(): HTMLElement | null { + return this.elementRef.nativeElement; + } + + private destroy$ = new Subject(); + private _focusTrap = true; + + /** @hidden */ + constructor( + private elementRef: ElementRef, + protected platformUtil: PlatformUtil) { + } + + /** + * Sets whether the Tab key focus is trapped within the element. + * + * @example + * ```html + *
+ * ``` + */ + @Input('igxFocusTrap') + public set focusTrap(focusTrap: boolean) { + this._focusTrap = focusTrap; + } + + /** @hidden */ + public get focusTrap(): boolean { + return this._focusTrap; + } + + /** @hidden */ + public ngAfterViewInit(): void { + fromEvent(this.element, 'keydown') + .pipe(takeUntil(this.destroy$)) + .subscribe((event: KeyboardEvent) => { + if (this._focusTrap && event.key === this.platformUtil.KEYMAP.TAB) { + this.handleTab(event); + } + }); + } + + /** @hidden */ + public ngOnDestroy() { + this.destroy$.complete(); + } + + private handleTab(event) { + const elements = this.getFocusableElements(this.element); + if (elements.length > 0) { + const focusedElement = this.getFocusedElement(); + const focusedElementIndex = elements.findIndex((element) => element as HTMLElement === focusedElement); + const direction = event.shiftKey ? -1 : 1; + let nextFocusableElementIndex = focusedElementIndex + direction; + if (nextFocusableElementIndex < 0) { + nextFocusableElementIndex = elements.length - 1; + } + if (nextFocusableElementIndex >= elements.length) { + nextFocusableElementIndex = 0; + } + (elements[nextFocusableElementIndex] as HTMLElement).focus(); + } else { + this.element.focus(); + } + + event.preventDefault(); + } + + private getFocusableElements(element: Element) { + return Array.from(element.querySelectorAll( + 'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])' + )).filter(el => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden')); + } + + private getFocusedElement(): HTMLElement | null { + let activeElement = + typeof document !== 'undefined' && document + ? (document.activeElement as HTMLElement | null) + : null; + + while (activeElement && activeElement.shadowRoot) { + const newActiveElement = activeElement.shadowRoot.activeElement as HTMLElement | null; + if (newActiveElement === activeElement) { + break; + } else { + activeElement = newActiveElement; + } + } + + return activeElement; + } +} + +/** + * @hidden + */ +@NgModule({ + declarations: [IgxFocusTrapDirective], + exports: [IgxFocusTrapDirective] +}) +export class IgxFocusTrapModule { } diff --git a/projects/igniteui-angular/src/lib/navigation-drawer/navigation-drawer.component.spec.ts b/projects/igniteui-angular/src/lib/navigation-drawer/navigation-drawer.component.spec.ts index 246c4f1c6b7..30f98119d2b 100644 --- a/projects/igniteui-angular/src/lib/navigation-drawer/navigation-drawer.component.spec.ts +++ b/projects/igniteui-angular/src/lib/navigation-drawer/navigation-drawer.component.spec.ts @@ -273,7 +273,7 @@ describe('Navigation Drawer', () => { })); it('should stay at 100% parent height when pinned', waitForAsync(() => { - const template = `
+ const template = `
{ TestBed.overrideComponent(TestComponentPin, { set: { template } }); TestBed.compileComponents() .then(() => { + document.body.style.overflow = 'hidden'; const fixture = TestBed.createComponent(TestComponentPin); + fixture.detectChanges(); const windowHeight = window.innerHeight; const container = fixture.debugElement.query(By.css('div')).nativeElement; const navdrawer = fixture.debugElement.query(By.css('igx-nav-drawer > aside')).nativeElement; diff --git a/projects/igniteui-angular/src/public_api.ts b/projects/igniteui-angular/src/public_api.ts index d04eadfcbce..db590e8e6df 100644 --- a/projects/igniteui-angular/src/public_api.ts +++ b/projects/igniteui-angular/src/public_api.ts @@ -20,6 +20,7 @@ export * from './lib/directives/drag-drop/drag-drop.strategy'; export * from './lib/directives/drag-drop/drag-drop.directive'; export * from './lib/directives/filter/filter.directive'; export * from './lib/directives/focus/focus.directive'; +export * from './lib/directives/focus-trap/focus-trap.directive'; export * from './lib/directives/for-of/for_of.directive'; export * from './lib/directives/layout/layout.directive'; export * from './lib/directives/mask/mask.directive'; diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 2b300c5fe8e..294339e5e39 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -21,6 +21,7 @@ import { IgxExpansionPanelModule, IgxFilterModule, IgxFocusModule, + IgxFocusTrapModule, IgxForOfModule, IgxInputGroupModule, IgxLayoutModule, @@ -69,6 +70,7 @@ const igniteModules = [ IgxExpansionPanelModule, IgxFilterModule, IgxFocusModule, + IgxFocusTrapModule, IgxForOfModule, IgxInputGroupModule, IgxLayoutModule,