1/*! 2 * angular-ui-mask 3 * https://github.com/angular-ui/ui-mask 4 * Version: 1.8.7 - 2016-07-26T15:59:07.992Z 5 * License: MIT 6 */ 7 8 9(function () { 10'use strict'; 11/* 12 Attaches input mask onto input element 13 */ 14angular.module('ui.mask', []) 15 .value('uiMaskConfig', { 16 maskDefinitions: { 17 '9': /\d/, 18 'A': /[a-zA-Z]/, 19 '*': /[a-zA-Z0-9]/ 20 }, 21 clearOnBlur: true, 22 clearOnBlurPlaceholder: false, 23 escChar: '\\', 24 eventsToHandle: ['input', 'keyup', 'click', 'focus'], 25 addDefaultPlaceholder: true, 26 allowInvalidValue: false 27 }) 28 .provider('uiMask.Config', function() { 29 var options = {}; 30 31 this.maskDefinitions = function(maskDefinitions) { 32 return options.maskDefinitions = maskDefinitions; 33 }; 34 this.clearOnBlur = function(clearOnBlur) { 35 return options.clearOnBlur = clearOnBlur; 36 }; 37 this.clearOnBlurPlaceholder = function(clearOnBlurPlaceholder) { 38 return options.clearOnBlurPlaceholder = clearOnBlurPlaceholder; 39 }; 40 this.eventsToHandle = function(eventsToHandle) { 41 return options.eventsToHandle = eventsToHandle; 42 }; 43 this.addDefaultPlaceholder = function(addDefaultPlaceholder) { 44 return options.addDefaultPlaceholder = addDefaultPlaceholder; 45 }; 46 this.allowInvalidValue = function(allowInvalidValue) { 47 return options.allowInvalidValue = allowInvalidValue; 48 }; 49 this.$get = ['uiMaskConfig', function(uiMaskConfig) { 50 var tempOptions = uiMaskConfig; 51 for(var prop in options) { 52 if (angular.isObject(options[prop]) && !angular.isArray(options[prop])) { 53 angular.extend(tempOptions[prop], options[prop]); 54 } else { 55 tempOptions[prop] = options[prop]; 56 } 57 } 58 59 return tempOptions; 60 }]; 61 }) 62 .directive('uiMask', ['uiMask.Config', function(maskConfig) { 63 function isFocused (elem) { 64 return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); 65 } 66 67 return { 68 priority: 100, 69 require: 'ngModel', 70 restrict: 'A', 71 compile: function uiMaskCompilingFunction() { 72 var options = angular.copy(maskConfig); 73 74 return function uiMaskLinkingFunction(scope, iElement, iAttrs, controller) { 75 var maskProcessed = false, eventsBound = false, 76 maskCaretMap, maskPatterns, maskPlaceholder, maskComponents, 77 // Minimum required length of the value to be considered valid 78 minRequiredLength, 79 value, valueMasked, isValid, 80 // Vars for initializing/uninitializing 81 originalPlaceholder = iAttrs.placeholder, 82 originalMaxlength = iAttrs.maxlength, 83 // Vars used exclusively in eventHandler() 84 oldValue, oldValueUnmasked, oldCaretPosition, oldSelectionLength, 85 // Used for communicating if a backspace operation should be allowed between 86 // keydownHandler and eventHandler 87 preventBackspace; 88 89 var originalIsEmpty = controller.$isEmpty; 90 controller.$isEmpty = function(value) { 91 if (maskProcessed) { 92 return originalIsEmpty(unmaskValue(value || '')); 93 } else { 94 return originalIsEmpty(value); 95 } 96 }; 97 98 function initialize(maskAttr) { 99 if (!angular.isDefined(maskAttr)) { 100 return uninitialize(); 101 } 102 processRawMask(maskAttr); 103 if (!maskProcessed) { 104 return uninitialize(); 105 } 106 initializeElement(); 107 bindEventListeners(); 108 return true; 109 } 110 111 function initPlaceholder(placeholderAttr) { 112 if ( ! placeholderAttr) { 113 return; 114 } 115 116 maskPlaceholder = placeholderAttr; 117 118 // If the mask is processed, then we need to update the value 119 // but don't set the value if there is nothing entered into the element 120 // and there is a placeholder attribute on the element because that 121 // will only set the value as the blank maskPlaceholder 122 // and override the placeholder on the element 123 if (maskProcessed && !(iElement.val().length === 0 && angular.isDefined(iAttrs.placeholder))) { 124 iElement.val(maskValue(unmaskValue(iElement.val()))); 125 } 126 } 127 128 function initPlaceholderChar() { 129 return initialize(iAttrs.uiMask); 130 } 131 132 var modelViewValue = false;
133 iAttrs.$observe('modelViewValue', function(val) { 134 if (val === 'true') { 135 modelViewValue = true; 136 } 137 }); 138 139 iAttrs.$observe('allowInvalidValue', function(val) { 140 linkOptions.allowInvalidValue = val === '' 141 ? true 142 : !!val; 143 formatter(controller.$modelValue); 144 }); 145 146 function formatter(fromModelValue) { 147 if (!maskProcessed) { 148 return fromModelValue; 149 } 150 value = unmaskValue(fromModelValue || ''); 151 isValid = validateValue(value); 152 controller.$setValidity('mask', isValid); 153 154 if (!value.length) return undefined; 155 if (isValid || linkOptions.allowInvalidValue) { 156 return maskValue(value); 157 } else { 158 return undefined; 159 } 160 } 161 162 function parser(fromViewValue) { 163 if (!maskProcessed) { 164 return fromViewValue; 165 } 166 value = unmaskValue(fromViewValue || ''); 167 isValid = validateValue(value); 168 // We have to set viewValue manually as the reformatting of the input 169 // value performed by eventHandler() doesn't happen until after 170 // this parser is called, which causes what the user sees in the input 171 // to be out-of-sync with what the controller's $viewValue is set to. 172 controller.$viewValue = value.length ? maskValue(value) : ''; 173 controller.$setValidity('mask', isValid); 174 175 if (isValid || linkOptions.allowInvalidValue) { 176 return modelViewValue ? controller.$viewValue : value; 177 } 178 } 179 180 var linkOptions = {}; 181 182 if (iAttrs.uiOptions) { 183 linkOptions = scope.$eval('[' + iAttrs.uiOptions + ']'); 184 if (angular.isObject(linkOptions[0])) { 185 // we can't use angular.copy nor angular.extend, they lack the power to do a deep merge 186 linkOptions = (function(original, current) { 187 for (var i in original) { 188 if (Object.prototype.hasOwnProperty.call(original, i)) { 189 if (current[i] === undefined) { 190 current[i] = angular.copy(original[i]); 191 } else { 192 if (angular.isObject(current[i]) && !angular.isArray(current[i])) { 193 current[i] = angular.extend({}, original[i], current[i]); 194 } 195 } 196 } 197 } 198 return current; 199 })(options, linkOptions[0]); 200 } else { 201 linkOptions = options; //gotta be a better way to do this.. 202 } 203 } else { 204 linkOptions = options; 205 } 206 207 iAttrs.$observe('uiMask', initialize); 208 if (angular.isDefined(iAttrs.uiMaskPlaceholder)) { 209 iAttrs.$observe('uiMaskPlaceholder', initPlaceholder); 210 } 211 else { 212 iAttrs.$observe('placeholder', initPlaceholder); 213 } 214 if (angular.isDefined(iAttrs.uiMaskPlaceholderChar)) { 215 iAttrs.$observe('uiMaskPlaceholderChar', initPlaceholderChar); 216 } 217 218 controller.$formatters.unshift(formatter); 219 controller.$parsers.unshift(parser); 220 221 function uninitialize() { 222 maskProcessed = false;
223 unbindEventListeners(); 224 225 if (angular.isDefined(originalPlaceholder)) { 226 iElement.attr('placeholder', originalPlaceholder); 227 } else { 228 iElement.removeAttr('placeholder'); 229 } 230 231 if (angular.isDefined(originalMaxlength)) { 232 iElement.attr('maxlength', originalMaxlength); 233 } else { 234 iElement.removeAttr('maxlength'); 235 } 236 237 iElement.val(controller.$modelValue); 238 controller.$viewValue = controller.$modelValue; 239 return false; 240 } 241 242 function initializeElement() { 243 value = oldValueUnmasked = unmaskValue(controller.$modelValue || ''); 244 valueMasked = oldValue = maskValue(value); 245 isValid = validateValue(value); 246 if (iAttrs.maxlength) { // Double maxlength to allow pasting new val at end of mask 247 iElement.attr('maxlength', maskCaretMap[maskCaretMap.length - 1] * 2); 248 } 249 if ( ! originalPlaceholder && linkOptions.addDefaultPlaceholder) { 250 iElement.attr('placeholder', maskPlaceholder); 251 } 252 var viewValue = controller.$modelValue; 253 var idx = controller.$formatters.length; 254 while(idx--) { 255 viewValue = controller.$formatters[idx](viewValue); 256 } 257 controller.$viewValue = viewValue || ''; 258 controller.$render(); 259 // Not using $setViewValue so we don't clobber the model value and dirty the form 260 // without any kind of user interaction. 261 } 262 263 function bindEventListeners() { 264 if (eventsBound) { 265 return; 266 } 267 iElement.bind('blur', blurHandler); 268 iElement.bind('mousedown mouseup', mouseDownUpHandler); 269 iElement.bind('keydown', keydownHandler); 270 iElement.bind(linkOptions.eventsToHandle.join(' '), eventHandler); 271 eventsBound = true; 272 } 273 274 function unbindEventListeners() { 275 if (!eventsBound) { 276 return; 277 } 278 iElement.unbind('blur', blurHandler); 279 iElement.unbind('mousedown', mouseDownUpHandler); 280 iElement.unbind('mouseup', mouseDownUpHandler); 281 iElement.unbind('keydown', keydownHandler); 282 iElement.unbind('input', eventHandler); 283 iElement.unbind('keyup', eventHandler); 284 iElement.unbind('click', eventHandler); 285 iElement.unbind('focus', eventHandler); 286 eventsBound = false; 287 } 288 289 function validateValue(value) { 290 // Zero-length value validity is ngRequired's determination 291 return value.length ? value.length >= minRequiredLength : true; 292 } 293 294 function unmaskValue(value) { 295 var valueUnmasked = '', 296 input = iElement[0], 297 maskPatternsCopy = maskPatterns.slice(), 298 selectionStart = oldCaretPosition, 299 selectionEnd = selectionStart + getSelectionLength(input), 300 valueOffset, valueDelta, tempValue = ''; 301 // Preprocess by stripping mask components from value 302 value = value.toString(); 303 valueOffset = 0; 304 valueDelta = value.length - maskPlaceholder.length; 305 angular.forEach(maskComponents, function(component) { 306 var position = component.position; 307 //Only try and replace the component if the component position is not within the selected range 308 //If component was in selected range then it was removed with the user input so no need to try and remove that component 309 if (!(position >= selectionStart && position < selectionEnd)) { 310 if (position >= selectionStart) { 311 position += valueDelta; 312 } 313 if (value.substring(position, position + component.value.length) === component.value) { 314 tempValue += value.slice(valueOffset, position);// + value.slice(position + component.value.length); 315 valueOffset = position + component.value.length; 316 } 317 } 318 }); 319 value = tempValue + value.slice(valueOffset); 320 angular.forEach(value.split(''), function(chr) { 321 if (maskPatternsCopy.length && maskPatternsCopy[0].test(chr)) { 322 valueUnmasked += chr; 323 maskPatternsCopy.shift(); 324 } 325 }); 326 327 return valueUnmasked; 328 } 329 330 function maskValue(unmaskedValue) { 331 var valueMasked = '', 332 maskCaretMapCopy = maskCaretMap.slice(); 333 334 angular.forEach(maskPlaceholder.split(''), function(chr, i) { 335 if (unmaskedValue.length && i === maskCaretMapCopy[0]) { 336 valueMasked += unmaskedValue.charAt(0) || '_'; 337 unmaskedValue = unmaskedValue.substr(1); 338 maskCaretMapCopy.shift(); 339 } 340 else { 341 valueMasked += chr; 342 } 343 }); 344 return valueMasked; 345 } 346 347 function getPlaceholderChar(i) { 348 var placeholder = angular.isDefined(iAttrs.uiMaskPlaceholder) ? iAttrs.uiMaskPlaceholder : iAttrs.placeholder, 349 defaultPlaceholderChar; 350 351 if (angular.isDefined(placeholder) && placeholder[i]) { 352 return placeholder[i]; 353 } else { 354 defaultPlaceholderChar = angular.isDefined(iAttrs.uiMaskPlaceholderChar) && iAttrs.uiMaskPlaceholderChar ? iAttrs.uiMaskPlaceholderChar : '_'; 355 return (defaultPlaceholderChar.toLowerCase() === 'space') ? ' ' : defaultPlaceholderChar[0]; 356 } 357 } 358 359 // Generate array of mask components that will be stripped from a masked value 360 // before processing to prevent mask components from being added to the unmasked value. 361 // E.g., a mask pattern of '+7 9999' won't have the 7 bleed into the unmasked value. 362 function getMaskComponents() { 363 var maskPlaceholderChars = maskPlaceholder.split(''), 364 maskPlaceholderCopy, components; 365 366 //maskCaretMap can have bad values if the input has the ui-mask attribute implemented as an obversable property, e.g. the demo page 367 if (maskCaretMap && !isNaN(maskCaretMap[0])) { 368 //Instead of trying to manipulate the RegEx based on the placeholder characters 369 //we can simply replace the placeholder characters based on the already built 370 //maskCaretMap to underscores and leave the original working RegEx to get the proper 371 //mask components 372 angular.forEach(maskCaretMap, function(value) { 373 maskPlaceholderChars[value] = '_'; 374 }); 375 } 376 maskPlaceholderCopy = maskPlaceholderChars.join(''); 377 components = maskPlaceholderCopy.replace(/[_]+/g, '_').split('_'); 378 components = components.filter(function(s) { 379 return s !== ''; 380 }); 381 382 // need a string search offset in cases where the mask contains multiple identical components 383 // E.g., a mask of 99.99.99-999.99 384 var offset = 0; 385 return components.map(function(c) { 386 var componentPosition = maskPlaceholderCopy.indexOf(c, offset); 387 offset = componentPosition + 1; 388 return { 389 value: c, 390 position: componentPosition 391 }; 392 }); 393 } 394 395 function processRawMask(mask) { 396 var characterCount = 0; 397 398 maskCaretMap = []; 399 maskPatterns = []; 400 maskPlaceholder = ''; 401 402 if (angular.isString(mask)) { 403 minRequiredLength = 0; 404 405 var isOptional = false, 406 numberOfOptionalCharacters = 0, 407 splitMask = mask.split(''); 408 409 var inEscape = false;
410 angular.forEach(splitMask, function(chr, i) { 411 if (inEscape) { 412 inEscape = false; 413 maskPlaceholder += chr; 414 characterCount++; 415 } 416 else if (linkOptions.escChar === chr) { 417 inEscape = true; 418 } 419 else if (linkOptions.maskDefinitions[chr]) { 420 maskCaretMap.push(characterCount); 421 422 maskPlaceholder += getPlaceholderChar(i - numberOfOptionalCharacters); 423 maskPatterns.push(linkOptions.maskDefinitions[chr]); 424 425 characterCount++; 426 if (!isOptional) { 427 minRequiredLength++; 428 } 429 430 isOptional = false;
431 } 432 else if (chr === '?') { 433 isOptional = true; 434 numberOfOptionalCharacters++; 435 } 436 else { 437 maskPlaceholder += chr; 438 characterCount++; 439 } 440 }); 441 } 442 // Caret position immediately following last position is valid. 443 maskCaretMap.push(maskCaretMap.slice().pop() + 1); 444 445 maskComponents = getMaskComponents(); 446 maskProcessed = maskCaretMap.length > 1 ? true : false; 447 } 448 449 var prevValue = iElement.val(); 450 function blurHandler() { 451 if (linkOptions.clearOnBlur || ((linkOptions.clearOnBlurPlaceholder) && (value.length === 0) && iAttrs.placeholder)) { 452 oldCaretPosition = 0; 453 oldSelectionLength = 0; 454 if (!isValid || value.length === 0) { 455 valueMasked = ''; 456 iElement.val(''); 457 scope.$apply(function() { 458 //only $setViewValue when not $pristine to avoid changing $pristine state. 459 if (!controller.$pristine) { 460 controller.$setViewValue(''); 461 } 462 }); 463 } 464 } 465 //Check for different value and trigger change. 466 //Check for different value and trigger change. 467 if (value !== prevValue) { 468 // #157 Fix the bug from the trigger when backspacing exactly on the first letter (emptying the field) 469 // and then blurring out. 470 // Angular uses html element and calls setViewValue(element.value.trim()), setting it to the trimmed mask 471 // when it should be empty 472 var currentVal = iElement.val(); 473 var isTemporarilyEmpty = value === '' && currentVal && angular.isDefined(iAttrs.uiMaskPlaceholderChar) && iAttrs.uiMaskPlaceholderChar === 'space'; 474 if(isTemporarilyEmpty) { 475 iElement.val(''); 476 } 477 triggerChangeEvent(iElement[0]); 478 if(isTemporarilyEmpty) { 479 iElement.val(currentVal); 480 } 481 } 482 prevValue = value; 483 } 484 485 function triggerChangeEvent(element) { 486 var change; 487 if (angular.isFunction(window.Event) && !element.fireEvent) { 488 // modern browsers and Edge 489 try { 490 change = new Event('change', { 491 view: window, 492 bubbles: true, 493 cancelable: false 494 }); 495 } catch (ex) { 496 //this is for certain mobile browsers that have the Event object 497 //but don't support the Event constructor #168 498 change = document.createEvent('HTMLEvents'); 499 change.initEvent('change', false, true); 500 } finally { 501 element.dispatchEvent(change); 502 } 503 } else if ('createEvent' in document) { 504 // older browsers 505 change = document.createEvent('HTMLEvents'); 506 change.initEvent('change', false, true); 507 element.dispatchEvent(change); 508 } 509 else if (element.fireEvent) { 510 // IE <= 11 511 element.fireEvent('onchange'); 512 } 513 } 514 515 function mouseDownUpHandler(e) { 516 if (e.type === 'mousedown') { 517 iElement.bind('mouseout', mouseoutHandler); 518 } else { 519 iElement.unbind('mouseout', mouseoutHandler); 520 } 521 } 522 523 iElement.bind('mousedown mouseup', mouseDownUpHandler); 524 525 function mouseoutHandler() { 526 /*jshint validthis: true */ 527 oldSelectionLength = getSelectionLength(this); 528 iElement.unbind('mouseout', mouseoutHandler); 529 } 530 531 function keydownHandler(e) { 532 /*jshint validthis: true */ 533 var isKeyBackspace = e.which === 8, 534 caretPos = getCaretPosition(this) - 1 || 0, //value in keydown is pre change so bump caret position back to simulate post change 535 isCtrlZ = e.which === 90 && e.ctrlKey; //ctrl+z pressed 536 537 if (isKeyBackspace) { 538 while(caretPos >= 0) { 539 if (isValidCaretPosition(caretPos)) { 540 //re-adjust the caret position. 541 //Increment to account for the initial decrement to simulate post change caret position 542 setCaretPosition(this, caretPos + 1); 543 break; 544 } 545 caretPos--; 546 } 547 preventBackspace = caretPos === -1; 548 } 549 550 if (isCtrlZ) { 551 // prevent IE bug - value should be returned to initial state 552 iElement.val(''); 553 e.preventDefault(); 554 } 555 } 556 557 function eventHandler(e) { 558 /*jshint validthis: true */ 559 e = e || {}; 560 // Allows more efficient minification 561 var eventWhich = e.which, 562 eventType = e.type; 563 564 // Prevent shift and ctrl from mucking with old values 565 if (eventWhich === 16 || eventWhich === 91) { 566 return; 567 } 568 569 var val = iElement.val(), 570 valOld = oldValue, 571 valMasked, 572 valAltered = false, 573 valUnmasked = unmaskValue(val), 574 valUnmaskedOld = oldValueUnmasked, 575 caretPos = getCaretPosition(this) || 0, 576 caretPosOld = oldCaretPosition || 0, 577 caretPosDelta = caretPos - caretPosOld, 578 caretPosMin = maskCaretMap[0], 579 caretPosMax = maskCaretMap[valUnmasked.length] || maskCaretMap.slice().shift(), 580 selectionLenOld = oldSelectionLength || 0, 581 isSelected = getSelectionLength(this) > 0, 582 wasSelected = selectionLenOld > 0, 583 // Case: Typing a character to overwrite a selection 584 isAddition = (val.length > valOld.length) || (selectionLenOld && val.length > valOld.length - selectionLenOld), 585 // Case: Delete and backspace behave identically on a selection 586 isDeletion = (val.length < valOld.length) || (selectionLenOld && val.length === valOld.length - selectionLenOld), 587 isSelection = (eventWhich >= 37 && eventWhich <= 40) && e.shiftKey, // Arrow key codes 588 589 isKeyLeftArrow = eventWhich === 37, 590 // Necessary due to "input" event not providing a key code 591 isKeyBackspace = eventWhich === 8 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === -1)), 592 isKeyDelete = eventWhich === 46 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === 0) && !wasSelected), 593 // Handles cases where caret is moved and placed in front of invalid maskCaretMap position. Logic below 594 // ensures that, on click or leftward caret placement, caret is moved leftward until directly right of 595 // non-mask character. Also applied to click since users are (arguably) more likely to backspace 596 // a character when clicking within a filled input. 597 caretBumpBack = (isKeyLeftArrow || isKeyBackspace || eventType === 'click') && caretPos > caretPosMin; 598 599 oldSelectionLength = getSelectionLength(this); 600 601 // These events don't require any action 602 if (isSelection || (isSelected && (eventType === 'click' || eventType === 'keyup' || eventType === 'focus'))) { 603 return; 604 } 605 606 if (isKeyBackspace && preventBackspace) { 607 iElement.val(maskPlaceholder); 608 // This shouldn't be needed but for some reason after aggressive backspacing the controller $viewValue is incorrect. 609 // This keeps the $viewValue updated and correct. 610 scope.$apply(function () { 611 controller.$setViewValue(''); // $setViewValue should be run in angular context, otherwise the changes will be invisible to angular and user code. 612 }); 613 setCaretPosition(this, caretPosOld); 614 return; 615 } 616 617 // Value Handling 618 // ============== 619 620 // User attempted to delete but raw value was unaffected--correct this grievous offense 621 if ((eventType === 'input') && isDeletion && !wasSelected && valUnmasked === valUnmaskedOld) { 622 while (isKeyBackspace && caretPos > caretPosMin && !isValidCaretPosition(caretPos)) { 623 caretPos--; 624 } 625 while (isKeyDelete && caretPos < caretPosMax && maskCaretMap.indexOf(caretPos) === -1) { 626 caretPos++; 627 } 628 var charIndex = maskCaretMap.indexOf(caretPos); 629 // Strip out non-mask character that user would have deleted if mask hadn't been in the way. 630 valUnmasked = valUnmasked.substring(0, charIndex) + valUnmasked.substring(charIndex + 1); 631 632 // If value has not changed, don't want to call $setViewValue, may be caused by IE raising input event due to placeholder 633 if (valUnmasked !== valUnmaskedOld) 634 valAltered = true; 635 } 636 637 // Update values 638 valMasked = maskValue(valUnmasked); 639 640 oldValue = valMasked; 641 oldValueUnmasked = valUnmasked; 642 643 //additional check to fix the problem where the viewValue is out of sync with the value of the element. 644 //better fix for commit 2a83b5fb8312e71d220a497545f999fc82503bd9 (I think) 645 if (!valAltered && val.length > valMasked.length) 646 valAltered = true; 647 648 iElement.val(valMasked); 649
650 //we need this check. What could happen if you don't have it is that you'll set the model value without the user 651 //actually doing anything. Meaning, things like pristine and touched will be set. 652 if (valAltered) { 653 scope.$apply(function () { 654 controller.$setViewValue(valMasked); // $setViewValue should be run in angular context, otherwise the changes will be invisible to angular and user code. 655 }); 656 } 657 658 // Caret Repositioning 659 // =================== 660 661 // Ensure that typing always places caret ahead of typed character in cases where the first char of 662 // the input is a mask char and the caret is placed at the 0 position. 663 if (isAddition && (caretPos <= caretPosMin)) { 664 caretPos = caretPosMin + 1; 665 } 666 667 if (caretBumpBack) { 668 caretPos--; 669 } 670 671 // Make sure caret is within min and max position limits 672 caretPos = caretPos > caretPosMax ? caretPosMax : caretPos < caretPosMin ? caretPosMin : caretPos; 673 674 // Scoot the caret back or forth until it's in a non-mask position and within min/max position limits 675 while (!isValidCaretPosition(caretPos) && caretPos > caretPosMin && caretPos < caretPosMax) { 676 caretPos += caretBumpBack ? -1 : 1; 677 } 678 679 if ((caretBumpBack && caretPos < caretPosMax) || (isAddition && !isValidCaretPosition(caretPosOld))) { 680 caretPos++; 681 } 682 oldCaretPosition = caretPos; 683 setCaretPosition(this, caretPos); 684 } 685 686 function isValidCaretPosition(pos) { 687 return maskCaretMap.indexOf(pos) > -1; 688 } 689 690 function getCaretPosition(input) { 691 if (!input) 692 return 0; 693 if (input.selectionStart !== undefined) { 694 return input.selectionStart; 695 } else if (document.selection) { 696 if (isFocused(iElement[0])) { 697 // Curse you IE 698 input.focus(); 699 var selection = document.selection.createRange(); 700 selection.moveStart('character', input.value ? -input.value.length : 0); 701 return selection.text.length; 702 } 703 } 704 return 0; 705 } 706 707 function setCaretPosition(input, pos) { 708 if (!input) 709 return 0; 710 if (input.offsetWidth === 0 || input.offsetHeight === 0) { 711 return; // Input's hidden 712 } 713 if (input.setSelectionRange) { 714 if (isFocused(iElement[0])) { 715 input.focus(); 716 input.setSelectionRange(pos, pos); 717 } 718 } 719 else if (input.createTextRange) { 720 // Curse you IE 721 var range = input.createTextRange(); 722 range.collapse(true); 723 range.moveEnd('character', pos); 724 range.moveStart('character', pos); 725 range.select(); 726 } 727 } 728 729 function getSelectionLength(input) { 730 if (!input) 731 return 0; 732 if (input.selectionStart !== undefined) { 733 return (input.selectionEnd - input.selectionStart); 734 } 735 if (window.getSelection) { 736 return (window.getSelection().toString().length); 737 } 738 if (document.selection) { 739 return (document.selection.createRange().text.length); 740 } 741 return 0; 742 } 743 744 // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf 745 if (!Array.prototype.indexOf) { 746 Array.prototype.indexOf = function(searchElement /*, fromIndex */) { 747 if (this === null) { 748 throw new TypeError(); 749 } 750 var t = Object(this); 751 var len = t.length >>> 0; 752 if (len === 0) { 753 return -1; 754 } 755 var n = 0; 756 if (arguments.length > 1) { 757 n = Number(arguments[1]); 758 if (n !== n) { // shortcut for verifying if it's
758NaN 759 n = 0; 760 } else if (n !== 0 && n !== Infinity && n !== -Infinity) { 761 n = (n > 0 || -1) * Math.floor(Math.abs(n)); 762 } 763 } 764 if (n >= len) { 765 return -1; 766 } 767 var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); 768 for (; k < len; k++) { 769 if (k in t && t[k] === searchElement) { 770 return k; 771 } 772 } 773 return -1; 774 }; 775 } 776 777 }; 778 } 779 }; 780 } 781 ]); 782 783}());
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.