eventhandlers.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. import {
  2. caret, determineNewCaretPosition,
  3. getBuffer, getBufferTemplate,
  4. getLastValidPosition, isMask,
  5. resetMaskSet,
  6. seekNext,
  7. seekPrevious,
  8. translatePosition
  9. } from "./positioning";
  10. import {keyCode, keys} from "./keycode.js";
  11. import {iemobile, iphone} from "./environment";
  12. import {handleRemove, isComplete, isSelection, isValid} from "./validation";
  13. import {applyInputValue, checkVal, clearOptionalTail, HandleNativePlaceholder, writeBuffer} from "./inputHandling";
  14. import {getPlaceholder, getTest} from "./validation-tests";
  15. export {EventHandlers};
  16. var EventHandlers = {
  17. keydownEvent: function (e, checkval, writeOut, strict, ndx) {
  18. const inputmask = this.inputmask, opts = inputmask.opts, $ = inputmask.dependencyLib,
  19. maskset = inputmask.maskset;
  20. var input = this,
  21. $input = $(input),
  22. c = e.key,
  23. pos = caret.call(inputmask, input);
  24. var kdResult = opts.onKeyDown.call(this, e, getBuffer.call(inputmask), pos, opts);
  25. if (kdResult !== undefined) return kdResult;
  26. //backspace, delete, and escape get special treatment
  27. if (c === keys.Backspace || c === keys.Delete || (iphone && c === keys.BACKSPACE_SAFARI) || (e.ctrlKey && c === keys.x && !("oncut" in input))) { //backspace/delete
  28. e.preventDefault(); //stop default action but allow propagation
  29. handleRemove.call(inputmask, input, c, pos);
  30. writeBuffer(input, getBuffer.call(inputmask, true), maskset.p, e, input.inputmask._valueGet() !== getBuffer.call(inputmask).join(""));
  31. } else if (c === keys.End || c === keys.PageDown) { //when END or PAGE_DOWN pressed set position at lastmatch
  32. e.preventDefault();
  33. var caretPos = seekNext.call(inputmask, getLastValidPosition.call(inputmask));
  34. caret.call(inputmask, input, e.shiftKey ? pos.begin : caretPos, caretPos, true);
  35. } else if ((c === keys.Home && !e.shiftKey) || c === keys.PageUp) { //Home or page_up
  36. e.preventDefault();
  37. caret.call(inputmask, input, 0, e.shiftKey ? pos.begin : 0, true);
  38. } else if (((opts.undoOnEscape && c === keys.Escape) || (false && c === keys.z && e.ctrlKey)) && e.altKey !== true) { //escape && undo && #762
  39. checkVal(input, true, false, inputmask.undoValue.split(""));
  40. $input.trigger("click");
  41. } else if (c === keys.Insert && !(e.shiftKey || e.ctrlKey) && inputmask.userOptions.insertMode === undefined) { //insert
  42. if (!isSelection.call(inputmask, pos)) {
  43. opts.insertMode = !opts.insertMode;
  44. caret.call(inputmask, input, pos.begin, pos.begin);
  45. } else opts.insertMode = !opts.insertMode;
  46. } else if (opts.tabThrough === true && c === keys.Tab) {
  47. if (e.shiftKey === true) {
  48. pos.end = seekPrevious.call(inputmask, pos.end, true);
  49. if (getTest.call(inputmask, pos.end - 1).match.static === true) {
  50. pos.end--;
  51. }
  52. pos.begin = seekPrevious.call(inputmask, pos.end, true);
  53. if (pos.begin >= 0 && pos.end > 0) {
  54. e.preventDefault();
  55. caret.call(inputmask, input, pos.begin, pos.end);
  56. }
  57. } else {
  58. pos.begin = seekNext.call(inputmask, pos.begin, true);
  59. pos.end = seekNext.call(inputmask, pos.begin, true);
  60. if (pos.end < maskset.maskLength) pos.end--;
  61. if (pos.begin <= maskset.maskLength) {
  62. e.preventDefault();
  63. caret.call(inputmask, input, pos.begin, pos.end);
  64. }
  65. }
  66. } else if (!e.shiftKey) {
  67. if (opts.insertModeVisual && opts.insertMode === false) {
  68. if (c === keys.Right) {
  69. setTimeout(function () {
  70. var caretPos = caret.call(inputmask, input);
  71. caret.call(inputmask, input, caretPos.begin);
  72. }, 0);
  73. } else if (c === keys.Left) {
  74. setTimeout(function () {
  75. var caretPos = {
  76. begin: translatePosition.call(inputmask, input.inputmask.caretPos.begin),
  77. end: translatePosition.call(inputmask, input.inputmask.caretPos.end)
  78. };
  79. if (inputmask.isRTL) {
  80. caret.call(inputmask, input, caretPos.begin + (caretPos.begin === maskset.maskLength ? 0 : 1));
  81. } else {
  82. caret.call(inputmask, input, caretPos.begin - (caretPos.begin === 0 ? 0 : 1));
  83. }
  84. }, 0);
  85. }
  86. }
  87. }
  88. inputmask.ignorable = opts.ignorables.includes(c);
  89. return EventHandlers.keypressEvent.call(this, e, checkval, writeOut, strict, ndx);
  90. },
  91. keypressEvent: function (e, checkval, writeOut, strict, ndx) {
  92. const inputmask = this.inputmask || this, opts = inputmask.opts, $ = inputmask.dependencyLib,
  93. maskset = inputmask.maskset;
  94. var input = inputmask.el,
  95. $input = $(input),
  96. c = e.key;
  97. if (checkval !== true && (!(e.ctrlKey && e.altKey) && (e.ctrlKey || e.metaKey || inputmask.ignorable))) {
  98. if (c === keys.Enter && inputmask.undoValue !== inputmask._valueGet(true)) {
  99. inputmask.undoValue = inputmask._valueGet(true);
  100. // e.preventDefault();
  101. setTimeout(function () {
  102. $input.trigger("change");
  103. }, 0);
  104. }
  105. inputmask.skipInputEvent = true; //skip the input as otherwise the skipped char could be picked up for validation by the inputfallback
  106. return true;
  107. } else if (c) {
  108. //special treat the decimal separator
  109. // if ((k === 44 || k === 46) && e.location === 3 && opts.radixPoint !== "") k = opts.radixPoint.charCodeAt(0);
  110. var pos = checkval ? {
  111. begin: ndx,
  112. end: ndx
  113. } : caret.call(inputmask, input),
  114. forwardPosition;
  115. //allow for character substitution
  116. c = opts.substitutes[c] || c;
  117. maskset.writeOutBuffer = true;
  118. var valResult = isValid.call(inputmask, pos, c, strict, undefined, undefined, undefined, checkval);
  119. if (valResult !== false) {
  120. resetMaskSet.call(inputmask, true);
  121. forwardPosition = valResult.caret !== undefined ? valResult.caret : seekNext.call(inputmask, valResult.pos.begin ? valResult.pos.begin : valResult.pos);
  122. maskset.p = forwardPosition; //needed for checkval
  123. }
  124. forwardPosition = ((opts.numericInput && valResult.caret === undefined) ? seekPrevious.call(inputmask, forwardPosition) : forwardPosition);
  125. if (writeOut !== false) {
  126. setTimeout(function () {
  127. opts.onKeyValidation.call(input, c, valResult);
  128. }, 0);
  129. if (maskset.writeOutBuffer && valResult !== false) {
  130. var buffer = getBuffer.call(inputmask);
  131. writeBuffer(input, buffer, forwardPosition, e, checkval !== true);
  132. }
  133. }
  134. e.preventDefault();
  135. if (checkval) {
  136. if (valResult !== false) valResult.forwardPosition = forwardPosition;
  137. return valResult;
  138. }
  139. }
  140. },
  141. keyupEvent: function (e) {
  142. const inputmask = this.inputmask;
  143. if (inputmask.isComposing) {
  144. if (e.keyCode === keyCode.KEY_229 || e.key === keys.Enter)
  145. inputmask.$el.trigger("input");
  146. }
  147. },
  148. pasteEvent: function (e) {
  149. const inputmask = this.inputmask, opts = inputmask.opts;
  150. var input = this,
  151. inputValue = inputmask._valueGet(true),
  152. caretPos = caret.call(inputmask, input),
  153. tempValue;
  154. if (inputmask.isRTL) {
  155. tempValue = caretPos.end;
  156. caretPos.end = translatePosition.call(inputmask, caretPos.begin);
  157. caretPos.begin = translatePosition.call(inputmask, tempValue);
  158. }
  159. var valueBeforeCaret = inputValue.substr(0, caretPos.begin),
  160. valueAfterCaret = inputValue.substr(caretPos.end, inputValue.length);
  161. if (valueBeforeCaret == (inputmask.isRTL ? getBufferTemplate.call(inputmask).slice().reverse() : getBufferTemplate.call(inputmask)).slice(0, caretPos.begin).join("")) valueBeforeCaret = "";
  162. if (valueAfterCaret == (inputmask.isRTL ? getBufferTemplate.call(inputmask).slice().reverse() : getBufferTemplate.call(inputmask)).slice(caretPos.end).join("")) valueAfterCaret = "";
  163. if (window.clipboardData && window.clipboardData.getData) { // IE
  164. inputValue = valueBeforeCaret + window.clipboardData.getData("Text") + valueAfterCaret;
  165. } else if (e.clipboardData && e.clipboardData.getData) {
  166. inputValue = valueBeforeCaret + e.clipboardData.getData("text/plain") + valueAfterCaret;
  167. } else {
  168. return true;
  169. } //allow native paste event as fallback ~ masking will continue by inputfallback
  170. var pasteValue = inputValue;
  171. if (inputmask.isRTL) {
  172. pasteValue = pasteValue.split("");
  173. for (let c of getBufferTemplate.call(inputmask)) {
  174. if (pasteValue[0] === c)
  175. pasteValue.shift();
  176. }
  177. pasteValue = pasteValue.join("");
  178. }
  179. if (typeof opts.onBeforePaste === "function") {
  180. pasteValue = opts.onBeforePaste.call(inputmask, pasteValue, opts);
  181. if (pasteValue === false) {
  182. return false;
  183. }
  184. if (!pasteValue) {
  185. pasteValue = inputValue;
  186. }
  187. }
  188. checkVal(input, true, false, pasteValue.toString().split(""), e);
  189. e.preventDefault();
  190. },
  191. inputFallBackEvent: function (e) { //fallback when keypress is not triggered
  192. const inputmask = this.inputmask, opts = inputmask.opts, $ = inputmask.dependencyLib;
  193. function ieMobileHandler(input, inputValue, caretPos) {
  194. if (iemobile) { //iemobile just sets the character at the end althought the caret position is correctly set
  195. var inputChar = inputValue.replace(getBuffer.call(inputmask).join(""), "");
  196. if (inputChar.length === 1) {
  197. var iv = inputValue.split("");
  198. iv.splice(caretPos.begin, 0, inputChar);
  199. inputValue = iv.join("");
  200. }
  201. }
  202. return inputValue;
  203. }
  204. function analyseChanges(inputValue, buffer, caretPos) {
  205. var frontPart = inputValue.substr(0, caretPos.begin).split(""),
  206. backPart = inputValue.substr(caretPos.begin).split(""),
  207. frontBufferPart = buffer.substr(0, caretPos.begin).split(""),
  208. backBufferPart = buffer.substr(caretPos.begin).split("");
  209. var fpl = frontPart.length >= frontBufferPart.length ? frontPart.length : frontBufferPart.length,
  210. bpl = backPart.length >= backBufferPart.length ? backPart.length : backBufferPart.length,
  211. bl, i, action = "", data = [], marker = "~", placeholder;
  212. //align buffers
  213. while (frontPart.length < fpl) frontPart.push(marker);
  214. while (frontBufferPart.length < fpl) frontBufferPart.push(marker);
  215. while (backPart.length < bpl) backPart.unshift(marker);
  216. while (backBufferPart.length < bpl) backBufferPart.unshift(marker);
  217. var newBuffer = frontPart.concat(backPart);
  218. var oldBuffer = frontBufferPart.concat(backBufferPart);
  219. // console.log("N " + newBuffer);
  220. // console.log("O " + oldBuffer);
  221. for (i = 0, bl = newBuffer.length; i < bl; i++) {
  222. placeholder = getPlaceholder.call(inputmask, translatePosition.call(inputmask, i));
  223. switch (action) {
  224. case "insertText":
  225. if (oldBuffer[i - 1] === newBuffer[i] && caretPos.begin == newBuffer.length - 1) {
  226. data.push(newBuffer[i]);
  227. }
  228. i = bl;
  229. break;
  230. case "insertReplacementText":
  231. if (newBuffer[i] === marker) { //extend selection
  232. caretPos.end++;
  233. } else {
  234. // breakout loop
  235. i = bl;
  236. }
  237. break;
  238. case "deleteContentBackward":
  239. if (newBuffer[i] === marker) {
  240. caretPos.end++;
  241. } else {
  242. //breakout loop
  243. i = bl;
  244. }
  245. break;
  246. default:
  247. if (newBuffer[i] !== oldBuffer[i]) {
  248. if ((newBuffer[i + 1] === marker || newBuffer[i + 1] === placeholder || newBuffer[i + 1] === undefined) && ((oldBuffer[i] === placeholder && oldBuffer[i + 1] === marker) || oldBuffer[i] === marker)) { //basic insert
  249. action = "insertText";
  250. data.push(newBuffer[i]);
  251. caretPos.begin--;
  252. caretPos.end--;
  253. } else if (oldBuffer[i + 1] === marker && oldBuffer[i] === newBuffer[i + 1]) { //insert between
  254. action = "insertText";
  255. data.push(newBuffer[i]);
  256. caretPos.begin--;
  257. caretPos.end--;
  258. } else if (newBuffer[i] !== placeholder && newBuffer[i] !== marker &&
  259. (newBuffer[i + 1] === marker || (oldBuffer[i] !== newBuffer[i] && oldBuffer[i + 1] === newBuffer[i + 1] /*single char replacement*/))) { //replace selection
  260. action = "insertReplacementText";
  261. data.push(newBuffer[i]);
  262. caretPos.begin--;
  263. } else if (newBuffer[i] === marker) { //delete~backspace
  264. action = "deleteContentBackward";
  265. if (isMask.call(inputmask, translatePosition.call(inputmask, i), true) || oldBuffer[i] === opts.radixPoint) caretPos.end++;
  266. } else {
  267. i = bl;
  268. }
  269. }
  270. break;
  271. }
  272. }
  273. return {
  274. action: action,
  275. data: data,
  276. caret: caretPos
  277. };
  278. }
  279. var input = this,
  280. inputValue = input.inputmask._valueGet(true),
  281. buffer = (inputmask.isRTL ? getBuffer.call(inputmask).slice().reverse() : getBuffer.call(inputmask)).join(""),
  282. caretPos = caret.call(inputmask, input, undefined, undefined, true);
  283. if (buffer !== inputValue) {
  284. inputValue = ieMobileHandler(input, inputValue, caretPos);
  285. var changes = analyseChanges(inputValue, buffer, caretPos);
  286. // console.log(JSON.stringify(changes));
  287. if ((input.inputmask.shadowRoot || input.ownerDocument).activeElement !== input) {
  288. input.focus();
  289. }
  290. writeBuffer(input, getBuffer.call(inputmask));
  291. caret.call(inputmask, input, caretPos.begin, caretPos.end, true);
  292. switch (changes.action) {
  293. case "insertText":
  294. case "insertReplacementText":
  295. changes.data.forEach(function (entry, ndx) {
  296. var keypress = new $.Event("keypress");
  297. keypress.key = entry;
  298. inputmask.ignorable = false; //make sure ignorable is ignored ;-)
  299. EventHandlers.keypressEvent.call(input, keypress);
  300. });
  301. setTimeout(function () { //#2195 trigger keyup to help some other plugins to track changes
  302. inputmask.$el.trigger("keyup");
  303. }, 0);
  304. break;
  305. case "deleteContentBackward":
  306. var keydown = new $.Event("keydown");
  307. keydown.key = keys.Backspace;
  308. EventHandlers.keydownEvent.call(input, keydown);
  309. break;
  310. default:
  311. applyInputValue(input, inputValue);
  312. break;
  313. }
  314. e.preventDefault();
  315. }
  316. },
  317. compositionendEvent: function (e) {
  318. const inputmask = this.inputmask;
  319. inputmask.isComposing = false;
  320. inputmask.$el.trigger("input");
  321. },
  322. setValueEvent: function (e) {
  323. const inputmask = this.inputmask;
  324. var input = this,
  325. value = (e && e.detail) ? e.detail[0] : arguments[1];
  326. if (value === undefined) {
  327. value = input.inputmask._valueGet(true);
  328. }
  329. applyInputValue(input, value);
  330. if ((e.detail && e.detail[1] !== undefined) || arguments[2] !== undefined) {
  331. caret.call(inputmask, input, e.detail ? e.detail[1] : arguments[2]);
  332. }
  333. }
  334. ,
  335. focusEvent: function (e) {
  336. const inputmask = this.inputmask, opts = inputmask.opts;
  337. var input = this,
  338. nptValue = input.inputmask._valueGet();
  339. if (opts.showMaskOnFocus) {
  340. if (nptValue !== getBuffer.call(inputmask).join("")) {
  341. writeBuffer(input, getBuffer.call(inputmask), seekNext.call(inputmask, getLastValidPosition.call(inputmask)));
  342. } /*else if (mouseEnter === false) { //only executed on focus without mouseenter
  343. caret(input, seekNext(getLastValidPosition()));
  344. }*/
  345. }
  346. if (opts.positionCaretOnTab === true && inputmask.mouseEnter === false && (!isComplete.call(inputmask, getBuffer.call(inputmask)) || getLastValidPosition.call(inputmask) === -1)) {
  347. EventHandlers.clickEvent.apply(input, [e, true]);
  348. }
  349. inputmask.undoValue = inputmask._valueGet(true);
  350. },
  351. invalidEvent: function (e) {
  352. this.inputmask.validationEvent = true;
  353. },
  354. mouseleaveEvent: function () {
  355. const inputmask = this.inputmask, opts = inputmask.opts;
  356. var input = this;
  357. inputmask.mouseEnter = false;
  358. if (opts.clearMaskOnLostFocus && (input.inputmask.shadowRoot || input.ownerDocument).activeElement !== input) {
  359. HandleNativePlaceholder(input, inputmask.originalPlaceholder);
  360. }
  361. },
  362. clickEvent: function (e, tabbed) {
  363. const inputmask = this.inputmask;
  364. var input = this;
  365. if ((input.inputmask.shadowRoot || input.ownerDocument).activeElement === input) {
  366. var newCaretPosition = determineNewCaretPosition.call(inputmask, caret.call(inputmask, input), tabbed);
  367. if (newCaretPosition !== undefined) {
  368. caret.call(inputmask, input, newCaretPosition);
  369. }
  370. }
  371. },
  372. cutEvent: function (e) {
  373. const inputmask = this.inputmask, maskset = inputmask.maskset;
  374. var input = this,
  375. pos = caret.call(inputmask, input);
  376. //correct clipboardData
  377. var clipData = inputmask.isRTL ? getBuffer.call(inputmask).slice(pos.end, pos.begin) : getBuffer.call(inputmask).slice(pos.begin, pos.end),
  378. clipDataText = inputmask.isRTL ? clipData.reverse().join("") : clipData.join("");
  379. if (window.navigator.clipboard) window.navigator.clipboard.writeText(clipDataText);
  380. else if (window.clipboardData && window.clipboardData.getData) { // IE
  381. window.clipboardData.setData("Text", clipDataText);
  382. }
  383. handleRemove.call(inputmask, input, keys.Delete, pos);
  384. writeBuffer(input, getBuffer.call(inputmask), maskset.p, e, inputmask.undoValue !== inputmask._valueGet(true));
  385. },
  386. blurEvent: function (e) {
  387. const inputmask = this.inputmask, opts = inputmask.opts, $ = inputmask.dependencyLib;
  388. var $input = $(this),
  389. input = this;
  390. if (input.inputmask) {
  391. HandleNativePlaceholder(input, inputmask.originalPlaceholder);
  392. var nptValue = input.inputmask._valueGet(),
  393. buffer = getBuffer.call(inputmask).slice();
  394. if (nptValue !== "") {
  395. if (opts.clearMaskOnLostFocus) {
  396. if (getLastValidPosition.call(inputmask) === -1 && nptValue === getBufferTemplate.call(inputmask).join("")) {
  397. buffer = [];
  398. } else { //clearout optional tail of the mask
  399. clearOptionalTail.call(inputmask, buffer);
  400. }
  401. }
  402. if (isComplete.call(inputmask, buffer) === false) {
  403. setTimeout(function () {
  404. $input.trigger("incomplete");
  405. }, 0);
  406. if (opts.clearIncomplete) {
  407. resetMaskSet.call(inputmask);
  408. if (opts.clearMaskOnLostFocus) {
  409. buffer = [];
  410. } else {
  411. buffer = getBufferTemplate.call(inputmask).slice();
  412. }
  413. }
  414. }
  415. writeBuffer(input, buffer, undefined, e);
  416. }
  417. if (inputmask.undoValue !== inputmask._valueGet(true)) {
  418. inputmask.undoValue = inputmask._valueGet(true);
  419. $input.trigger("change");
  420. }
  421. }
  422. }
  423. ,
  424. mouseenterEvent: function () {
  425. const inputmask = this.inputmask, opts = inputmask.opts;
  426. var input = this;
  427. inputmask.mouseEnter = true;
  428. if ((input.inputmask.shadowRoot || input.ownerDocument).activeElement !== input) {
  429. var bufferTemplate = (inputmask.isRTL ? getBufferTemplate.call(inputmask).slice().reverse() : getBufferTemplate.call(inputmask)).join("");
  430. if (inputmask.placeholder !== bufferTemplate && input.placeholder !== inputmask.originalPlaceholder) {
  431. inputmask.originalPlaceholder = input.placeholder;
  432. }
  433. if (opts.showMaskOnHover) {
  434. HandleNativePlaceholder(input, bufferTemplate);
  435. }
  436. }
  437. }
  438. ,
  439. submitEvent: function () { //trigger change on submit if any
  440. const inputmask = this.inputmask, opts = inputmask.opts;
  441. if (inputmask.undoValue !== inputmask._valueGet(true)) {
  442. inputmask.$el.trigger("change");
  443. }
  444. if (/*opts.clearMaskOnLostFocus && */getLastValidPosition.call(inputmask) === -1 && inputmask._valueGet && inputmask._valueGet() === getBufferTemplate.call(inputmask).join("")) {
  445. inputmask._valueSet(""); //clear masktemplete on submit and still has focus
  446. }
  447. if (opts.clearIncomplete && isComplete.call(inputmask, getBuffer.call(inputmask)) === false) {
  448. inputmask._valueSet("");
  449. }
  450. if (opts.removeMaskOnSubmit) {
  451. inputmask._valueSet(inputmask.unmaskedvalue(), true);
  452. setTimeout(function () {
  453. writeBuffer(inputmask.el, getBuffer.call(inputmask));
  454. }, 0);
  455. }
  456. }
  457. ,
  458. resetEvent: function () {
  459. const inputmask = this.inputmask;
  460. inputmask.refreshValue = true; //indicate a forced refresh when there is a call to the value before leaving the triggering event fn
  461. setTimeout(function () {
  462. applyInputValue(inputmask.el, inputmask._valueGet(true));
  463. }, 0);
  464. }
  465. };