positioning.js 14 KB

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