From a4ff3c5a48740bfa5f7d857875a7a70e5d077ff0 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Sat, 3 Dec 2016 13:56:30 +0100 Subject: [PATCH] refactor(list): simplify list component scss * Simplifies the List components SCSS * Fixes Dense Mode for List and introduces super elegant approach * Make SCSS more modular by having a base and dynamic variables * Remove unnecessary DOM node level for button wrap. * No longer wrap proxied elements inside of buttons * Multiple Secondary Items should align properly and have consistent spacing. * Dense Mode should not cut characaters on different environments. Fixes #6152. Fixes #8890 References #8482. --- src/components/list/demoBasicUsage/index.html | 8 +- .../list/demoListControls/style.css | 21 +- src/components/list/list-base.scss | 317 +++++++++++ src/components/list/list.js | 34 +- src/components/list/list.scss | 495 +++--------------- src/components/list/list.spec.js | 197 ++++--- src/components/toolbar/toolbar.scss | 2 +- 7 files changed, 546 insertions(+), 528 deletions(-) create mode 100644 src/components/list/list-base.scss diff --git a/src/components/list/demoBasicUsage/index.html b/src/components/list/demoBasicUsage/index.html index acb97477fc2..50b5eae0f81 100644 --- a/src/components/list/demoBasicUsage/index.html +++ b/src/components/list/demoBasicUsage/index.html @@ -54,11 +54,11 @@

{{ todos[1].who }}

Classes - + {{phone.options.face}} -
+

{{ phone.number }}

{{ phone.type }}

@@ -127,11 +127,11 @@

{{ todos[1].who }}

Classes - + {{phone.options.face}} -
+

{{ phone.number }}

{{ phone.type }}

diff --git a/src/components/list/demoListControls/style.css b/src/components/list/demoListControls/style.css index 39430335b45..65432bb4292 100644 --- a/src/components/list/demoListControls/style.css +++ b/src/components/list/demoListControls/style.css @@ -1,17 +1,16 @@ md-divider { - margin-top: 0; - margin-bottom: 0; + margin-top: 0; + margin-bottom: 0; } md-list { - padding-top:0; + padding-top: 0; } -md-list-item > p, -md-list-item > .md-list-item-inner > p, -md-list-item .md-list-item-inner > p, -md-list-item .md-list-item-inner > .md-list-item-inner > p { - -webkit-user-select: none; /* Chrome all / Safari all */ - -moz-user-select: none; /* Firefox all */ - -ms-user-select: none; /* IE 10+ */ - user-select: none; /* Likely future */ + +md-list-item.md-list-item-content > p, +md-list-item .md-list-item-content > p { + -webkit-user-select: none; /* Chrome all / Safari all */ + -moz-user-select: none; /* Firefox all */ + -ms-user-select: none; /* IE 10+ */ + user-select: none; /* Likely future */ } \ No newline at end of file diff --git a/src/components/list/list-base.scss b/src/components/list/list-base.scss new file mode 100644 index 00000000000..7b81f0fe460 --- /dev/null +++ b/src/components/list/list-base.scss @@ -0,0 +1,317 @@ +/** External component calculations */ +$md-checkbox-width: 3 * $baseline-grid !default; +$md-button-margin: rem(0.800) !default; +$md-button-spacing: $button-left-right-padding + $md-button-margin !default; + +/** Static List Item Variables */ +$md-list-item-indention: $baseline-grid * 7 !default; +$md-list-item-padding: $baseline-grid * 2 !default; +$md-list-item-h3-margin: 0 0 0px 0 !default; +$md-list-item-h4-margin: 3px 0 1px 0 !default; +$md-list-item-h4-font-weight: 400 !default; +$md-list-item-p-margin: 0 0 0 0 !default; +$md-list-item-subheader-height: 6 * $baseline-grid !default; + +/** Resets the checkbox component */ +@mixin md-reset-checkbox() { + md-checkbox { + align-self: center; + margin: 0; + + .md-label { + display: none; + } + } +} + +/** Resets the switch component to properly align */ +@mixin md-reset-switch() { + md-switch { + margin-top: 0; + margin-bottom: 0; + + @include rtl-prop(margin-right, margin-left, -6px, 0); + } +} + +/** Reset browsers default paragraph styles */ +@mixin md-reset-paragraph() { + p { + margin: 0; + flex: 1; + } +} + +/** Reset default md-button behavior like uppercase and text alignment */ +@mixin md-reset-button() { + &.md-button { + font-size: inherit; + text-transform: none; + + @include rtl(text-align, left, right); + + &, > .md-ripple-container { + border-radius: 0; + margin: 0; + padding: 0; + } + } +} + +/** Mixin to overwrite the size of an md-icon element */ +@mixin md-icon-size($icon-size) { + min-width: $icon-size; + min-height: $icon-size; + height: $icon-size; + width: $icon-size; + + svg { + width: $icon-size; + height: $icon-size; + } +} + +/** Creates an overlaying button, which will be used to execute the primary action */ +@mixin md-button-wrap() { + &.md-button-wrap { + position: relative; + + .md-button-wrap-executor { + position: absolute; + top: 0; + left: 0; + + height: 100%; + width: 100%; + + @include md-reset-button(); + } + } +} + +/** + * Mixin to style user specified content inside of the list item. + */ +@mixin md-list-item-content($list-icon-width, $list-avatar-width, $list-avatar-padding-dense) { + + .md-avatar, .md-avatar-icon, > md-icon:first-child { + box-sizing: content-box; + margin-top: $baseline-grid; + margin-bottom: $baseline-grid; + border-radius: 50%; + + flex-shrink: 0; // Per https://bugs.webkit.org/show_bug.cgi?id=146020 + } + + .md-avatar { + width: $list-avatar-width; + height: $list-avatar-width; + + @include rtl(margin-right, $md-list-item-indention - $list-avatar-width, 0); + @include rtl(margin-left, 0, $md-list-item-indention - $list-avatar-width); + } + + .md-avatar-icon { + $avatar-icon-width: $list-icon-width + ($list-avatar-padding-dense * 2); + padding: $list-avatar-padding-dense; + + @include md-icon-size($list-icon-width); + + @include rtl(margin-right, $md-list-item-indention - $avatar-icon-width, 0); + @include rtl(margin-left, 0, $md-list-item-indention - $avatar-icon-width); + } + + /* Primary elements at start of the list item content. */ + > md-icon:first-child:not(.md-avatar-icon) { + @include md-icon-size($list-icon-width); + + @include rtl(margin-right, $md-list-item-indention - $list-icon-width, 0); + @include rtl(margin-left, 0, $md-list-item-indention - $list-icon-width); + } + + > md-checkbox:first-child { + @include rtl(margin-right, $md-list-item-indention - $md-checkbox-width, 0); + @include rtl(margin-left, 0, $md-list-item-indention - $md-checkbox-width); + } + + md-checkbox { + width: $md-checkbox-width; + } + + md-divider { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + + &[md-inset] { + @include rtl-prop(left, right, $md-list-item-indention + $md-list-item-padding, auto); + width: calc(100% - #{$md-list-item-indention + $md-list-item-padding}); + margin: 0 !important; + } + } + +} + +/** + * Mixin to enable multi-line support in the current list item. + */ +@mixin md-list-item-multi-line($list-height-2, $list-height-3, $list-title-font-size, + $list-title-line-height, $list-subtitle-font-size, + $list-subtitle-line-height) { + + &.md-2-line { + min-height: $list-height-2; + @include ie11-min-height-flexbug($list-height-2); + } + + &.md-3-line { + min-height: $list-height-3; + @include ie11-min-height-flexbug($list-height-3); + + &.md-list-item-content, .md-list-item-content { + > md-icon:first-child, > .md-avatar, > .md-avatar-icon { + margin-top: $baseline-grid * 2; + margin-bottom: auto; + } + } + } + + &.md-3-line, &.md-2-line { + text-overflow: ellipsis; + overflow: hidden; + + &.md-offset { + @include rtl-prop(margin-left, margin-right, $md-list-item-indention, 0); + } + + &.md-long-text { + margin-top: $baseline-grid; + margin-bottom: $baseline-grid; + } + + &.md-list-item-content, .md-list-item-content { + + h3 { + font-size: $list-title-font-size; + font-weight: 400; + letter-spacing: 0.010em; + margin: $md-list-item-h3-margin; + line-height: $list-title-line-height; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + h4 { + font-size: $list-subtitle-font-size; + letter-spacing: 0.010em; + margin: $md-list-item-h4-margin; + font-weight: $md-list-item-h4-font-weight; + line-height: $list-title-line-height; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + p { + font-size: $list-subtitle-font-size; + font-weight: 500; + letter-spacing: 0.010em; + margin: $md-list-item-p-margin; + line-height: $list-subtitle-line-height; + } + + } + } +} + + +/** + * Creates the basic md-list with the specific dynamic variables. + */ +@mixin md-list($list-font-size, $list-padding-vertical) { + display: block; + padding: $list-padding-vertical 0; + font-size: $list-font-size; + + .md-subheader { + font-size: $body-font-size-base; + font-weight: 500; + letter-spacing: 0.010em; + height: $md-list-item-subheader-height; + } +} + +/** + * Creates the md-list-item styles with the specific dynamic variables. + */ +@mixin md-list-item($list-height-base, $list-height-2, $list-height-3, $list-avatar-width, + $list-icon-width, $list-title-font-size, $list-title-line-height, + $list-subtitle-font-size, $list-subtitle-line-height, + $list-avatar-padding-dense) { + + position: relative; + display: flex; + flex: 1; + + min-height: $list-height-base; + @include ie11-min-height-flexbug($list-height-base); + + &.md-clickable { + cursor: pointer; + } + + .md-secondary-container { + display: flex; + align-items: center; + flex-shrink: 0; // Per https://bugs.webkit.org/show_bug.cgi?id=146020 + + /** Move Container to the End */ + margin: auto; + @include rtl(margin-right, 0, auto); + @include rtl(margin-left, auto, 0); + + /** Reset component styles */ + @include md-reset-switch(); + + // md-checkbox should have same spacing as other secondary buttons. + md-checkbox { + margin: 0 $md-button-spacing; + + &:last-child { + @include rtl-prop(margin-right, margin-left, 0, $md-button-spacing); + } + } + + // md-button should not have any margin when being the last secondary item. + .md-button:last-child, md-menu:last-child > .md-button { + @include rtl-prop(margin-right, margin-left, 0, $md-button-margin); + } + } + + @include md-button-wrap(); + @include md-reset-checkbox(); + @include md-reset-paragraph(); + @include md-list-item-multi-line( + $list-height-2, $list-height-3, $list-title-font-size, $list-title-line-height, + $list-subtitle-font-size, $list-subtitle-line-height + ); + + &.md-list-item-content, .md-list-item-content { + /** Vertical Alignment */ + display: flex; + flex: 1; + flex-direction: row; + justify-content: flex-start; + align-items: center; + + padding: 0 $md-list-item-padding; + + /** Custom styling for child content */ + @include md-list-item-content( + $list-icon-width, $list-avatar-width, $list-avatar-padding-dense + ); + } + +} \ No newline at end of file diff --git a/src/components/list/list.js b/src/components/list/list.js index ad68023d4c4..16f17c32f6d 100644 --- a/src/components/list/list.js +++ b/src/components/list/list.js @@ -256,9 +256,11 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { if (hasProxiedElement) { wrapIn('div'); } else { - tEl.addClass('md-no-proxy'); + tEl.addClass('md-no-proxy md-list-item-content'); } + } else { + tEl.addClass('md-list-item-content'); } wrapSecondaryItems(); @@ -314,20 +316,16 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { function wrapIn(type) { if (type == 'div') { - itemContainer = angular.element('
'); + itemContainer = angular.element('
'); itemContainer.append(tEl.contents()); tEl.addClass('md-proxy-focus'); } else { // Element which holds the default list-item content. - itemContainer = angular.element( - '
'+ - '
'+ - '
' - ); + itemContainer = angular.element('
'); // Button which shows ripple and executes primary action. var buttonWrap = angular.element( - '' + '' ); // Expect the root element to have a label set. If not set, determine the label from the text content. @@ -342,11 +340,10 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { buttonWrap.addClass('md-no-focus'); } - // Append the button wrap before our list-item content, because it will overlay in relative. - itemContainer.prepend(buttonWrap); - itemContainer.children().eq(1).append(tEl.contents()); + itemContainer.append(tEl.contents()); - tEl.addClass('_md-button-wrap'); + tEl.addClass('md-button-wrap'); + tEl.prepend(buttonWrap) } tEl[0].setAttribute('tabindex', '-1'); @@ -364,9 +361,11 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { } function wrapSecondaryItem(secondaryItem, container) { - // If the current secondary item is not a button, but contains a ng-click attribute, - // the secondary item will be automatically wrapped inside of a button. - if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) { + // If the current secondary item is not a button or proxied element, + // but contains a ng-click attribute, the secondary item will be automatically + // wrapped inside of a button. + if (secondaryItem && !isButton(secondaryItem) && !isProxiedElement(secondaryItem) + && secondaryItem.hasAttribute('ng-click')) { $mdAria.expect(secondaryItem, 'aria-label'); var buttonWrapper = angular.element(''); @@ -382,7 +381,8 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { secondaryItem = buttonWrapper[0]; } - if (secondaryItem && (!hasClickEvent(secondaryItem) || (!tAttrs.ngClick && isProxiedElement(secondaryItem)))) { + if (secondaryItem && !tAttrs.ngClick && !hasClickEvent(secondaryItem) + && 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'); @@ -443,7 +443,7 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { var proxies = [], firstElement = $element[0].firstElementChild, - isButtonWrap = $element.hasClass('_md-button-wrap'), + isButtonWrap = $element.hasClass('md-button-wrap'), clickChild = isButtonWrap ? firstElement.firstElementChild : firstElement, hasClick = clickChild && hasClickEvent(clickChild), noProxies = $element.hasClass('md-no-proxy'); diff --git a/src/components/list/list.scss b/src/components/list/list.scss index a4fd150e2d6..81cd25cfc23 100644 --- a/src/components/list/list.scss +++ b/src/components/list/list.scss @@ -1,419 +1,90 @@ -$dense-baseline-grid: $baseline-grid / 2 !default; - -$list-h3-margin: 0 0 0px 0 !default; -$list-h4-margin: 3px 0 1px 0 !default; -$list-h4-font-weight: 400 !default; -$list-header-line-height: 1.2em !default; -$list-p-margin: 0 0 0 0 !default; -$list-p-line-height: 1.6em !default; - -$list-padding-top: $baseline-grid !default; -$list-padding-right: 0px !default; -$list-padding-left: 0px !default; -$list-padding-bottom: $baseline-grid !default; - -$item-padding-top: 0px !default; -$item-padding-right: 0px !default; -$item-padding-left: 0px !default; -$item-padding-bottom: 0px !default; -$list-item-padding-vertical: 0px !default; -$list-item-padding-horizontal: $baseline-grid * 2 !default; -$list-item-primary-width: $baseline-grid * 7 !default; -$list-item-primary-avatar-width: $baseline-grid * 5 !default; -$list-item-primary-icon-width: $baseline-grid * 3 !default; -$list-item-secondary-left-margin: $baseline-grid * 2 !default; -$list-item-secondary-button-width: $baseline-grid * 6 !default; -$list-item-inset-divider-offset: 9 * $baseline-grid !default; -$list-item-height: 6 * $baseline-grid !default; -$list-item-two-line-height: 9 * $baseline-grid !default; -$list-item-three-line-height: 11 * $baseline-grid !default; - -$list-item-dense-height: 12 * $dense-baseline-grid !default; -$list-item-dense-two-line-height: 15 * $dense-baseline-grid !default; -$list-item-dense-three-line-height: 19 * $dense-baseline-grid !default; -$list-item-dense-primary-icon-width: $dense-baseline-grid * 5 !default; -$list-item-dense-primary-avatar-width: $dense-baseline-grid * 9 !default; -$list-item-dense-header-font-size: round($subhead-font-size-base * 0.8) !default; -$list-item-dense-font-size: round($body-font-size-base * 0.85) !default; -$list-item-dense-line-height: 1.05 !default; +/** + * Dynamic variables which can change due higher pixel density. + */ +$md-list-item-height-1: 6 * $baseline-grid !default; +$md-list-item-height-2: 9 * $baseline-grid !default; +$md-list-item-height-3: 11 * $baseline-grid !default; +$md-list-item-font-size: rem(1.6); +$md-list-item-avatar-width: $baseline-grid * 5 !default; +$md-list-item-icon-width: $baseline-grid * 3 !default; +$md-list-item-title-font-size: $subhead-font-size-base !default; +$md-list-item-subtitle-font-size: $body-font-size-base !default; +$md-list-item-title-line-height: 1.2em !default; +$md-list-item-subtitle-line-height: 1.6em; +$md-list-padding-vertical: $baseline-grid !default; +$md-list-item-avatar-padding: $baseline-grid !default; + +/** + * High Pixel Density mode for the list component. + */ +$md-list-item-dense-dpi: 192; + +//TODO(devversion): make function generic and use for other components +@function dense($pixels) { + $densePixels: ($pixels * 160) / $md-list-item-dense-dpi; + @return round(($densePixels * 10) / 10); +} +/** Dynamic variables in high density pixels */ +$md-list-item-height-1-dense: dense($md-list-item-height-1); +$md-list-item-height-2-dense: dense($md-list-item-height-2); +$md-list-item-height-3-dense: 9.5 * $baseline-grid; +$md-list-item-font-size-dense: dense($md-list-item-font-size); +$md-list-item-avatar-width-dense: 4.5 * $baseline-grid; +$md-list-item-icon-width-dense: dense($md-list-item-icon-width); +$md-list-item-title-font-size-dense: dense($subhead-font-size-base); +$md-list-item-subtitle-font-size-dense: dense($body-font-size-base); +$md-list-item-title-line-height-dense: 1.05em; +$md-list-item-subtitle-line-height-dense: $md-list-item-title-line-height-dense; +$md-list-padding-vertical-dense: $baseline-grid / 2; +$md-list-item-avatar-padding-dense: $baseline-grid; + +/** + * Basic List Component + */ md-list { - display: block; - padding: $list-padding-top $list-padding-right $list-padding-bottom $list-padding-left; - - .md-subheader { - font-size: $body-font-size-base; - font-weight: 500; - letter-spacing: 0.010em; - line-height: $list-header-line-height; - } - - &.md-dense { - md-list-item { - &, - .md-list-item-inner { - min-height: $list-item-dense-height; - @include ie11-min-height-flexbug($list-item-dense-height); - - - // Layout for controls in primary or secondary divs, or auto-infered first child - - md-icon:first-child { - width: $list-item-dense-primary-icon-width; - height: $list-item-dense-primary-icon-width; - } - - > md-icon:first-child:not(.md-avatar-icon) { - @include rtl-prop(margin-right, margin-left, $list-item-primary-width - $list-item-dense-primary-icon-width, auto); - } - .md-avatar, .md-avatar-icon { - @include rtl-prop(margin-right, margin-left, $list-item-primary-width - $list-item-dense-primary-avatar-width, auto); - } - .md-avatar { - flex: none; - width: $list-item-dense-primary-avatar-width; - height: $list-item-dense-primary-avatar-width; - } - } - - &.md-2-line, - &.md-3-line { - &, & > .md-no-style { - .md-list-item-text { - &.md-offset { - @include rtl-prop(margin-left, margin-right, $list-item-primary-width, auto); - } - - h3, - h4, - p { - line-height: $list-item-dense-line-height; - font-size: $list-item-dense-font-size; - } - - h3 { - font-size: $list-item-dense-header-font-size; - } - } - } - } - - &.md-2-line { - &, & > .md-no-style { - min-height: $list-item-dense-two-line-height; - @include ie11-min-height-flexbug($list-item-dense-two-line-height); - - > .md-avatar, .md-avatar-icon { - margin-top: $baseline-grid * 1.5; - } - } - } - - &.md-3-line { - &, & > .md-no-style { - - min-height: $list-item-dense-three-line-height; - @include ie11-min-height-flexbug($list-item-dense-three-line-height); - - > md-icon:first-child, - > .md-avatar { - margin-top: $baseline-grid * 2; - } - } - } - } - } + @include md-list( + $md-list-item-font-size, + $md-list-padding-vertical + ); } md-list-item { - // Ensure nested dividers are properly positioned - position: relative; - - &.md-proxy-focus.md-focused .md-no-style { - transition: background-color 0.15s linear; - } - - &._md-button-wrap { - position: relative; - - > div.md-button:first-child { - // Layout - Vertically align the item content. - display: flex; - align-items: center; - justify-content: flex-start; - - padding: 0 16px; - margin: 0; - - font-weight: 400; - @include rtl(text-align, left, right); - border: medium none; - - // The button executor should fill the whole list item. - > .md-button:first-child { - position: absolute; - top: 0; - left: 0; - height: 100%; - - margin: 0; - padding: 0; - } - - .md-list-item-inner { - // The list item content should fill the complete width. - width: 100%; - min-height: inherit; - } - - } - - } - - &.md-no-proxy, - .md-no-style { - position: relative; - padding: $list-item-padding-vertical $list-item-padding-horizontal; - - // Layout [flex='auto'] - flex: 1 1 auto; - - &.md-button { - font-size: inherit; - height: inherit; - @include rtl(text-align, left, right); - text-transform: none; - width: 100%; - white-space: normal; - flex-direction: inherit; - align-items: inherit; - border-radius: 0; - margin: 0; - - & > .md-ripple-container { - border-radius: 0; - } - } - &:focus { - outline: none - } - } - &.md-clickable:hover { - cursor: pointer; - } - - md-divider { - position: absolute; - bottom: 0; - @include rtl-prop(left, right, 0, auto); - width: 100%; - &[md-inset] { - @include rtl-prop(left, right, $list-item-inset-divider-offset, auto); - width: calc(100% - #{$list-item-inset-divider-offset}); - margin: 0 !important; - } - } - - &, - .md-list-item-inner { - - // Layout [flex layout-align='start center'] - display: flex; - justify-content: flex-start; - align-items: center; - - min-height: $list-item-height; - @include ie11-min-height-flexbug($list-item-height); - - height: auto; - - // Layout for controls in primary or secondary divs, or auto-infered first child - & > div.md-primary > md-icon:not(.md-avatar-icon), - & > div.md-secondary > md-icon:not(.md-avatar-icon), - & > md-icon:first-child:not(.md-avatar-icon), - > md-icon.md-secondary:not(.md-avatar-icon) { - width: $list-item-primary-icon-width; - margin-top: 16px; - margin-bottom: 12px; - box-sizing: content-box; - } - & > div.md-primary > md-checkbox, - & > div.md-secondary > md-checkbox, - & > md-checkbox, - md-checkbox.md-secondary { - align-self: center; - .md-label { - display: none; - } - } - - & > md-icon:first-child:not(.md-avatar-icon) { - @include rtl-prop(margin-right, margin-left, $list-item-primary-width - $list-item-primary-icon-width, auto); - } - - & .md-avatar, .md-avatar-icon { - margin-top: $baseline-grid; - margin-bottom: $baseline-grid; - @include rtl-prop(margin-right, margin-left, $list-item-primary-width - $list-item-primary-avatar-width, auto); - border-radius: 50%; - box-sizing: content-box; - } - & .md-avatar { - flex: none; - width: $list-item-primary-avatar-width; - height: $list-item-primary-avatar-width; - } - & .md-avatar-icon { - padding: 8px; - - // Set the width/height to the same as the icon to fix issue on iOS Safari where the - // height: 100% was causing it to be larger than it's parent - svg { - width: $icon-size; - height: $icon-size; - } - } - - & > md-checkbox { - width: 3 * $baseline-grid; - @include rtl(margin-left, 3px, 29px); - @include rtl(margin-right, 29px, 3px); - margin-top: 16px; - } - - .md-secondary-container { - display: flex; - align-items: center; - - // Per W3C: https://www.w3.org/TR/css-flexbox/#flex-common - // By default, flex items won’t shrink below their minimum content size. - // Safari doesn't follow that specification due to a bug and expects the developer to - // explicitly disable flex shrinking. - flex-shrink: 0; - - // Using margin auto to move them to the end of the list item is more elegant, because it has - // a lower priority than a flex filler and isn't introducing any overflow issues. - // The margin on the top is important to align multiple secondary items vertically. - margin: auto; - - @include rtl(margin-right, 0, auto); - @include rtl(margin-left, auto, 0); - - .md-button, .md-icon-button { - &:last-of-type { - // Reset 6px margin for the button. - @include rtl-prop(margin-right, margin-left, 0, auto); - } - } - - md-checkbox { - margin-top: 0; - margin-bottom: 0; - - &:last-child { - width: 3 * $baseline-grid; - @include rtl-prop(margin-right, margin-left, 0, auto); - } - } - - md-switch { - margin-top: 0; - margin-bottom: 0; - - @include rtl-prop(margin-right, margin-left, -6px, auto); - } - } - - & > p, & > .md-list-item-inner > p { - flex: 1 1 auto; - margin: 0; - } - } - - &.md-2-line, - &.md-3-line { - &, & > .md-no-style { - align-items: flex-start; - justify-content: center; - - &.md-long-text { - margin-top: $baseline-grid; - margin-bottom: $baseline-grid; - } - - .md-list-item-text { - flex: 1 1 auto; - margin: auto; - text-overflow: ellipsis; - overflow: hidden; - - &.md-offset { - @include rtl-prop(margin-left, margin-right, $list-item-primary-width, auto); - } - - h3 { - font-size: $subhead-font-size-base; - font-weight: 400; - letter-spacing: 0.010em; - margin: $list-h3-margin; - line-height: $list-header-line-height; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - h4 { - font-size: $body-font-size-base; - letter-spacing: 0.010em; - margin: $list-h4-margin; - font-weight: $list-h4-font-weight; - line-height: $list-header-line-height; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - p { - font-size: $body-font-size-base; - font-weight: 500; - letter-spacing: 0.010em; - margin: $list-p-margin; - line-height: $list-p-line-height; - } - } - } - } - - &.md-2-line { - &, & > .md-no-style { - height: auto; - - min-height: $list-item-two-line-height; - @include ie11-min-height-flexbug($list-item-two-line-height); - - > .md-avatar, .md-avatar-icon { - margin-top: $baseline-grid * 1.5; - } - - > md-icon:first-child { - align-self: flex-start; - } - - .md-list-item-text { - flex: 1 1 auto; - } - } - } - - &.md-3-line { - &, & > .md-no-style { - height: auto; - - min-height: $list-item-three-line-height; - @include ie11-min-height-flexbug($list-item-three-line-height); + @include md-list-item( + $md-list-item-height-1, + $md-list-item-height-2, + $md-list-item-height-3, + $md-list-item-avatar-width, + $md-list-item-icon-width, + $md-list-item-title-font-size, + $md-list-item-title-line-height, + $md-list-item-subtitle-font-size, + $md-list-item-subtitle-line-height, + $md-list-item-avatar-padding + ); +} - > md-icon:first-child, - > .md-avatar { - margin-top: $baseline-grid * 2; - } - } - } +/** + * List Component in Dense Mode + */ +md-list.md-dense { + @include md-list( + $md-list-item-font-size-dense, + $md-list-padding-vertical-dense + ); } + +md-list.md-dense md-list-item { + @include md-list-item( + $md-list-item-height-1-dense, + $md-list-item-height-2-dense, + $md-list-item-height-3-dense, + $md-list-item-avatar-width-dense, + $md-list-item-icon-width-dense, + $md-list-item-title-font-size-dense, + $md-list-item-title-line-height-dense, + $md-list-item-subtitle-font-size-dense, + $md-list-item-subtitle-line-height-dense, + $md-list-item-avatar-padding-dense + ); +} \ No newline at end of file diff --git a/src/components/list/list.spec.js b/src/components/list/list.spec.js index 8bc5b3600d9..0f70c40fe4c 100644 --- a/src/components/list/list.spec.js +++ b/src/components/list/list.spec.js @@ -88,6 +88,7 @@ describe('mdListItem directive', function() { switchEl.click(); expect($rootScope.modelVal).toBeTruthy(); + expect(listItem).toHaveClass('md-list-item-content'); expect(listItem).not.toHaveClass('md-clickable') }); @@ -173,119 +174,109 @@ describe('mdListItem directive', function() { var listItem = setup( '' + '

Hello world

' + - '
'); + '' + ); - // List items, which are clickable always contain a button wrap at the top level. - var buttonWrap = listItem.children().eq(0); - expect(listItem).toHaveClass('_md-button-wrap'); + expect(listItem).toHaveClass('md-button-wrap'); - // The button wrap should contain the button executor, the inner content and the - // secondary item container as children. - expect(buttonWrap.children().length).toBe(3); + // The item should contain the button executor and the item content. + expect(listItem.children().length).toBe(2); - var buttonExecutor = buttonWrap.children()[0]; + var buttonExecutor = listItem.children()[0]; // The list item should forward the click and disabled attributes. expect(buttonExecutor.hasAttribute('ng-click')).toBe(true); expect(buttonExecutor.hasAttribute('ng-disabled')).toBe(true); - var innerContent = buttonWrap.children()[1]; + var itemContent = listItem.children()[1]; - expect(innerContent.nodeName).toBe('DIV'); - expect(innerContent.firstElementChild.nodeName).toBe('P'); + expect(itemContent.nodeName).toBe('DIV'); + expect(itemContent.firstElementChild.nodeName).toBe('P'); }); it('creates buttons when used with ng-dblclick', function() { var listItem = setup( '' + '

Hello world

' + - '
'); + '' + ); - // List items, which are clickable always contain a button wrap at the top level. - var buttonWrap = listItem.children().eq(0); - expect(listItem).toHaveClass('_md-button-wrap'); + expect(listItem).toHaveClass('md-button-wrap'); - // The button wrap should contain the button executor, the inner content and the - // secondary item container as children. - expect(buttonWrap.children().length).toBe(3); + // The item should contain the button executor and the item content. + expect(listItem.children().length).toBe(2); - var buttonExecutor = buttonWrap.children()[0]; + var buttonExecutor = listItem.children()[0]; // The list item should forward the click and disabled attributes. expect(buttonExecutor.hasAttribute('ng-dblclick')).toBe(true); expect(buttonExecutor.hasAttribute('ng-disabled')).toBe(true); - var innerContent = buttonWrap.children()[1]; + var itemContent = listItem.children()[1]; - expect(innerContent.nodeName).toBe('DIV'); - expect(innerContent.firstElementChild.nodeName).toBe('P'); + expect(itemContent.nodeName).toBe('DIV'); + expect(itemContent.firstElementChild.nodeName).toBe('P'); }); it('creates buttons when used with ui-sref', function() { var listItem = setup( '' + '

Hello world

' + - '
'); + '' + ); - // List items, which are clickable always contain a button wrap at the top level. - var buttonWrap = listItem.children().eq(0); - expect(listItem).toHaveClass('_md-button-wrap'); + expect(listItem).toHaveClass('md-button-wrap'); - // The button wrap should contain the button executor, the inner content and the - // secondary item container as children. - expect(buttonWrap.children().length).toBe(3); + // The item should contain the button executor and the item content. + expect(listItem.children().length).toBe(2); - var buttonExecutor = buttonWrap.children()[0]; + var buttonExecutor = listItem.children()[0]; // The list item should forward the ui-sref attribute. expect(buttonExecutor.hasAttribute('ui-sref')).toBe(true); - var innerContent = buttonWrap.children()[1]; + var itemContent = listItem.children()[1]; - expect(innerContent.nodeName).toBe('DIV'); - expect(innerContent.firstElementChild.nodeName).toBe('P'); + expect(itemContent.nodeName).toBe('DIV'); + expect(itemContent.firstElementChild.nodeName).toBe('P'); }); it('creates buttons when used with href', function() { var listItem = setup( '' + '

Hello world

' + - '
'); + '' + ); - // List items, which are clickable always contain a button wrap at the top level. - var buttonWrap = listItem.children().eq(0); - expect(listItem).toHaveClass('_md-button-wrap'); + expect(listItem).toHaveClass('md-button-wrap'); - // The button wrap should contain the button executor, the inner content and the - // secondary item container as children. - expect(buttonWrap.children().length).toBe(3); + // The item should contain the button executor and the item content. + expect(listItem.children().length).toBe(2); - var buttonExecutor = buttonWrap.children()[0]; + var buttonExecutor = listItem.children()[0]; // The list item should forward the href attribute. expect(buttonExecutor.hasAttribute('href')).toBe(true); - var innerContent = buttonWrap.children()[1]; + var itemContent = listItem.children()[1]; - expect(innerContent.nodeName).toBe('DIV'); - expect(innerContent.firstElementChild.nodeName).toBe('P'); + expect(itemContent.nodeName).toBe('DIV'); + expect(itemContent.firstElementChild.nodeName).toBe('P'); }); it('should forward the md-no-focus class', function() { var listItem = setup( '' + '

Clickable - Without Focus Style

' + - '
'); + '' + ); - // List items, which are clickable always contain a button wrap at the top level. - var buttonWrap = listItem.children().eq(0); - expect(listItem).toHaveClass('_md-button-wrap'); + expect(listItem).toHaveClass('md-button-wrap'); - // The button wrap should contain the button executor, the inner content and the - // secondary item container as children. - expect(buttonWrap.children().length).toBe(3); + // The item should contain the button executor and the item content. + expect(listItem.children().length).toBe(2); - var buttonExecutor = buttonWrap.children(); + var buttonExecutor = listItem.children(); // The list item should forward the href and md-no-focus-style attribute. expect(buttonExecutor.attr('ng-click')).toBeTruthy(); @@ -295,12 +286,11 @@ describe('mdListItem directive', function() { it('moves aria-label to primary action', function() { var listItem = setup(''); - var buttonWrap = listItem.children().eq(0); - expect(listItem).toHaveClass('_md-button-wrap'); + expect(listItem).toHaveClass('md-button-wrap'); // The actual click button will be a child of the button.md-no-style wrapper. - var buttonExecutor = buttonWrap.children()[0]; - + var buttonExecutor = listItem.children()[0]; + expect(buttonExecutor.nodeName).toBe('BUTTON'); expect(buttonExecutor.getAttribute('aria-label')).toBe('Hello'); }); @@ -310,19 +300,17 @@ describe('mdListItem directive', function() { '' + '

Hello World

' + '' + - '
'); + '' + ); - // First child is our button wrap - var firstChild = listItem.children().eq(0); - expect(firstChild[0].nodeName).toBe('DIV'); + expect(listItem).toHaveClass('md-button-wrap'); - expect(listItem).toHaveClass('_md-button-wrap'); + // The item should contain the button executor and the item content. + expect(listItem.children().length).toBe(2); - // It should contain three elements, the button overlay, inner content - // and the secondary container. - expect(firstChild.children().length).toBe(3); + var itemContent = listItem.children().eq(1); + var secondaryContainer = itemContent.children().eq(1); - var secondaryContainer = firstChild.children().eq(2); expect(secondaryContainer).toHaveClass('md-secondary-container'); // The secondary container should contain the md-icon, @@ -335,19 +323,17 @@ describe('mdListItem directive', function() { '' + '

Hello World

' + '' + - '
'); + '' + ); - // First child is our button wrap - var firstChild = listItem.children().eq(0); - expect(firstChild[0].nodeName).toBe('DIV'); + expect(listItem).toHaveClass('md-button-wrap'); - expect(listItem).toHaveClass('_md-button-wrap'); + // The item should contain the button executor and the item content. + expect(listItem.children().length).toBe(2); - // It should contain three elements, the button overlay, inner content - // and the secondary container. - expect(firstChild.children().length).toBe(3); + var itemContent = listItem.children().eq(1); + var secondaryContainer = itemContent.children().eq(1); - var secondaryContainer = firstChild.children().eq(2); expect(secondaryContainer).toHaveClass('md-secondary-container'); // The secondary container should contain the md-icon, @@ -361,25 +347,70 @@ describe('mdListItem directive', function() { expect(iconButton.firstElementChild.hasAttribute('ng-show')).toBe(false); }); + it('should not create a parent button for proxied secondary elements with ng-click', function() { + var listItem = setup( + '' + + '

Hello World

' + + '' + + '
' + ); + + expect(listItem).toHaveClass('md-button-wrap'); + + // The item should contain the button executor and the item content. + expect(listItem.children().length).toBe(2); + + var itemContent = listItem.children().eq(1); + var secondaryContainer = itemContent.children().eq(1); + + expect(secondaryContainer).toHaveClass('md-secondary-container'); + + // The secondary container should contain the md-checkbox, without any button as parent. + var checkboxItem = secondaryContainer.children()[0]; + + expect(checkboxItem.nodeName).toBe('MD-CHECKBOX'); + expect(checkboxItem.hasAttribute('ng-click')).toBe(true); + }); + + it('should not use as a proxied element when using ng-click on the element', function() { + var listItem = setup( + '' + + '

Hello World

' + + '' + + '
' + ); + + var checkboxEl = listItem.find('md-checkbox'); + + expect($rootScope.isChecked).toBeFalsy(); + + // Element which handles all clicks for the list item. + var clickTarget = listItem[0].querySelector('div'); + + clickTarget.click(); + expect($rootScope.isChecked).toBeFalsy(); + + checkboxEl[0].click(); + expect($rootScope.isChecked).toBeTruthy(); + }); + it('moves multiple md-secondary items outside of the button', function() { var listItem = setup( '' + '

Hello World

' + '' + '' + - '
'); + '' + ); - // First child is our button wrap - var firstChild = listItem.children().eq(0); - expect(firstChild[0].nodeName).toBe('DIV'); + expect(listItem).toHaveClass('md-button-wrap'); - expect(listItem).toHaveClass('_md-button-wrap'); + // The item should contain the button executor and the item content. + expect(listItem.children().length).toBe(2); - // It should contain three elements, the button overlay, inner content, - // and the secondary container. - expect(firstChild.children().length).toBe(3); + var itemContent = listItem.children().eq(1); + var secondaryContainer = itemContent.children().eq(1); - var secondaryContainer = firstChild.children().eq(2); expect(secondaryContainer).toHaveClass('md-secondary-container'); // The secondary container should hold the two secondary items. diff --git a/src/components/toolbar/toolbar.scss b/src/components/toolbar/toolbar.scss index df7b12e960b..5cdda330c9e 100644 --- a/src/components/toolbar/toolbar.scss +++ b/src/components/toolbar/toolbar.scss @@ -74,7 +74,7 @@ md-toolbar { } ~ md-content { - > md-list { + > md-list, md-list.md-dense { padding: 0; md-list-item:last-child {