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

Commit

Permalink
fix(list): make secondary items static
Browse files Browse the repository at this point in the history
* The secondary items always had an absolute position, which causes issues with overflowing.
* This commit refactors the list to make the secondary items static.
* Fixes multiple secondary padding (as in specs - consistent)
* Fixes Proxy Scan for secondary items container
* Improved tests for the list component (more clear and more safe)

Fixes #7500. Fixes #2759.
  • Loading branch information
devversion committed Mar 22, 2016
1 parent 991cd7a commit c0ea4ae
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 185 deletions.
7 changes: 1 addition & 6 deletions src/components/list/demoListControls/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,4 @@ md-list-item ._md-list-item-inner > ._md-list-item-inner > p {
-moz-user-select: none; /* Firefox all */
-ms-user-select: none; /* IE 10+ */
user-select: none; /* Likely future */
}

/* Add some right padding so that the text doesn't overlap the buttons */
.secondary-button-padding p {
padding-right: 100px;
}
}
113 changes: 55 additions & 58 deletions src/components/list/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,12 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
restrict: 'E',
controller: 'MdListController',
compile: function(tEl, tAttrs) {

// Check for proxy controls (no ng-click on parent, and a control inside)
var secondaryItems = tEl[0].querySelectorAll('.md-secondary');
var hasProxiedElement;
var proxyElement;
var itemContainer = tEl;

tEl[0].setAttribute('role', 'listitem');

Expand Down Expand Up @@ -130,14 +132,13 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
}

function wrapIn(type) {
var container;
if (type == 'div') {
container = angular.element('<div class="_md-no-style _md-list-item-inner">');
container.append(tEl.contents());
itemContainer = angular.element('<div class="_md-no-style _md-list-item-inner">');
itemContainer.append(tEl.contents());
tEl.addClass('_md-proxy-focus');
} else {
// Element which holds the default list-item content.
container = angular.element(
itemContainer = angular.element(
'<div class="md-button _md-no-style">'+
' <div class="_md-list-item-inner"></div>'+
'</div>'
Expand All @@ -152,58 +153,48 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
copyAttributes(tEl[0], buttonWrap[0]);

// Append the button wrap before our list-item content, because it will overlay in relative.
container.prepend(buttonWrap);
container.children().eq(1).append(tEl.contents());
itemContainer.prepend(buttonWrap);
itemContainer.children().eq(1).append(tEl.contents());

tEl.addClass('_md-button-wrap');
}

tEl[0].setAttribute('tabindex', '-1');
tEl.append(container);
tEl.append(itemContainer);
}

function wrapSecondaryItems() {
if (secondaryItems.length === 1) {
wrapSecondaryItem(secondaryItems[0], tEl);
} else if (secondaryItems.length > 1) {
var secondaryItemsWrapper = angular.element('<div class="_md-secondary-container">');
angular.forEach(secondaryItems, function(secondaryItem) {
wrapSecondaryItem(secondaryItem, secondaryItemsWrapper, true);
});
tEl.append(secondaryItemsWrapper);
}
var secondaryItemsWrapper = angular.element('<div class="_md-secondary-container">');

angular.forEach(secondaryItems, function(secondaryItem) {
wrapSecondaryItem(secondaryItem, secondaryItemsWrapper);
});

// Since the secondary item container is static we need to fill the remaing space.
var spaceFiller = angular.element('<div class="flex"></div>');
itemContainer.append(spaceFiller);

itemContainer.append(secondaryItemsWrapper);
}

function wrapSecondaryItem(secondaryItem, container, hasSecondaryItemsWrapper) {
function wrapSecondaryItem(secondaryItem, container) {
if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) {
$mdAria.expect(secondaryItem, 'aria-label');
var buttonWrapper;
if (hasSecondaryItemsWrapper) {
buttonWrapper = angular.element('<md-button class="md-icon-button">');
} else {
buttonWrapper = angular.element('<md-button class="_md-secondary-container md-icon-button">');
}
var buttonWrapper = angular.element('<md-button class="md-secondary md-icon-button">');
copyAttributes(secondaryItem, buttonWrapper[0]);
secondaryItem.setAttribute('tabindex', '-1');
secondaryItem.classList.remove('md-secondary');
buttonWrapper.append(secondaryItem);
secondaryItem = buttonWrapper[0];
}

// Check for a secondary item and move it outside
if ( secondaryItem && (
secondaryItem.hasAttribute('ng-click') ||
( tAttrs.ngClick &&
isProxiedElement(secondaryItem) )
)) {
// When using multiple secondary items we need to remove their secondary class to be
// orderd correctly in the list-item
if (hasSecondaryItemsWrapper) {
secondaryItem.classList.remove('md-secondary');
}
tEl.addClass('md-with-secondary');
container.append(secondaryItem);
if (secondaryItem && (!hasClickEvent(secondaryItem) || (!tAttrs.ngClick && isProxiedElement(secondaryItem)))) {
// In this case we remove the secondary class, so we can identify it later, when we searching for the
// proxy items.
angular.element(secondaryItem).removeClass('md-secondary');
}

tEl.addClass('md-with-secondary');
container.append(secondaryItem);
}

function copyAttributes(item, wrapper) {
Expand All @@ -227,14 +218,23 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
return nodeName == "MD-BUTTON" || nodeName == "BUTTON";
}

function hasClickEvent (element) {
var attr = element.attributes;
for (var i = 0; i < attr.length; i++) {
if (tAttrs.$normalize(attr[i].name) === 'ngClick') return true;
}
return false;
}

return postLink;

function postLink($scope, $element, $attr, ctrl) {

var proxies = [],
firstChild = $element[0].firstElementChild,
hasClick = firstChild && firstChild.firstElementChild &&
hasClickEvent(firstChild.firstElementChild);
var proxies = [],
firstElement = $element[0].firstElementChild,
isButtonWrap = $element.hasClass('_md-button-wrap'),
clickChild = isButtonWrap ? firstElement.firstElementChild : firstElement,
hasClick = clickChild && hasClickEvent(clickChild);

computeProxies();
computeClickable();
Expand All @@ -260,22 +260,19 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
});
}

function hasClickEvent (element) {
var attr = element.attributes;
for (var i = 0; i < attr.length; i++) {
if ($attr.$normalize(attr[i].name) === 'ngClick') return true;
}
return false;
}

function computeProxies() {
var children = $element.children();
if (children.length && !children[0].hasAttribute('ng-click')) {
if (firstElement && firstElement.children && !hasClick) {

angular.forEach(proxiedTypes, function(type) {
angular.forEach(firstChild.querySelectorAll(type), function(child) {

// All elements which are not capable for being used a proxy have the .md-secondary class
// applied. These items had been sorted out in the secondary wrap function.
angular.forEach(firstElement.querySelectorAll(type + ':not(.md-secondary)'), function(child) {
proxies.push(child);
});
});

}
}
function computeClickable() {
Expand All @@ -288,12 +285,12 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
}
}

var firstChildKeypressListener = function(e) {
var clickChildKeypressListener = function(e) {
if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA' && !e.target.isContentEditable) {
var keyCode = e.which || e.keyCode;
if (keyCode == $mdConstant.KEY_CODE.SPACE) {
if (firstChild) {
firstChild.click();
if (clickChild) {
clickChild.click();
e.preventDefault();
e.stopPropagation();
}
Expand All @@ -302,16 +299,16 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
};

if (!hasClick && !proxies.length) {
firstChild && firstChild.addEventListener('keypress', firstChildKeypressListener);
clickChild && clickChild.addEventListener('keypress', clickChildKeypressListener);
}

$element.off('click');
$element.off('keypress');

if (proxies.length == 1 && firstChild) {
if (proxies.length == 1 && clickChild) {
$element.children().eq(0).on('click', function(e) {
var parentButton = $mdUtil.getClosest(e.target, 'BUTTON');
if (!parentButton && firstChild.contains(e.target)) {
if (!parentButton && clickChild.contains(e.target)) {
angular.forEach(proxies, function(proxy) {
if (e.target !== proxy && !proxy.contains(e.target)) {
angular.element(proxy).triggerHandler('click');
Expand All @@ -322,7 +319,7 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
}

$scope.$on('$destroy', function () {
firstChild && firstChild.removeEventListener('keypress', firstChildKeypressListener);
clickChild && clickChild.removeEventListener('keypress', clickChildKeypressListener);
});
}
}
Expand Down
80 changes: 31 additions & 49 deletions src/components/list/list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,6 @@ md-list-item {
outline: none
}
}
&.md-with-secondary {
position: relative;
}
&.md-clickable:hover {
cursor: pointer;
}
Expand Down Expand Up @@ -263,12 +260,7 @@ md-list-item {
& > md-icon:first-child:not(.md-avatar-icon) {
@include rtl-prop(margin-right, margin-left, $list-item-primary-width - $list-item-primary-icon-width);
}
& > md-checkbox {
width: 3 * $baseline-grid;
@include rtl(margin-left, 3px, 29px);
@include rtl(margin-right, 29px,3px);
margin-top: 16px;
}

& .md-avatar, .md-avatar-icon {
margin-top: $baseline-grid;
margin-bottom: $baseline-grid;
Expand All @@ -284,57 +276,47 @@ md-list-item {
padding: 8px;
}

md-checkbox.md-secondary,
md-switch.md-secondary {
margin-top: 0;
margin-bottom: 0;
}

md-checkbox.md-secondary {
@include rtl-prop(margin-right, margin-left, 0);
& > md-checkbox {
width: 3 * $baseline-grid;
@include rtl(margin-left, 3px, 29px);
@include rtl(margin-right, 29px, 3px);
margin-top: 16px;
}

md-switch.md-secondary {
@include rtl-prop(margin-right, margin-left, -6px);
}
._md-secondary-container {
display: flex;
align-items: center;

button.md-button._md-secondary-container {
background-color: transparent;
align-self: center;
border-radius: 50%;
margin: 0px;
min-width: 0px;
height: 100%;
margin: auto;

.md-ripple,
.md-ripple-container {
border-radius: 50%;
.md-button, .md-icon-button {
&:last-of-type {
// Reset 6px margin for the button.
@include rtl-prop(margin-right, margin-left, 0px);
}
}
}

._md-secondary-container {
@include rtl-prop(margin-right, margin-left, -12px);
md-checkbox {
margin-top: 0;
margin-bottom: 0;

&.md-icon-button {
@include rtl-prop(margin-right, margin-left, -6px);
&:last-child {
width: 3 * $baseline-grid;
@include rtl-prop(margin-right, margin-left, 0);
}
}
}

._md-secondary-container,
.md-secondary {
position: absolute;
top: 50%;
margin: 0;
@include rtl-prop(right, left, $list-item-padding-horizontal);
transform: translate3d(0, -50%, 0);
}
md-switch {
margin-top: 0;
margin-bottom: 0;

& > .md-button._md-secondary-container > .md-secondary {
@include rtl-prop(margin-left, margin-right, 0);
position: static;
@include rtl-prop(margin-right, margin-left, -6px);
}
}

& > p, & > ._md-list-item-inner > p {
flex: 1;
flex: 1 1 auto;
margin: 0;
}
}
Expand All @@ -351,7 +333,7 @@ md-list-item {
}

.md-list-item-text {
flex: 1;
flex: 1 1 auto;
margin: auto;
text-overflow: ellipsis;
overflow: hidden;
Expand Down Expand Up @@ -405,7 +387,7 @@ md-list-item {
align-self: flex-start;
}
.md-list-item-text {
flex: 1;
flex: 1 1 auto;
}
}
}
Expand Down
Loading

0 comments on commit c0ea4ae

Please sign in to comment.