UNPKG

@testing-library/user-event

Version:
156 lines (153 loc) 6.3 kB
import { setUIValue, clearInitialValue, getUIValue } from '../document/UI.js'; import { isElementType } from '../utils/misc/isElementType.js'; import '../utils/dataTransfer/Clipboard.js'; import { isValidDateOrTimeValue, buildTimeValue } from '../utils/edit/timeValue.js'; import { supportsMaxLength, getMaxLength } from '../utils/edit/maxLength.js'; import { getNextCursorPosition } from '../utils/focus/cursor.js'; import { commitValueAfterInput } from '../document/trackValue.js'; import { getInputRange } from './selection/getInputRange.js'; import { setSelection } from './selection/setSelection.js'; function isDateOrTime(element) { return isElementType(element, 'input') && [ 'date', 'time' ].includes(element.type); } function input(instance, element, data, inputType = 'insertText') { const inputRange = getInputRange(element); /* istanbul ignore if */ if (!inputRange) { return; } // There is no `beforeinput` event on `date` and `time` input if (!isDateOrTime(element)) { const unprevented = instance.dispatchUIEvent(element, 'beforeinput', { inputType, data }); if (!unprevented) { return; } } if ('startContainer' in inputRange) { editContenteditable(instance, element, inputRange, data, inputType); } else { editInputElement(instance, element, inputRange, data, inputType); } } function editContenteditable(instance, element, inputRange, data, inputType) { let del = false; if (!inputRange.collapsed) { del = true; inputRange.deleteContents(); } else if ([ 'deleteContentBackward', 'deleteContentForward' ].includes(inputType)) { const nextPosition = getNextCursorPosition(inputRange.startContainer, inputRange.startOffset, inputType === 'deleteContentBackward' ? -1 : 1, inputType); if (nextPosition) { del = true; const delRange = inputRange.cloneRange(); if (delRange.comparePoint(nextPosition.node, nextPosition.offset) < 0) { delRange.setStart(nextPosition.node, nextPosition.offset); } else { delRange.setEnd(nextPosition.node, nextPosition.offset); } delRange.deleteContents(); } } if (data) { if (inputRange.endContainer.nodeType === 3) { const offset = inputRange.endOffset; inputRange.endContainer.insertData(offset, data); inputRange.setStart(inputRange.endContainer, offset + data.length); inputRange.setEnd(inputRange.endContainer, offset + data.length); } else { const text = element.ownerDocument.createTextNode(data); inputRange.insertNode(text); inputRange.setStart(text, data.length); inputRange.setEnd(text, data.length); } } if (del || data) { instance.dispatchUIEvent(element, 'input', { inputType }); } } function editInputElement(instance, element, inputRange, data, inputType) { let dataToInsert = data; if (supportsMaxLength(element)) { const maxLength = getMaxLength(element); if (maxLength !== undefined && data.length > 0) { const spaceUntilMaxLength = maxLength - element.value.length; if (spaceUntilMaxLength > 0) { dataToInsert = data.substring(0, spaceUntilMaxLength); } else { return; } } } const { newValue, newOffset, oldValue } = calculateNewValue(dataToInsert, element, inputRange, inputType); if (newValue === oldValue && newOffset === inputRange.startOffset && newOffset === inputRange.endOffset) { return; } if (isElementType(element, 'input', { type: 'number' }) && !isValidNumberInput(newValue)) { return; } setUIValue(element, newValue); setSelection({ focusNode: element, anchorOffset: newOffset, focusOffset: newOffset }); if (isDateOrTime(element)) { if (isValidDateOrTimeValue(element, newValue)) { commitInput(instance, element, newOffset, {}); instance.dispatchUIEvent(element, 'change'); clearInitialValue(element); } } else { commitInput(instance, element, newOffset, { data, inputType }); } } function calculateNewValue(inputData, node, { startOffset, endOffset }, inputType) { const value = getUIValue(node); const prologEnd = Math.max(0, startOffset === endOffset && inputType === 'deleteContentBackward' ? startOffset - 1 : startOffset); const prolog = value.substring(0, prologEnd); const epilogStart = Math.min(value.length, startOffset === endOffset && inputType === 'deleteContentForward' ? startOffset + 1 : endOffset); const epilog = value.substring(epilogStart, value.length); let newValue = `${prolog}${inputData}${epilog}`; let newOffset = prologEnd + inputData.length; if (isElementType(node, 'input', { type: 'time' })) { const builtValue = buildTimeValue(newValue); if (builtValue !== '' && isValidDateOrTimeValue(node, builtValue)) { newValue = builtValue; newOffset = builtValue.length; } } return { oldValue: value, newValue, newOffset }; } function commitInput(instance, element, newOffset, inputInit) { instance.dispatchUIEvent(element, 'input', inputInit); commitValueAfterInput(element, newOffset); } function isValidNumberInput(value) { var _value_match, _value_match1; // the browser allows some invalid input but not others // it allows up to two '-' at any place before any 'e' or one directly following 'e' // it allows one '.' at any place before e const valueParts = value.split('e', 2); 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])); } export { input };