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

Commit

Permalink
feat(subheader): add subheader component
Browse files Browse the repository at this point in the history
references: #216
  • Loading branch information
rschmukler committed Sep 15, 2014
1 parent d5d7e8d commit 93e8f3c
Show file tree
Hide file tree
Showing 21 changed files with 589 additions and 17 deletions.
4 changes: 4 additions & 0 deletions config/build.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ module.exports = {
'src/components/animate/noEffect.js',
'src/components/animate/inkCssRipple.js',

// Sticky Components
'src/components/sticky/sticky.js',

// Components
'src/components/buttons/buttons.js',
'src/components/card/card.js',
Expand All @@ -89,6 +92,7 @@ module.exports = {
'src/components/sidenav/sidenav.js',
'src/components/slider/slider.js',
'src/components/switch/switch.js',
'src/components/subheader/subheader.js',
'src/components/tabs/tabs.js',
'src/components/tabs/js/*.js',
'src/components/toast/toast.js',
Expand Down
1 change: 1 addition & 0 deletions src/base/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var Constant = {
BUTTON : 'button',
CHECKBOX : 'checkbox',
DIALOG : 'dialog',
HEADING : 'heading',
LIST : 'list',
LIST_ITEM : 'listitem',
RADIO : 'radio',
Expand Down
39 changes: 39 additions & 0 deletions src/base/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,28 @@ var Util = {
return false;
},

/**
* Returns a function, that, as long as it continues to be invoked, will not
* be triggered. The function will be called after it stops being called for
* N milliseconds.
* @param fn Function function to call
* @param wait Number number of ms to wait before calling function
* @param immediate Boolean causes the function to be called on the leading instead of trailing
* @returns {Function}
*/
debounce: function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate) func.apply(context, args);
};
},

/**
* Checks if two elements have the same parent
*/
Expand Down Expand Up @@ -105,6 +127,23 @@ var Util = {
return target;
}

},

/**
* Wraps an element with a tag
*
* @param el element to wrap
* @param tag tag to wrap it with
* @param [className] optional class to apply to the wrapper
* @returns new element
*
*/
wrap: function(el, tag, className) {
if(el.hasOwnProperty(0)) { el = el[0]; }
var wrapper = document.createElement(tag);
wrapper.className += className;
wrapper.appendChild(el.parentNode.replaceChild(wrapper, el));
return angular.element(wrapper);
}

};
Expand Down
4 changes: 3 additions & 1 deletion src/components/content/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ angular.module('material.components.content', [
function materialContentDirective() {
return {
restrict: 'E',
controller: angular.noop,
controller: function($scope, $element) {
this.$element = $element;
},
link: function($scope, $element, $attr) {
$scope.$broadcast('$materialContentLoaded', $element);
}
Expand Down
14 changes: 9 additions & 5 deletions src/components/list/_list.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
material-list {
padding: $list-padding-top $list-padding-right $list-padding-bottom $list-padding-left;

&.with-subheader {
padding-top: 0px;
}
}

material-item {
Expand Down Expand Up @@ -35,16 +39,16 @@ material-item-content {

text-overflow: ellipsis;

h2 {
margin: $list-h2-margin;
font-weight: $list-h2-font-weight;
font-size: $list-h2-font-size;
}
h3 {
margin: $list-h3-margin;
font-weight: $list-h3-font-weight;
font-size: $list-h3-font-size;
}
h4 {
margin: $list-h4-margin;
font-weight: $list-h4-font-weight;
font-size: $list-h4-font-size;
}
p {
margin: $list-p-margin;
font-size: $list-p-font-size;
Expand Down
4 changes: 2 additions & 2 deletions src/components/list/demo1/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<img ng-src="{{item.face}}" class="face" alt="{{item.who}}">
</div>
<div class="material-tile-content">
<h2>{{item.what}}</h2>
<h3>{{item.who}}</h3>
<h3>{{item.what}}</h3>
<h4>{{item.who}}</h4>
<p>
{{item.notes}}
</p>
Expand Down
4 changes: 2 additions & 2 deletions src/components/list/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ angular.module('material.components.list', [])
* <img ng-src="{{item.face}}" class="face" alt="{{item.who}}">
* </div>
* <div class="material-tile-content">
* <h2>{{item.what}}</h2>
* <h3>{{item.who}}</h3>
* <h3>{{item.what}}</h3>
* <h4>{{item.who}}</h4>
* <p>
* {{item.notes}}
* </p>
Expand Down
8 changes: 8 additions & 0 deletions src/components/sticky/_sticky.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[material-sticky-active] {
position: fixed;
z-index: 2;
}

[material-sticky-transition] {
position: absolute;
}
243 changes: 243 additions & 0 deletions src/components/sticky/sticky.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/**
* @ngdoc module
* @name material.components.sticky
* @description
*
* Sticky effects for material
*/

angular.module('material.components.sticky', [])
.factory('$materialSticky', ['$window', '$document', '$$rAF', MaterialSticky])
.directive('materialSticky', ['$materialSticky', MaterialStickyDirective]);

/**
* @ngdoc factory
* @name $materialSticky
* @module material.components.sticky
*
* @description
* The `$materialSticky`service provides a mixin to make elements sticky.
*
* @returns A `$materialSticky` function that takes `$el` as an argument.
*/

function MaterialSticky($window, $document, $$rAF) {
var browserStickySupport;

/**
* Registers an element as sticky, used internally by directives to register themselves
*/


function registerStickyElement(scope, $el) {
scope.$on('$destroy', function() { registerStickyElement.$deregister($el); });
$el = Util.wrap($el, 'div', 'sticky-container'),
$container = $el.controller('materialContent').$element;

if(!$container) { throw new Error('$materialSticky used outside of material-contant'); }

var elements = $container.data('$stickyEls') || [];
elements.push($el);
$container.data('$stickyEls', elements);

// check sticky support on first register
if(browserStickySupport === undefined) {
browserStickySupport = checkStickySupport($el);
} else if(browserStickySupport) {
$el.css({position: browserStickySupport, top: '0px'});
}

var debouncedCheck = $container.data('$stickyCheck') || $$rAF.debounce(checkElements.bind(undefined, $container));
$container.data('$stickyCheck', debouncedCheck);


if(!browserStickySupport) {
if(elements.length == 1) {
$container.on('scroll', debouncedCheck);
}
scanElements($container);
}
}


// Deregister a sticky element, useful for $destroy event.
registerStickyElement.$deregister = function($el) {
var $container = $el.controller('materialContent').$element;
var elements = $container.data('$stickyEls') || [];
var innerElements = elements.map(function(el) { return el.children(0); });
var index = innerElements.indexOf($el);
if(index !== -1) {
elements[index].replaceWith($el);
elements.splice(index, 1);
if(elements.length === 0) {
$container.off('scroll', $container.data('$stickyCheck'));
}
}
};

return registerStickyElement;

function checkStickySupport($el) {
var stickyProps = ['sticky', '-webkit-sticky'];
for(var i = 0; i < stickyProps.length; ++i) {
$el.css({position: stickyProps[i], top: '0px'});
if($window.getComputedStyle($el[0]).position == stickyProps[i]) {
return stickyProps[i];
}
}
$el.css({position: undefined, top: undefined});
return false;
}


/* *
* Function to prepare our lookups so we can go quick!
* */

function scanElements($container) {
var elements = $container.data('$stickyEls');
if(browserStickySupport) return; // don't need to do anything if we have native sticky
targetElementIndex = 0;
// Sort based on position in the window, and assign an active index
orderedElements = elements.sort(function(a, b) {
return rect(a).top - rect(b).top;
});

$container.data('$stickyOrderedEls', orderedElements);

// Iterate over our sorted elements and find the one that is active
(function findTargetElement() {
var scroll = $container.scrollTop();
for(var i = 0; i < orderedElements.length ; ++i) {
if(rect(orderedElements[i].children(0)).bottom > 0) {
targetElementIndex = i > 0 ? i - 1 : i;
} else {
targetElementIndex = i;
}
}
$container.data('$stickyTarget', targetElementIndex);
})();
}

function checkElements($container) {
var next; // pointer to next target

// Convenience getter for the target element
var targetElementIndex = $container.data('$stickyTarget'),
orderedElements = $container.data('$stickyOrderedEls');

var content = targetElement().children(0);
var contentRect = rect(content),
targetRect = rect(targetElement());

var scrollingDown = false,
currentScroll = $container.scrollTop(),
lastScroll = $container.data('$stickyLastScroll');

if(currentScroll > ($container.data('$stickyLastScroll') || 0)) {
scrollingDown = true;
}
$container.data('$stickyLastScroll', currentScroll);

var stickyActive = content.attr('material-sticky-active');


// If we are scrollingDown, sticky, and are being pushed off screen by a different element, increment
if(scrollingDown && stickyActive && contentRect.bottom <= 0 && targetElementIndex < orderedElements.length - 1) {
targetElement().children(0).removeAttr('material-sticky-active');
targetElement().css({height: null});
incrementElement();
}
//If we are going up, and our normal position would be rendered not sticky, un-sticky ourselves
else if(!scrollingDown && stickyActive && targetRect.top > 0) {
targetElement().children(0).removeAttr('material-sticky-active');
targetElement().css({height: null});
incrementElement(-1);
content.attr('material-sticky-active', true);
content.css({top: -1 * contentRect.height});
targetElement().css({height: contentRect.height});
}
// If we are going off screen and haven't been made sticky yet, go sticky
else if(scrollingDown && contentRect.top <= 0 && !stickyActive) {
content.attr('material-sticky-active', true);
targetElement().css({height: contentRect.height});
contentRect = rect(content);
next = targetElement(+1);
var offset = 0;
if(next) {
nextRect = rect(next.children(0));
if(rectsAreTouching(contentRect, nextRect)) {
offset = nextRect.top - contentRect.bottom;
}
}
content.css({top: Math.min(offset, 0)});
}

var nextRect, offsetAmount, currentTop;
if(scrollingDown) {
// check if we need to push
next = targetElement(+1);
if(next) {
nextRect = rect(next.children(0));
if(rectsAreTouching(contentRect, nextRect)) {
offsetAmount = contentRect.bottom - nextRect.top;
currentTop = content.css('top');
if(currentTop == 'auto') { currentTop = 0; }
else { currentTop = parseInt(currentTop, 10); }
content.css({top: currentTop - offsetAmount});
}
}
} else if(targetElementIndex < orderedElements.length - 1 && contentRect.top < 0) {
// we need to pull
nextRect = rect(targetElement(+1).children(0));
offsetAmount = contentRect.bottom - nextRect.top;
currentTop = content.css('top');
if(currentTop == 'auto') { currentTop = 0; }
else { currentTop = parseInt(currentTop, 10); }
content.css({top: Math.min(currentTop - offsetAmount, 0)});
}

function incrementElement(inc) {
inc = inc || 1;
targetElementIndex += inc;
content = targetElement().children(0);
contentRect = rect(content);
$container.data('$stickyTarget', targetElementIndex);
}

function targetElement(indexModifier) {
indexModifier = indexModifier || 0;
if(targetElementIndex === undefined) return undefined;
return orderedElements[targetElementIndex + indexModifier];
}
}

function rectsAreTouching(first, second) {
return first.bottom >= second.top;
}

// Helper functions to get position of element

function rect($el) {
return $el.hasOwnProperty(0) ? $el[0].getBoundingClientRect() : $el.getBoundingClientRect();
}


}

/**
* @ngdoc directive
* @name materialSticky
* @module material.components.sticky
*
* @description
* Directive to consume the $materialSticky directive
*
* @returns A material-sticky directive
*/
function MaterialStickyDirective($materialSticky) {
return {
restrict: 'A',
link: $materialSticky
};
}
Loading

0 comments on commit 93e8f3c

Please sign in to comment.