mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-06-22 18:34:17 +00:00
MODULE-TYPO
- Sass set-up - md-list - md-theming (install)
This commit is contained in:
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
|
||||
<div ng-controller="AppCtrl">
|
||||
<md-content class="md-padding">
|
||||
|
||||
<h3>
|
||||
RGB <span ng-attr-style="border: 1px solid #333; background: rgb({{color.red}},{{color.green}},{{color.blue}})"> </span>
|
||||
</h3>
|
||||
|
||||
<div layout>
|
||||
<div flex="10" layout layout-align="center center">
|
||||
<span>R</span>
|
||||
</div>
|
||||
<md-slider flex min="0" max="255" ng-model="color.red" aria-label="red" id="red-slider" class="md-warn">
|
||||
</md-slider>
|
||||
<div flex="20" layout layout-align="center center">
|
||||
<input type="number" ng-model="color.red" aria-label="red" aria-controls="red-slider">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div layout>
|
||||
<div flex="10" layout layout-align="center center">
|
||||
<span>G</span>
|
||||
</div>
|
||||
<md-slider flex ng-model="color.green" min="0" max="255" aria-label="green" id="green-slider" class="md-accent">
|
||||
</md-slider>
|
||||
<div flex="20" layout layout-align="center center">
|
||||
<input type="number" ng-model="color.green" aria-label="green" aria-controls="green-slider">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div layout>
|
||||
<div flex="10" layout layout-align="center center">
|
||||
<span>B</span>
|
||||
</div>
|
||||
<md-slider flex ng-model="color.blue" min="0" max="255" aria-label="blue" id="blue-slider">
|
||||
</md-slider>
|
||||
<div flex="20" layout layout-align="center center">
|
||||
<input type="number" ng-model="color.blue" aria-label="blue" aria-controls="blue-slider">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Rating: {{rating}}/5</h3>
|
||||
<md-slider md-discrete ng-model="rating" step="1" min="1" max="5" aria-label="rating">
|
||||
</md-slider>
|
||||
|
||||
<h3>Disabled</h3>
|
||||
<md-slider ng-model="disabled1" ng-disabled="true" aria-label="Disabled 1"></md-slider>
|
||||
<md-slider ng-model="disabled2" ng-disabled="true" aria-label="Disabled 2"></md-slider>
|
||||
|
||||
<h3>Disabled, Discrete</h3>
|
||||
<md-slider ng-model="disabled1" ng-disabled="true" step="3" md-discrete min="0" max="10" aria-label="Disabled discrete 1"></md-slider>
|
||||
<md-slider ng-model="disabled2" ng-disabled="true" step="10" md-discrete aria-label="Disabled discrete 2"></md-slider>
|
||||
|
||||
</md-content>
|
||||
</div>
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
|
||||
angular.module('sliderDemo1', ['ngMaterial'])
|
||||
|
||||
.controller('AppCtrl', function($scope) {
|
||||
|
||||
$scope.color = {
|
||||
red: Math.floor(Math.random() * 255),
|
||||
green: Math.floor(Math.random() * 255),
|
||||
blue: Math.floor(Math.random() * 255)
|
||||
};
|
||||
|
||||
$scope.rating = 3;
|
||||
$scope.disabled1 = 0;
|
||||
$scope.disabled2 = 70;
|
||||
|
||||
});
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
|
||||
input[type="number"] {
|
||||
text-align: center;
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
md-slider.md-THEME_NAME-theme {
|
||||
|
||||
.md-track {
|
||||
background-color: '{{foreground-3}}';
|
||||
}
|
||||
.md-track-ticks {
|
||||
background-color: '{{foreground-4}}';
|
||||
}
|
||||
.md-focus-thumb {
|
||||
background-color: '{{foreground-2}}';
|
||||
}
|
||||
.md-focus-ring {
|
||||
border-color: '{{foreground-4}}';
|
||||
}
|
||||
.md-disabled-thumb {
|
||||
border-color: '{{background-hue-3}}';
|
||||
}
|
||||
&.md-min .md-thumb:after {
|
||||
background-color: '{{background-hue-3}}';
|
||||
}
|
||||
|
||||
.md-track.md-track-fill {
|
||||
background-color: '{{primary-color}}';
|
||||
}
|
||||
.md-thumb:after {
|
||||
border-color: '{{primary-color}}';
|
||||
background-color: '{{primary-color}}';
|
||||
}
|
||||
.md-sign {
|
||||
background-color: '{{primary-color}}';
|
||||
&:after {
|
||||
border-top-color: '{{primary-color}}';
|
||||
}
|
||||
}
|
||||
.md-thumb-text {
|
||||
color: '{{primary-contrast}}';
|
||||
}
|
||||
|
||||
&.md-warn {
|
||||
.md-track-fill {
|
||||
background-color: '{{warn-color}}';
|
||||
}
|
||||
.md-thumb:after {
|
||||
border-color: '{{warn-color}}';
|
||||
background-color: '{{warn-color}}';
|
||||
}
|
||||
.md-sign {
|
||||
background-color: '{{warn-color}}';
|
||||
|
||||
&:after {
|
||||
border-top-color: '{{warn-color}}';
|
||||
}
|
||||
}
|
||||
.md-thumb-text {
|
||||
color: '{{warn-contrast}}';
|
||||
}
|
||||
}
|
||||
|
||||
&.md-accent {
|
||||
.md-track-fill {
|
||||
background-color: '{{accent-color}}';
|
||||
}
|
||||
.md-thumb:after {
|
||||
border-color: '{{accent-color}}';
|
||||
background-color: '{{accent-color}}';
|
||||
}
|
||||
.md-sign {
|
||||
background-color: '{{accent-color}}';
|
||||
|
||||
&:after {
|
||||
border-top-color: '{{accent-color}}';
|
||||
}
|
||||
}
|
||||
.md-thumb-text {
|
||||
color: '{{accent-contrast}}';
|
||||
}
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
.md-thumb:after {
|
||||
border-color: '{{foreground-3}}';
|
||||
}
|
||||
&:not(.md-min) .md-thumb:after {
|
||||
background-color: '{{foreground-3}}';
|
||||
}
|
||||
}
|
||||
}
|
||||
+403
@@ -0,0 +1,403 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc module
|
||||
* @name material.components.slider
|
||||
*/
|
||||
angular.module('material.components.slider', [
|
||||
'material.core'
|
||||
])
|
||||
.directive('mdSlider', SliderDirective);
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name mdSlider
|
||||
* @module material.components.slider
|
||||
* @restrict E
|
||||
* @description
|
||||
* The `<md-slider>` component allows the user to choose from a range of
|
||||
* values.
|
||||
*
|
||||
* It has two modes: 'normal' mode, where the user slides between a wide range
|
||||
* of values, and 'discrete' mode, where the user slides between only a few
|
||||
* select values.
|
||||
*
|
||||
* To enable discrete mode, add the `md-discrete` attribute to a slider,
|
||||
* and use the `step` attribute to change the distance between
|
||||
* values the user is allowed to pick.
|
||||
*
|
||||
* @usage
|
||||
* <h4>Normal Mode</h4>
|
||||
* <hljs lang="html">
|
||||
* <md-slider ng-model="myValue" min="5" max="500">
|
||||
* </md-slider>
|
||||
* </hljs>
|
||||
* <h4>Discrete Mode</h4>
|
||||
* <hljs lang="html">
|
||||
* <md-slider md-discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
|
||||
* </md-slider>
|
||||
* </hljs>
|
||||
*
|
||||
* @param {boolean=} md-discrete Whether to enable discrete mode.
|
||||
* @param {number=} step The distance between values the user is allowed to pick. Default 1.
|
||||
* @param {number=} min The minimum value the user is allowed to pick. Default 0.
|
||||
* @param {number=} max The maximum value the user is allowed to pick. Default 100.
|
||||
*/
|
||||
function SliderDirective($mdTheming) {
|
||||
return {
|
||||
scope: {},
|
||||
require: ['?ngModel', 'mdSlider'],
|
||||
controller: SliderController,
|
||||
template:
|
||||
'<div class="md-track-container">' +
|
||||
'<div class="md-track"></div>' +
|
||||
'<div class="md-track md-track-fill"></div>' +
|
||||
'<div class="md-track-ticks"></div>' +
|
||||
'</div>' +
|
||||
'<div class="md-thumb-container">' +
|
||||
'<div class="md-thumb"></div>' +
|
||||
'<div class="md-focus-thumb"></div>' +
|
||||
'<div class="md-focus-ring"></div>' +
|
||||
'<div class="md-sign">' +
|
||||
'<span class="md-thumb-text"></span>' +
|
||||
'</div>' +
|
||||
'<div class="md-disabled-thumb"></div>' +
|
||||
'</div>',
|
||||
link: postLink
|
||||
};
|
||||
|
||||
function postLink(scope, element, attr, ctrls) {
|
||||
$mdTheming(element);
|
||||
var ngModelCtrl = ctrls[0] || {
|
||||
// Mock ngModelController if it doesn't exist to give us
|
||||
// the minimum functionality needed
|
||||
$setViewValue: function(val) {
|
||||
this.$viewValue = val;
|
||||
this.$viewChangeListeners.forEach(function(cb) { cb(); });
|
||||
},
|
||||
$parsers: [],
|
||||
$formatters: [],
|
||||
$viewChangeListeners: []
|
||||
};
|
||||
|
||||
var sliderCtrl = ctrls[1];
|
||||
sliderCtrl.init(ngModelCtrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We use a controller for all the logic so that we can expose a few
|
||||
* things to unit tests
|
||||
*/
|
||||
function SliderController($scope, $element, $attrs, $$rAF, $window, $mdAria, $mdUtil, $mdConstant) {
|
||||
|
||||
this.init = function init(ngModelCtrl) {
|
||||
var thumb = angular.element($element[0].querySelector('.md-thumb'));
|
||||
var thumbText = angular.element($element[0].querySelector('.md-thumb-text'));
|
||||
var thumbContainer = thumb.parent();
|
||||
var trackContainer = angular.element($element[0].querySelector('.md-track-container'));
|
||||
var activeTrack = angular.element($element[0].querySelector('.md-track-fill'));
|
||||
var tickContainer = angular.element($element[0].querySelector('.md-track-ticks'));
|
||||
var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
|
||||
|
||||
// Default values, overridable by $attrss
|
||||
$attrs.min ? $attrs.$observe('min', updateMin) : updateMin(0);
|
||||
$attrs.max ? $attrs.$observe('max', updateMax) : updateMax(100);
|
||||
$attrs.step ? $attrs.$observe('step', updateStep) : updateStep(1);
|
||||
|
||||
// We have to manually stop the $watch on ngDisabled because it exists
|
||||
// on the parent $scope, and won't be automatically destroyed when
|
||||
// the component is destroyed.
|
||||
var stopDisabledWatch = angular.noop;
|
||||
if ($attrs.ngDisabled) {
|
||||
stopDisabledWatch = $scope.$parent.$watch($attrs.ngDisabled, updateAriaDisabled);
|
||||
}
|
||||
|
||||
$mdAria.expect($element, 'aria-label');
|
||||
|
||||
$element.attr('tabIndex', 0);
|
||||
$element.attr('role', 'slider');
|
||||
$element.on('keydown', keydownListener);
|
||||
|
||||
var hammertime = new Hammer($element[0], {
|
||||
recognizers: [
|
||||
[Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL }]
|
||||
]
|
||||
});
|
||||
hammertime.on('hammer.input', onInput);
|
||||
hammertime.on('panstart', onPanStart);
|
||||
hammertime.on('pan', onPan);
|
||||
hammertime.on('panend', onPanEnd);
|
||||
|
||||
// On resize, recalculate the slider's dimensions and re-render
|
||||
function updateAll() {
|
||||
refreshSliderDimensions();
|
||||
ngModelRender();
|
||||
redrawTicks();
|
||||
}
|
||||
setTimeout(updateAll);
|
||||
|
||||
var debouncedUpdateAll = $$rAF.debounce(updateAll);
|
||||
angular.element($window).on('resize', debouncedUpdateAll);
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
angular.element($window).off('resize', debouncedUpdateAll);
|
||||
hammertime.destroy();
|
||||
stopDisabledWatch();
|
||||
});
|
||||
|
||||
ngModelCtrl.$render = ngModelRender;
|
||||
ngModelCtrl.$viewChangeListeners.push(ngModelRender);
|
||||
ngModelCtrl.$formatters.push(minMaxValidator);
|
||||
ngModelCtrl.$formatters.push(stepValidator);
|
||||
|
||||
/**
|
||||
* Attributes
|
||||
*/
|
||||
var min;
|
||||
var max;
|
||||
var step;
|
||||
function updateMin(value) {
|
||||
min = parseFloat(value);
|
||||
$element.attr('aria-valuemin', value);
|
||||
updateAll();
|
||||
}
|
||||
function updateMax(value) {
|
||||
max = parseFloat(value);
|
||||
$element.attr('aria-valuemax', value);
|
||||
updateAll();
|
||||
}
|
||||
function updateStep(value) {
|
||||
step = parseFloat(value);
|
||||
redrawTicks();
|
||||
}
|
||||
function updateAriaDisabled(isDisabled) {
|
||||
$element.attr('aria-disabled', !!isDisabled);
|
||||
}
|
||||
|
||||
// Draw the ticks with canvas.
|
||||
// The alternative to drawing ticks with canvas is to draw one $element for each tick,
|
||||
// which could quickly become a performance bottleneck.
|
||||
var tickCanvas, tickCtx;
|
||||
function redrawTicks() {
|
||||
if (!angular.isDefined($attrs.mdDiscrete)) return;
|
||||
|
||||
var numSteps = Math.floor( (max - min) / step );
|
||||
if (!tickCanvas) {
|
||||
var trackTicksStyle = $window.getComputedStyle(tickContainer[0]);
|
||||
tickCanvas = angular.element('<canvas style="position:absolute;">');
|
||||
tickCtx = tickCanvas[0].getContext('2d');
|
||||
tickCtx.fillStyle = trackTicksStyle.backgroundColor || 'black';
|
||||
tickContainer.append(tickCanvas);
|
||||
}
|
||||
var dimensions = getSliderDimensions();
|
||||
tickCanvas[0].width = dimensions.width;
|
||||
tickCanvas[0].height = dimensions.height;
|
||||
|
||||
var distance;
|
||||
for (var i = 0; i <= numSteps; i++) {
|
||||
distance = Math.floor(dimensions.width * (i / numSteps));
|
||||
tickCtx.fillRect(distance - 1, 0, 2, dimensions.height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refreshing Dimensions
|
||||
*/
|
||||
var sliderDimensions = {};
|
||||
refreshSliderDimensions();
|
||||
function refreshSliderDimensions() {
|
||||
sliderDimensions = trackContainer[0].getBoundingClientRect();
|
||||
}
|
||||
function getSliderDimensions() {
|
||||
throttledRefreshDimensions();
|
||||
return sliderDimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* left/right arrow listener
|
||||
*/
|
||||
function keydownListener(ev) {
|
||||
if($element[0].hasAttribute('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var changeAmount;
|
||||
if (ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) {
|
||||
changeAmount = -step;
|
||||
} else if (ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) {
|
||||
changeAmount = step;
|
||||
}
|
||||
if (changeAmount) {
|
||||
if (ev.metaKey || ev.ctrlKey || ev.altKey) {
|
||||
changeAmount *= 4;
|
||||
}
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
$scope.$evalAsync(function() {
|
||||
setModelValue(ngModelCtrl.$viewValue + changeAmount);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ngModel setters and validators
|
||||
*/
|
||||
function setModelValue(value) {
|
||||
ngModelCtrl.$setViewValue( minMaxValidator(stepValidator(value)) );
|
||||
}
|
||||
function ngModelRender() {
|
||||
|
||||
if (isNaN(ngModelCtrl.$viewValue)) {
|
||||
ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
|
||||
}
|
||||
|
||||
var percent = (ngModelCtrl.$viewValue - min) / (max - min);
|
||||
$scope.modelValue = ngModelCtrl.$viewValue;
|
||||
$element.attr('aria-valuenow', ngModelCtrl.$viewValue);
|
||||
setSliderPercent(percent);
|
||||
thumbText.text( ngModelCtrl.$viewValue );
|
||||
}
|
||||
|
||||
function minMaxValidator(value) {
|
||||
if (angular.isNumber(value)) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
}
|
||||
function stepValidator(value) {
|
||||
if (angular.isNumber(value)) {
|
||||
return Math.round(value / step) * step;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param percent 0-1
|
||||
*/
|
||||
function setSliderPercent(percent) {
|
||||
activeTrack.css('width', (percent * 100) + '%');
|
||||
thumbContainer.css(
|
||||
$mdConstant.CSS.TRANSFORM,
|
||||
'translate3d(' + getSliderDimensions().width * percent + 'px,0,0)'
|
||||
);
|
||||
$element.toggleClass('md-min', percent === 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Slide listeners
|
||||
*/
|
||||
var isSliding = false;
|
||||
var isDiscrete = angular.isDefined($attrs.mdDiscrete);
|
||||
|
||||
function onInput(ev) {
|
||||
if (!isSliding && ev.eventType === Hammer.INPUT_START &&
|
||||
!$element[0].hasAttribute('disabled')) {
|
||||
|
||||
isSliding = true;
|
||||
|
||||
$element.addClass('active');
|
||||
$element[0].focus();
|
||||
refreshSliderDimensions();
|
||||
|
||||
onPan(ev);
|
||||
|
||||
ev.srcEvent.stopPropagation();
|
||||
|
||||
} else if (isSliding && ev.eventType === Hammer.INPUT_END) {
|
||||
|
||||
if ( isSliding && isDiscrete ) onPanEnd(ev);
|
||||
isSliding = false;
|
||||
|
||||
$element.removeClass('panning active');
|
||||
}
|
||||
}
|
||||
function onPanStart() {
|
||||
if (!isSliding) return;
|
||||
$element.addClass('panning');
|
||||
}
|
||||
function onPan(ev) {
|
||||
if (!isSliding) return;
|
||||
|
||||
// While panning discrete, update only the
|
||||
// visual positioning but not the model value.
|
||||
|
||||
if ( isDiscrete ) adjustThumbPosition( ev.center.x );
|
||||
else doSlide( ev.center.x );
|
||||
|
||||
ev.preventDefault();
|
||||
ev.srcEvent.stopPropagation();
|
||||
}
|
||||
|
||||
function onPanEnd(ev) {
|
||||
if ( isDiscrete && !$element[0].hasAttribute('disabled') ) {
|
||||
// Convert exact to closest discrete value.
|
||||
// Slide animate the thumb... and then update the model value.
|
||||
|
||||
var exactVal = percentToValue( positionToPercent( ev.center.x ));
|
||||
var closestVal = minMaxValidator( stepValidator(exactVal) );
|
||||
|
||||
setSliderPercent( valueToPercent(closestVal));
|
||||
$$rAF(function(){
|
||||
setModelValue( closestVal );
|
||||
});
|
||||
|
||||
ev.preventDefault();
|
||||
ev.srcEvent.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose for testing
|
||||
*/
|
||||
this._onInput = onInput;
|
||||
this._onPanStart = onPanStart;
|
||||
this._onPan = onPan;
|
||||
|
||||
/**
|
||||
* Slide the UI by changing the model value
|
||||
* @param x
|
||||
*/
|
||||
function doSlide( x ) {
|
||||
$scope.$evalAsync( function() {
|
||||
setModelValue( percentToValue( positionToPercent(x) ));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide the UI without changing the model (while dragging/panning)
|
||||
* @param x
|
||||
*/
|
||||
function adjustThumbPosition( x ) {
|
||||
var exactVal = percentToValue( positionToPercent( x ));
|
||||
var closestVal = minMaxValidator( stepValidator(exactVal) );
|
||||
setSliderPercent( positionToPercent(x) );
|
||||
thumbText.text( closestVal );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert horizontal position on slider to percentage value of offset from beginning...
|
||||
* @param x
|
||||
* @returns {number}
|
||||
*/
|
||||
function positionToPercent( x ) {
|
||||
return Math.max(0, Math.min(1, (x - sliderDimensions.left) / (sliderDimensions.width)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert percentage offset on slide to equivalent model value
|
||||
* @param percent
|
||||
* @returns {*}
|
||||
*/
|
||||
function percentToValue( percent ) {
|
||||
return (min + percent * (max - min));
|
||||
}
|
||||
|
||||
function valueToPercent( val ) {
|
||||
return (val - min)/(max - min);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
})();
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
$slider-background-color: rgb(200, 200, 200) !default;
|
||||
$slider-height: 48px !default;
|
||||
|
||||
$slider-track-height: 2px !default;
|
||||
$slider-thumb-width: 32px !default;
|
||||
$slider-thumb-height: $slider-thumb-width !default;
|
||||
|
||||
$slider-thumb-default-scale: 0.5 !default;
|
||||
$slider-thumb-hover-scale: 0.6 !default;
|
||||
$slider-thumb-focus-scale: 0.85 !default;
|
||||
$slider-thumb-disabled-scale: 0.35 !default;
|
||||
$slider-thumb-disabled-border: 6px !default;
|
||||
|
||||
$slider-focus-thumb-width: 48px !default;
|
||||
$slider-focus-thumb-height: $slider-focus-thumb-width !default;
|
||||
$slider-focus-ring-border-width: 3px !default;
|
||||
|
||||
$slider-arrow-height: 16px !default;
|
||||
$slider-arrow-width: 28px !default;
|
||||
|
||||
$slider-sign-height: 28px !default;
|
||||
$slider-sign-width: 28px !default;
|
||||
$slider-sign-top: ($slider-height / 2) - ($slider-thumb-default-scale * $slider-thumb-height / 2) - ($slider-sign-height) - ($slider-arrow-height) + 8px !default;
|
||||
|
||||
@keyframes sliderFocusThumb {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.0);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin slider-thumb-position($width: $slider-thumb-width, $height: $slider-thumb-height) {
|
||||
position: absolute;
|
||||
left: -$width / 2;
|
||||
top: ($slider-height / 2) - ($height / 2);
|
||||
width: $width;
|
||||
height: $height;
|
||||
border-radius: max($width, $height);
|
||||
}
|
||||
|
||||
md-slider {
|
||||
|
||||
height: $slider-height;
|
||||
position: relative;
|
||||
display: block;
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
|
||||
/**
|
||||
* Track
|
||||
*/
|
||||
.md-track-container {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: ($slider-height / 2) - ($slider-track-height) / 2;
|
||||
height: $slider-track-height;
|
||||
}
|
||||
.md-track {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.md-track-fill {
|
||||
transition: width 0.05s linear;
|
||||
}
|
||||
.md-track-ticks {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slider thumb
|
||||
*/
|
||||
.md-thumb-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translate3d(0,0,0);
|
||||
transition: transform 0.1s linear;
|
||||
}
|
||||
.md-thumb {
|
||||
z-index: 1;
|
||||
|
||||
// Positioning the outer area of the thumb 6px bigger than it needs to be keeps
|
||||
// the :after area being clipped by the background of the focus-thumb animation.
|
||||
@include slider-thumb-position($slider-thumb-width + 6, $slider-thumb-height + 6);
|
||||
|
||||
// We render thumb in an :after selector to fix an obscure problem with the
|
||||
// thumb being clipped by the focus-ring and focus-thumb while running the focus
|
||||
// animation.
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
width: $slider-thumb-width;
|
||||
height: $slider-thumb-height;
|
||||
border-radius: max($slider-thumb-width, $slider-thumb-height);
|
||||
border: 3px solid;
|
||||
}
|
||||
|
||||
transform: scale($slider-thumb-default-scale);
|
||||
transition: all 0.1s linear;
|
||||
}
|
||||
|
||||
/* The sign that's focused in discrete mode */
|
||||
.md-sign {
|
||||
|
||||
/* Center the children (slider-thumb-text) */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: absolute;
|
||||
left: -($slider-sign-height / 2);
|
||||
top: $slider-sign-top;
|
||||
width: $slider-sign-width;
|
||||
height: $slider-sign-height;
|
||||
border-radius: max($slider-sign-height, $slider-sign-width);
|
||||
|
||||
transform: scale(0.4) translate3d(0,(-$slider-sign-top + 8) / 0.4,0);
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
/* The arrow pointing down under the sign */
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
left: -($slider-sign-width / 2 - $slider-arrow-width / 2);
|
||||
border-radius: $slider-arrow-height;
|
||||
top: 19px;
|
||||
border-left: $slider-arrow-width / 2 solid transparent;
|
||||
border-right: $slider-arrow-width / 2 solid transparent;
|
||||
border-top: $slider-arrow-height solid;
|
||||
|
||||
opacity: 0;
|
||||
transform: translate3d(0,-8px,0);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.md-thumb-text {
|
||||
z-index: 1;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The border/background that comes in when focused in non-discrete mode
|
||||
*/
|
||||
.md-focus-thumb {
|
||||
@include slider-thumb-position($slider-focus-thumb-width, $slider-focus-thumb-height);
|
||||
display: none;
|
||||
opacity: 0;
|
||||
background-color: #C0C0C0;
|
||||
animation: sliderFocusThumb 0.4s linear;
|
||||
}
|
||||
.md-focus-ring {
|
||||
@include slider-thumb-position($slider-focus-thumb-width, $slider-focus-thumb-height);
|
||||
border: 2px solid #D6D6D6;
|
||||
background-color: transparent;
|
||||
transform: scale(0);
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
.md-disabled-thumb {
|
||||
@include slider-thumb-position(
|
||||
$slider-thumb-width + $slider-thumb-disabled-border * 2,
|
||||
$slider-thumb-height + $slider-thumb-disabled-border * 2
|
||||
);
|
||||
transform: scale($slider-thumb-disabled-scale);
|
||||
border: $slider-thumb-disabled-border solid;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.md-min {
|
||||
.md-thumb {
|
||||
&:after {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
.md-sign {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Don't animate left/right while panning */
|
||||
&.panning {
|
||||
.md-thumb-container,
|
||||
.md-track-fill {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not([md-discrete]) {
|
||||
/* Hide the sign and ticks in non-discrete mode */
|
||||
.md-track-ticks,
|
||||
.md-sign {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:not([disabled]) {
|
||||
&:hover {
|
||||
.md-thumb {
|
||||
transform: scale($slider-thumb-hover-scale);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.active {
|
||||
.md-focus-thumb {
|
||||
display: block;
|
||||
}
|
||||
.md-focus-ring {
|
||||
transform: scale(1);
|
||||
}
|
||||
.md-thumb {
|
||||
transform: scale($slider-thumb-focus-scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[md-discrete] {
|
||||
/* Hide the focus thumb in discrete mode */
|
||||
.md-focus-thumb,
|
||||
.md-focus-ring {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:not([disabled]) {
|
||||
&:focus,
|
||||
&.active {
|
||||
.md-sign,
|
||||
.md-sign:after {
|
||||
opacity: 1;
|
||||
transform: translate3d(0,0,0) scale(1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
.md-track-fill {
|
||||
display: none;
|
||||
}
|
||||
.md-sign {
|
||||
display: none;
|
||||
}
|
||||
.md-thumb {
|
||||
transform: scale($slider-thumb-disabled-scale);
|
||||
}
|
||||
.md-disabled-thumb {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
|
||||
describe('md-slider', function() {
|
||||
|
||||
function simulateEventAt( centerX, eventType ) {
|
||||
return {
|
||||
eventType: eventType,
|
||||
center: { x: centerX },
|
||||
preventDefault: angular.noop,
|
||||
srcEvent : {
|
||||
stopPropagation : angular.noop
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(TestUtil.mockRaf);
|
||||
beforeEach(module('ngAria'));
|
||||
beforeEach(module('material.components.slider'));
|
||||
|
||||
it('should set model on press', inject(function($compile, $rootScope, $timeout) {
|
||||
var slider = $compile('<md-slider ng-model="value" min="0" max="100">')($rootScope);
|
||||
$rootScope.$apply('value = 50');
|
||||
var sliderCtrl = slider.controller('mdSlider');
|
||||
|
||||
spyOn(slider[0].querySelector('.md-track-container'), 'getBoundingClientRect').andReturn({
|
||||
width: 100,
|
||||
left: 0,
|
||||
right: 0
|
||||
});
|
||||
|
||||
sliderCtrl._onInput( simulateEventAt( 30, Hammer.INPUT_START ));
|
||||
$timeout.flush();
|
||||
expect($rootScope.value).toBe(30);
|
||||
|
||||
//When going past max, it should clamp to max
|
||||
sliderCtrl._onPan( simulateEventAt( 500 ));
|
||||
$timeout.flush();
|
||||
expect($rootScope.value).toBe(100);
|
||||
|
||||
sliderCtrl._onPan( simulateEventAt( 50 ));
|
||||
$timeout.flush();
|
||||
expect($rootScope.value).toBe(50);
|
||||
}));
|
||||
|
||||
it('should set model on drag', inject(function($compile, $rootScope, $timeout) {
|
||||
var slider = $compile('<md-slider ng-model="value" min="0" max="100" md-discrete>')($rootScope);
|
||||
$rootScope.$apply('value = 50');
|
||||
var sliderCtrl = slider.controller('mdSlider');
|
||||
|
||||
spyOn(slider[0].querySelector('.md-track-container'), 'getBoundingClientRect').andReturn({
|
||||
width: 100,
|
||||
left: 0,
|
||||
right: 0
|
||||
});
|
||||
|
||||
sliderCtrl._onInput( simulateEventAt( 30, Hammer.INPUT_START ));
|
||||
$timeout.flush();
|
||||
|
||||
sliderCtrl._onPan( simulateEventAt( 80 ));
|
||||
expect(slider[0].querySelector('.md-thumb-text').textContent).toBe('80');
|
||||
}));
|
||||
|
||||
it('should increment model on right arrow', inject(function($compile, $rootScope, $timeout, $mdConstant) {
|
||||
var slider = $compile(
|
||||
'<md-slider min="100" max="104" step="2" ng-model="model">'
|
||||
)($rootScope);
|
||||
|
||||
$rootScope.$apply('model = 100');
|
||||
|
||||
slider.triggerHandler({
|
||||
type: 'keydown',
|
||||
keyCode: $mdConstant.KEY_CODE.RIGHT_ARROW
|
||||
});
|
||||
$timeout.flush();
|
||||
expect($rootScope.model).toBe(102);
|
||||
|
||||
slider.triggerHandler({
|
||||
type: 'keydown',
|
||||
keyCode: $mdConstant.KEY_CODE.RIGHT_ARROW
|
||||
});
|
||||
$timeout.flush();
|
||||
expect($rootScope.model).toBe(104);
|
||||
|
||||
// Stays at max
|
||||
slider.triggerHandler({
|
||||
type: 'keydown',
|
||||
keyCode: $mdConstant.KEY_CODE.RIGHT_ARROW
|
||||
});
|
||||
$timeout.flush();
|
||||
expect($rootScope.model).toBe(104);
|
||||
}));
|
||||
|
||||
it('should decrement model on left arrow', inject(function($compile, $rootScope, $timeout, $mdConstant) {
|
||||
var slider = $compile(
|
||||
'<md-slider min="100" max="104" step="2" ng-model="model">'
|
||||
)($rootScope);
|
||||
|
||||
$rootScope.$apply('model = 104');
|
||||
|
||||
slider.triggerHandler({
|
||||
type: 'keydown',
|
||||
keyCode: $mdConstant.KEY_CODE.LEFT_ARROW
|
||||
});
|
||||
$timeout.flush();
|
||||
expect($rootScope.model).toBe(102);
|
||||
|
||||
slider.triggerHandler({
|
||||
type: 'keydown',
|
||||
keyCode: $mdConstant.KEY_CODE.LEFT_ARROW
|
||||
});
|
||||
$timeout.flush();
|
||||
expect($rootScope.model).toBe(100);
|
||||
|
||||
// Stays at min
|
||||
slider.triggerHandler({
|
||||
type: 'keydown',
|
||||
keyCode: $mdConstant.KEY_CODE.LEFT_ARROW
|
||||
});
|
||||
$timeout.flush();
|
||||
expect($rootScope.model).toBe(100);
|
||||
}));
|
||||
|
||||
it('should update the thumb text', inject(function($compile, $rootScope, $timeout, $mdConstant) {
|
||||
var slider = $compile('<md-slider ng-model="value" min="0" max="100">')($rootScope);
|
||||
var sliderCtrl = slider.controller('mdSlider');
|
||||
|
||||
spyOn(slider[0].querySelector('.md-track-container'), 'getBoundingClientRect').andReturn({
|
||||
width: 100,
|
||||
left: 0,
|
||||
right: 0
|
||||
});
|
||||
|
||||
sliderCtrl._onInput( simulateEventAt( 30, Hammer.INPUT_START ));
|
||||
$timeout.flush();
|
||||
expect(slider[0].querySelector('.md-thumb-text').textContent).toBe('30');
|
||||
|
||||
slider.triggerHandler({
|
||||
type: 'keydown',
|
||||
keyCode: $mdConstant.KEY_CODE.LEFT_ARROW
|
||||
});
|
||||
$timeout.flush();
|
||||
expect(slider[0].querySelector('.md-thumb-text').textContent).toBe('29');
|
||||
|
||||
sliderCtrl._onPan( simulateEventAt( 30 ));
|
||||
$timeout.flush();
|
||||
expect(slider[0].querySelector('.md-thumb-text').textContent).toBe('30');
|
||||
}));
|
||||
|
||||
it('should update the thumb text with the model value when using ng-change', inject(function($compile, $rootScope, $timeout) {
|
||||
$scope = $rootScope.$new();
|
||||
|
||||
$scope.stayAt50 = function () {
|
||||
$scope.value = 50;
|
||||
};
|
||||
|
||||
var slider = $compile('<md-slider ng-model="value" min="0" max="100" ng-change="stayAt50()">')($scope);
|
||||
var sliderCtrl = slider.controller('mdSlider');
|
||||
|
||||
spyOn(slider[0].querySelector('.md-track-container'), 'getBoundingClientRect').andReturn({
|
||||
width: 100,
|
||||
left: 0,
|
||||
right: 0
|
||||
});
|
||||
|
||||
sliderCtrl._onInput( simulateEventAt( 30, Hammer.INPUT_START ));
|
||||
$timeout.flush();
|
||||
expect($scope.value).toBe(50);
|
||||
expect(slider[0].querySelector('.md-thumb-text').textContent).toBe('50');
|
||||
}));
|
||||
|
||||
it('should call $log.warn if aria-label isnt provided', inject(function($compile, $rootScope, $timeout, $log) {
|
||||
spyOn($log, "warn");
|
||||
var element = $compile(
|
||||
'<md-slider min="100" max="104" step="2" ng-model="model"></md-slider>'
|
||||
)($rootScope);
|
||||
expect($log.warn).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should not call $log.warn if aria-label is provided', inject(function($compile, $rootScope, $timeout, $log) {
|
||||
spyOn($log, "warn");
|
||||
var element = $compile(
|
||||
'<md-slider aria-label="banana" min="100" max="104" step="2" ng-model="model"></md-slider>'
|
||||
)($rootScope);
|
||||
expect($log.warn).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should add aria attributes', inject(function($compile, $rootScope, $timeout, $mdConstant){
|
||||
var slider = $compile(
|
||||
'<md-slider min="100" max="104" step="2" ng-model="model">'
|
||||
)($rootScope);
|
||||
|
||||
$rootScope.$apply('model = 102');
|
||||
|
||||
expect(slider.attr('role')).toEqual('slider');
|
||||
expect(slider.attr('aria-valuemin')).toEqual('100');
|
||||
expect(slider.attr('aria-valuemax')).toEqual('104');
|
||||
expect(slider.attr('aria-valuenow')).toEqual('102');
|
||||
|
||||
slider.triggerHandler({
|
||||
type: 'keydown',
|
||||
keyCode: $mdConstant.KEY_CODE.LEFT_ARROW
|
||||
});
|
||||
$timeout.flush();
|
||||
expect(slider.attr('aria-valuenow')).toEqual('100');
|
||||
}));
|
||||
});
|
||||
Reference in New Issue
Block a user