summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnna Koskinen <Ansku@users.noreply.github.com>2018-10-10 13:26:13 +0300
committerGitHub <noreply@github.com>2018-10-10 13:26:13 +0300
commit4e5cf07e6c8c94c12ef9d8b274c0c54b5e0fe423 (patch)
tree7d70edaf71bc254a9bacf88ba6ca8791c290183d
parent16764a20786326b891b4d2fc80512a61a7c53d3e (diff)
downloadvaadin-framework-4e5cf07e6c8c94c12ef9d8b274c0c54b5e0fe423.tar.gz
vaadin-framework-4e5cf07e6c8c94c12ef9d8b274c0c54b5e0fe423.zip
Updates to scrolled TabSheet resize logic and Valo right-alignment. (#11133)
- When a TabSheet is scrolled to an end and then resized bigger, more tabs should appear to the left. - When a TabSheet is right-aligned in Valo and scrolled to the end, last tab shouldn't be partially hidden behind the scroller buttons. - Shouldn't allow attempts to scroll into directions where there is nothing left to scroll to, even if the current tab isn't fully visible. Fixes #807
-rw-r--r--client/src/main/java/com/vaadin/client/ui/VTabsheet.java139
-rw-r--r--themes/src/main/themes/VAADIN/themes/valo/components/_tabsheet.scss8
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/tabsheet/ScrolledTabSheetResize.java48
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/tabsheet/ScrolledTabSheetResizeTest.java155
4 files changed, 338 insertions, 12 deletions
diff --git a/client/src/main/java/com/vaadin/client/ui/VTabsheet.java b/client/src/main/java/com/vaadin/client/ui/VTabsheet.java
index 123f89bb36..82f805db8f 100644
--- a/client/src/main/java/com/vaadin/client/ui/VTabsheet.java
+++ b/client/src/main/java/com/vaadin/client/ui/VTabsheet.java
@@ -16,8 +16,10 @@
package com.vaadin.client.ui;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import com.google.gwt.aria.client.Id;
import com.google.gwt.aria.client.LiveValue;
@@ -272,6 +274,9 @@ public class VTabsheet extends VTabsheetBase
public void recalculateCaptionWidth() {
tabCaption.setWidth(tabCaption.getRequiredWidth() + "px");
+ if (isVisible()) {
+ tabBar.tabWidths.put(this, getOffsetWidth());
+ }
}
@Override
@@ -443,6 +448,9 @@ public class VTabsheet extends VTabsheetBase
private VTabsheet tabsheet;
+ /** For internal use only. May be removed or replaced in the future. */
+ private Map<Tab, Integer> tabWidths = new HashMap<Tab, Integer>();
+
TabBar(VTabsheet tabsheet) {
this.tabsheet = tabsheet;
@@ -502,6 +510,7 @@ public class VTabsheet extends VTabsheetBase
getTabsheet().selectionHandler.registerTab(t);
t.setCloseHandler(this);
+ tabWidths.put(t, t.getOffsetWidth());
return t;
}
@@ -522,6 +531,18 @@ public class VTabsheet extends VTabsheetBase
return (Tab) super.getWidget(index);
}
+ private int getTabIndex(Tab tab) {
+ if (tab == null) {
+ return -1;
+ }
+ for (int i = 0; i < getTabCount(); i++) {
+ if (tab.equals(getTab(i))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
private int getTabIndex(String tabId) {
if (tabId == null) {
return -1;
@@ -593,6 +614,7 @@ public class VTabsheet extends VTabsheetBase
}
remove(tab);
+ tabWidths.remove(tab);
/*
* If this widget was selected we need to unmark it as the last
@@ -616,6 +638,13 @@ public class VTabsheet extends VTabsheetBase
}
}
+ private int getLastKnownTabWidth(Tab tab) {
+ if (tabWidths.containsKey(tab)) {
+ return tabWidths.get(tab);
+ }
+ return 0;
+ }
+
private int selectNewShownTab(int oldPosition) {
// After removing a tab, find a new scroll position. In most
// cases the scroll position does not change, but if the tab
@@ -686,6 +715,13 @@ public class VTabsheet extends VTabsheetBase
}
/**
+ * Returns the index of the last visible tab on the server
+ */
+ private int getLastVisibleTab() {
+ return getPreviousVisibleTab(getTabCount());
+ }
+
+ /**
* Find the previous visible tab. Returns -1 if none is found.
*
* @param i
@@ -702,7 +738,7 @@ public class VTabsheet extends VTabsheetBase
public int scrollLeft(int currentFirstVisible) {
int prevVisible = getPreviousVisibleTab(currentFirstVisible);
- if (prevVisible == -1) {
+ if (prevVisible < 0) {
return -1;
}
@@ -715,7 +751,7 @@ public class VTabsheet extends VTabsheetBase
public int scrollRight(int currentFirstVisible) {
int nextVisible = getNextVisibleTab(currentFirstVisible);
- if (nextVisible == -1) {
+ if (nextVisible < 0) {
return -1;
}
Tab currentFirst = getTab(currentFirstVisible);
@@ -1335,14 +1371,44 @@ public class VTabsheet extends VTabsheetBase
scrollerIndex = tb.getNextVisibleTab(scrollerIndex);
}
+ TableCellElement spacerCell = ((TableElement) tb.getElement().cast())
+ .getRows().getItem(0).getCells().getItem(tb.getTabCount());
+ if (scroller.getStyle().getDisplay() != "none") {
+ spacerCell.getStyle().setPropertyPx("minWidth",
+ scroller.getOffsetWidth());
+ spacerCell.getStyle().setPropertyPx("minHeight", 1);
+ } else {
+ spacerCell.getStyle().setProperty("minWidth", "0");
+ spacerCell.getStyle().setProperty("minHeight", "0");
+ }
+
+ // check if hidden tabs need to be scrolled back into view
+ int firstVisibleIndex = tb.getFirstVisibleTabClient();
+ if (firstVisibleIndex != 0 && getTabCount() > 0
+ && getLeftGap() + getRightGap() > 0) {
+ int hiddenCount = tb.getTabCount();
+ if (firstVisibleIndex > 0) {
+ hiddenCount -= firstVisibleIndex;
+ }
+ int counter = 0;
+ while ((getLeftGap() + getRightGap() > getFirstOutOfViewWidth())
+ && counter < hiddenCount) {
+ tb.scrollLeft(tb.getFirstVisibleTabClient());
+ scrollerIndex = tb.getFirstVisibleTabClient();
+ ++counter;
+ }
+ }
+
boolean scrolled = isScrolledTabs();
boolean clipped = isClippedTabs();
if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) {
scroller.getStyle().clearDisplay();
- DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
+ scrollerPrev.setPropertyString("className", SCROLLER_CLASSNAME
+ (scrolled ? "Prev" : PREV_SCROLLER_DISABLED_CLASSNAME));
- DOM.setElementProperty(scrollerNext, "className",
- SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
+ scrollerNext.setPropertyString("className",
+ SCROLLER_CLASSNAME + (clipped
+ && scrollerIndex != tb.getLastVisibleTab() ? "Next"
+ : "Next-disabled"));
// the active tab should be focusable if and only if it is visible
boolean isActiveTabVisible = scrollerIndex <= activeTabIndex
@@ -1367,6 +1433,48 @@ public class VTabsheet extends VTabsheetBase
}
}
+ private int getLeftGap() {
+ int firstVisibleIndex = tb.getFirstVisibleTabClient();
+ int gap;
+ if (firstVisibleIndex < 0) {
+ // no tabs are visible, the entire empty space is returned
+ // through getRightGap()
+ gap = 0;
+ } else {
+ Element tabContainer = tb.getElement().getParentElement();
+ Tab firstVisibleTab = tb.getTab(firstVisibleIndex);
+ gap = firstVisibleTab.getAbsoluteLeft()
+ - tabContainer.getAbsoluteLeft();
+ }
+ return gap > 0 ? gap : 0;
+ }
+
+ private int getRightGap() {
+ int lastVisibleIndex = tb.getLastVisibleTab();
+ Element tabContainer = tb.getElement().getParentElement();
+ int gap;
+ if (lastVisibleIndex < 0) {
+ // no tabs visible, return the whole available width
+ gap = getOffsetWidth() - scroller.getOffsetWidth();
+ } else {
+ Tab lastVisibleTab = tb.getTab(lastVisibleIndex);
+ gap = tabContainer.getAbsoluteRight()
+ - lastVisibleTab.getAbsoluteLeft()
+ - lastVisibleTab.getOffsetWidth()
+ - scroller.getOffsetWidth() - 2;
+ }
+ return gap > 0 ? gap : 0;
+ }
+
+ private int getFirstOutOfViewWidth() {
+ Tab firstTabOutOfView = tb.getTab(
+ tb.getPreviousVisibleTab(tb.getFirstVisibleTabClient()));
+ if (firstTabOutOfView != null) {
+ return tb.getLastKnownTabWidth(firstTabOutOfView);
+ }
+ return 0;
+ }
+
/** For internal use only. May be removed or replaced in the future. */
public void showAllTabs() {
scrollerIndex = tb.getFirstVisibleTab();
@@ -1385,10 +1493,8 @@ public class VTabsheet extends VTabsheetBase
}
private boolean isClippedTabs() {
- return (tb.getOffsetWidth() - DOM.getElementPropertyInt(
- (Element) tb.getContainerElement().getLastChild().cast(),
- "offsetWidth")) > getOffsetWidth()
- - (isScrolledTabs() ? scroller.getOffsetWidth() : 0);
+ return (tb.getOffsetWidth() - getSpacerWidth()) > getOffsetWidth()
+ - (isScrolledTabs() ? scroller.getOffsetWidth() : 0);
}
private boolean isClipped(Tab tab) {
@@ -1396,6 +1502,12 @@ public class VTabsheet extends VTabsheetBase
+ getOffsetWidth() - scroller.getOffsetWidth();
}
+ private int getSpacerWidth() {
+ int spacerWidth = ((Element) tb.getContainerElement().getLastChild()
+ .cast()).getPropertyInt("offsetWidth");
+ return spacerWidth;
+ }
+
@Override
protected void clearPaintables() {
@@ -1934,13 +2046,16 @@ public class VTabsheet extends VTabsheetBase
// On IE8 a tab with false visibility would have the bounds of the
// full TabBar.
if (!tab.isVisible()) {
- while (!tab.isVisible()) {
+ while (!tab.isVisible() && scrollerIndex > 0) {
scrollerIndex = tb.scrollLeft(scrollerIndex);
}
updateTabScroller();
- } else if (isClipped(tab)) {
- while (isClipped(tab) && scrollerIndex != -1) {
+ } else if (isClipped(tab)
+ && scrollerIndex < tb.getLastVisibleTab()) {
+ int tabIndex = tb.getTabIndex(tab);
+ while (isClipped(tab) && scrollerIndex >= 0
+ && scrollerIndex < tabIndex) {
scrollerIndex = tb.scrollRight(scrollerIndex);
}
updateTabScroller();
diff --git a/themes/src/main/themes/VAADIN/themes/valo/components/_tabsheet.scss b/themes/src/main/themes/VAADIN/themes/valo/components/_tabsheet.scss
index 9287c97b66..e0fa665020 100644
--- a/themes/src/main/themes/VAADIN/themes/valo/components/_tabsheet.scss
+++ b/themes/src/main/themes/VAADIN/themes/valo/components/_tabsheet.scss
@@ -131,6 +131,14 @@ $v-tabsheet-content-animation-enabled: $v-animations-enabled !default;
.#{$primary-stylename}-right-aligned-tabs {
@include valo-tabsheet-align-tabs-style($align: right);
+
+ .#{$primary-stylename}-spacertd {
+ display: inline-block !important;
+ }
+
+ .#{$primary-stylename}-scroller {
+ padding-left: 9px;
+ }
}
.#{$primary-stylename}-padded-tabbar {
diff --git a/uitest/src/main/java/com/vaadin/tests/components/tabsheet/ScrolledTabSheetResize.java b/uitest/src/main/java/com/vaadin/tests/components/tabsheet/ScrolledTabSheetResize.java
new file mode 100644
index 0000000000..c84ba5e3ef
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/tabsheet/ScrolledTabSheetResize.java
@@ -0,0 +1,48 @@
+package com.vaadin.tests.components.tabsheet;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.TabSheet;
+import com.vaadin.ui.TabSheet.Tab;
+
+/**
+ * Test class for resizing a scrolled TabSheet.
+ *
+ * @author Vaadin Ltd
+ */
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class ScrolledTabSheetResize extends AbstractTestUI {
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ TabSheet tabSheet = new TabSheet();
+ tabSheet.setSizeFull();
+
+ for (int i = 0; i < 20; i++) {
+ String caption = "Tab " + i;
+ Label label = new Label(caption);
+
+ Tab tab = tabSheet.addTab(label, caption);
+ tab.setClosable(true);
+ }
+
+ addComponent(tabSheet);
+ addComponent(new Button("use reindeer", e -> {
+ setTheme("reindeer");
+ }));
+ }
+
+ @Override
+ protected String getTestDescription() {
+ return "When tabs are scrolled to the end and the TabSheet is made bigger, more tabs should become visible.";
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 807;
+ }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/tabsheet/ScrolledTabSheetResizeTest.java b/uitest/src/test/java/com/vaadin/tests/components/tabsheet/ScrolledTabSheetResizeTest.java
new file mode 100644
index 0000000000..f40c07823f
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/tabsheet/ScrolledTabSheetResizeTest.java
@@ -0,0 +1,155 @@
+package com.vaadin.tests.components.tabsheet;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.elements.TabSheetElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class ScrolledTabSheetResizeTest extends MultiBrowserTest {
+
+ @Override
+ public void setup() throws Exception {
+ super.setup();
+ openTestURL();
+ }
+
+ @Test
+ public void testReindeer() throws IOException, InterruptedException {
+ $(ButtonElement.class).first().click();
+ StringBuilder exceptions = new StringBuilder();
+ boolean failed = false;
+ // upper limit is determined by the amount of tabs,
+ // lower end by limits set by Selenium version
+ for (int i = 1400; i >= 650; i = i - 50) {
+ try {
+ testResize(i);
+ } catch (Exception e) {
+ if (failed) {
+ exceptions.append(" --- ");
+ }
+ failed = true;
+ exceptions.append(i + ": " + e.getMessage());
+ } catch (AssertionError e) {
+ if (failed) {
+ exceptions.append(" --- ");
+ }
+ failed = true;
+ exceptions.append(i + ": " + e.getMessage());
+ }
+ }
+ if (failed) {
+ fail("Combined error report: " + exceptions.toString());
+ }
+ }
+
+ @Test
+ public void testValo() throws IOException, InterruptedException {
+ StringBuilder exceptions = new StringBuilder();
+ boolean failed = false;
+ // upper limit is determined by the amount of tabs (wider than for
+ // reindeer), lower end by limits set by Selenium version
+ for (int i = 1550; i >= 650; i = i - 50) {
+ try {
+ testResize(i);
+ } catch (Exception e) {
+ if (failed) {
+ exceptions.append(" --- ");
+ }
+ failed = true;
+ exceptions.append(i + ": " + e.getMessage());
+ } catch (AssertionError e) {
+ if (failed) {
+ exceptions.append(" --- ");
+ }
+ failed = true;
+ exceptions.append(i + ": " + e.getMessage());
+ }
+ }
+ if (failed) {
+ fail("Combined error report: " + exceptions.toString());
+ }
+ }
+
+ private void testResize(int start)
+ throws IOException, InterruptedException {
+ testBench().resizeViewPortTo(start, 600);
+
+ int iterations = 0;
+ while (scrollRight() && iterations < 50) {
+ ++iterations;
+ }
+
+ // FIXME: TabSheet definitely still has issues,
+ // but it's moving to a better direction.
+
+ // Sometimes the test never realises that scrolling has
+ // reached the end, but this is not critical as long as
+ // the other criteria is fulfilled.
+ // If we decide otherwise, uncomment the following check:
+ // if (iterations >= 50) {
+ // fail("scrolling right never reaches the end");
+ // }
+
+ // This fails on some specific widths by ~15-20 pixels, likewise
+ // deemed as non-critical for now so commented out.
+ // assertNoExtraRoom(start);
+
+ testBench().resizeViewPortTo(start + 150, 600);
+
+ assertNoExtraRoom(start + 150);
+ }
+
+ private void assertNoExtraRoom(int width) {
+ TabSheetElement ts = $(TabSheetElement.class).first();
+ WebElement scroller = ts
+ .findElement(By.className("v-tabsheet-scroller"));
+ List<WebElement> tabs = ts
+ .findElements(By.className("v-tabsheet-tabitemcell"));
+ WebElement lastTab = tabs.get(tabs.size() - 1);
+
+ assertEquals("Tab 19",
+ lastTab.findElement(By.className("v-captiontext")).getText());
+
+ int tabWidth = lastTab.getSize().width;
+ int tabRight = lastTab.getLocation().x + tabWidth;
+ int scrollerLeft = scroller.findElement(By.tagName("button"))
+ .getLocation().x;
+
+ assertThat("Not scrolled to the end (width: " + width + ")",
+ scrollerLeft, greaterThan(tabRight));
+ // technically this should probably be just greaterThan,
+ // but one pixel's difference is irrelevant for now
+ assertThat("Too big gap (width: " + width + ")", tabWidth,
+ greaterThanOrEqualTo(scrollerLeft - tabRight));
+ }
+
+ /*
+ * Scroll the tabsheet bar to the right.
+ */
+ private boolean scrollRight() throws InterruptedException {
+ List<WebElement> scrollElements = getDriver()
+ .findElements(By.className("v-tabsheet-scrollerNext"));
+ if (!scrollElements.isEmpty()) {
+ TestBenchElement rightScrollElement = (TestBenchElement) scrollElements
+ .get(0);
+ rightScrollElement.click(5, 5);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+}