Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit c93fdad

Browse files
crisbetojelbourn
authored andcommitted
fix(tabs): don't set aria-controls when there is no content; better empty tab handling (#9763)
* Fixes the tabs directive setting the `controls` attribute when a tab doesn't have content. * Fixes the tabs adding unnecessary elements when a tab's content consists only of whitespace or it doesn't have content, but any of it's siblings do. * Switches to a faster way of checking whether a tab element has content. * Adds unit tests for the `md-no-tab-content` behavior. Fixes #9108.
1 parent dbc52d0 commit c93fdad

File tree

3 files changed

+84
-11
lines changed

3 files changed

+84
-11
lines changed

src/components/tabs/js/tabsController.js

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
4444
ctrl.hasFocus = false;
4545
ctrl.lastClick = true;
4646
ctrl.shouldCenterTabs = shouldCenterTabs();
47+
ctrl.tabContentPrefix = 'tab-content-';
4748

4849
// define public methods
4950
ctrl.updatePagination = $mdUtil.debounce(updatePagination, 100);
@@ -334,7 +335,7 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
334335
tab = elements.tabs[ i ];
335336
if (tab.offsetLeft + tab.offsetWidth > totalWidth) break;
336337
}
337-
338+
338339
if (viewportWidth > tab.offsetWidth) {
339340
//Canvas width *greater* than tab width: usual positioning
340341
ctrl.offsetLeft = fixOffset(tab.offsetLeft);
@@ -357,7 +358,7 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
357358
tab = elements.tabs[ i ];
358359
if (tab.offsetLeft + tab.offsetWidth >= ctrl.offsetLeft) break;
359360
}
360-
361+
361362
if (elements.canvas.clientWidth > tab.offsetWidth) {
362363
//Canvas width *greater* than tab width: usual positioning
363364
ctrl.offsetLeft = fixOffset(tab.offsetLeft + tab.offsetWidth - elements.canvas.clientWidth);
@@ -433,18 +434,22 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
433434
return !ctrl.lastClick
434435
&& ctrl.hasFocus && this.getIndex() === ctrl.focusIndex;
435436
},
436-
id: $mdUtil.nextUid()
437+
id: $mdUtil.nextUid(),
438+
hasContent: !!(tabData.template && tabData.template.trim())
437439
},
438440
tab = angular.extend(proto, tabData);
439441
if (angular.isDefined(index)) {
440442
ctrl.tabs.splice(index, 0, tab);
441443
} else {
442444
ctrl.tabs.push(tab);
443445
}
446+
444447
processQueue();
445448
updateHasContent();
446449
$mdUtil.nextTick(function () {
447450
updatePagination();
451+
setAriaControls(tab);
452+
448453
// if autoselect is enabled, select the newly added tab
449454
if (hasLoaded && ctrl.autoselect) $mdUtil.nextTick(function () {
450455
$mdUtil.nextTick(function () { select(ctrl.tabs.indexOf(tab)); });
@@ -699,9 +704,14 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
699704
*/
700705
function updateHasContent () {
701706
var hasContent = false;
702-
angular.forEach(ctrl.tabs, function (tab) {
703-
if (tab.template) hasContent = true;
704-
});
707+
708+
for (var i = 0; i < ctrl.tabs.length; i++) {
709+
if (ctrl.tabs[i].hasContent) {
710+
hasContent = true;
711+
break;
712+
}
713+
}
714+
705715
ctrl.hasContent = hasContent;
706716
}
707717

@@ -851,4 +861,16 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
851861
var options = { colorElement: angular.element(elements.inkBar) };
852862
$mdTabInkRipple.attach(scope, element, options);
853863
}
864+
865+
/**
866+
* Sets the `aria-controls` attribute to the elements that
867+
* correspond to the passed-in tab.
868+
* @param tab
869+
*/
870+
function setAriaControls (tab) {
871+
if (tab.hasContent) {
872+
var nodes = $element[0].querySelectorAll('[md-tab-id="' + tab.id + '"]');
873+
angular.element(nodes).attr('aria-controls', ctrl.tabContentPrefix + tab.id);
874+
}
875+
}
854876
}

src/components/tabs/js/tabsDirective.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ function MdTabs ($$mdSvgRegistry) {
148148
'class="md-tab" ' +
149149
'ng-repeat="tab in $mdTabsCtrl.tabs" ' +
150150
'role="tab" ' +
151-
'aria-controls="tab-content-{{::tab.id}}" ' +
151+
'md-tab-id="{{::tab.id}}"' +
152152
'aria-selected="{{tab.isActive()}}" ' +
153153
'aria-disabled="{{tab.scope.disabled || \'false\'}}" ' +
154154
'ng-click="$mdTabsCtrl.select(tab.getIndex())" ' +
@@ -169,7 +169,7 @@ function MdTabs ($$mdSvgRegistry) {
169169
'class="md-tab" ' +
170170
'tabindex="-1" ' +
171171
'id="tab-item-{{::tab.id}}" ' +
172-
'aria-controls="tab-content-{{::tab.id}}" ' +
172+
'md-tab-id="{{::tab.id}}"' +
173173
'aria-selected="{{tab.isActive()}}" ' +
174174
'aria-disabled="{{tab.scope.disabled || \'false\'}}" ' +
175175
'ng-focus="$mdTabsCtrl.hasFocus = true" ' +
@@ -182,13 +182,13 @@ function MdTabs ($$mdSvgRegistry) {
182182
'</md-tabs-wrapper> ' +
183183
'<md-tabs-content-wrapper ng-show="$mdTabsCtrl.hasContent && $mdTabsCtrl.selectedIndex >= 0" class="_md"> ' +
184184
'<md-tab-content ' +
185-
'id="tab-content-{{::tab.id}}" ' +
185+
'id="{{:: $mdTabsCtrl.tabContentPrefix + tab.id}}" ' +
186186
'class="_md" ' +
187187
'role="tabpanel" ' +
188188
'aria-labelledby="tab-item-{{::tab.id}}" ' +
189189
'md-swipe-left="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(1)" ' +
190190
'md-swipe-right="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(-1)" ' +
191-
'ng-if="$mdTabsCtrl.hasContent" ' +
191+
'ng-if="tab.hasContent" ' +
192192
'ng-repeat="(index, tab) in $mdTabsCtrl.tabs" ' +
193193
'ng-class="{ ' +
194194
'\'md-no-transition\': $mdTabsCtrl.lastSelectedIndex == null, ' +

src/components/tabs/tabs.spec.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,11 +338,16 @@ describe('<md-tabs>', function () {
338338

339339
done();
340340
});
341-
})
341+
});
342342
});
343343
});
344344

345345
describe('aria', function () {
346+
var $timeout;
347+
348+
beforeEach(inject(function(_$timeout_) {
349+
$timeout = _$timeout_;
350+
}));
346351

347352
it('should link tab content to tabItem with auto-generated ids', function () {
348353
var tabs = setup('<md-tabs>' +
@@ -351,6 +356,8 @@ describe('<md-tabs>', function () {
351356
var tabItem = tabs.find('md-dummy-tab');
352357
var tabContent = angular.element(tabs[ 0 ].querySelector('md-tab-content'));
353358

359+
$timeout.flush();
360+
354361
expect(tabs.find('md-tabs-canvas').attr('role')).toBe('tablist');
355362

356363
expect(tabItem.attr('id')).toBeTruthy();
@@ -381,6 +388,18 @@ describe('<md-tabs>', function () {
381388

382389
expect(tabItem.attr('role')).toBe('tab');
383390
});
391+
392+
it('should not set `aria-controls` if the tab does not have content', function () {
393+
var tabs = setup(
394+
'<md-tabs>' +
395+
'<md-tab label="label!"></md-tab>' +
396+
'</md-tabs>'
397+
);
398+
399+
$timeout.flush();
400+
401+
expect(tabs.find('md-dummy-tab').attr('aria-controls')).toBeFalsy();
402+
});
384403
});
385404

386405
describe('<md-tab>', function () {
@@ -525,4 +544,36 @@ describe('<md-tabs>', function () {
525544
element.remove();
526545
}));
527546
});
547+
548+
describe('no element content', function() {
549+
it('should not add the `md-no-tab-content` class if the element has content', function() {
550+
var tabs = setup(
551+
'<md-tabs>' +
552+
'<md-tab label="label!">content!</md-tab>' +
553+
'</md-tabs>'
554+
);
555+
556+
expect(tabs).not.toHaveClass('md-no-tab-content');
557+
});
558+
559+
it('should add the `md-no-tab-content` class if the element does not have content', function() {
560+
var tabs = setup(
561+
'<md-tabs>' +
562+
'<md-tab label="label!"></md-tab>' +
563+
'</md-tabs>'
564+
);
565+
566+
expect(tabs).toHaveClass('md-no-tab-content');
567+
});
568+
569+
it('should trim before determining whether the element has content', function() {
570+
var tabs = setup(
571+
'<md-tabs>' +
572+
'<md-tab label="label!">\n\n\n</md-tab>' +
573+
'</md-tabs>'
574+
);
575+
576+
expect(tabs).toHaveClass('md-no-tab-content');
577+
});
578+
});
528579
});

0 commit comments

Comments
 (0)