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

Commit 9bb78e0

Browse files
devversionThomasBurleson
authored andcommitted
fix(list): make secondary items static
* 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. Closes #7651
1 parent 5e043a1 commit 9bb78e0

File tree

4 files changed

+244
-185
lines changed

4 files changed

+244
-185
lines changed

src/components/list/demoListControls/style.css

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,4 @@ md-list-item ._md-list-item-inner > ._md-list-item-inner > p {
1414
-moz-user-select: none; /* Firefox all */
1515
-ms-user-select: none; /* IE 10+ */
1616
user-select: none; /* Likely future */
17-
}
18-
19-
/* Add some right padding so that the text doesn't overlap the buttons */
20-
.secondary-button-padding p {
21-
padding-right: 100px;
22-
}
17+
}

src/components/list/list.js

Lines changed: 55 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,12 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
8888
restrict: 'E',
8989
controller: 'MdListController',
9090
compile: function(tEl, tAttrs) {
91+
9192
// Check for proxy controls (no ng-click on parent, and a control inside)
9293
var secondaryItems = tEl[0].querySelectorAll('.md-secondary');
9394
var hasProxiedElement;
9495
var proxyElement;
96+
var itemContainer = tEl;
9597

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

@@ -130,14 +132,13 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
130132
}
131133

132134
function wrapIn(type) {
133-
var container;
134135
if (type == 'div') {
135-
container = angular.element('<div class="_md-no-style _md-list-item-inner">');
136-
container.append(tEl.contents());
136+
itemContainer = angular.element('<div class="_md-no-style _md-list-item-inner">');
137+
itemContainer.append(tEl.contents());
137138
tEl.addClass('_md-proxy-focus');
138139
} else {
139140
// Element which holds the default list-item content.
140-
container = angular.element(
141+
itemContainer = angular.element(
141142
'<div class="md-button _md-no-style">'+
142143
' <div class="_md-list-item-inner"></div>'+
143144
'</div>'
@@ -152,58 +153,48 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
152153
copyAttributes(tEl[0], buttonWrap[0]);
153154

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

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

161162
tEl[0].setAttribute('tabindex', '-1');
162-
tEl.append(container);
163+
tEl.append(itemContainer);
163164
}
164165

165166
function wrapSecondaryItems() {
166-
if (secondaryItems.length === 1) {
167-
wrapSecondaryItem(secondaryItems[0], tEl);
168-
} else if (secondaryItems.length > 1) {
169-
var secondaryItemsWrapper = angular.element('<div class="_md-secondary-container">');
170-
angular.forEach(secondaryItems, function(secondaryItem) {
171-
wrapSecondaryItem(secondaryItem, secondaryItemsWrapper, true);
172-
});
173-
tEl.append(secondaryItemsWrapper);
174-
}
167+
var secondaryItemsWrapper = angular.element('<div class="_md-secondary-container">');
168+
169+
angular.forEach(secondaryItems, function(secondaryItem) {
170+
wrapSecondaryItem(secondaryItem, secondaryItemsWrapper);
171+
});
172+
173+
// Since the secondary item container is static we need to fill the remaing space.
174+
var spaceFiller = angular.element('<div class="flex"></div>');
175+
itemContainer.append(spaceFiller);
176+
177+
itemContainer.append(secondaryItemsWrapper);
175178
}
176179

177-
function wrapSecondaryItem(secondaryItem, container, hasSecondaryItemsWrapper) {
180+
function wrapSecondaryItem(secondaryItem, container) {
178181
if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) {
179182
$mdAria.expect(secondaryItem, 'aria-label');
180-
var buttonWrapper;
181-
if (hasSecondaryItemsWrapper) {
182-
buttonWrapper = angular.element('<md-button class="md-icon-button">');
183-
} else {
184-
buttonWrapper = angular.element('<md-button class="_md-secondary-container md-icon-button">');
185-
}
183+
var buttonWrapper = angular.element('<md-button class="md-secondary md-icon-button">');
186184
copyAttributes(secondaryItem, buttonWrapper[0]);
187185
secondaryItem.setAttribute('tabindex', '-1');
188-
secondaryItem.classList.remove('md-secondary');
189186
buttonWrapper.append(secondaryItem);
190187
secondaryItem = buttonWrapper[0];
191188
}
192189

193-
// Check for a secondary item and move it outside
194-
if ( secondaryItem && (
195-
secondaryItem.hasAttribute('ng-click') ||
196-
( tAttrs.ngClick &&
197-
isProxiedElement(secondaryItem) )
198-
)) {
199-
// When using multiple secondary items we need to remove their secondary class to be
200-
// orderd correctly in the list-item
201-
if (hasSecondaryItemsWrapper) {
202-
secondaryItem.classList.remove('md-secondary');
203-
}
204-
tEl.addClass('md-with-secondary');
205-
container.append(secondaryItem);
190+
if (secondaryItem && (!hasClickEvent(secondaryItem) || (!tAttrs.ngClick && isProxiedElement(secondaryItem)))) {
191+
// In this case we remove the secondary class, so we can identify it later, when we searching for the
192+
// proxy items.
193+
angular.element(secondaryItem).removeClass('md-secondary');
206194
}
195+
196+
tEl.addClass('md-with-secondary');
197+
container.append(secondaryItem);
207198
}
208199

209200
function copyAttributes(item, wrapper) {
@@ -228,14 +219,23 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
228219
return nodeName == "MD-BUTTON" || nodeName == "BUTTON";
229220
}
230221

222+
function hasClickEvent (element) {
223+
var attr = element.attributes;
224+
for (var i = 0; i < attr.length; i++) {
225+
if (tAttrs.$normalize(attr[i].name) === 'ngClick') return true;
226+
}
227+
return false;
228+
}
229+
231230
return postLink;
232231

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

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

240240
computeProxies();
241241
computeClickable();
@@ -261,22 +261,19 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
261261
});
262262
}
263263

264-
function hasClickEvent (element) {
265-
var attr = element.attributes;
266-
for (var i = 0; i < attr.length; i++) {
267-
if ($attr.$normalize(attr[i].name) === 'ngClick') return true;
268-
}
269-
return false;
270-
}
271264

272265
function computeProxies() {
273-
var children = $element.children();
274-
if (children.length && !children[0].hasAttribute('ng-click')) {
266+
if (firstElement && firstElement.children && !hasClick) {
267+
275268
angular.forEach(proxiedTypes, function(type) {
276-
angular.forEach(firstChild.querySelectorAll(type), function(child) {
269+
270+
// All elements which are not capable for being used a proxy have the .md-secondary class
271+
// applied. These items had been sorted out in the secondary wrap function.
272+
angular.forEach(firstElement.querySelectorAll(type + ':not(.md-secondary)'), function(child) {
277273
proxies.push(child);
278274
});
279275
});
276+
280277
}
281278
}
282279
function computeClickable() {
@@ -289,12 +286,12 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
289286
}
290287
}
291288

292-
var firstChildKeypressListener = function(e) {
289+
var clickChildKeypressListener = function(e) {
293290
if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA' && !e.target.isContentEditable) {
294291
var keyCode = e.which || e.keyCode;
295292
if (keyCode == $mdConstant.KEY_CODE.SPACE) {
296-
if (firstChild) {
297-
firstChild.click();
293+
if (clickChild) {
294+
clickChild.click();
298295
e.preventDefault();
299296
e.stopPropagation();
300297
}
@@ -303,16 +300,16 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
303300
};
304301

305302
if (!hasClick && !proxies.length) {
306-
firstChild && firstChild.addEventListener('keypress', firstChildKeypressListener);
303+
clickChild && clickChild.addEventListener('keypress', clickChildKeypressListener);
307304
}
308305

309306
$element.off('click');
310307
$element.off('keypress');
311308

312-
if (proxies.length == 1 && firstChild) {
309+
if (proxies.length == 1 && clickChild) {
313310
$element.children().eq(0).on('click', function(e) {
314311
var parentButton = $mdUtil.getClosest(e.target, 'BUTTON');
315-
if (!parentButton && firstChild.contains(e.target)) {
312+
if (!parentButton && clickChild.contains(e.target)) {
316313
angular.forEach(proxies, function(proxy) {
317314
if (e.target !== proxy && !proxy.contains(e.target)) {
318315
angular.element(proxy).triggerHandler('click');
@@ -323,7 +320,7 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
323320
}
324321

325322
$scope.$on('$destroy', function () {
326-
firstChild && firstChild.removeEventListener('keypress', firstChildKeypressListener);
323+
clickChild && clickChild.removeEventListener('keypress', clickChildKeypressListener);
327324
});
328325
}
329326
}

src/components/list/list.scss

Lines changed: 31 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,6 @@ md-list-item {
213213
outline: none
214214
}
215215
}
216-
&.md-with-secondary {
217-
position: relative;
218-
}
219216
&.md-clickable:hover {
220217
cursor: pointer;
221218
}
@@ -263,12 +260,7 @@ md-list-item {
263260
& > md-icon:first-child:not(.md-avatar-icon) {
264261
@include rtl-prop(margin-right, margin-left, $list-item-primary-width - $list-item-primary-icon-width);
265262
}
266-
& > md-checkbox {
267-
width: 3 * $baseline-grid;
268-
@include rtl(margin-left, 3px, 29px);
269-
@include rtl(margin-right, 29px,3px);
270-
margin-top: 16px;
271-
}
263+
272264
& .md-avatar, .md-avatar-icon {
273265
margin-top: $baseline-grid;
274266
margin-bottom: $baseline-grid;
@@ -284,57 +276,47 @@ md-list-item {
284276
padding: 8px;
285277
}
286278

287-
md-checkbox.md-secondary,
288-
md-switch.md-secondary {
289-
margin-top: 0;
290-
margin-bottom: 0;
291-
}
292-
293-
md-checkbox.md-secondary {
294-
@include rtl-prop(margin-right, margin-left, 0);
279+
& > md-checkbox {
280+
width: 3 * $baseline-grid;
281+
@include rtl(margin-left, 3px, 29px);
282+
@include rtl(margin-right, 29px, 3px);
283+
margin-top: 16px;
295284
}
296285

297-
md-switch.md-secondary {
298-
@include rtl-prop(margin-right, margin-left, -6px);
299-
}
286+
._md-secondary-container {
287+
display: flex;
288+
align-items: center;
300289

301-
button.md-button._md-secondary-container {
302-
background-color: transparent;
303-
align-self: center;
304-
border-radius: 50%;
305-
margin: 0px;
306-
min-width: 0px;
290+
height: 100%;
291+
margin: auto;
307292

308-
.md-ripple,
309-
.md-ripple-container {
310-
border-radius: 50%;
293+
.md-button, .md-icon-button {
294+
&:last-of-type {
295+
// Reset 6px margin for the button.
296+
@include rtl-prop(margin-right, margin-left, 0px);
297+
}
311298
}
312-
}
313299

314-
._md-secondary-container {
315-
@include rtl-prop(margin-right, margin-left, -12px);
300+
md-checkbox {
301+
margin-top: 0;
302+
margin-bottom: 0;
316303

317-
&.md-icon-button {
318-
@include rtl-prop(margin-right, margin-left, -6px);
304+
&:last-child {
305+
width: 3 * $baseline-grid;
306+
@include rtl-prop(margin-right, margin-left, 0);
307+
}
319308
}
320-
}
321309

322-
._md-secondary-container,
323-
.md-secondary {
324-
position: absolute;
325-
top: 50%;
326-
margin: 0;
327-
@include rtl-prop(right, left, $list-item-padding-horizontal);
328-
transform: translate3d(0, -50%, 0);
329-
}
310+
md-switch {
311+
margin-top: 0;
312+
margin-bottom: 0;
330313

331-
& > .md-button._md-secondary-container > .md-secondary {
332-
@include rtl-prop(margin-left, margin-right, 0);
333-
position: static;
314+
@include rtl-prop(margin-right, margin-left, -6px);
315+
}
334316
}
335317

336318
& > p, & > ._md-list-item-inner > p {
337-
flex: 1;
319+
flex: 1 1 auto;
338320
margin: 0;
339321
}
340322
}
@@ -351,7 +333,7 @@ md-list-item {
351333
}
352334

353335
.md-list-item-text {
354-
flex: 1;
336+
flex: 1 1 auto;
355337
margin: auto;
356338
text-overflow: ellipsis;
357339
overflow: hidden;
@@ -405,7 +387,7 @@ md-list-item {
405387
align-self: flex-start;
406388
}
407389
.md-list-item-text {
408-
flex: 1;
390+
flex: 1 1 auto;
409391
}
410392
}
411393
}

0 commit comments

Comments
 (0)