Skip to content

Commit 75713b0

Browse files
authored
fix(material/paginator): prevent keyboard nav to disabled buttons (#30627)
1 parent 103ac7c commit 75713b0

File tree

3 files changed

+21
-1
lines changed

3 files changed

+21
-1
lines changed

src/material/button/button-base.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ export class MatButtonBase implements AfterViewInit, OnDestroy {
129129
/**
130130
* Natively disabled buttons prevent focus and any pointer events from reaching the button.
131131
* In some scenarios this might not be desirable, because it can prevent users from finding out
132-
* why the button is disabled (e.g. via tooltip).
132+
* why the button is disabled (e.g. via tooltip). This is also useful for buttons that may
133+
* become disabled when activated, which would cause focus to be transferred to the document
134+
* body instead of remaining on the button.
133135
*
134136
* Enabling this input will change the button so that it is styled to be disabled and will be
135137
* marked as `aria-disabled`, but it will allow the button to receive events and focus.

src/material/paginator/paginator.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@
4141
{{_intl.getRangeLabel(pageIndex, pageSize, length)}}
4242
</div>
4343

44+
<!--
45+
The buttons use `disabledInteractive` so that they can retain focus if they become disabled,
46+
otherwise focus is moved to the document body. However, users should not be able to navigate
47+
into these buttons, so `tabindex` is set to -1 when disabled.
48+
-->
49+
4450
@if (showFirstLastButtons) {
4551
<button matIconButton type="button"
4652
class="mat-mdc-paginator-navigation-first"
@@ -50,6 +56,7 @@
5056
[matTooltipDisabled]="_previousButtonsDisabled()"
5157
matTooltipPosition="above"
5258
[disabled]="_previousButtonsDisabled()"
59+
[tabindex]="_previousButtonsDisabled() ? -1 : null"
5360
disabledInteractive>
5461
<svg class="mat-mdc-paginator-icon"
5562
viewBox="0 0 24 24"
@@ -67,6 +74,7 @@
6774
[matTooltipDisabled]="_previousButtonsDisabled()"
6875
matTooltipPosition="above"
6976
[disabled]="_previousButtonsDisabled()"
77+
[tabindex]="_previousButtonsDisabled() ? -1 : null"
7078
disabledInteractive>
7179
<svg class="mat-mdc-paginator-icon"
7280
viewBox="0 0 24 24"
@@ -83,6 +91,7 @@
8391
[matTooltipDisabled]="_nextButtonsDisabled()"
8492
matTooltipPosition="above"
8593
[disabled]="_nextButtonsDisabled()"
94+
[tabindex]="_nextButtonsDisabled() ? -1 : null"
8695
disabledInteractive>
8796
<svg class="mat-mdc-paginator-icon"
8897
viewBox="0 0 24 24"
@@ -100,6 +109,7 @@
100109
[matTooltipDisabled]="_nextButtonsDisabled()"
101110
matTooltipPosition="above"
102111
[disabled]="_nextButtonsDisabled()"
112+
[tabindex]="_nextButtonsDisabled() ? -1 : null"
103113
disabledInteractive>
104114
<svg class="mat-mdc-paginator-icon"
105115
viewBox="0 0 24 24"

src/material/paginator/paginator.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,19 +568,27 @@ describe('MatPaginator', () => {
568568

569569
expect(select.disabled).toBe(false);
570570
expect(getPreviousButton(fixture).hasAttribute('disabled')).toBe(false);
571+
expect(getPreviousButton(fixture).hasAttribute('tabindex')).toBe(false);
571572
expect(getNextButton(fixture).hasAttribute('disabled')).toBe(false);
573+
expect(getNextButton(fixture).hasAttribute('tabindex')).toBe(false);
572574
expect(getFirstButton(fixture).hasAttribute('disabled')).toBe(false);
575+
expect(getFirstButton(fixture).hasAttribute('tabindex')).toBe(false);
573576
expect(getLastButton(fixture).hasAttribute('disabled')).toBe(false);
577+
expect(getLastButton(fixture).hasAttribute('tabindex')).toBe(false);
574578

575579
fixture.componentInstance.disabled = true;
576580
fixture.changeDetectorRef.markForCheck();
577581
fixture.detectChanges();
578582

579583
expect(select.disabled).toBe(true);
580584
expect(getPreviousButton(fixture).hasAttribute('aria-disabled')).toBe(true);
585+
expect(getPreviousButton(fixture).getAttribute('tabindex')).toBe('-1');
581586
expect(getNextButton(fixture).hasAttribute('aria-disabled')).toBe(true);
587+
expect(getNextButton(fixture).getAttribute('tabindex')).toBe('-1');
582588
expect(getFirstButton(fixture).hasAttribute('aria-disabled')).toBe(true);
589+
expect(getFirstButton(fixture).getAttribute('tabindex')).toBe('-1');
583590
expect(getLastButton(fixture).hasAttribute('aria-disabled')).toBe(true);
591+
expect(getLastButton(fixture).getAttribute('tabindex')).toBe('-1');
584592
});
585593

586594
it('should be able to configure the default options via a provider', () => {

0 commit comments

Comments
 (0)