mask.js 11 KB

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