217 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
			
		
		
	
	
			217 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
| /*
 | |
|  * angular-elastic v2.4.0
 | |
|  * (c) 2014 Monospaced http://monospaced.com
 | |
|  * License: MIT
 | |
|  */
 | |
| 
 | |
| angular.module('monospaced.elastic', [])
 | |
| 
 | |
|   .constant('msdElasticConfig', {
 | |
|     append: ''
 | |
|   })
 | |
| 
 | |
|   .directive('msdElastic', [
 | |
|     '$timeout', '$window', 'msdElasticConfig',
 | |
|     function($timeout, $window, config) {
 | |
|       'use strict';
 | |
| 
 | |
|       return {
 | |
|         require: 'ngModel',
 | |
|         restrict: 'A, C',
 | |
|         link: function(scope, element, attrs, ngModel) {
 | |
| 
 | |
|           // cache a reference to the DOM element
 | |
|           var ta = element[0],
 | |
|               $ta = element;
 | |
| 
 | |
|           // ensure the element is a textarea, and browser is capable
 | |
|           if (ta.nodeName !== 'TEXTAREA' || !$window.getComputedStyle) {
 | |
|             return;
 | |
|           }
 | |
| 
 | |
|           // set these properties before measuring dimensions
 | |
|           $ta.css({
 | |
|             'overflow': 'hidden',
 | |
|             'overflow-y': 'hidden',
 | |
|             'word-wrap': 'break-word'
 | |
|           });
 | |
| 
 | |
|           // force text reflow
 | |
|           var text = ta.value;
 | |
|           ta.value = '';
 | |
|           ta.value = text;
 | |
| 
 | |
|           var append = attrs.msdElastic ? attrs.msdElastic.replace(/\\n/g, '\n') : config.append,
 | |
|               $win = angular.element($window),
 | |
|               mirrorInitStyle = 'position: absolute; top: -999px; right: auto; bottom: auto;' +
 | |
|                                 'left: 0; overflow: hidden; -webkit-box-sizing: content-box;' +
 | |
|                                 '-moz-box-sizing: content-box; box-sizing: content-box;' +
 | |
|                                 'min-height: 0 !important; height: 0 !important; padding: 0;' +
 | |
|                                 'word-wrap: break-word; border: 0;',
 | |
|               $mirror = angular.element('<textarea tabindex="-1" ' +
 | |
|                                         'style="' + mirrorInitStyle + '"/>').data('elastic', true),
 | |
|               mirror = $mirror[0],
 | |
|               taStyle = getComputedStyle(ta),
 | |
|               resize = taStyle.getPropertyValue('resize'),
 | |
|               borderBox = taStyle.getPropertyValue('box-sizing') === 'border-box' ||
 | |
|                           taStyle.getPropertyValue('-moz-box-sizing') === 'border-box' ||
 | |
|                           taStyle.getPropertyValue('-webkit-box-sizing') === 'border-box',
 | |
|               boxOuter = !borderBox ? {width: 0, height: 0} : {
 | |
|                             width:  parseInt(taStyle.getPropertyValue('border-right-width'), 10) +
 | |
|                                     parseInt(taStyle.getPropertyValue('padding-right'), 10) +
 | |
|                                     parseInt(taStyle.getPropertyValue('padding-left'), 10) +
 | |
|                                     parseInt(taStyle.getPropertyValue('border-left-width'), 10),
 | |
|                             height: parseInt(taStyle.getPropertyValue('border-top-width'), 10) +
 | |
|                                     parseInt(taStyle.getPropertyValue('padding-top'), 10) +
 | |
|                                     parseInt(taStyle.getPropertyValue('padding-bottom'), 10) +
 | |
|                                     parseInt(taStyle.getPropertyValue('border-bottom-width'), 10)
 | |
|                           },
 | |
|               minHeightValue = parseInt(taStyle.getPropertyValue('min-height'), 10),
 | |
|               heightValue = parseInt(taStyle.getPropertyValue('height'), 10),
 | |
|               minHeight = Math.max(minHeightValue, heightValue) - boxOuter.height,
 | |
|               maxHeight = parseInt(taStyle.getPropertyValue('max-height'), 10),
 | |
|               mirrored,
 | |
|               active,
 | |
|               copyStyle = ['font-family',
 | |
|                            'font-size',
 | |
|                            'font-weight',
 | |
|                            'font-style',
 | |
|                            'letter-spacing',
 | |
|                            'line-height',
 | |
|                            'text-transform',
 | |
|                            'word-spacing',
 | |
|                            'text-indent'];
 | |
| 
 | |
|           // exit if elastic already applied (or is the mirror element)
 | |
|           if ($ta.data('elastic')) {
 | |
|             return;
 | |
|           }
 | |
| 
 | |
|           // Opera returns max-height of -1 if not set
 | |
|           maxHeight = maxHeight && maxHeight > 0 ? maxHeight : 9e4;
 | |
| 
 | |
|           // append mirror to the DOM
 | |
|           if (mirror.parentNode !== document.body) {
 | |
|             angular.element(document.body).append(mirror);
 | |
|           }
 | |
| 
 | |
|           // set resize and apply elastic
 | |
|           $ta.css({
 | |
|             'resize': (resize === 'none' || resize === 'vertical') ? 'none' : 'horizontal'
 | |
|           }).data('elastic', true);
 | |
| 
 | |
|           /*
 | |
|            * methods
 | |
|            */
 | |
| 
 | |
|           function initMirror() {
 | |
|             var mirrorStyle = mirrorInitStyle;
 | |
| 
 | |
|             mirrored = ta;
 | |
|             // copy the essential styles from the textarea to the mirror
 | |
|             taStyle = getComputedStyle(ta);
 | |
|             angular.forEach(copyStyle, function(val) {
 | |
|               mirrorStyle += val + ':' + taStyle.getPropertyValue(val) + ';';
 | |
|             });
 | |
|             mirror.setAttribute('style', mirrorStyle);
 | |
|           }
 | |
| 
 | |
|           function adjust() {
 | |
|             var taHeight,
 | |
|                 taComputedStyleWidth,
 | |
|                 mirrorHeight,
 | |
|                 width,
 | |
|                 overflow;
 | |
| 
 | |
|             if (mirrored !== ta) {
 | |
|               initMirror();
 | |
|             }
 | |
| 
 | |
|             // active flag prevents actions in function from calling adjust again
 | |
|             if (!active) {
 | |
|               active = true;
 | |
| 
 | |
|               mirror.value = ta.value + append; // optional whitespace to improve animation
 | |
|               mirror.style.overflowY = ta.style.overflowY;
 | |
| 
 | |
|               taHeight = ta.style.height === '' ? 'auto' : parseInt(ta.style.height, 10);
 | |
| 
 | |
|               taComputedStyleWidth = getComputedStyle(ta).getPropertyValue('width');
 | |
| 
 | |
|               // ensure getComputedStyle has returned a readable 'used value' pixel width
 | |
|               if (taComputedStyleWidth.substr(taComputedStyleWidth.length - 2, 2) === 'px') {
 | |
|                 // update mirror width in case the textarea width has changed
 | |
|                 width = parseInt(taComputedStyleWidth, 10) - boxOuter.width;
 | |
|                 mirror.style.width = width + 'px';
 | |
|               }
 | |
| 
 | |
|               mirrorHeight = mirror.scrollHeight;
 | |
| 
 | |
|               if (mirrorHeight > maxHeight) {
 | |
|                 mirrorHeight = maxHeight;
 | |
|                 overflow = 'scroll';
 | |
|               } else if (mirrorHeight < minHeight) {
 | |
|                 mirrorHeight = minHeight;
 | |
|               }
 | |
|               mirrorHeight += boxOuter.height;
 | |
| 
 | |
|               ta.style.overflowY = overflow || 'hidden';
 | |
| 
 | |
|               if (taHeight !== mirrorHeight) {
 | |
|                 ta.style.height = mirrorHeight + 'px';
 | |
|                 scope.$emit('elastic:resize', $ta);
 | |
|               }
 | |
| 
 | |
|               // small delay to prevent an infinite loop
 | |
|               $timeout(function() {
 | |
|                 active = false;
 | |
|               }, 1);
 | |
| 
 | |
|             }
 | |
|           }
 | |
| 
 | |
|           function forceAdjust() {
 | |
|             active = false;
 | |
|             adjust();
 | |
|           }
 | |
| 
 | |
|           /*
 | |
|            * initialise
 | |
|            */
 | |
| 
 | |
|           // listen
 | |
|           if ('onpropertychange' in ta && 'oninput' in ta) {
 | |
|             // IE9
 | |
|             ta['oninput'] = ta.onkeyup = adjust;
 | |
|           } else {
 | |
|             ta['oninput'] = adjust;
 | |
|           }
 | |
| 
 | |
|           $win.bind('resize', forceAdjust);
 | |
| 
 | |
|           scope.$watch(function() {
 | |
|             return ngModel.$modelValue;
 | |
|           }, function(newValue) {
 | |
|             forceAdjust();
 | |
|           });
 | |
| 
 | |
|           scope.$on('elastic:adjust', function() {
 | |
|             initMirror();
 | |
|             forceAdjust();
 | |
|           });
 | |
| 
 | |
|           $timeout(adjust);
 | |
| 
 | |
|           /*
 | |
|            * destroy
 | |
|            */
 | |
| 
 | |
|           scope.$on('$destroy', function() {
 | |
|             $mirror.remove();
 | |
|             $win.unbind('resize', forceAdjust);
 | |
|           });
 | |
|         }
 | |
|       };
 | |
|     }
 | |
|   ]);
 |