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

Commit 338ca27

Browse files
topherfangiokara
authored andcommitted
fix(tabs): add proper RTL support. (#9301)
Fixes #9292.
1 parent 072f832 commit 338ca27

File tree

5 files changed

+463
-58
lines changed

5 files changed

+463
-58
lines changed

src/components/tabs/demoDynamicTabs/script.js

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,32 @@
66

77
function AppCtrl ($scope, $log) {
88
var tabs = [
9-
{ title: 'One', content: "Tabs will become paginated if there isn't enough room for them."},
10-
{ title: 'Two', content: "You can swipe left and right on a mobile device to change tabs."},
11-
{ title: 'Three', content: "You can bind the selected tab via the selected attribute on the md-tabs element."},
12-
{ title: 'Four', content: "If you set the selected tab binding to -1, it will leave no tab selected."},
13-
{ title: 'Five', content: "If you remove a tab, it will try to select a new one."},
14-
{ title: 'Six', content: "There's an ink bar that follows the selected tab, you can turn it off if you want."},
15-
{ title: 'Seven', content: "If you set ng-disabled on a tab, it becomes unselectable. If the currently selected tab becomes disabled, it will try to select the next tab."},
16-
{ title: 'Eight', content: "If you look at the source, you're using tabs to look at a demo for tabs. Recursion!"},
17-
{ title: 'Nine', content: "If you set md-theme=\"green\" on the md-tabs element, you'll get green tabs."},
18-
{ title: 'Ten', content: "If you're still reading this, you should just go check out the API docs for tabs!"}
19-
],
20-
selected = null,
21-
previous = null;
9+
{ title: 'Zero (AKA 0, Cero, One - One, -Nineteen + 19, and so forth and so on and continuing into what seems like infinity.)', content: 'Woah...that is a really long title!' },
10+
{ title: 'One', content: "Tabs will become paginated if there isn't enough room for them."},
11+
{ title: 'Two', content: "You can swipe left and right on a mobile device to change tabs."},
12+
{ title: 'Three', content: "You can bind the selected tab via the selected attribute on the md-tabs element."},
13+
{ title: 'Four', content: "If you set the selected tab binding to -1, it will leave no tab selected."},
14+
{ title: 'Five', content: "If you remove a tab, it will try to select a new one."},
15+
{ title: 'Six', content: "There's an ink bar that follows the selected tab, you can turn it off if you want."},
16+
{ title: 'Seven', content: "If you set ng-disabled on a tab, it becomes unselectable. If the currently selected tab becomes disabled, it will try to select the next tab."},
17+
{ title: 'Eight', content: "If you look at the source, you're using tabs to look at a demo for tabs. Recursion!"},
18+
{ title: 'Nine', content: "If you set md-theme=\"green\" on the md-tabs element, you'll get green tabs."},
19+
{ title: 'Ten', content: "If you're still reading this, you should just go check out the API docs for tabs!"},
20+
{ title: 'Eleven', content: "If you're still reading this, you should just go check out the API docs for tabs!"},
21+
{ title: 'Twelve', content: "If you're still reading this, you should just go check out the API docs for tabs!"},
22+
{ title: 'Thirteen', content: "If you're still reading this, you should just go check out the API docs for tabs!"},
23+
{ title: 'Fourteen', content: "If you're still reading this, you should just go check out the API docs for tabs!"},
24+
{ title: 'Fifteen', content: "If you're still reading this, you should just go check out the API docs for tabs!"},
25+
{ title: 'Sixteen', content: "If you're still reading this, you should just go check out the API docs for tabs!"},
26+
{ title: 'Seventeen', content: "If you're still reading this, you should just go check out the API docs for tabs!"},
27+
{ title: 'Eighteen', content: "If you're still reading this, you should just go check out the API docs for tabs!"},
28+
{ title: 'Nineteen', content: "If you're still reading this, you should just go check out the API docs for tabs!"},
29+
{ title: 'Twenty', content: "If you're still reading this, you should just go check out the API docs for tabs!"}
30+
],
31+
selected = null,
32+
previous = null;
2233
$scope.tabs = tabs;
23-
$scope.selectedIndex = 2;
34+
$scope.selectedIndex = 0;
2435
$scope.$watch('selectedIndex', function(current, old){
2536
previous = selected;
2637
selected = tabs[current];

src/components/tabs/js/tabsController.js

Lines changed: 87 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ angular
66
* @ngInject
77
*/
88
function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipple, $mdUtil,
9-
$animateCss, $attrs, $compile, $mdTheming, $mdInteraction) {
9+
$animateCss, $attrs, $compile, $mdTheming, $mdInteraction,
10+
MdTabsPaginationService) {
1011
// define private properties
1112
var ctrl = this,
1213
locked = false,
@@ -189,9 +190,16 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
189190
if (newWidth !== oldWidth) {
190191
var elements = getElements();
191192

193+
// Set the max width for the real tabs
192194
angular.forEach(elements.tabs, function(tab) {
193195
tab.style.maxWidth = newWidth + 'px';
194196
});
197+
198+
// Set the max width for the dummy tabs too
199+
angular.forEach(elements.dummies, function(tab) {
200+
tab.style.maxWidth = newWidth + 'px';
201+
});
202+
195203
$mdUtil.nextTick(ctrl.updateInkBarStyles);
196204
}
197205
}
@@ -221,7 +229,10 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
221229
*/
222230
function handleOffsetChange (left) {
223231
var elements = getElements();
224-
var newValue = ctrl.shouldCenterTabs ? '' : '-' + left + 'px';
232+
var newValue = ((ctrl.shouldCenterTabs || isRtl() ? '' : '-') + left + 'px');
233+
234+
// Fix double-negative which can happen with RTL support
235+
newValue = newValue.replace('--', '');
225236

226237
angular.element(elements.paging).css($mdConstant.CSS.TRANSFORM, 'translate3d(' + newValue + ', 0, 0)');
227238
$scope.$broadcast('$mdTabsPaginationChanged');
@@ -347,48 +358,23 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
347358
* Slides the tabs over approximately one page forward.
348359
*/
349360
function nextPage () {
350-
var elements = getElements();
351-
var viewportWidth = elements.canvas.clientWidth,
352-
totalWidth = viewportWidth + ctrl.offsetLeft,
353-
i, tab;
354-
for (i = 0; i < elements.tabs.length; i++) {
355-
tab = elements.tabs[ i ];
356-
if (tab.offsetLeft + tab.offsetWidth > totalWidth) break;
357-
}
361+
if (!ctrl.canPageForward()) { return }
358362

359-
if (viewportWidth > tab.offsetWidth) {
360-
//Canvas width *greater* than tab width: usual positioning
361-
ctrl.offsetLeft = fixOffset(tab.offsetLeft);
362-
} else {
363-
/**
364-
* Canvas width *smaller* than tab width: positioning at the *end* of current tab to let
365-
* pagination "for loop" to proceed correctly on next tab when nextPage() is called again
366-
*/
367-
ctrl.offsetLeft = fixOffset(tab.offsetLeft + (tab.offsetWidth - viewportWidth + 1));
368-
}
363+
var newOffset = MdTabsPaginationService.increasePageOffset(getElements(), ctrl.offsetLeft);
364+
365+
ctrl.offsetLeft = fixOffset(newOffset);
369366
}
370367

371368
/**
372369
* Slides the tabs over approximately one page backward.
373370
*/
374371
function previousPage () {
375-
var i, tab, elements = getElements();
372+
if (!ctrl.canPageBack()) { return }
376373

377-
for (i = 0; i < elements.tabs.length; i++) {
378-
tab = elements.tabs[ i ];
379-
if (tab.offsetLeft + tab.offsetWidth >= ctrl.offsetLeft) break;
380-
}
374+
var newOffset = MdTabsPaginationService.decreasePageOffset(getElements(), ctrl.offsetLeft);
381375

382-
if (elements.canvas.clientWidth > tab.offsetWidth) {
383-
//Canvas width *greater* than tab width: usual positioning
384-
ctrl.offsetLeft = fixOffset(tab.offsetLeft + tab.offsetWidth - elements.canvas.clientWidth);
385-
} else {
386-
/**
387-
* Canvas width *smaller* than tab width: positioning at the *beginning* of current tab to let
388-
* pagination "for loop" to break correctly on previous tab when previousPage() is called again
389-
*/
390-
ctrl.offsetLeft = fixOffset(tab.offsetLeft);
391-
}
376+
// Set the new offset
377+
ctrl.offsetLeft = fixOffset(newOffset);
392378
}
393379

394380
/**
@@ -397,6 +383,7 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
397383
function handleWindowResize () {
398384
ctrl.lastSelectedIndex = ctrl.selectedIndex;
399385
ctrl.offsetLeft = fixOffset(ctrl.offsetLeft);
386+
400387
$mdUtil.nextTick(function () {
401388
ctrl.updateInkBarStyles();
402389
updatePagination();
@@ -493,6 +480,8 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
493480
elements.canvas = elements.wrapper.querySelector('md-tabs-canvas');
494481
elements.paging = elements.canvas.querySelector('md-pagination-wrapper');
495482
elements.inkBar = elements.paging.querySelector('md-ink-bar');
483+
elements.nextButton = node.querySelector('md-next-button');
484+
elements.prevButton = node.querySelector('md-prev-button');
496485

497486
elements.contents = node.querySelectorAll('md-tabs-content-wrapper > md-tab-content');
498487
elements.tabs = elements.paging.querySelectorAll('md-tab-item');
@@ -506,6 +495,7 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
506495
* @returns {boolean}
507496
*/
508497
function canPageBack () {
498+
// This works for both LTR and RTL
509499
return ctrl.offsetLeft > 0;
510500
}
511501

@@ -516,6 +506,11 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
516506
function canPageForward () {
517507
var elements = getElements();
518508
var lastTab = elements.tabs[ elements.tabs.length - 1 ];
509+
510+
if (isRtl()) {
511+
return ctrl.offsetLeft < elements.paging.offsetWidth - elements.canvas.offsetWidth;
512+
}
513+
519514
return lastTab && lastTab.offsetLeft + lastTab.offsetWidth > elements.canvas.clientWidth +
520515
ctrl.offsetLeft;
521516
}
@@ -563,7 +558,7 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
563558
if (ctrl.noPagination || !loaded) return false;
564559
var canvasWidth = $element.prop('clientWidth');
565560

566-
angular.forEach(getElements().dummies, function (tab) {
561+
angular.forEach(getElements().tabs, function (tab) {
567562
canvasWidth -= tab.offsetWidth;
568563
});
569564

@@ -622,7 +617,7 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
622617
* @returns {number}
623618
*/
624619
function calcPagingWidth () {
625-
return calcTabsWidth(getElements().dummies);
620+
return calcTabsWidth(getElements().tabs);
626621
}
627622

628623
function calcTabsWidth(tabs) {
@@ -640,7 +635,28 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
640635
}
641636

642637
function getMaxTabWidth () {
643-
return $element.prop('clientWidth');
638+
var elements = getElements(),
639+
containerWidth = elements.canvas.clientWidth,
640+
641+
// See https://material.google.com/components/tabs.html#tabs-specs
642+
specMax = 264;
643+
644+
// Do the spec maximum, or the canvas width; whichever is *smaller* (tabs larger than the canvas
645+
// width can break the pagination) but not less than 0
646+
return Math.max(0, Math.min(containerWidth - 1, specMax));
647+
}
648+
649+
function getMinTabWidth() {
650+
var elements = getElements(),
651+
containerWidth = elements.canvas.clientWidth,
652+
xsBreakpoint = 600,
653+
654+
// See https://material.google.com/components/tabs.html#tabs-specs
655+
specMin = containerWidth > xsBreakpoint ? 160 : 72;
656+
657+
// Do the spec minimum, or the canvas width; whichever is *smaller* (tabs larger than the canvas
658+
// width can break the pagination) but not less than 0
659+
return Math.max(0, Math.min(containerWidth - 1, specMin));
644660
}
645661

646662
/**
@@ -693,9 +709,25 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
693709
if (ctrl.shouldCenterTabs) return;
694710
var tab = elements.tabs[ index ],
695711
left = tab.offsetLeft,
696-
right = tab.offsetWidth + left;
697-
ctrl.offsetLeft = Math.max(ctrl.offsetLeft, fixOffset(right - elements.canvas.clientWidth + 32 * 2));
698-
ctrl.offsetLeft = Math.min(ctrl.offsetLeft, fixOffset(left));
712+
right = tab.offsetWidth + left,
713+
extraOffset = 32;
714+
715+
// If we are selecting the first tab (in LTR and RTL), always set the offset to 0
716+
if (index == 0) {
717+
ctrl.offsetLeft = 0;
718+
return;
719+
}
720+
721+
if (isRtl()) {
722+
var tabWidthsBefore = calcTabsWidth(Array.prototype.slice.call(elements.tabs, 0, index));
723+
var tabWidthsIncluding = calcTabsWidth(Array.prototype.slice.call(elements.tabs, 0, index + 1));
724+
725+
ctrl.offsetLeft = Math.min(ctrl.offsetLeft, fixOffset(tabWidthsBefore));
726+
ctrl.offsetLeft = Math.max(ctrl.offsetLeft, fixOffset(tabWidthsIncluding - elements.canvas.clientWidth));
727+
} else {
728+
ctrl.offsetLeft = Math.max(ctrl.offsetLeft, fixOffset(right - elements.canvas.clientWidth + extraOffset));
729+
ctrl.offsetLeft = Math.min(ctrl.offsetLeft, fixOffset(left));
730+
}
699731
}
700732

701733
/**
@@ -852,10 +884,18 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
852884
var elements = getElements();
853885

854886
if (!elements.tabs.length || !ctrl.shouldPaginate) return 0;
887+
855888
var lastTab = elements.tabs[ elements.tabs.length - 1 ],
856889
totalWidth = lastTab.offsetLeft + lastTab.offsetWidth;
857-
value = Math.max(0, value);
858-
value = Math.min(totalWidth - elements.canvas.clientWidth, value);
890+
891+
if (isRtl()) {
892+
value = Math.min(elements.paging.offsetWidth - elements.canvas.clientWidth, value);
893+
value = Math.max(0, value);
894+
} else {
895+
value = Math.max(0, value);
896+
value = Math.min(totalWidth - elements.canvas.clientWidth, value);
897+
}
898+
859899
return value;
860900
}
861901

@@ -881,4 +921,8 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
881921
angular.element(nodes).attr('aria-controls', ctrl.tabContentPrefix + tab.id);
882922
}
883923
}
924+
925+
function isRtl() {
926+
return ($mdUtil.bidi() == 'rtl');
927+
}
884928
}

src/components/tabs/tabs.scss

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ md-tabs-wrapper {
105105
left: 50%;
106106
transform: translate3d(-50%, -50%, 0);
107107
}
108+
109+
// For RTL tabs, rotate the buttons
110+
[dir="rtl"] & {
111+
transform: rotateY(180deg) translateY(-50%);
112+
}
108113
}
109114
md-prev-button {
110115
@include rtl-prop(left, right, 0, auto);
@@ -113,6 +118,8 @@ md-tabs-wrapper {
113118
md-next-button {
114119
@include rtl-prop(right, left, 0, auto);
115120
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE3LjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPiA8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPiA8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPiA8ZyBpZD0iSGVhZGVyIj4gPGc+IDxyZWN0IHg9Ii02MTgiIHk9Ii0xMzM2IiBmaWxsPSJub25lIiB3aWR0aD0iMTQwMCIgaGVpZ2h0PSIzNjAwIi8+IDwvZz4gPC9nPiA8ZyBpZD0iTGFiZWwiPiA8L2c+IDxnIGlkPSJJY29uIj4gPGc+IDxwb2x5Z29uIHBvaW50cz0iMTAsNiA4LjYsNy40IDEzLjIsMTIgOC42LDE2LjYgMTAsMTggMTYsMTIgCQkiIHN0eWxlPSJmaWxsOndoaXRlOyIvPiA8cmVjdCBmaWxsPSJub25lIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz4gPC9nPiA8L2c+IDxnIGlkPSJHcmlkIiBkaXNwbGF5PSJub25lIj4gPGcgZGlzcGxheT0iaW5saW5lIj4gPC9nPiA8L2c+IDwvc3ZnPg0K');
121+
122+
// In regular mode, we need to flip the chevron icon to point the other way
116123
md-icon {
117124
transform: translate3d(-50%, -50%, 0) rotate(180deg);
118125
}
@@ -283,7 +290,7 @@ md-tab {
283290
}
284291
}
285292

286-
md-toolbar + md-tabs {
293+
md-toolbar + md-tabs, md-toolbar + md-dialog-content md-tabs {
287294
border-top-left-radius: 0;
288295
border-top-right-radius: 0;
289296
}

0 commit comments

Comments
 (0)