source: trip-planner-front/node_modules/angular-material/modules/js/slider/slider.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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