@@ -28,7 +28,13 @@ const NAME_EDIT_TEMPLATE = `
28
28
</div>
29
29
` ;
30
30
31
- const WEIGHT_EDIT_TEMPLATE = `<div> Just a placeholder </div>` ;
31
+ const WEIGHT_EDIT_TEMPLATE = `
32
+ <div>
33
+ <form #f="ngForm" cdkEditControl>
34
+ <input>
35
+ </form>
36
+ </div>
37
+ ` ;
32
38
33
39
const CELL_TEMPLATE = `
34
40
{{element.name}}
@@ -40,7 +46,7 @@ const CELL_TEMPLATE = `
40
46
41
47
const POPOVER_EDIT_DIRECTIVE_NAME = `[cdkPopoverEdit]="nameEdit" [cdkPopoverEditColspan]="colspan"` ;
42
48
43
- const POPOVER_EDIT_DIRECTIVE_WEIGHT = `[cdkPopoverEdit]="weightEdit"` ;
49
+ const POPOVER_EDIT_DIRECTIVE_WEIGHT = `[cdkPopoverEdit]="weightEdit" cdkPopoverEditTabOut ` ;
44
50
45
51
interface PeriodicElement {
46
52
name : string ;
@@ -74,13 +80,13 @@ abstract class BaseTestComponent {
74
80
return getRows ( this . table . nativeElement ) ;
75
81
}
76
82
77
- getEditCell ( rowIndex = 0 ) {
83
+ getEditCell ( rowIndex = 0 , cellIndex = 1 ) {
78
84
const row = this . getRows ( ) [ rowIndex ] ;
79
- return getCells ( row ) [ 1 ] ;
85
+ return getCells ( row ) [ cellIndex ] ;
80
86
}
81
87
82
- focusEditCell ( rowIndex = 0 ) {
83
- this . getEditCell ( rowIndex ) . focus ( ) ;
88
+ focusEditCell ( rowIndex = 0 , cellIndex = 1 ) {
89
+ this . getEditCell ( rowIndex , cellIndex ) . focus ( ) ;
84
90
}
85
91
86
92
getOpenButton ( rowIndex = 0 ) {
@@ -91,17 +97,21 @@ abstract class BaseTestComponent {
91
97
this . getOpenButton ( rowIndex ) ! . click ( ) ;
92
98
}
93
99
94
- openLens ( rowIndex = 0 ) {
95
- this . focusEditCell ( rowIndex ) ;
96
- this . getEditCell ( rowIndex ) . dispatchEvent (
97
- new KeyboardEvent ( 'keyup' , { bubbles : true , key : 'Enter' } ) ) ;
100
+ openLens ( rowIndex = 0 , cellIndex = 1 ) {
101
+ this . focusEditCell ( rowIndex , cellIndex ) ;
102
+ this . getEditCell ( rowIndex , cellIndex )
103
+ . dispatchEvent ( new KeyboardEvent ( 'keyup' , { bubbles : true , key : 'Enter' } ) ) ;
98
104
flush ( ) ;
99
105
}
100
106
101
107
getEditPane ( ) {
102
108
return document . querySelector ( '.cdk-edit-pane' ) ;
103
109
}
104
110
111
+ getEditBoundingBox ( ) {
112
+ return document . querySelector ( '.cdk-overlay-connected-position-bounding-box' ) ;
113
+ }
114
+
105
115
getInput ( ) {
106
116
return document . querySelector ( 'input' ) as HTMLInputElement | null ;
107
117
}
@@ -366,68 +376,125 @@ describe('CDK Popover Edit', () => {
366
376
} ) ) ;
367
377
} ) ;
368
378
369
- describe ( 'arrow key focus manipulation' , ( ) => {
370
- const dispatchKey = ( cell : HTMLElement , keyCode : number ) =>
371
- dispatchKeyboardEvent ( cell , 'keydown' , keyCode , cell ) ;
372
-
379
+ describe ( 'focus manipulation' , ( ) => {
373
380
const getRowCells = ( ) => component . getRows ( ) . map ( getCells ) ;
374
381
375
- it ( 'moves focus up/down/left/right and prevents default' , ( ) => {
376
- const rowCells = getRowCells ( ) ;
382
+ describe ( 'arrow keys' , ( ) => {
383
+ const dispatchKey = ( cell : HTMLElement , keyCode : number ) =>
384
+ dispatchKeyboardEvent ( cell , 'keydown' , keyCode , cell ) ;
377
385
378
- // Focus the upper-left editable cell.
379
- rowCells [ 0 ] [ 1 ] . focus ( ) ;
386
+ it ( 'moves focus up/down/left/right and prevents default' , ( ) => {
387
+ const rowCells = getRowCells ( ) ;
380
388
381
- const downEvent = dispatchKey ( rowCells [ 0 ] [ 1 ] , DOWN_ARROW ) ;
382
- expect ( document . activeElement ) . toBe ( rowCells [ 1 ] [ 1 ] ) ;
383
- expect ( downEvent . defaultPrevented ) . toBe ( true ) ;
389
+ // Focus the upper-left editable cell.
390
+ rowCells [ 0 ] [ 1 ] . focus ( ) ;
384
391
385
- const rightEvent = dispatchKey ( rowCells [ 1 ] [ 1 ] , RIGHT_ARROW ) ;
386
- expect ( document . activeElement ) . toBe ( rowCells [ 1 ] [ 2 ] ) ;
387
- expect ( rightEvent . defaultPrevented ) . toBe ( true ) ;
392
+ const downEvent = dispatchKey ( rowCells [ 0 ] [ 1 ] , DOWN_ARROW ) ;
393
+ expect ( document . activeElement ) . toBe ( rowCells [ 1 ] [ 1 ] ) ;
394
+ expect ( downEvent . defaultPrevented ) . toBe ( true ) ;
388
395
389
- const upEvent = dispatchKey ( rowCells [ 1 ] [ 2 ] , UP_ARROW ) ;
390
- expect ( document . activeElement ) . toBe ( rowCells [ 0 ] [ 2 ] ) ;
391
- expect ( upEvent . defaultPrevented ) . toBe ( true ) ;
396
+ const rightEvent = dispatchKey ( rowCells [ 1 ] [ 1 ] , RIGHT_ARROW ) ;
397
+ expect ( document . activeElement ) . toBe ( rowCells [ 1 ] [ 2 ] ) ;
398
+ expect ( rightEvent . defaultPrevented ) . toBe ( true ) ;
392
399
393
- const leftEvent = dispatchKey ( rowCells [ 0 ] [ 2 ] , LEFT_ARROW ) ;
394
- expect ( document . activeElement ) . toBe ( rowCells [ 0 ] [ 1 ] ) ;
395
- expect ( leftEvent . defaultPrevented ) . toBe ( true ) ;
396
- } ) ;
400
+ const upEvent = dispatchKey ( rowCells [ 1 ] [ 2 ] , UP_ARROW ) ;
401
+ expect ( document . activeElement ) . toBe ( rowCells [ 0 ] [ 2 ] ) ;
402
+ expect ( upEvent . defaultPrevented ) . toBe ( true ) ;
397
403
398
- it ( 'wraps around when reaching start or end of a row, skipping non-editable cells' , ( ) => {
399
- const rowCells = getRowCells ( ) ;
404
+ const leftEvent = dispatchKey ( rowCells [ 0 ] [ 2 ] , LEFT_ARROW ) ;
405
+ expect ( document . activeElement ) . toBe ( rowCells [ 0 ] [ 1 ] ) ;
406
+ expect ( leftEvent . defaultPrevented ) . toBe ( true ) ;
407
+ } ) ;
400
408
401
- // Focus the upper-right editable cell.
402
- rowCells [ 0 ] [ 2 ] . focus ( ) ;
409
+ it ( 'wraps around when reaching start or end of a row, skipping non-editable cells' ,
410
+ ( ) => {
411
+ const rowCells = getRowCells ( ) ;
403
412
404
- dispatchKey ( rowCells [ 0 ] [ 2 ] , RIGHT_ARROW ) ;
405
- expect ( document . activeElement ) . toBe ( rowCells [ 1 ] [ 1 ] ) ;
413
+ // Focus the upper-right editable cell.
414
+ rowCells [ 0 ] [ 2 ] . focus ( ) ;
406
415
407
- dispatchKey ( rowCells [ 1 ] [ 1 ] , LEFT_ARROW ) ;
408
- expect ( document . activeElement ) . toBe ( rowCells [ 0 ] [ 2 ] ) ;
409
- } ) ;
416
+ dispatchKey ( rowCells [ 0 ] [ 2 ] , RIGHT_ARROW ) ;
417
+ expect ( document . activeElement ) . toBe ( rowCells [ 1 ] [ 1 ] ) ;
418
+
419
+ dispatchKey ( rowCells [ 1 ] [ 1 ] , LEFT_ARROW ) ;
420
+ expect ( document . activeElement ) . toBe ( rowCells [ 0 ] [ 2 ] ) ;
421
+ } ) ;
422
+
423
+ it ( 'does not fall off top or bottom of the table' , ( ) => {
424
+ const rowCells = getRowCells ( ) ;
425
+
426
+ // Focus the upper-left editable cell.
427
+ rowCells [ 0 ] [ 1 ] . focus ( ) ;
410
428
411
- it ( 'does not fall off top or bottom of the table' , ( ) => {
412
- const rowCells = getRowCells ( ) ;
429
+ dispatchKey ( rowCells [ 0 ] [ 1 ] , UP_ARROW ) ;
430
+ expect ( document . activeElement ) . toBe ( rowCells [ 0 ] [ 1 ] ) ;
413
431
414
- // Focus the upper-left editable cell.
415
- rowCells [ 0 ] [ 1 ] . focus ( ) ;
432
+ // Focus the bottom-left editable cell.
433
+ rowCells [ 4 ] [ 1 ] . focus ( ) ;
434
+ dispatchKey ( rowCells [ 4 ] [ 1 ] , DOWN_ARROW ) ;
435
+ expect ( document . activeElement ) . toBe ( rowCells [ 4 ] [ 1 ] ) ;
436
+ } ) ;
416
437
417
- dispatchKey ( rowCells [ 0 ] [ 1 ] , UP_ARROW ) ;
418
- expect ( document . activeElement ) . toBe ( rowCells [ 0 ] [ 1 ] ) ;
438
+ it ( 'ignores non arrow key events' , ( ) => {
439
+ component . focusEditCell ( ) ;
440
+ const cell = component . getEditCell ( ) ;
419
441
420
- // Focus the bottom-left editable cell.
421
- rowCells [ 4 ] [ 1 ] . focus ( ) ;
422
- dispatchKey ( rowCells [ 4 ] [ 1 ] , DOWN_ARROW ) ;
423
- expect ( document . activeElement ) . toBe ( rowCells [ 4 ] [ 1 ] ) ;
442
+ expect ( dispatchKey ( cell , TAB ) . defaultPrevented ) . toBe ( false ) ;
443
+ } ) ;
424
444
} ) ;
425
445
426
- it ( 'ignores non arrow key events' , ( ) => {
427
- component . focusEditCell ( ) ;
428
- const cell = component . getEditCell ( ) ;
446
+ describe ( 'lens focus trapping behavior' , ( ) => {
447
+ const getFocusablePaneElements = ( ) =>
448
+ ( Array . from ( component . getEditBoundingBox ( ) ! . querySelectorAll ( '*' ) ) as HTMLElement [ ] )
449
+ . filter ( element => element . tabIndex >= 0 ) ;
450
+
451
+ it ( 'keeps focus within the lens by default' , fakeAsync ( ( ) => {
452
+ // Open the name lens which has the default behavior.
453
+ component . openLens ( ) ;
454
+
455
+ const focusableElements = getFocusablePaneElements ( ) ;
456
+
457
+ // Focus the last element (end focus trap anchor).
458
+ console . log ( focusableElements [ focusableElements . length - 1 ] ) ;
459
+ focusableElements [ focusableElements . length - 1 ] . focus ( ) ;
460
+ flush ( ) ;
461
+
462
+ // Focus should have moved to the top of the lens.
463
+ expect ( document . activeElement ) . toBe ( focusableElements [ 1 ] ) ;
464
+ expect ( component . lensIsOpen ( ) ) . toBe ( true ) ;
465
+ } ) ) ;
466
+
467
+ it ( 'moves focus to the next cell when focus leaves end of lens with cdkPopoverEditTabOut' ,
468
+ fakeAsync ( ( ) => {
469
+ // Open the weight lens which has tab out behavior.
470
+ component . openLens ( 0 , 2 ) ;
471
+
472
+ const focusableElements = getFocusablePaneElements ( ) ;
473
+
474
+ // Focus the last element (end focus trap anchor).
475
+ focusableElements [ focusableElements . length - 1 ] . focus ( ) ;
476
+ flush ( ) ;
477
+
478
+ // Focus should have moved to the next editable cell.
479
+ expect ( document . activeElement ) . toBe ( component . getEditCell ( 1 , 1 ) ) ;
480
+ expect ( component . lensIsOpen ( ) ) . toBe ( false ) ;
481
+ } ) ) ;
482
+
483
+ it ( 'moves focus to the previous cell when focus leaves end of lens with cdkPopoverEditTabOut' ,
484
+ fakeAsync ( ( ) => {
485
+ // Open the weight lens which has tab out behavior.
486
+ component . openLens ( 0 , 2 ) ;
487
+
488
+ const focusableElements = getFocusablePaneElements ( ) ;
489
+
490
+ // Focus the first (start focus trap anchor).
491
+ focusableElements [ 0 ] . focus ( ) ;
492
+ flush ( ) ;
429
493
430
- expect ( dispatchKey ( cell , TAB ) . defaultPrevented ) . toBe ( false ) ;
494
+ // Focus should have moved to the next editable cell.
495
+ expect ( document . activeElement ) . toBe ( component . getEditCell ( 0 , 1 ) ) ;
496
+ expect ( component . lensIsOpen ( ) ) . toBe ( false ) ;
497
+ } ) ) ;
431
498
} ) ;
432
499
} ) ;
433
500
0 commit comments