257 lines
15 KiB
JavaScript
257 lines
15 KiB
JavaScript
define(["require", "exports", "tslib", "react", "../TextField", "../../../Utilities", "./inputMask", "@fluentui/react-hooks"], function (require, exports, tslib_1, React, TextField_1, Utilities_1, inputMask_1, react_hooks_1) {
|
|
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.MaskedTextField = exports.DEFAULT_MASK_CHAR = void 0;
|
|
var COMPONENT_NAME = 'MaskedTextField';
|
|
var useComponentRef = function (componentRef, internalState, textField) {
|
|
React.useImperativeHandle(componentRef, function () { return ({
|
|
get value() {
|
|
var value = '';
|
|
for (var i = 0; i < internalState.maskCharData.length; i++) {
|
|
if (!internalState.maskCharData[i].value) {
|
|
return undefined;
|
|
}
|
|
value += internalState.maskCharData[i].value;
|
|
}
|
|
return value;
|
|
},
|
|
get selectionStart() {
|
|
return textField.current && textField.current.selectionStart !== null ? textField.current.selectionStart : -1;
|
|
},
|
|
get selectionEnd() {
|
|
return textField.current && textField.current.selectionEnd ? textField.current.selectionEnd : -1;
|
|
},
|
|
focus: function () {
|
|
textField.current && textField.current.focus();
|
|
},
|
|
blur: function () {
|
|
textField.current && textField.current.blur();
|
|
},
|
|
select: function () {
|
|
textField.current && textField.current.select();
|
|
},
|
|
setSelectionStart: function (value) {
|
|
textField.current && textField.current.setSelectionStart(value);
|
|
},
|
|
setSelectionEnd: function (value) {
|
|
textField.current && textField.current.setSelectionEnd(value);
|
|
},
|
|
setSelectionRange: function (start, end) {
|
|
textField.current && textField.current.setSelectionRange(start, end);
|
|
},
|
|
}); }, [internalState, textField]);
|
|
};
|
|
exports.DEFAULT_MASK_CHAR = '_';
|
|
exports.MaskedTextField = React.forwardRef(function (props, ref) {
|
|
var textField = React.useRef(null);
|
|
var componentRef = props.componentRef, onFocus = props.onFocus, onBlur = props.onBlur, onMouseDown = props.onMouseDown, onMouseUp = props.onMouseUp, onChange = props.onChange, onPaste = props.onPaste, onKeyDown = props.onKeyDown, mask = props.mask, _a = props.maskChar, maskChar = _a === void 0 ? exports.DEFAULT_MASK_CHAR : _a, _b = props.maskFormat, maskFormat = _b === void 0 ? inputMask_1.DEFAULT_MASK_FORMAT_CHARS : _b, value = props.value;
|
|
var internalState = (0, react_hooks_1.useConst)(function () { return ({
|
|
maskCharData: (0, inputMask_1.parseMask)(mask, maskFormat),
|
|
isFocused: false,
|
|
moveCursorOnMouseUp: false,
|
|
changeSelectionData: null,
|
|
}); });
|
|
/** The index into the rendered value of the first unfilled format character */
|
|
var _c = React.useState(), maskCursorPosition = _c[0], setMaskCursorPosition = _c[1];
|
|
/**
|
|
* The mask string formatted with the input value.
|
|
* This is what is displayed inside the TextField
|
|
* @example
|
|
* `Phone Number: 12_ - 4___`
|
|
*/
|
|
var _d = React.useState(function () {
|
|
return (0, inputMask_1.getMaskDisplay)(mask, internalState.maskCharData, maskChar);
|
|
}), displayValue = _d[0], setDisplayValue = _d[1];
|
|
var setValue = React.useCallback(function (newValue) {
|
|
var valueIndex = 0;
|
|
var charDataIndex = 0;
|
|
while (valueIndex < newValue.length && charDataIndex < internalState.maskCharData.length) {
|
|
// Test if the next character in the new value fits the next format character
|
|
var testVal = newValue[valueIndex];
|
|
if (internalState.maskCharData[charDataIndex].format.test(testVal)) {
|
|
internalState.maskCharData[charDataIndex].value = testVal;
|
|
charDataIndex++;
|
|
}
|
|
valueIndex++;
|
|
}
|
|
}, [internalState]);
|
|
var handleFocus = React.useCallback(function (ev) {
|
|
onFocus === null || onFocus === void 0 ? void 0 : onFocus(ev);
|
|
internalState.isFocused = true;
|
|
// Move the cursor position to the leftmost unfilled position
|
|
for (var i = 0; i < internalState.maskCharData.length; i++) {
|
|
if (!internalState.maskCharData[i].value) {
|
|
setMaskCursorPosition(internalState.maskCharData[i].displayIndex);
|
|
break;
|
|
}
|
|
}
|
|
}, [internalState, onFocus]);
|
|
var handleBlur = React.useCallback(function (ev) {
|
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(ev);
|
|
internalState.isFocused = false;
|
|
internalState.moveCursorOnMouseUp = true;
|
|
}, [internalState, onBlur]);
|
|
var handleMouseDown = React.useCallback(function (ev) {
|
|
onMouseDown === null || onMouseDown === void 0 ? void 0 : onMouseDown(ev);
|
|
if (!internalState.isFocused) {
|
|
internalState.moveCursorOnMouseUp = true;
|
|
}
|
|
}, [internalState, onMouseDown]);
|
|
var handleMouseUp = React.useCallback(function (ev) {
|
|
onMouseUp === null || onMouseUp === void 0 ? void 0 : onMouseUp(ev);
|
|
// Move the cursor on mouseUp after focusing the textField
|
|
if (internalState.moveCursorOnMouseUp) {
|
|
internalState.moveCursorOnMouseUp = false;
|
|
// Move the cursor position to the rightmost unfilled position
|
|
for (var i = 0; i < internalState.maskCharData.length; i++) {
|
|
if (!internalState.maskCharData[i].value) {
|
|
setMaskCursorPosition(internalState.maskCharData[i].displayIndex);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}, [internalState, onMouseUp]);
|
|
var handleInputChange = React.useCallback(function (ev, inputValue) {
|
|
if (internalState.changeSelectionData === null && textField.current) {
|
|
internalState.changeSelectionData = {
|
|
changeType: 'default',
|
|
selectionStart: textField.current.selectionStart !== null ? textField.current.selectionStart : -1,
|
|
selectionEnd: textField.current.selectionEnd !== null ? textField.current.selectionEnd : -1,
|
|
};
|
|
}
|
|
if (!internalState.changeSelectionData) {
|
|
return;
|
|
}
|
|
// The initial value of cursorPos does not matter
|
|
var cursorPos = 0;
|
|
var _a = internalState.changeSelectionData, changeType = _a.changeType, selectionStart = _a.selectionStart, selectionEnd = _a.selectionEnd;
|
|
if (changeType === 'textPasted') {
|
|
var charsSelected = selectionEnd - selectionStart;
|
|
var charCount = inputValue.length + charsSelected - displayValue.length;
|
|
var startPos = selectionStart;
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
var pastedString = inputValue.substr(startPos, charCount);
|
|
// Clear any selected characters
|
|
if (charsSelected) {
|
|
internalState.maskCharData = (0, inputMask_1.clearRange)(internalState.maskCharData, selectionStart, charsSelected);
|
|
}
|
|
cursorPos = (0, inputMask_1.insertString)(internalState.maskCharData, startPos, pastedString);
|
|
}
|
|
else if (changeType === 'delete' || changeType === 'backspace') {
|
|
// isDel is true If the characters are removed LTR, otherwise RTL
|
|
var isDel = changeType === 'delete';
|
|
var charCount = selectionEnd - selectionStart;
|
|
if (charCount) {
|
|
// charCount is > 0 if range was deleted
|
|
internalState.maskCharData = (0, inputMask_1.clearRange)(internalState.maskCharData, selectionStart, charCount);
|
|
cursorPos = (0, inputMask_1.getRightFormatIndex)(internalState.maskCharData, selectionStart);
|
|
}
|
|
else {
|
|
// If charCount === 0, there was no selection and a single character was deleted
|
|
if (isDel) {
|
|
internalState.maskCharData = (0, inputMask_1.clearNext)(internalState.maskCharData, selectionStart);
|
|
cursorPos = (0, inputMask_1.getRightFormatIndex)(internalState.maskCharData, selectionStart);
|
|
}
|
|
else {
|
|
internalState.maskCharData = (0, inputMask_1.clearPrev)(internalState.maskCharData, selectionStart);
|
|
cursorPos = (0, inputMask_1.getLeftFormatIndex)(internalState.maskCharData, selectionStart);
|
|
}
|
|
}
|
|
}
|
|
else if (inputValue.length > displayValue.length) {
|
|
// This case is if the user added characters
|
|
var charCount = inputValue.length - displayValue.length;
|
|
var startPos = selectionEnd - charCount;
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
var enteredString = inputValue.substr(startPos, charCount);
|
|
cursorPos = (0, inputMask_1.insertString)(internalState.maskCharData, startPos, enteredString);
|
|
}
|
|
else if (inputValue.length <= displayValue.length) {
|
|
/**
|
|
* This case is reached only if the user has selected a block of 1 or more
|
|
* characters and input a character replacing the characters they've selected.
|
|
*/
|
|
var charCount = 1;
|
|
var selectCount = displayValue.length + charCount - inputValue.length;
|
|
var startPos = selectionEnd - charCount;
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
var enteredString = inputValue.substr(startPos, charCount);
|
|
// Clear the selected range
|
|
internalState.maskCharData = (0, inputMask_1.clearRange)(internalState.maskCharData, startPos, selectCount);
|
|
// Insert the printed character
|
|
cursorPos = (0, inputMask_1.insertString)(internalState.maskCharData, startPos, enteredString);
|
|
}
|
|
internalState.changeSelectionData = null;
|
|
var newValue = (0, inputMask_1.getMaskDisplay)(mask, internalState.maskCharData, maskChar);
|
|
setDisplayValue(newValue);
|
|
setMaskCursorPosition(cursorPos);
|
|
// Perform onChange after input has been processed. Return value is expected to be the displayed text
|
|
onChange === null || onChange === void 0 ? void 0 : onChange(ev, newValue);
|
|
}, [displayValue.length, internalState, mask, maskChar, onChange]);
|
|
var handleKeyDown = React.useCallback(function (ev) {
|
|
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(ev);
|
|
internalState.changeSelectionData = null;
|
|
if (textField.current && textField.current.value) {
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
var keyCode = ev.keyCode, ctrlKey = ev.ctrlKey, metaKey = ev.metaKey;
|
|
// Ignore ctrl and meta keydown
|
|
if (ctrlKey || metaKey) {
|
|
return;
|
|
}
|
|
// On backspace or delete, store the selection and the keyCode
|
|
if (keyCode === Utilities_1.KeyCodes.backspace || keyCode === Utilities_1.KeyCodes.del) {
|
|
var selectionStart = ev.target.selectionStart;
|
|
var selectionEnd = ev.target.selectionEnd;
|
|
// Check if backspace or delete press is valid.
|
|
if (!(keyCode === Utilities_1.KeyCodes.backspace && selectionEnd && selectionEnd > 0) &&
|
|
!(keyCode === Utilities_1.KeyCodes.del && selectionStart !== null && selectionStart < textField.current.value.length)) {
|
|
return;
|
|
}
|
|
internalState.changeSelectionData = {
|
|
changeType: keyCode === Utilities_1.KeyCodes.backspace ? 'backspace' : 'delete',
|
|
selectionStart: selectionStart !== null ? selectionStart : -1,
|
|
selectionEnd: selectionEnd !== null ? selectionEnd : -1,
|
|
};
|
|
}
|
|
}
|
|
}, [internalState, onKeyDown]);
|
|
var handlePaste = React.useCallback(function (ev) {
|
|
onPaste === null || onPaste === void 0 ? void 0 : onPaste(ev);
|
|
var selectionStart = ev.target.selectionStart;
|
|
var selectionEnd = ev.target.selectionEnd;
|
|
// Store the paste selection range
|
|
internalState.changeSelectionData = {
|
|
changeType: 'textPasted',
|
|
selectionStart: selectionStart !== null ? selectionStart : -1,
|
|
selectionEnd: selectionEnd !== null ? selectionEnd : -1,
|
|
};
|
|
}, [internalState, onPaste]);
|
|
// Updates the display value if mask or value props change.
|
|
React.useEffect(function () {
|
|
internalState.maskCharData = (0, inputMask_1.parseMask)(mask, maskFormat);
|
|
value !== undefined && setValue(value);
|
|
setDisplayValue((0, inputMask_1.getMaskDisplay)(mask, internalState.maskCharData, maskChar));
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- Should only update when mask or value changes.
|
|
}, [mask, value]);
|
|
// Run before browser paint to avoid flickering from selection reset.
|
|
(0, react_hooks_1.useIsomorphicLayoutEffect)(function () {
|
|
// Move the cursor to position before paint.
|
|
if (maskCursorPosition !== undefined && textField.current) {
|
|
textField.current.setSelectionRange(maskCursorPosition, maskCursorPosition);
|
|
}
|
|
}, [maskCursorPosition]);
|
|
// Run after browser paint.
|
|
React.useEffect(function () {
|
|
// Move the cursor to the start of the mask format after values update.
|
|
if (internalState.isFocused && maskCursorPosition !== undefined && textField.current) {
|
|
textField.current.setSelectionRange(maskCursorPosition, maskCursorPosition);
|
|
}
|
|
});
|
|
useComponentRef(componentRef, internalState, textField);
|
|
return (React.createElement(TextField_1.TextField, tslib_1.__assign({}, props, {
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
elementRef: ref, onFocus: handleFocus, onBlur: handleBlur, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onChange: handleInputChange, onKeyDown: handleKeyDown, onPaste: handlePaste, value: displayValue || '', componentRef: textField })));
|
|
});
|
|
exports.MaskedTextField.displayName = COMPONENT_NAME;
|
|
});
|
|
//# sourceMappingURL=MaskedTextField.js.map
|