mask.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import {keys} from "./keycode.js";
  2. import {caret, getBuffer, getBufferTemplate, getLastValidPosition, resetMaskSet, seekNext} from "./positioning";
  3. import {applyInputValue, clearOptionalTail, writeBuffer} from "./inputHandling";
  4. import {EventRuler} from "./eventruler";
  5. import {iphone, mobile} from "./environment";
  6. import {isComplete} from "./validation";
  7. import {EventHandlers} from "./eventhandlers";
  8. export {mask};
  9. //todo put on the prototype?
  10. function mask() {
  11. const inputmask = this,
  12. opts = this.opts,
  13. el = this.el, $ = this.dependencyLib;
  14. function isElementTypeSupported(input, opts) {
  15. function patchValueProperty(npt) {
  16. var valueGet;
  17. var valueSet;
  18. function patchValhook(type) {
  19. if ($.valHooks && ($.valHooks[type] === undefined || $.valHooks[type].inputmaskpatch !== true)) {
  20. var valhookGet = $.valHooks[type] && $.valHooks[type].get ? $.valHooks[type].get : function (elem) {
  21. return elem.value;
  22. };
  23. var valhookSet = $.valHooks[type] && $.valHooks[type].set ? $.valHooks[type].set : function (elem, value) {
  24. elem.value = value;
  25. return elem;
  26. };
  27. $.valHooks[type] = {
  28. get: function (elem) {
  29. if (elem.inputmask) {
  30. if (elem.inputmask.opts.autoUnmask) {
  31. return elem.inputmask.unmaskedvalue();
  32. } else {
  33. var result = valhookGet(elem);
  34. return getLastValidPosition.call(inputmask, undefined, undefined, elem.inputmask.maskset.validPositions) !== -1 || opts.nullable !== true ? result : "";
  35. }
  36. } else {
  37. return valhookGet(elem);
  38. }
  39. },
  40. set: function (elem, value) {
  41. var result = valhookSet(elem, value);
  42. if (elem.inputmask) {
  43. applyInputValue(elem, value);
  44. }
  45. return result;
  46. },
  47. inputmaskpatch: true
  48. };
  49. }
  50. }
  51. function getter() {
  52. if (this.inputmask) {
  53. return this.inputmask.opts.autoUnmask ?
  54. this.inputmask.unmaskedvalue() :
  55. (getLastValidPosition.call(inputmask) !== -1 || opts.nullable !== true ?
  56. (((this.inputmask.shadowRoot || this.ownerDocument).activeElement) === this && opts.clearMaskOnLostFocus ?
  57. (inputmask.isRTL ? clearOptionalTail.call(inputmask, getBuffer.call(inputmask).slice()).reverse() : clearOptionalTail.call(inputmask, getBuffer.call(inputmask).slice())).join("") :
  58. valueGet.call(this)) :
  59. "");
  60. } else {
  61. return valueGet.call(this);
  62. }
  63. }
  64. function setter(value) {
  65. valueSet.call(this, value);
  66. if (this.inputmask) {
  67. applyInputValue(this, value);
  68. }
  69. }
  70. function installNativeValueSetFallback(npt) {
  71. EventRuler.on(npt, "mouseenter", function () {
  72. let input = this,
  73. value = input.inputmask._valueGet(true),
  74. bufferValue = (input.inputmask.isRTL ? getBuffer.call(input.inputmask).slice().reverse() : getBuffer.call(input.inputmask)).join("");
  75. if (value != bufferValue) {
  76. applyInputValue(input, value);
  77. }
  78. });
  79. }
  80. if (!npt.inputmask.__valueGet) {
  81. if (opts.noValuePatching !== true) {
  82. if (Object.getOwnPropertyDescriptor) {
  83. var valueProperty = Object.getPrototypeOf ? Object.getOwnPropertyDescriptor(Object.getPrototypeOf(npt), "value") : undefined;
  84. if (valueProperty && valueProperty.get && valueProperty.set) {
  85. valueGet = valueProperty.get;
  86. valueSet = valueProperty.set;
  87. Object.defineProperty(npt, "value", {
  88. get: getter,
  89. set: setter,
  90. configurable: true
  91. });
  92. } else if (npt.tagName.toLowerCase() !== "input") {
  93. valueGet = function () {
  94. return this.textContent;
  95. };
  96. valueSet = function (value) {
  97. this.textContent = value;
  98. };
  99. Object.defineProperty(npt, "value", {
  100. get: getter,
  101. set: setter,
  102. configurable: true
  103. });
  104. }
  105. } else if (document.__lookupGetter__ && npt.__lookupGetter__("value")) {
  106. valueGet = npt.__lookupGetter__("value");
  107. valueSet = npt.__lookupSetter__("value");
  108. npt.__defineGetter__("value", getter);
  109. npt.__defineSetter__("value", setter);
  110. }
  111. npt.inputmask.__valueGet = valueGet; //store native property getter
  112. npt.inputmask.__valueSet = valueSet; //store native property setter
  113. }
  114. npt.inputmask._valueGet = function (overruleRTL) {
  115. return inputmask.isRTL && overruleRTL !== true ? valueGet.call(this.el).split("").reverse().join("") : valueGet.call(this.el);
  116. };
  117. npt.inputmask._valueSet = function (value, overruleRTL) { //null check is needed for IE8 => otherwise converts to "null"
  118. valueSet.call(this.el, (value === null || value === undefined) ? "" : ((overruleRTL !== true && inputmask.isRTL) ? value.split("").reverse().join("") : value));
  119. };
  120. if (valueGet === undefined) { //jquery.val fallback
  121. valueGet = function () {
  122. return this.value;
  123. };
  124. valueSet = function (value) {
  125. this.value = value;
  126. };
  127. patchValhook(npt.type);
  128. installNativeValueSetFallback(npt);
  129. }
  130. }
  131. }
  132. if (input.tagName.toLowerCase() !== "textarea") {
  133. opts.ignorables.push(keys.Enter);
  134. }
  135. var elementType = input.getAttribute("type");
  136. var isSupported = (input.tagName.toLowerCase() === "input" && opts.supportsInputType.includes(elementType)) || input.isContentEditable || input.tagName.toLowerCase() === "textarea";
  137. if (!isSupported) {
  138. if (input.tagName.toLowerCase() === "input") {
  139. var el = document.createElement("input");
  140. el.setAttribute("type", elementType);
  141. isSupported = el.type === "text"; //apply mask only if the type is not natively supported
  142. el = null;
  143. } else {
  144. isSupported = "partial";
  145. }
  146. }
  147. if (isSupported !== false) {
  148. patchValueProperty(input);
  149. } else {
  150. input.inputmask = undefined;
  151. }
  152. return isSupported;
  153. }
  154. //unbind all events - to make sure that no other mask will interfere when re-masking
  155. EventRuler.off(el);
  156. var isSupported = isElementTypeSupported(el, opts);
  157. if (isSupported !== false) {
  158. inputmask.originalPlaceholder = el.placeholder;
  159. //read maxlength prop from el
  160. inputmask.maxLength = el !== undefined ? el.maxLength : undefined;
  161. if (inputmask.maxLength === -1) inputmask.maxLength = undefined;
  162. if ("inputMode" in el && el.getAttribute("inputmode") === null) {
  163. el.inputMode = opts.inputmode;
  164. el.setAttribute("inputmode", opts.inputmode);
  165. }
  166. if (isSupported === true) {
  167. opts.showMaskOnFocus = opts.showMaskOnFocus && ["cc-number", "cc-exp"].indexOf(el.autocomplete) === -1;
  168. if (iphone) {
  169. //selecting the caret shows as a selection on iphone
  170. opts.insertModeVisual = false;
  171. //disable autocorrect
  172. el.setAttribute("autocorrect", "off");
  173. }
  174. //bind events
  175. EventRuler.on(el, "submit", EventHandlers.submitEvent);
  176. EventRuler.on(el, "reset", EventHandlers.resetEvent);
  177. EventRuler.on(el, "blur", EventHandlers.blurEvent);
  178. EventRuler.on(el, "focus", EventHandlers.focusEvent);
  179. EventRuler.on(el, "invalid", EventHandlers.invalidEvent);
  180. EventRuler.on(el, "click", EventHandlers.clickEvent);
  181. EventRuler.on(el, "mouseleave", EventHandlers.mouseleaveEvent);
  182. EventRuler.on(el, "mouseenter", EventHandlers.mouseenterEvent);
  183. EventRuler.on(el, "paste", EventHandlers.pasteEvent);
  184. EventRuler.on(el, "cut", EventHandlers.cutEvent);
  185. EventRuler.on(el, "complete", opts.oncomplete);
  186. EventRuler.on(el, "incomplete", opts.onincomplete);
  187. EventRuler.on(el, "cleared", opts.oncleared);
  188. if (opts.inputEventOnly !== true) {
  189. EventRuler.on(el, "keydown", EventHandlers.keydownEvent);
  190. // EventRuler.on(el, "keypress", EventHandlers.keypressEvent);
  191. EventRuler.on(el, "keyup", EventHandlers.keyupEvent);
  192. }
  193. if (mobile || opts.inputEventOnly) {
  194. el.removeAttribute("maxLength");
  195. }
  196. EventRuler.on(el, "input", EventHandlers.inputFallBackEvent);
  197. EventRuler.on(el, "compositionend", EventHandlers.compositionendEvent);
  198. // EventRuler.on(el, "beforeinput", EventHandlers.beforeInputEvent); //https://github.com/w3c/input-events - to implement
  199. }
  200. EventRuler.on(el, "setvalue", EventHandlers.setValueEvent);
  201. //apply mask
  202. getBufferTemplate.call(inputmask).join(""); //initialize the buffer and getmasklength
  203. inputmask.undoValue = inputmask._valueGet(true);
  204. var activeElement = (el.inputmask.shadowRoot || el.ownerDocument).activeElement;
  205. if (el.inputmask._valueGet(true) !== "" || opts.clearMaskOnLostFocus === false || activeElement === el) {
  206. applyInputValue(el, el.inputmask._valueGet(true), opts);
  207. var buffer = getBuffer.call(inputmask).slice();
  208. if (isComplete.call(inputmask, buffer) === false) {
  209. if (opts.clearIncomplete) {
  210. resetMaskSet.call(inputmask);
  211. }
  212. }
  213. if (opts.clearMaskOnLostFocus && activeElement !== el) {
  214. if (getLastValidPosition.call(inputmask) === -1) {
  215. buffer = [];
  216. } else {
  217. clearOptionalTail.call(inputmask, buffer);
  218. }
  219. }
  220. if (opts.clearMaskOnLostFocus === false || (opts.showMaskOnFocus && activeElement === el) || el.inputmask._valueGet(true) !== "") {
  221. writeBuffer(el, buffer);
  222. }
  223. if (activeElement === el) { //position the caret when in focus
  224. caret.call(inputmask, el, seekNext.call(inputmask, getLastValidPosition.call(inputmask)));
  225. }
  226. }
  227. }
  228. }