input.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. 'use strict';
  2. var UI = require('../document/UI.js');
  3. var isElementType = require('../utils/misc/isElementType.js');
  4. require('../utils/dataTransfer/Clipboard.js');
  5. var timeValue = require('../utils/edit/timeValue.js');
  6. var maxLength = require('../utils/edit/maxLength.js');
  7. var cursor = require('../utils/focus/cursor.js');
  8. var trackValue = require('../document/trackValue.js');
  9. var getInputRange = require('./selection/getInputRange.js');
  10. var setSelection = require('./selection/setSelection.js');
  11. function isDateOrTime(element) {
  12. return isElementType.isElementType(element, 'input') && [
  13. 'date',
  14. 'time'
  15. ].includes(element.type);
  16. }
  17. function input(instance, element, data, inputType = 'insertText') {
  18. const inputRange = getInputRange.getInputRange(element);
  19. /* istanbul ignore if */ if (!inputRange) {
  20. return;
  21. }
  22. // There is no `beforeinput` event on `date` and `time` input
  23. if (!isDateOrTime(element)) {
  24. const unprevented = instance.dispatchUIEvent(element, 'beforeinput', {
  25. inputType,
  26. data
  27. });
  28. if (!unprevented) {
  29. return;
  30. }
  31. }
  32. if ('startContainer' in inputRange) {
  33. editContenteditable(instance, element, inputRange, data, inputType);
  34. } else {
  35. editInputElement(instance, element, inputRange, data, inputType);
  36. }
  37. }
  38. function editContenteditable(instance, element, inputRange, data, inputType) {
  39. let del = false;
  40. if (!inputRange.collapsed) {
  41. del = true;
  42. inputRange.deleteContents();
  43. } else if ([
  44. 'deleteContentBackward',
  45. 'deleteContentForward'
  46. ].includes(inputType)) {
  47. const nextPosition = cursor.getNextCursorPosition(inputRange.startContainer, inputRange.startOffset, inputType === 'deleteContentBackward' ? -1 : 1, inputType);
  48. if (nextPosition) {
  49. del = true;
  50. const delRange = inputRange.cloneRange();
  51. if (delRange.comparePoint(nextPosition.node, nextPosition.offset) < 0) {
  52. delRange.setStart(nextPosition.node, nextPosition.offset);
  53. } else {
  54. delRange.setEnd(nextPosition.node, nextPosition.offset);
  55. }
  56. delRange.deleteContents();
  57. }
  58. }
  59. if (data) {
  60. if (inputRange.endContainer.nodeType === 3) {
  61. const offset = inputRange.endOffset;
  62. inputRange.endContainer.insertData(offset, data);
  63. inputRange.setStart(inputRange.endContainer, offset + data.length);
  64. inputRange.setEnd(inputRange.endContainer, offset + data.length);
  65. } else {
  66. const text = element.ownerDocument.createTextNode(data);
  67. inputRange.insertNode(text);
  68. inputRange.setStart(text, data.length);
  69. inputRange.setEnd(text, data.length);
  70. }
  71. }
  72. if (del || data) {
  73. instance.dispatchUIEvent(element, 'input', {
  74. inputType
  75. });
  76. }
  77. }
  78. function editInputElement(instance, element, inputRange, data, inputType) {
  79. let dataToInsert = data;
  80. if (maxLength.supportsMaxLength(element)) {
  81. const maxLength$1 = maxLength.getMaxLength(element);
  82. if (maxLength$1 !== undefined && data.length > 0) {
  83. const spaceUntilMaxLength = maxLength$1 - element.value.length;
  84. if (spaceUntilMaxLength > 0) {
  85. dataToInsert = data.substring(0, spaceUntilMaxLength);
  86. } else {
  87. return;
  88. }
  89. }
  90. }
  91. const { newValue, newOffset, oldValue } = calculateNewValue(dataToInsert, element, inputRange, inputType);
  92. if (newValue === oldValue && newOffset === inputRange.startOffset && newOffset === inputRange.endOffset) {
  93. return;
  94. }
  95. if (isElementType.isElementType(element, 'input', {
  96. type: 'number'
  97. }) && !isValidNumberInput(newValue)) {
  98. return;
  99. }
  100. UI.setUIValue(element, newValue);
  101. setSelection.setSelection({
  102. focusNode: element,
  103. anchorOffset: newOffset,
  104. focusOffset: newOffset
  105. });
  106. if (isDateOrTime(element)) {
  107. if (timeValue.isValidDateOrTimeValue(element, newValue)) {
  108. commitInput(instance, element, newOffset, {});
  109. instance.dispatchUIEvent(element, 'change');
  110. UI.clearInitialValue(element);
  111. }
  112. } else {
  113. commitInput(instance, element, newOffset, {
  114. data,
  115. inputType
  116. });
  117. }
  118. }
  119. function calculateNewValue(inputData, node, { startOffset, endOffset }, inputType) {
  120. const value = UI.getUIValue(node);
  121. const prologEnd = Math.max(0, startOffset === endOffset && inputType === 'deleteContentBackward' ? startOffset - 1 : startOffset);
  122. const prolog = value.substring(0, prologEnd);
  123. const epilogStart = Math.min(value.length, startOffset === endOffset && inputType === 'deleteContentForward' ? startOffset + 1 : endOffset);
  124. const epilog = value.substring(epilogStart, value.length);
  125. let newValue = `${prolog}${inputData}${epilog}`;
  126. let newOffset = prologEnd + inputData.length;
  127. if (isElementType.isElementType(node, 'input', {
  128. type: 'time'
  129. })) {
  130. const builtValue = timeValue.buildTimeValue(newValue);
  131. if (builtValue !== '' && timeValue.isValidDateOrTimeValue(node, builtValue)) {
  132. newValue = builtValue;
  133. newOffset = builtValue.length;
  134. }
  135. }
  136. return {
  137. oldValue: value,
  138. newValue,
  139. newOffset
  140. };
  141. }
  142. function commitInput(instance, element, newOffset, inputInit) {
  143. instance.dispatchUIEvent(element, 'input', inputInit);
  144. trackValue.commitValueAfterInput(element, newOffset);
  145. }
  146. function isValidNumberInput(value) {
  147. var _value_match, _value_match1;
  148. // the browser allows some invalid input but not others
  149. // it allows up to two '-' at any place before any 'e' or one directly following 'e'
  150. // it allows one '.' at any place before e
  151. const valueParts = value.split('e', 2);
  152. return !(/[^\d.\-e]/.test(value) || Number((_value_match = value.match(/-/g)) === null || _value_match === undefined ? undefined : _value_match.length) > 2 || Number((_value_match1 = value.match(/\./g)) === null || _value_match1 === undefined ? undefined : _value_match1.length) > 1 || valueParts[1] && !/^-?\d*$/.test(valueParts[1]));
  153. }
  154. exports.input = input;