Commit 515f0f0d authored by fukino's avatar fukino Committed by Commit bot

Files.app: Add touch feedback on breadcrumbs.

- When the bread crumbs is updated, we need to keep DOMs for the common path prefix to continue the animation for touch feedback.
- Made each crumbs as <button>, so I removed the unnecessary event listener for 'keydown' in location_line.js.
- Fixed minor issue on truncation algorithm, to prevent crumbs from being too small (smaller than '...').

BUG=446616
TEST=manual test: Check the animation on touch on breadcrumbs, and Check the truncation when window width is changed.

Review URL: https://codereview.chromium.org/1026693003

Cr-Commit-Position: refs/heads/master@{#321554}
parent 25d14cc7
...@@ -307,6 +307,30 @@ select[size='1']:hover { ...@@ -307,6 +307,30 @@ select[size='1']:hover {
border-image-slice: 5; border-image-slice: 5;
} }
/* Style for <button>s to have similar style with Polymer's <paper-button>. */
.imitate-paper-button {
-webkit-user-select: none;
background: transparent;
border: 0;
border-image: none;
border-radius: 3px;
box-sizing: border-box;
color: inherit;
cursor: pointer;
display: inline-block;
font: inherit;
height: auto;
margin: 0 0.29em;
min-width: 5.14em;
outline: none;
overflow: hidden;
padding: 0.7em 0.57em;
position: relative;
text-align: center;
text-transform: uppercase;
z-index: 0;
}
/* Gray progress bar. */ /* Gray progress bar. */
.progress-bar { .progress-bar {
background-color: #e6e6e6; background-color: #e6e6e6;
......
...@@ -294,22 +294,21 @@ body.check-select button:hover { ...@@ -294,22 +294,21 @@ body.check-select button:hover {
width: 32px; width: 32px;
} }
.dialog-header .icon-button paper-ripple { .dialog-header button paper-ripple {
color: black; color: rgb(40, 40, 40);
} }
body.check-select .dialog-header .icon-button paper-ripple { body.check-select .dialog-header button paper-ripple {
rgb(90, 90, 90); color: rgb(90, 90, 90);
} }
.dialog-header .icon-button:focus:not(.using-mouse):not([tabindex='-1']), .dialog-header
.dialog-header .combobutton:focus { button:focus:not(.using-mouse):not([tabindex='-1']):not(:active) {
background-color: rgba(90, 90, 90, 0.15); background-color: rgba(90, 90, 90, 0.15);
} }
body.check-select .dialog-header body.check-select .dialog-header
.icon-button:focus:not(.using-mouse):not([tabindex='-1']), button:focus:not(.using-mouse):not([tabindex='-1']):not(:active) {
body.check-select .dialog-header .combobutton:focus {
background-color: rgba(153, 153, 153, 0.20); background-color: rgba(153, 153, 153, 0.20);
} }
...@@ -792,20 +791,31 @@ body.check-select .breadcrumbs { ...@@ -792,20 +791,31 @@ body.check-select .breadcrumbs {
} }
.breadcrumbs > [collapsed] { .breadcrumbs > [collapsed] {
box-sizing: content-box !important;
width: 1em; width: 1em;
} }
/* A single directory name in the list of path breadcrumbs. */ /* A single directory name in the list of path breadcrumbs. */
.breadcrumb-path { .breadcrumbs .breadcrumb-path {
box-sizing: border-box;
cursor: pointer; cursor: pointer;
flex: none; flex: none;
overflow: hidden; margin-left: 0;
margin-right: 0;
min-width: 0;
padding-left: 5px;
padding-right: 5px;
text-overflow: ellipsis; text-overflow: ellipsis;
text-transform: none;
white-space: nowrap; white-space: nowrap;
} }
.breadcrumbs .breadcrumb-path:active {
color: inherit;
}
/* The final breadcrumb, representing the current directory. */ /* The final breadcrumb, representing the current directory. */
.breadcrumb-path.breadcrumb-last { .breadcrumbs .breadcrumb-path.breadcrumb-last {
cursor: default; cursor: default;
} }
...@@ -816,7 +826,7 @@ body.check-select .breadcrumbs { ...@@ -816,7 +826,7 @@ body.check-select .breadcrumbs {
url(../images/files/ui/2x/arrow_right_white.png) 2x) center no-repeat; url(../images/files/ui/2x/arrow_right_white.png) 2x) center no-repeat;
flex: none; flex: none;
height: 16px; height: 16px;
width: 26px; width: 16px;
} }
html[dir='rtl'] .breadcrumbs .separator { html[dir='rtl'] .breadcrumbs .separator {
......
...@@ -14,6 +14,7 @@ function LocationLine(breadcrumbs, volumeManager) { ...@@ -14,6 +14,7 @@ function LocationLine(breadcrumbs, volumeManager) {
this.breadcrumbs_ = breadcrumbs; this.breadcrumbs_ = breadcrumbs;
this.volumeManager_ = volumeManager; this.volumeManager_ = volumeManager;
this.entry_ = null; this.entry_ = null;
this.components_ = [];
} }
/** /**
...@@ -101,38 +102,69 @@ LocationLine.prototype.getComponents_ = function(entry) { ...@@ -101,38 +102,69 @@ LocationLine.prototype.getComponents_ = function(entry) {
* @private * @private
*/ */
LocationLine.prototype.update_ = function(components) { LocationLine.prototype.update_ = function(components) {
this.breadcrumbs_.textContent = ''; this.components_ = components;
this.breadcrumbs_.hidden = false;
var doc = this.breadcrumbs_.ownerDocument; // Make the new breadcrumbs temporarily.
var newBreadcrumbs = document.createElement('div');
for (var i = 0; i < components.length; i++) { for (var i = 0; i < components.length; i++) {
// Add a component. // Add a component.
var component = components[i]; var component = components[i];
var div = doc.createElement('div'); var button = document.createElement('button');
div.classList.add('breadcrumb-path', 'entry-name'); button.classList.add(
div.textContent = component.name; 'breadcrumb-path', 'entry-name', 'imitate-paper-button');
div.tabIndex = 8; button.textContent = component.name;
div.addEventListener('click', this.execute_.bind(this, div, component)); button.addEventListener('click', this.onClick_.bind(this, i));
div.addEventListener('keydown', function(div, component, event) { newBreadcrumbs.appendChild(button);
// If the pressed key is either Enter or Space.
if (event.keyCode == 13 || event.keyCode == 32) var ripple = document.createElement('paper-ripple');
this.execute_(div, component); ripple.setAttribute('fit', '');
}.bind(this, div, component)); button.appendChild(ripple);
this.breadcrumbs_.appendChild(div);
// If this is the last component, break here. // If this is the last component, break here.
if (i === components.length - 1) { if (i === components.length - 1)
div.classList.add('breadcrumb-last');
div.tabIndex = -1;
break; break;
}
// Add a separator. // Add a separator.
var separator = doc.createElement('span'); var separator = document.createElement('span');
separator.classList.add('separator'); separator.classList.add('separator');
this.breadcrumbs_.appendChild(separator); newBreadcrumbs.appendChild(separator);
}
// Replace the shown breadcrumbs with the new one, keeping the DOMs for common
// prefix of the path.
// 1. Forward the references to the path element while in the common prefix.
var childOriginal = this.breadcrumbs_.firstChild;
var childNew = newBreadcrumbs.firstChild;
var cnt = 0;
while (childOriginal && childNew &&
childOriginal.textContent === childNew.textContent) {
childOriginal = childOriginal.nextSibling;
childNew = childNew.nextSibling;
cnt++;
}
// 2. Remove all elements in original breadcrumbs which are not in the common
// prefix.
while (childOriginal) {
var childToRemove = childOriginal;
childOriginal = childOriginal.nextSibling;
this.breadcrumbs_.removeChild(childToRemove);
}
// 3. Append new elements after the common prefix.
while (childNew) {
var childToAppend = childNew;
childNew = childNew.nextSibling;
this.breadcrumbs_.appendChild(childToAppend);
}
// 4. Reset the tab index and class 'breadcrumb-last'.
for (var el = this.breadcrumbs_.firstChild; el; el = el.nextSibling) {
if (el.classList.contains('breadcrumb-path')) {
var isLast = !el.nextSibling;
el.tabIndex = isLast ? -1 : 8;
el.classList.toggle('breadcrumb-last', isLast);
}
} }
this.breadcrumbs_.hidden = false;
this.truncate(); this.truncate();
}; };
...@@ -148,6 +180,7 @@ LocationLine.prototype.truncate = function() { ...@@ -148,6 +180,7 @@ LocationLine.prototype.truncate = function() {
for (var item = this.breadcrumbs_.firstChild; item; item = item.nextSibling) { for (var item = this.breadcrumbs_.firstChild; item; item = item.nextSibling) {
item.removeAttribute('style'); item.removeAttribute('style');
item.removeAttribute('collapsed'); item.removeAttribute('collapsed');
item.removeAttribute('hidden');
} }
var containerWidth = this.breadcrumbs_.clientWidth; var containerWidth = this.breadcrumbs_.clientWidth;
...@@ -179,6 +212,12 @@ LocationLine.prototype.truncate = function() { ...@@ -179,6 +212,12 @@ LocationLine.prototype.truncate = function() {
maxPathWidth = Math.min(pathWidth, maxPathWidth); maxPathWidth = Math.min(pathWidth, maxPathWidth);
var parentCrumb = lastSeparator.previousSibling; var parentCrumb = lastSeparator.previousSibling;
// Pre-calculate the minimum width for crumbs.
parentCrumb.setAttribute('collapsed', '');
var minCrumbWidth = parentCrumb.clientWidth;
parentCrumb.removeAttribute('collapsed');
var collapsedWidth = 0; var collapsedWidth = 0;
if (parentCrumb && pathWidth - maxPathWidth > parentCrumb.clientWidth) { if (parentCrumb && pathWidth - maxPathWidth > parentCrumb.clientWidth) {
// At least one crumb is hidden completely (or almost completely). // At least one crumb is hidden completely (or almost completely).
...@@ -202,18 +241,28 @@ LocationLine.prototype.truncate = function() { ...@@ -202,18 +241,28 @@ LocationLine.prototype.truncate = function() {
item = item.nextSibling) { item = item.nextSibling) {
// TODO(serya): Mixing access item.clientWidth and modifying style and // TODO(serya): Mixing access item.clientWidth and modifying style and
// attributes could cause multiple layout reflows. // attributes could cause multiple layout reflows.
if (pathWidth + item.clientWidth <= maxPathWidth) { if (pathWidth === maxPathWidth) {
pathWidth += item.clientWidth; item.setAttribute('hidden', '');
} else if (pathWidth == maxPathWidth) { } else {
item.style.width = '0'; if (item.classList.contains('separator')) {
} else if (item.classList.contains('separator')) { // If the current separator and the following crumb don't fit in the
// Do not truncate separator. Instead let the last crumb be longer. // breadcrumbs area, hide remaining separators and crumbs.
item.style.width = '0'; if (pathWidth + item.clientWidth + minCrumbWidth > maxPathWidth) {
item.setAttribute('hidden', '');
maxPathWidth = pathWidth; maxPathWidth = pathWidth;
} else { } else {
// Truncate the last visible crumb. pathWidth += item.clientWidth;
}
} else {
// If the current crumb doesn't fully fit in the breadcrumbs area,
// shorten the crumb and hide remaining separators and crums.
if (pathWidth + item.clientWidth > maxPathWidth) {
item.style.width = (maxPathWidth - pathWidth) + 'px'; item.style.width = (maxPathWidth - pathWidth) + 'px';
pathWidth = maxPathWidth; pathWidth = maxPathWidth;
} else {
pathWidth += item.clientWidth;
}
}
} }
} }
...@@ -232,16 +281,22 @@ LocationLine.prototype.hide = function() { ...@@ -232,16 +281,22 @@ LocationLine.prototype.hide = function() {
/** /**
* Execute an element. * Execute an element.
* @param {!Element} element Element to be executed. * @param {number} index The index of clicked path component.
* @param {!LocationLine.PathComponent} pathComponent Path Component object of * @param {!Event} event The MouseEvent object.
* the element.
* @private * @private
*/ */
LocationLine.prototype.execute_ = function(element, pathComponent) { LocationLine.prototype.onClick_ = function(index, event) {
if (!element.classList.contains('breadcrumb-path') || if (index >= this.components_.length - 1)
element.classList.contains('breadcrumb-last'))
return; return;
// Remove 'focused' state from the clicked button.
var button = event.target;
while (button && !button.classList.contains('breadcrumb-path'))
button = button.parentElement;
if (button)
button.blur();
var pathComponent = this.components_[index];
pathComponent.resolveEntry().then(function(entry) { pathComponent.resolveEntry().then(function(entry) {
var pathClickEvent = new Event('pathclick'); var pathClickEvent = new Event('pathclick');
pathClickEvent.entry = entry; pathClickEvent.entry = entry;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment