positioning.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. import window from "./global/window";
  2. import { checkAlternationMatch } from "./validation";
  3. import {
  4. determineTestTemplate,
  5. getMaskTemplate,
  6. getPlaceholder,
  7. getTest,
  8. getTests,
  9. getTestTemplate
  10. } from "./validation-tests";
  11. export {
  12. caret,
  13. determineLastRequiredPosition,
  14. determineNewCaretPosition,
  15. getBuffer,
  16. getBufferTemplate,
  17. getLastValidPosition,
  18. isMask,
  19. resetMaskSet,
  20. seekNext,
  21. seekPrevious,
  22. translatePosition
  23. };
  24. // tobe put on prototype?
  25. function caret(input, begin, end, notranslate, isDelete) {
  26. const inputmask = this,
  27. opts = this.opts;
  28. let range;
  29. if (begin !== undefined) {
  30. if (Array.isArray(begin)) {
  31. end = inputmask.isRTL ? begin[0] : begin[1];
  32. begin = inputmask.isRTL ? begin[1] : begin[0];
  33. }
  34. if (begin.begin !== undefined) {
  35. end = inputmask.isRTL ? begin.begin : begin.end;
  36. begin = inputmask.isRTL ? begin.end : begin.begin;
  37. }
  38. if (typeof begin === "number") {
  39. begin = notranslate ? begin : translatePosition.call(inputmask, begin);
  40. end = notranslate ? end : translatePosition.call(inputmask, end);
  41. end = typeof end === "number" ? end : begin;
  42. // if (!$(input).is(":visible")) {
  43. // return;
  44. // }
  45. const scrollCalc =
  46. parseInt(
  47. ((input.ownerDocument.defaultView || window).getComputedStyle
  48. ? (input.ownerDocument.defaultView || window).getComputedStyle(
  49. input,
  50. null
  51. )
  52. : input.currentStyle
  53. ).fontSize
  54. ) * end;
  55. input.scrollLeft = scrollCalc > input.scrollWidth ? scrollCalc : 0;
  56. input.inputmask.caretPos = { begin, end }; // track caret internally
  57. if (opts.insertModeVisual && opts.insertMode === false && begin === end) {
  58. if (!isDelete) {
  59. end++; // set visualization for insert/overwrite mode
  60. }
  61. }
  62. if (input === input.getRootNode().activeElement) {
  63. if ("setSelectionRange" in input) {
  64. input.setSelectionRange(begin, end);
  65. } else if (window.getSelection) {
  66. range = document.createRange();
  67. if (input.firstChild === undefined || input.firstChild === null) {
  68. const textNode = document.createTextNode("");
  69. input.appendChild(textNode);
  70. }
  71. range.setStart(
  72. input.firstChild,
  73. begin < input.inputmask._valueGet().length
  74. ? begin
  75. : input.inputmask._valueGet().length
  76. );
  77. range.setEnd(
  78. input.firstChild,
  79. end < input.inputmask._valueGet().length
  80. ? end
  81. : input.inputmask._valueGet().length
  82. );
  83. range.collapse(true);
  84. const sel = window.getSelection();
  85. sel.removeAllRanges();
  86. sel.addRange(range);
  87. // input.focus();
  88. } else if (input.createTextRange) {
  89. range = input.createTextRange();
  90. range.collapse(true);
  91. range.moveEnd("character", end);
  92. range.moveStart("character", begin);
  93. range.select();
  94. }
  95. input.inputmask.caretHook === undefined ||
  96. input.inputmask.caretHook.call(inputmask, { begin, end });
  97. }
  98. }
  99. } else {
  100. if ("selectionStart" in input && "selectionEnd" in input) {
  101. begin = input.selectionStart;
  102. end = input.selectionEnd;
  103. } else if (window.getSelection) {
  104. range = window.getSelection().getRangeAt(0);
  105. if (
  106. range.commonAncestorContainer.parentNode === input ||
  107. range.commonAncestorContainer === input
  108. ) {
  109. begin = range.startOffset;
  110. end = range.endOffset;
  111. }
  112. } else if (document.selection && document.selection.createRange) {
  113. range = document.selection.createRange();
  114. begin =
  115. 0 -
  116. range
  117. .duplicate()
  118. .moveStart("character", -input.inputmask._valueGet().length);
  119. end = begin + range.text.length;
  120. }
  121. // if (opts.insertModeVisual && opts.insertMode === false && begin === (end - 1)) end--; //correct caret for insert/overwrite mode
  122. /* eslint-disable consistent-return */
  123. return {
  124. begin: notranslate ? begin : translatePosition.call(inputmask, begin),
  125. end: notranslate ? end : translatePosition.call(inputmask, end)
  126. };
  127. /* eslint-enable consistent-return */
  128. }
  129. }
  130. // tobe put on prototype?
  131. function determineLastRequiredPosition(returnDefinition) {
  132. const inputmask = this,
  133. { maskset, dependencyLib: $ } = inputmask,
  134. lvp = getLastValidPosition.call(inputmask),
  135. positions = {},
  136. lvTest = maskset.validPositions[lvp],
  137. buffer = getMaskTemplate.call(
  138. inputmask,
  139. true,
  140. getLastValidPosition.call(inputmask),
  141. true,
  142. true
  143. );
  144. let bl = buffer.length,
  145. pos,
  146. ndxIntlzr = lvTest !== undefined ? lvTest.locator.slice() : undefined,
  147. testPos;
  148. for (pos = lvp + 1; pos < buffer.length; pos++) {
  149. testPos = getTestTemplate.call(inputmask, pos, ndxIntlzr, pos - 1);
  150. ndxIntlzr = testPos.locator.slice();
  151. positions[pos] = $.extend(true, {}, testPos);
  152. }
  153. const lvTestAlt =
  154. lvTest && lvTest.alternation !== undefined
  155. ? lvTest.locator[lvTest.alternation]
  156. : undefined;
  157. for (pos = bl - 1; pos > lvp; pos--) {
  158. testPos = positions[pos];
  159. if (
  160. (testPos.match.optionality ||
  161. (testPos.match.optionalQuantifier && testPos.match.newBlockMarker) ||
  162. (lvTestAlt &&
  163. ((lvTestAlt !== positions[pos].locator[lvTest.alternation] &&
  164. testPos.match.static !== true) ||
  165. (testPos.match.static === true &&
  166. testPos.locator[lvTest.alternation] &&
  167. checkAlternationMatch.call(
  168. inputmask,
  169. testPos.locator[lvTest.alternation].toString().split(","),
  170. lvTestAlt.toString().split(",")
  171. ) &&
  172. getTests.call(inputmask, pos)[0].def !== "")))) &&
  173. buffer[pos] === getPlaceholder.call(inputmask, pos, testPos.match)
  174. ) {
  175. bl--;
  176. if (testPos.match.optionality) {
  177. // find the last position that is not optional ~ isoptional and newblockmarker == "master"
  178. let prevPos = pos;
  179. while (prevPos > 0) {
  180. const test = getTest.call(inputmask, prevPos);
  181. if (
  182. test.match.newBlockMarker === "master" ||
  183. test.match.newBlockMarker === true
  184. ) {
  185. break;
  186. }
  187. prevPos--;
  188. }
  189. if (maskset.validPositions[prevPos] !== undefined) {
  190. break;
  191. }
  192. }
  193. } else {
  194. break;
  195. }
  196. }
  197. // no extra required positions
  198. if (pos === lvp) {
  199. bl = pos;
  200. }
  201. return returnDefinition
  202. ? {
  203. l: bl,
  204. def: positions[bl] ? positions[bl].match : undefined
  205. }
  206. : bl;
  207. }
  208. // tobe put on prototype?
  209. function determineNewCaretPosition(
  210. selectedCaret,
  211. tabbed,
  212. positionCaretOnClick
  213. ) {
  214. const inputmask = this,
  215. { maskset, opts } = inputmask;
  216. let clickPosition, lvclickPosition, lastPosition;
  217. function doRadixFocus(clickPos) {
  218. if (opts.radixPoint !== "" && opts.digits !== 0) {
  219. const vps = maskset.validPositions;
  220. if (vps[clickPos] === undefined || vps[clickPos].input === undefined) {
  221. if (clickPos < seekNext.call(inputmask, -1)) return true;
  222. const radixPos = getBuffer.call(inputmask).indexOf(opts.radixPoint);
  223. if (radixPos !== -1) {
  224. for (let vp = 0, vpl = vps.length; vp < vpl; vp++) {
  225. if (
  226. vps[vp] &&
  227. radixPos < vp &&
  228. vps[vp].input !== getPlaceholder.call(inputmask, vp)
  229. ) {
  230. return false;
  231. }
  232. }
  233. return true;
  234. }
  235. }
  236. }
  237. return false;
  238. }
  239. if (tabbed) {
  240. if (inputmask.isRTL) {
  241. selectedCaret.end = selectedCaret.begin;
  242. } else {
  243. selectedCaret.begin = selectedCaret.end;
  244. }
  245. }
  246. if (selectedCaret.begin === selectedCaret.end) {
  247. positionCaretOnClick = positionCaretOnClick || opts.positionCaretOnClick;
  248. switch (positionCaretOnClick) {
  249. case "none":
  250. break;
  251. case "select":
  252. selectedCaret = { begin: 0, end: getBuffer.call(inputmask).length };
  253. break;
  254. case "ignore":
  255. selectedCaret.end = selectedCaret.begin = seekNext.call(
  256. inputmask,
  257. getLastValidPosition.call(inputmask)
  258. );
  259. break;
  260. case "radixFocus":
  261. if (inputmask.clicked > 1 && maskset.validPositions.length === 0) break;
  262. if (doRadixFocus(selectedCaret.begin)) {
  263. const radixPos = getBuffer
  264. .call(inputmask)
  265. .join("")
  266. .indexOf(opts.radixPoint);
  267. selectedCaret.end = selectedCaret.begin = opts.numericInput
  268. ? seekNext.call(inputmask, radixPos)
  269. : radixPos;
  270. break;
  271. } // fallback to lvp
  272. // eslint-disable-next-line no-fallthrough
  273. default: // lvp:
  274. clickPosition = selectedCaret.begin;
  275. lvclickPosition = getLastValidPosition.call(
  276. inputmask,
  277. clickPosition,
  278. true
  279. );
  280. lastPosition = seekNext.call(
  281. inputmask,
  282. lvclickPosition === -1 && !isMask.call(inputmask, 0)
  283. ? -1
  284. : lvclickPosition
  285. );
  286. if (clickPosition <= lastPosition) {
  287. selectedCaret.end = selectedCaret.begin = !isMask.call(
  288. inputmask,
  289. clickPosition,
  290. false,
  291. true
  292. )
  293. ? seekNext.call(inputmask, clickPosition)
  294. : clickPosition;
  295. } else {
  296. const lvp = maskset.validPositions[lvclickPosition],
  297. tt = getTestTemplate.call(
  298. inputmask,
  299. lastPosition,
  300. lvp ? lvp.match.locator : undefined,
  301. lvp
  302. ),
  303. placeholder = getPlaceholder.call(
  304. inputmask,
  305. lastPosition,
  306. tt.match
  307. );
  308. if (
  309. (placeholder !== "" &&
  310. getBuffer.call(inputmask)[lastPosition] !== placeholder &&
  311. tt.match.optionalQuantifier !== true &&
  312. tt.match.newBlockMarker !== true) ||
  313. (!isMask.call(inputmask, lastPosition, opts.keepStatic, true) &&
  314. tt.match.def === placeholder)
  315. ) {
  316. const newPos = seekNext.call(inputmask, lastPosition);
  317. if (clickPosition >= newPos || clickPosition === lastPosition) {
  318. lastPosition = newPos;
  319. }
  320. }
  321. selectedCaret.end = selectedCaret.begin = lastPosition;
  322. }
  323. }
  324. return selectedCaret;
  325. }
  326. }
  327. // tobe put on prototype?
  328. function getBuffer(noCache) {
  329. const inputmask = this,
  330. { maskset } = inputmask;
  331. if (maskset.buffer === undefined || noCache === true) {
  332. maskset.buffer = getMaskTemplate.call(
  333. inputmask,
  334. true,
  335. getLastValidPosition.call(inputmask),
  336. true
  337. );
  338. if (maskset._buffer === undefined) maskset._buffer = maskset.buffer.slice();
  339. }
  340. return maskset.buffer;
  341. }
  342. // tobe put on prototype?
  343. function getBufferTemplate() {
  344. const inputmask = this,
  345. maskset = this.maskset;
  346. if (maskset._buffer === undefined) {
  347. // generate template
  348. maskset._buffer = getMaskTemplate.call(inputmask, false, 1);
  349. if (maskset.buffer === undefined) maskset.buffer = maskset._buffer.slice();
  350. }
  351. return maskset._buffer;
  352. }
  353. // tobe put on prototype?
  354. function getLastValidPosition(closestTo, strict, validPositions) {
  355. const maskset = this.maskset;
  356. let before = -1,
  357. after = -1;
  358. const valids = validPositions || maskset.validPositions; // for use in valhook ~ context switch
  359. if (closestTo === undefined) closestTo = -1;
  360. for (let psNdx = 0, vpl = valids.length; psNdx < vpl; psNdx++) {
  361. if (valids[psNdx] && (strict || valids[psNdx].generatedInput !== true)) {
  362. if (psNdx <= closestTo) before = psNdx;
  363. if (psNdx >= closestTo) after = psNdx;
  364. }
  365. }
  366. return before === -1 || before === closestTo
  367. ? after
  368. : after === -1
  369. ? before
  370. : closestTo - before < after - closestTo
  371. ? before
  372. : after;
  373. }
  374. // tobe put on prototype?
  375. function isMask(pos, strict, fuzzy) {
  376. const inputmask = this,
  377. maskset = this.maskset;
  378. let test = getTestTemplate.call(inputmask, pos).match;
  379. if (test.def === "") test = getTest.call(inputmask, pos).match;
  380. if (test.static !== true) {
  381. return test.fn;
  382. }
  383. if (
  384. fuzzy === true &&
  385. maskset.validPositions[pos] !== undefined &&
  386. maskset.validPositions[pos].generatedInput !== true
  387. ) {
  388. return true;
  389. }
  390. if (strict !== true && pos > -1) {
  391. if (fuzzy) {
  392. // check on the number of tests
  393. const tests = getTests.call(inputmask, pos);
  394. return (
  395. tests.length > 1 + (tests[tests.length - 1].match.def === "" ? 1 : 0)
  396. );
  397. }
  398. // else based on the template
  399. const testTemplate = determineTestTemplate.call(
  400. inputmask,
  401. pos,
  402. getTests.call(inputmask, pos)
  403. ),
  404. testPlaceHolder = getPlaceholder.call(inputmask, pos, testTemplate.match);
  405. return testTemplate.match.def !== testPlaceHolder;
  406. }
  407. return false;
  408. }
  409. // tobe put on prototype?
  410. // soft ~ undefined reset validpositions; soft = false also reset tests; soft = true only reset the maskset
  411. function resetMaskSet(soft) {
  412. const maskset = this.maskset;
  413. maskset.buffer = undefined;
  414. if (soft !== true) {
  415. maskset.validPositions = [];
  416. maskset.p = 0;
  417. }
  418. if (soft === false) {
  419. maskset.tests = {};
  420. maskset.jitOffset = {};
  421. }
  422. }
  423. // tobe put on prototype?
  424. function seekNext(pos, newBlock, fuzzy) {
  425. const inputmask = this;
  426. if (fuzzy === undefined) fuzzy = true;
  427. let position = pos + 1;
  428. while (
  429. getTest.call(inputmask, position).match.def !== "" &&
  430. ((newBlock === true &&
  431. (getTest.call(inputmask, position).match.newBlockMarker !== true ||
  432. !isMask.call(inputmask, position, undefined, true))) ||
  433. (newBlock !== true &&
  434. !isMask.call(inputmask, position, undefined, fuzzy)))
  435. ) {
  436. position++;
  437. }
  438. return position;
  439. }
  440. // tobe put on prototype?
  441. function seekPrevious(pos, newBlock) {
  442. const inputmask = this;
  443. let position = pos - 1;
  444. if (pos <= 0) return 0;
  445. while (
  446. position > 0 &&
  447. ((newBlock === true &&
  448. (getTest.call(inputmask, position).match.newBlockMarker !== true ||
  449. !isMask.call(inputmask, position, undefined, true))) ||
  450. (newBlock !== true && !isMask.call(inputmask, position, undefined, true)))
  451. ) {
  452. position--;
  453. }
  454. return position;
  455. }
  456. // tobe put on prototype?
  457. function translatePosition(pos) {
  458. const inputmask = this,
  459. opts = this.opts,
  460. el = this.el;
  461. if (
  462. inputmask.isRTL &&
  463. typeof pos === "number" &&
  464. (!opts.greedy || opts.placeholder !== "") &&
  465. el
  466. ) {
  467. pos = inputmask._valueGet().length - pos;
  468. if (pos < 0) pos = 0;
  469. }
  470. return pos;
  471. }