arnaucube
arnaucube/commonroutesAppPublic Self-hosted repository
commonroutesApp/www/lib/angular-material/modules/js/slider/slider.js
www/lib/angular-material/modules/js/slider/slider.jsRaw
  1/*!
  2 * Angular Material Design
  3 * https://github.com/angular/material
  4 * @license MIT
  5 * v1.1.1
  6 */
  7(function( window, angular, undefined ){
  8"use strict";
  9
 10  /**
 11   * @ngdoc module
 12   * @name material.components.slider
 13   */
 14SliderDirective.$inject = ["$$rAF", "$window", "$mdAria", "$mdUtil", "$mdConstant", "$mdTheming", "$mdGesture", "$parse", "$log", "$timeout"];
 15  angular.module('material.components.slider', [
 16    'material.core'
 17  ])
 18  .directive('mdSlider', SliderDirective)
 19  .directive('mdSliderContainer', SliderContainerDirective);
 20
 21/**
 22 * @ngdoc directive
 23 * @name mdSliderContainer
 24 * @module material.components.slider
 25 * @restrict E
 26 * @description
 27 * The `<md-slider-container>` contains slider with two other elements.
 28 *
 29 *
 30 * @usage
 31 * <h4>Normal Mode</h4>
 32 * <hljs lang="html">
 33 * </hljs>
 34 */
 35function SliderContainerDirective() {
 36  return {
 37    controller: function () {},
 38    compile: function (elem) {
 39      var slider = elem.find('md-slider');
 40
 41      if (!slider) {
 42        return;
 43      }
 44
 45      var vertical = slider.attr('md-vertical');
 46
 47      if (vertical !== undefined) {
 48        elem.attr('md-vertical', '');
 49      }
 50
 51      if(!slider.attr('flex')) {
 52        slider.attr('flex', '');
 53      }
 54
 55      return function postLink(scope, element, attr, ctrl) {
 56        element.addClass('_md');     // private md component indicator for styling
 57
 58        // We have to manually stop the $watch on ngDisabled because it exists
 59        // on the parent scope, and won't be automatically destroyed when
 60        // the component is destroyed.
 61        function setDisable(value) {
 62          element.children().attr('disabled', value);
 63          element.find('input').attr('disabled', value);
 64        }
 65
 66        var stopDisabledWatch = angular.noop;
 67
 68        if (attr.disabled) {
 69          setDisable(true);
 70        }
 71        else if (attr.ngDisabled) {
 72          stopDisabledWatch = scope.$watch(attr.ngDisabled, function (value) {
 73            setDisable(value);
 74          });
 75        }
 76
 77        scope.$on('$destroy', function () {
 78          stopDisabledWatch();
 79        });
 80
 81        var initialMaxWidth;
 82
 83        ctrl.fitInputWidthToTextLength = function (length) {
 84          var input = element[0].querySelector('md-input-container');
 85
 86          if (input) {
 87            var computedStyle = getComputedStyle(input);
 88            var minWidth = parseInt(computedStyle.minWidth);
 89            var padding = parseInt(computedStyle.padding) * 2;
 90
 91            initialMaxWidth = initialMaxWidth || parseInt(computedStyle.maxWidth);
 92            var newMaxWidth = Math.max(initialMaxWidth, minWidth + padding + (minWidth / 2 * length));
 93
 94            input.style.maxWidth = newMaxWidth + 'px';
 95          }
 96        };
 97      };
 98    }
 99  };
100}
101
102/**
103 * @ngdoc directive
104 * @name mdSlider
105 * @module material.components.slider
106 * @restrict E
107 * @description
108 * The `<md-slider>` component allows the user to choose from a range of
109 * values.
110 *
111 * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
112 * the slider is in the accent color by default. The primary color palette may be used with
113 * the `md-primary` class.
114 *
115 * It has two modes: 'normal' mode, where the user slides between a wide range
116 * of values, and 'discrete' mode, where the user slides between only a few
117 * select values.
118 *
119 * To enable discrete mode, add the `md-discrete` attribute to a slider,
120 * and use the `step` attribute to change the distance between
121 * values the user is allowed to pick.
122 *
123 * @usage
124 * <h4>Normal Mode</h4>
125 * <hljs lang="html">
126 * <md-slider ng-model="myValue" min="5" max="500">
127 * </md-slider>
128 * </hljs>
129 * <h4>Discrete Mode</h4>
130 * <hljs lang="html">
131 * <md-slider md-discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
132 * </md-slider>
133 * </hljs>
134 * <h4>Invert Mode</h4>
135 * <hljs lang="html">
136 * <md-slider md-invert ng-model="myValue" step="10" min="10" max="130">
137 * </md-slider>
138 * </hljs>
139 *
140 * @param {boolean=} md-discrete Whether to enable discrete mode.
141 * @param {boolean=} md-invert Whether to enable invert mode.
142 * @param {number=} step The distance between values the user is allowed to pick. Default 1.
143 * @param {number=} min The minimum value the user is allowed to pick. Default 0.
144 * @param {number=} max The maximum value the user is allowed to pick. Default 100.
145 * @param {number=} round The amount of numbers after the decimal point, maximum is 6 to prevent scientific notation. Default 3.
146 */
147function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdTheming, $mdGesture, $parse, $log, $timeout) {
148  return {
149    scope: {},
150    require: ['?ngModel', '?^mdSliderContainer'],
151    template:
152      '<div class="md-slider-wrapper">' +
153        '<div class="md-slider-content">' +
154          '<div class="md-track-container">' +
155            '<div class="md-track"></div>' +
156            '<div class="md-track md-track-fill"></div>' +
157            '<div class="md-track-ticks"></div>' +
158          '</div>' +
159          '<div class="md-thumb-container">' +
160            '<div class="md-thumb"></div>' +
161            '<div class="md-focus-thumb"></div>' +
162            '<div class="md-focus-ring"></div>' +
163            '<div class="md-sign">' +
164              '<span class="md-thumb-text"></span>' +
165            '</div>' +
166            '<div class="md-disabled-thumb"></div>' +
167          '</div>' +
168        '</div>' +
169      '</div>',
170    compile: compile
171  };
172
173  // **********************************************************
174  // Private Methods
175  // **********************************************************
176
177  function compile (tElement, tAttrs) {
178    var wrapper = angular.element(tElement[0].getElementsByClassName('md-slider-wrapper'));
179
180    var tabIndex = tAttrs.tabindex || 0;
181    wrapper.attr('tabindex', tabIndex);
182
183    if (tAttrs.disabled || tAttrs.ngDisabled) wrapper.attr('tabindex', -1);
184
185    wrapper.attr('role', 'slider');
186
187    $mdAria.expect(tElement, 'aria-label');
188
189    return postLink;
190  }
191
192  function postLink(scope, element, attr, ctrls) {
193    $mdTheming(element);
194    var ngModelCtrl = ctrls[0] || {
195      // Mock ngModelController if it doesn't exist to give us
196      // the minimum functionality needed
197      $setViewValue: function(val) {
198        this.$viewValue = val;
199        this.$viewChangeListeners.forEach(function(cb) { cb(); });
200      },
201      $parsers: [],
202      $formatters: [],
203      $viewChangeListeners: []
204    };
205
206    var containerCtrl = ctrls[1];
207    var container = angular.element($mdUtil.getClosest(element, '_md-slider-container', true));
208    var isDisabled = attr.ngDisabled ? angular.bind(null, $parse(attr.ngDisabled), scope.$parent) : function () {
209          return element[0].hasAttribute('disabled');
210        };
211
212    var thumb = angular.element(element[0].querySelector('.md-thumb'));
213    var thumbText = angular.element(element[0].querySelector('.md-thumb-text'));
214    var thumbContainer = thumb.parent();
215    var trackContainer = angular.element(element[0].querySelector('.md-track-container'));
216    var activeTrack = angular.element(element[0].querySelector('.md-track-fill'));
217    var tickContainer = angular.element(element[0].querySelector('.md-track-ticks'));
218    var wrapper = angular.element(element[0].getElementsByClassName('md-slider-wrapper'));
219    var content = angular.element(element[0].getElementsByClassName('md-slider-content'));
220    var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
221
222    // Default values, overridable by attrs
223    var DEFAULT_ROUND = 3;
224    var vertical = angular.isDefined(attr.mdVertical);
225    var discrete = angular.isDefined(attr.mdDiscrete);
226    var invert = angular.isDefined(attr.mdInvert);
227    angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(0);
228    angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(100);
229    angular.isDefined(attr.step)? attr.$observe('step', updateStep) : updateStep(1);
230    angular.isDefined(attr.round)? attr.$observe('round', updateRound) : updateRound(DEFAULT_ROUND);
231
232    // We have to manually stop the $watch on ngDisabled because it exists
233    // on the parent scope, and won't be automatically destroyed when
234    // the component is destroyed.
235    var stopDisabledWatch = angular.noop;
236    if (attr.ngDisabled) {
237      stopDisabledWatch = scope.$parent.$watch(attr.ngDisabled, updateAriaDisabled);
238    }
239
240    $mdGesture.register(wrapper, 'drag', { horizontal: !vertical });
241
242    scope.mouseActive = false;
243
244    wrapper
245      .on('keydown', keydownListener)
246      .on('mousedown', mouseDownListener)
247      .on('focus', focusListener)
248      .on('blur', blurListener)
249      .on('$md.pressdown', onPressDown)
250      .on('$md.pressup', onPressUp)
251      .on('$md.dragstart', onDragStart)
252      .on('$md.drag', onDrag)
253      .on('$md.dragend', onDragEnd);
254
255    // On resize, recalculate the slider's dimensions and re-render
256    function updateAll() {
257      refreshSliderDimensions();
258      ngModelRender();
259    }
260    setTimeout(updateAll, 0);
261
262    var debouncedUpdateAll = $$rAF.throttle(updateAll);
263    angular.element($window).on('resize', debouncedUpdateAll);
264
265    scope.$on('$destroy', function() {
266      angular.element($window).off('resize', debouncedUpdateAll);
267    });
268
269    ngModelCtrl.$render = ngModelRender;
270    ngModelCtrl.$viewChangeListeners.push(ngModelRender);
271    ngModelCtrl.$formatters.push(minMaxValidator);
272    ngModelCtrl.$formatters.push(stepValidator);
273
274    /**
275     * Attributes
276     */
277    var min;
278    var max;
279    var step;
280    var round;
281    function updateMin(value) {
282      min = parseFloat(value);
283      element.attr('aria-valuemin', value);
284      updateAll();
285    }
286    function updateMax(value) {
287      max = parseFloat(value);
288      element.attr('aria-valuemax', value);
289      updateAll();
290    }
291    function updateStep(value) {
292      step = parseFloat(value);
293    }
294    function updateRound(value) {
295      // Set max round digits to 6, after 6 the input uses scientific notation
296      round = minMaxValidator(parseInt(value), 0, 6);
297    }
298    function updateAriaDisabled() {
299      element.attr('aria-disabled', !!isDisabled());
300    }
301
302    // Draw the ticks with canvas.
303    // The alternative to drawing ticks with canvas is to draw one element for each tick,
304    // which could quickly become a performance bottleneck.
305    var tickCanvas, tickCtx;
306    function redrawTicks() {
307      if (!discrete || isDisabled()) return;
308      if ( angular.isUndefined(step) )         return;
309
310      if ( step <= 0 ) {
311        var msg = 'Slider step value must be greater than zero when in discrete mode';
312        $log.error(msg);
313        throw new Error(msg);
314      }
315
316      var numSteps = Math.floor( (max - min) / step );
317      if (!tickCanvas) {
318        tickCanvas = angular.element('<canvas>').css('position', 'absolute');
319        tickContainer.append(tickCanvas);
320
321        tickCtx = tickCanvas[0].getContext('2d');
322      }
323
324      var dimensions = getSliderDimensions();
325
326      // If `dimensions` doesn't have height and width it might be the first attempt so we will refresh dimensions
327      if (dimensions && !dimensions.height && !dimensions.width) {
328        refreshSliderDimensions();
329        dimensions = sliderDimensions;
330      }
331
332      tickCanvas[0].width = dimensions.width;
333      tickCanvas[0].height = dimensions.height;
334
335      var distance;
336      for (var i = 0; i <= numSteps; i++) {
337        var trackTicksStyle = $window.getComputedStyle(tickContainer[0]);
338        tickCtx.fillStyle = trackTicksStyle.color || 'black';
339
340        distance = Math.floor((vertical ? dimensions.height : dimensions.width) * (i / numSteps));
341
342        tickCtx.fillRect(vertical ? 0 : distance - 1,
343          vertical ? distance - 1 : 0,
344          vertical ? dimensions.width : 2,
345          vertical ? 2 : dimensions.height);
346      }
347    }
348
349    function clearTicks() {
350      if(tickCanvas && tickCtx) {
351        var dimensions = getSliderDimensions();
352        tickCtx.clearRect(0, 0, dimensions.width, dimensions.height);
353      }
354    }
355
356    /**
357     * Refreshing Dimensions
358     */
359    var sliderDimensions = {};
360    refreshSliderDimensions();
361    function refreshSliderDimensions() {
362      sliderDimensions = trackContainer[0].getBoundingClientRect();
363    }
364    function getSliderDimensions() {
365      throttledRefreshDimensions();
366      return sliderDimensions;
367    }
368
369    /**
370     * left/right/up/down arrow listener
371     */
372    function keydownListener(ev) {
373      if (isDisabled()) return;
374
375      var changeAmount;
376      if (vertical ? ev.keyCode === $mdConstant.KEY_CODE.DOWN_ARROW : ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) {
377        changeAmount = -step;
378      } else if (vertical ? ev.keyCode === $mdConstant.KEY_CODE.UP_ARROW : ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) {
379        changeAmount = step;
380      }
381      changeAmount = invert ? -changeAmount : changeAmount;
382      if (changeAmount) {
383        if (ev.metaKey || ev.ctrlKey || ev.altKey) {
384          changeAmount *= 4;
385        }
386        ev.preventDefault();
387        ev.stopPropagation();
388        scope.$evalAsync(function() {
389          setModelValue(ngModelCtrl.$viewValue + changeAmount);
390        });
391      }
392    }
393
394    function mouseDownListener() {
395      redrawTicks();
396
397      scope.mouseActive = true;
398      wrapper.removeClass('md-focused');
399
400      $timeout(function() {
401        scope.mouseActive = false;
402      }, 100);
403    }
404
405    function focusListener() {
406      if (scope.mouseActive === false) {
407        wrapper.addClass('md-focused');
408      }
409    }
410
411    function blurListener() {
412      wrapper.removeClass('md-focused');
413      element.removeClass('md-active');
414      clearTicks();
415    }
416
417    /**
418     * ngModel setters and validators
419     */
420    function setModelValue(value) {
421      ngModelCtrl.$setViewValue( minMaxValidator(stepValidator(value)) );
422    }
423    function ngModelRender() {
424      if (isNaN(ngModelCtrl.$viewValue)) {
425        ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
426      }
427
428      ngModelCtrl.$viewValue = minMaxValidator(ngModelCtrl.$viewValue);
429
430      var percent = valueToPercent(ngModelCtrl.$viewValue);
431      scope.modelValue = ngModelCtrl.$viewValue;
432      element.attr('aria-valuenow', ngModelCtrl.$viewValue);
433      setSliderPercent(percent);
434      thumbText.text( ngModelCtrl.$viewValue );
435    }
436
437    function minMaxValidator(value, minValue, maxValue) {
438      if (angular.isNumber(value)) {
439        minValue = angular.isNumber(minValue) ? minValue : min;
440        maxValue = angular.isNumber(maxValue) ? maxValue : max;
441
442        return Math.max(minValue, Math.min(maxValue, value));
443      }
444    }
445
446    function stepValidator(value) {
447      if (angular.isNumber(value)) {
448        var formattedValue = (Math.round((value - min) / step) * step + min);
449        formattedValue = (Math.round(formattedValue * Math.pow(10, round)) / Math.pow(10, round));
450
451        if (containerCtrl && containerCtrl.fitInputWidthToTextLength){
452          $mdUtil.debounce(function () {
453            containerCtrl.fitInputWidthToTextLength(formattedValue.toString().length);
454          }, 100)();
455        }
456
457        return formattedValue;
458      }
459    }
460
461    /**
462     * @param percent 0-1
463     */
464    function setSliderPercent(percent) {
465
466      percent = clamp(percent);
467
468      var thumbPosition = (percent * 100) + '%';
469      var activeTrackPercent = invert ? (1 - percent) * 100 + '%' : thumbPosition;
470
471      if (vertical) {
472        thumbContainer.css('bottom', thumbPosition);
473      }
474      else {
475        $mdUtil.bidiProperty(thumbContainer, 'left', 'right', thumbPosition);
476      }
477
478      
479      activeTrack.css(vertical ? 'height' : 'width', activeTrackPercent);
480
481      element.toggleClass((invert ? 'md-max' : 'md-min'), percent === 0);
482      element.toggleClass((invert ? 'md-min' : 'md-max'), percent === 1);
483    }
484
485    /**
486     * Slide listeners
487     */
488    var isDragging = false;
489
490    function onPressDown(ev) {
491      if (isDisabled()) return;
492
493      element.addClass('md-active');
494      element[0].focus();
495      refreshSliderDimensions();
496
497      var exactVal = percentToValue( positionToPercent( vertical ? ev.pointer.y : ev.pointer.x ));
498      var closestVal = minMaxValidator( stepValidator(exactVal) );
499      scope.$apply(function() {
500        setModelValue( closestVal );
501        setSliderPercent( valueToPercent(closestVal));
502      });
503    }
504    function onPressUp(ev) {
505      if (isDisabled()) return;
506
507      element.removeClass('md-dragging');
508
509      var exactVal = percentToValue( positionToPercent( vertical ? ev.pointer.y : ev.pointer.x ));
510      var closestVal = minMaxValidator( stepValidator(exactVal) );
511      scope.$apply(function() {
512        setModelValue(closestVal);
513        ngModelRender();
514      });
515    }
516    function onDragStart(ev) {
517      if (isDisabled()) return;
518      isDragging = true;
519
520      ev.stopPropagation();
521
522      element.addClass('md-dragging');
523      setSliderFromEvent(ev);
524    }
525    function onDrag(ev) {
526      if (!isDragging) return;
527      ev.stopPropagation();
528      setSliderFromEvent(ev);
529    }
530    function onDragEnd(ev) {
531      if (!isDragging) return;
532      ev.stopPropagation();
533      isDragging = false;
534    }
535
536    function setSliderFromEvent(ev) {
537      // While panning discrete, update only the
538      // visual positioning but not the model value.
539      if ( discrete ) adjustThumbPosition( vertical ? ev.pointer.y : ev.pointer.x );
540      else            doSlide( vertical ? ev.pointer.y : ev.pointer.x );
541    }
542
543    /**
544     * Slide the UI by changing the model value
545     * @param x
546     */
547    function doSlide( x ) {
548      scope.$evalAsync( function() {
549        setModelValue( percentToValue( positionToPercent(x) ));
550      });
551    }
552
553    /**
554     * Slide the UI without changing the model (while dragging/panning)
555     * @param x
556     */
557    function adjustThumbPosition( x ) {
558      var exactVal = percentToValue( positionToPercent( x ));
559      var closestVal = minMaxValidator( stepValidator(exactVal) );
560      setSliderPercent( positionToPercent(x) );
561      thumbText.text( closestVal );
562    }
563
564    /**
565    * Clamps the value to be between 0 and 1.
566    * @param {number} value The value to clamp.
567    * @returns {number}
568    */
569    function clamp(value) {
570      return Math.max(0, Math.min(value || 0, 1));
571    }
572
573    /**
574     * Convert position on slider to percentage value of offset from beginning...
575     * @param position
576     * @returns {number}
577     */
578    function positionToPercent( position ) {
579      var offset = vertical ? sliderDimensions.top : sliderDimensions.left;
580      var size = vertical ? sliderDimensions.height : sliderDimensions.width;
581      var calc = (position - offset) / size;
582
583      if (!vertical && $mdUtil.bidi() === 'rtl') {
584        calc = 1 - calc;
585      }
586
587      return Math.max(0, Math.min(1, vertical ? 1 - calc : calc));
588    }
589
590    /**
591     * Convert percentage offset on slide to equivalent model value
592     * @param percent
593     * @returns {*}
594     */
595    function percentToValue( percent ) {
596      var adjustedPercent = invert ? (1 - percent) : percent;
597      return (min + adjustedPercent * (max - min));
598    }
599
600    function valueToPercent( val ) {
601      var percent = (val - min) / (max - min);
602      return invert ? (1 - percent) : percent;
603    }
604  }
605}
606
607})(window, window.angular);