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