first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
@@ -0,0 +1,4 @@
import * as React from 'react';
import type { IMaskedTextFieldProps } from '../TextField.types';
export declare const DEFAULT_MASK_CHAR = "_";
export declare const MaskedTextField: React.FunctionComponent<IMaskedTextFieldProps>;
@@ -0,0 +1,258 @@
import { __assign } from "tslib";
import * as React from 'react';
import { TextField } from '../TextField';
import { KeyCodes } from '../../../Utilities';
import { clearNext, clearPrev, clearRange, DEFAULT_MASK_FORMAT_CHARS, getLeftFormatIndex, getMaskDisplay, getRightFormatIndex, insertString, parseMask, } from './inputMask';
import { useConst, useIsomorphicLayoutEffect } from '@fluentui/react-hooks';
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]);
};
export var DEFAULT_MASK_CHAR = '_';
export var 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 ? DEFAULT_MASK_CHAR : _a, _b = props.maskFormat, maskFormat = _b === void 0 ? DEFAULT_MASK_FORMAT_CHARS : _b, value = props.value;
var internalState = useConst(function () { return ({
maskCharData: 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 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 = clearRange(internalState.maskCharData, selectionStart, charsSelected);
}
cursorPos = 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 = clearRange(internalState.maskCharData, selectionStart, charCount);
cursorPos = getRightFormatIndex(internalState.maskCharData, selectionStart);
}
else {
// If charCount === 0, there was no selection and a single character was deleted
if (isDel) {
internalState.maskCharData = clearNext(internalState.maskCharData, selectionStart);
cursorPos = getRightFormatIndex(internalState.maskCharData, selectionStart);
}
else {
internalState.maskCharData = clearPrev(internalState.maskCharData, selectionStart);
cursorPos = 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 = 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 = clearRange(internalState.maskCharData, startPos, selectCount);
// Insert the printed character
cursorPos = insertString(internalState.maskCharData, startPos, enteredString);
}
internalState.changeSelectionData = null;
var newValue = 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 === KeyCodes.backspace || keyCode === KeyCodes.del) {
var selectionStart = ev.target.selectionStart;
var selectionEnd = ev.target.selectionEnd;
// Check if backspace or delete press is valid.
if (!(keyCode === KeyCodes.backspace && selectionEnd && selectionEnd > 0) &&
!(keyCode === KeyCodes.del && selectionStart !== null && selectionStart < textField.current.value.length)) {
return;
}
internalState.changeSelectionData = {
changeType: keyCode === 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 = parseMask(mask, maskFormat);
value !== undefined && setValue(value);
setDisplayValue(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.
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, __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 })));
});
MaskedTextField.displayName = COMPONENT_NAME;
//# sourceMappingURL=MaskedTextField.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,96 @@
export interface IMaskValue {
value?: string;
/**
* This index refers to the index in the displayMask rather than the inputMask.
* This means that any escaped characters do not count toward this index.
*/
displayIndex: number;
format: RegExp;
}
export declare const DEFAULT_MASK_FORMAT_CHARS: {
[key: string]: RegExp;
};
/**
* Takes in the mask string and the formatCharacters and returns an array of MaskValues
* Example:
* mask = 'Phone Number: (999) - 9999'
* return = [
* { value: undefined, displayIndex: 16, format: /[0-9]/ },
* { value: undefined, displayIndex: 17, format: /[0-9]/ },
* { value: undefined, displayIndex: 18, format: /[0-9]/ },
* { value: undefined, displayIndex: 22, format: /[0-9]/ },
* ]
*
* @param mask The string use to define the format of the displayed maskedValue.
* @param formatChars An object defining how certain characters in the mask should accept input.
*/
export declare function parseMask(mask: string | undefined, formatChars?: {
[key: string]: RegExp;
}): IMaskValue[];
/**
* Takes in the mask string, an array of MaskValues, and the maskCharacter
* returns the mask string formatted with the input values and maskCharacter.
* If the maskChar is undefined, the maskDisplay is truncated to the last filled format character.
* Example:
* mask = 'Phone Number: (999) 999 - 9999'
* maskCharData = '12345'
* maskChar = '_'
* return = 'Phone Number: (123) 45_ - ___'
*
* Example:
* mask = 'Phone Number: (999) 999 - 9999'
* value = '12345'
* maskChar = undefined
* return = 'Phone Number: (123) 45'
*
* @param mask The string use to define the format of the displayed maskedValue.
* @param maskCharData The input values to insert into the mask string for displaying.
* @param maskChar? A character to display in place of unfilled mask format characters.
*/
export declare function getMaskDisplay(mask: string | undefined, maskCharData: IMaskValue[], maskChar?: string): string;
/**
* Get the next format index right of or at a specified index.
* If no index exists, returns the rightmost index.
* @param maskCharData
* @param index
*/
export declare function getRightFormatIndex(maskCharData: IMaskValue[], index: number): number;
/**
* Get the next format index left of a specified index.
* If no index exists, returns the leftmost index.
* @param maskCharData
* @param index
*/
export declare function getLeftFormatIndex(maskCharData: IMaskValue[], index: number): number;
/**
* Deletes all values in maskCharData with a displayIndex that falls inside the specified range.
* maskCharData is modified inline and also returned.
* @param maskCharData
* @param selectionStart
* @param selectionCount
*/
export declare function clearRange(maskCharData: IMaskValue[], selectionStart: number, selectionCount: number): IMaskValue[];
/**
* Deletes the input character at or after a specified index and returns the new array of charData
* maskCharData is modified inline and also returned.
* @param maskCharData
* @param selectionStart
*/
export declare function clearNext(maskCharData: IMaskValue[], selectionStart: number): IMaskValue[];
/**
* Deletes the input character before a specified index and returns the new array of charData
* maskCharData is modified inline and also returned.
* @param maskCharData
* @param selectionStart
*/
export declare function clearPrev(maskCharData: IMaskValue[], selectionStart: number): IMaskValue[];
/**
* Deletes all values in maskCharData with a displayIndex that falls inside the specified range.
* Modifies the maskCharData inplace with the passed string and returns the display index of the
* next format character after the inserted string.
* @param maskCharData
* @param selectionStart
* @param selectionCount
* @return The displayIndex of the next format character
*/
export declare function insertString(maskCharData: IMaskValue[], selectionStart: number, newString: string): number;
@@ -0,0 +1,225 @@
export var DEFAULT_MASK_FORMAT_CHARS = {
'9': /[0-9]/,
a: /[a-zA-Z]/,
'*': /[a-zA-Z0-9]/,
};
/**
* Takes in the mask string and the formatCharacters and returns an array of MaskValues
* Example:
* mask = 'Phone Number: (999) - 9999'
* return = [
* { value: undefined, displayIndex: 16, format: /[0-9]/ },
* { value: undefined, displayIndex: 17, format: /[0-9]/ },
* { value: undefined, displayIndex: 18, format: /[0-9]/ },
* { value: undefined, displayIndex: 22, format: /[0-9]/ },
* ]
*
* @param mask The string use to define the format of the displayed maskedValue.
* @param formatChars An object defining how certain characters in the mask should accept input.
*/
export function parseMask(mask, formatChars) {
if (formatChars === void 0) { formatChars = DEFAULT_MASK_FORMAT_CHARS; }
if (!mask) {
return [];
}
var maskCharData = [];
// Count the escape characters in the mask string.
var escapedChars = 0;
for (var i = 0; i + escapedChars < mask.length; i++) {
var maskChar = mask.charAt(i + escapedChars);
if (maskChar === '\\') {
escapedChars++;
}
else {
// Check if the maskChar is a format character.
var maskFormat = formatChars[maskChar];
if (maskFormat) {
maskCharData.push({
/**
* Do not add escapedChars to the displayIndex.
* The index refers to a position in the mask's displayValue.
* Since the backslashes don't appear in the displayValue,
* we do not add them to the charData displayIndex.
*/
displayIndex: i,
format: maskFormat,
});
}
}
}
return maskCharData;
}
/**
* Takes in the mask string, an array of MaskValues, and the maskCharacter
* returns the mask string formatted with the input values and maskCharacter.
* If the maskChar is undefined, the maskDisplay is truncated to the last filled format character.
* Example:
* mask = 'Phone Number: (999) 999 - 9999'
* maskCharData = '12345'
* maskChar = '_'
* return = 'Phone Number: (123) 45_ - ___'
*
* Example:
* mask = 'Phone Number: (999) 999 - 9999'
* value = '12345'
* maskChar = undefined
* return = 'Phone Number: (123) 45'
*
* @param mask The string use to define the format of the displayed maskedValue.
* @param maskCharData The input values to insert into the mask string for displaying.
* @param maskChar? A character to display in place of unfilled mask format characters.
*/
export function getMaskDisplay(mask, maskCharData, maskChar) {
var maskDisplay = mask;
if (!maskDisplay) {
return '';
}
// Remove all backslashes
maskDisplay = maskDisplay.replace(/\\/g, '');
// lastDisplayIndex is is used to truncate the string if necessary.
var lastDisplayIndex = 0;
if (maskCharData.length > 0) {
lastDisplayIndex = maskCharData[0].displayIndex - 1;
}
/**
* For each input value, replace the character in the maskDisplay with the value.
* If there is no value set for the format character, use the maskChar.
*/
for (var _i = 0, maskCharData_1 = maskCharData; _i < maskCharData_1.length; _i++) {
var charData = maskCharData_1[_i];
var nextChar = ' ';
if (charData.value) {
nextChar = charData.value;
if (charData.displayIndex > lastDisplayIndex) {
lastDisplayIndex = charData.displayIndex;
}
}
else {
if (maskChar) {
nextChar = maskChar;
}
}
// Insert the character into the maskdisplay at its corresponding index
maskDisplay = maskDisplay.slice(0, charData.displayIndex) + nextChar + maskDisplay.slice(charData.displayIndex + 1);
}
// Cut off all mask characters after the last filled format value
if (!maskChar) {
maskDisplay = maskDisplay.slice(0, lastDisplayIndex + 1);
}
return maskDisplay;
}
/**
* Get the next format index right of or at a specified index.
* If no index exists, returns the rightmost index.
* @param maskCharData
* @param index
*/
export function getRightFormatIndex(maskCharData, index) {
for (var i = 0; i < maskCharData.length; i++) {
if (maskCharData[i].displayIndex >= index) {
return maskCharData[i].displayIndex;
}
}
return maskCharData[maskCharData.length - 1].displayIndex;
}
/**
* Get the next format index left of a specified index.
* If no index exists, returns the leftmost index.
* @param maskCharData
* @param index
*/
export function getLeftFormatIndex(maskCharData, index) {
for (var i = maskCharData.length - 1; i >= 0; i--) {
if (maskCharData[i].displayIndex < index) {
return maskCharData[i].displayIndex;
}
}
return maskCharData[0].displayIndex;
}
/**
* Deletes all values in maskCharData with a displayIndex that falls inside the specified range.
* maskCharData is modified inline and also returned.
* @param maskCharData
* @param selectionStart
* @param selectionCount
*/
export function clearRange(maskCharData, selectionStart, selectionCount) {
for (var i = 0; i < maskCharData.length; i++) {
if (maskCharData[i].displayIndex >= selectionStart) {
if (maskCharData[i].displayIndex >= selectionStart + selectionCount) {
break;
}
maskCharData[i].value = undefined;
}
}
return maskCharData;
}
/**
* Deletes the input character at or after a specified index and returns the new array of charData
* maskCharData is modified inline and also returned.
* @param maskCharData
* @param selectionStart
*/
export function clearNext(maskCharData, selectionStart) {
for (var i = 0; i < maskCharData.length; i++) {
if (maskCharData[i].displayIndex >= selectionStart) {
maskCharData[i].value = undefined;
break;
}
}
return maskCharData;
}
/**
* Deletes the input character before a specified index and returns the new array of charData
* maskCharData is modified inline and also returned.
* @param maskCharData
* @param selectionStart
*/
export function clearPrev(maskCharData, selectionStart) {
for (var i = maskCharData.length - 1; i >= 0; i--) {
if (maskCharData[i].displayIndex < selectionStart) {
maskCharData[i].value = undefined;
break;
}
}
return maskCharData;
}
/**
* Deletes all values in maskCharData with a displayIndex that falls inside the specified range.
* Modifies the maskCharData inplace with the passed string and returns the display index of the
* next format character after the inserted string.
* @param maskCharData
* @param selectionStart
* @param selectionCount
* @return The displayIndex of the next format character
*/
export function insertString(maskCharData, selectionStart, newString) {
var stringIndex = 0;
var nextIndex = 0;
var isStringInserted = false;
// Iterate through _maskCharData finding values with a displayIndex after the specified range start
for (var i = 0; i < maskCharData.length && stringIndex < newString.length; i++) {
if (maskCharData[i].displayIndex >= selectionStart) {
isStringInserted = true;
nextIndex = maskCharData[i].displayIndex;
// Find the next character in the newString that matches the format
while (stringIndex < newString.length) {
// If the character matches the format regexp, set the maskCharData to the new character
if (maskCharData[i].format.test(newString.charAt(stringIndex))) {
maskCharData[i].value = newString.charAt(stringIndex++);
// Set the nextIndex to the display index of the next mask format character.
if (i + 1 < maskCharData.length) {
nextIndex = maskCharData[i + 1].displayIndex;
}
else {
nextIndex++;
}
break;
}
stringIndex++;
}
}
}
return isStringInserted ? nextIndex : selectionStart;
}
//# sourceMappingURL=inputMask.js.map
File diff suppressed because one or more lines are too long