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,257 @@
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
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,238 @@
define(["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_MASK_FORMAT_CHARS = void 0;
exports.parseMask = parseMask;
exports.getMaskDisplay = getMaskDisplay;
exports.getRightFormatIndex = getRightFormatIndex;
exports.getLeftFormatIndex = getLeftFormatIndex;
exports.clearRange = clearRange;
exports.clearNext = clearNext;
exports.clearPrev = clearPrev;
exports.insertString = insertString;
exports.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.
*/
function parseMask(mask, formatChars) {
if (formatChars === void 0) { formatChars = exports.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.
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
@@ -0,0 +1,126 @@
import * as React from 'react';
import type { ITextField, ITextFieldProps } from './TextField.types';
import type { JSXElement } from '@fluentui/utilities';
/** @internal */
export interface ITextFieldState {
/** The currently displayed value if uncontrolled. */
uncontrolledValue: string | undefined;
/** Is true when the control has focus. */
isFocused?: boolean;
/**
* Dynamic error message returned by `onGetErrorMessage`.
* Use `this._errorMessage` to get the actual current error message.
*/
errorMessage: string | JSXElement;
/**
* Whether this field has `type='password'` and `canRevealPassword=true`, and the password is
* currently being revealed.
*/
isRevealingPassword?: boolean;
}
/** @internal */
export interface ITextFieldSnapshot {
/**
* If set, the text field is changing between single- and multi-line, so we'll need to reset
* selection/cursor after the change completes.
*/
selection?: [number | null, number | null];
}
export declare class TextFieldBase extends React.Component<ITextFieldProps, ITextFieldState, ITextFieldSnapshot> implements ITextField {
static defaultProps: ITextFieldProps;
/** Fallback ID if none is provided in props. Access proper value via `this._id`. */
private _fallbackId;
private _descriptionId;
private _labelId;
private _prefixId;
private _suffixId;
private _delayedValidate;
private _lastValidation;
private _latestValidateValue;
private _hasWarnedNullValue;
private _textElement;
private _classNames;
private _async;
/** Most recent value from a change or input event, to help avoid processing events twice */
private _lastChangeValue;
constructor(props: ITextFieldProps);
/**
* Gets the current value of the text field.
*/
get value(): string | undefined;
componentDidMount(): void;
componentWillUnmount(): void;
getSnapshotBeforeUpdate(prevProps: ITextFieldProps, prevState: ITextFieldState): ITextFieldSnapshot | null;
componentDidUpdate(prevProps: ITextFieldProps, prevState: ITextFieldState, snapshot: ITextFieldSnapshot): void;
render(): JSXElement;
/**
* Sets focus on the text field
*/
focus(): void;
/**
* Blurs the text field.
*/
blur(): void;
/**
* Selects the text field
*/
select(): void;
/**
* Sets the selection start of the text field to a specified value
*/
setSelectionStart(value: number): void;
/**
* Sets the selection end of the text field to a specified value
*/
setSelectionEnd(value: number): void;
/**
* Gets the selection start of the text field
*/
get selectionStart(): number | null;
/**
* Gets the selection end of the text field
*/
get selectionEnd(): number | null;
/**
* Sets the start and end positions of a selection in a text field.
* @param start - Index of the start of the selection.
* @param end - Index of the end of the selection.
*/
setSelectionRange(start: number, end: number): void;
private _warnControlledUsage;
/** Returns `props.id` if available, or a fallback if not. */
private get _id();
private get _isControlled();
private _onFocus;
private _onBlur;
private _onRenderLabel;
private _onRenderDescription;
private _onRenderPrefix;
private _onRenderSuffix;
/**
* Current error message from either `props.errorMessage` or the result of `props.onGetErrorMessage`.
*
* - If there is no validation error or we have not validated the input value, errorMessage is an empty string.
* - If we have done the validation and there is validation error, errorMessage is the validation error message.
*/
private get _errorMessage();
/**
* Renders error message based on the type of the message.
*
* - If error message is string, it will render using the built in styles.
* - If error message is an element, user has full control over how it's rendered.
*/
private _renderErrorMessage;
/**
* If a custom description render function is supplied then treat description as always available.
* Otherwise defer to the presence of description or error message text.
*/
private get _isDescriptionAvailable();
private _renderTextArea;
private _renderInput;
private _onRevealButtonClick;
private _onInputChange;
private _validate;
private _notifyAfterValidate;
private _adjustInputHeight;
}
@@ -0,0 +1,465 @@
define(["require", "exports", "tslib", "react", "../../Label", "../../Icon", "../../Utilities"], function (require, exports, tslib_1, React, Label_1, Icon_1, Utilities_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextFieldBase = void 0;
var getClassNames = (0, Utilities_1.classNamesFunction)();
var DEFAULT_STATE_VALUE = '';
var COMPONENT_NAME = 'TextField';
var REVEAL_ICON_NAME = 'RedEye';
var HIDE_ICON_NAME = 'Hide';
var TextFieldBase = /** @class */ (function (_super) {
tslib_1.__extends(TextFieldBase, _super);
function TextFieldBase(props) {
var _this = _super.call(this, props) || this;
_this._textElement = React.createRef();
_this._onFocus = function (ev) {
if (_this.props.onFocus) {
_this.props.onFocus(ev);
}
_this.setState({ isFocused: true }, function () {
if (_this.props.validateOnFocusIn) {
_this._validate(_this.value);
}
});
};
_this._onBlur = function (ev) {
if (_this.props.onBlur) {
_this.props.onBlur(ev);
}
_this.setState({ isFocused: false }, function () {
if (_this.props.validateOnFocusOut) {
_this._validate(_this.value);
}
});
};
_this._onRenderLabel = function (props) {
var label = props.label, required = props.required;
// IProcessedStyleSet definition requires casting for what Label expects as its styles prop
var labelStyles = _this._classNames.subComponentStyles
? _this._classNames.subComponentStyles.label
: undefined;
if (label) {
return (React.createElement(Label_1.Label, { required: required, htmlFor: _this._id, styles: labelStyles, disabled: props.disabled, id: _this._labelId }, props.label));
}
return null;
};
_this._onRenderDescription = function (props) {
if (props.description) {
return React.createElement("span", { className: _this._classNames.description }, props.description);
}
return null;
};
_this._onRevealButtonClick = function (event) {
_this.setState(function (prevState) { return ({ isRevealingPassword: !prevState.isRevealingPassword }); });
};
_this._onInputChange = function (event) {
// Previously, we needed to call both onInput and onChange due to some weird IE/React issues,
// which have *probably* been fixed now:
// - https://github.com/microsoft/fluentui/issues/744 (likely fixed)
// - https://github.com/microsoft/fluentui/issues/824 (confirmed fixed)
var _a, _b;
// TODO (Fabric 8?) - Switch to calling only onChange. This switch is pretty disruptive for
// tests (ours and maybe consumers' too), so it seemed best to do the switch in a major bump.
var element = event.target;
var value = element.value;
// Ignore this event if any of the following are true:
// - the value is undefined (in case one of the IE bugs comes back)
// - it's a duplicate event (important since onInputChange is called twice per actual user event)
// - it's the same as the previous value
var previousValue = _getValue(_this.props, _this.state) || '';
if (value === undefined || value === _this._lastChangeValue || value === previousValue) {
_this._lastChangeValue = undefined;
return;
}
_this._lastChangeValue = value;
(_b = (_a = _this.props).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, event, value);
if (!_this._isControlled) {
// ONLY if this is an uncontrolled component, update the displayed value.
// (Controlled components must update the `value` prop from `onChange`.)
_this.setState({ uncontrolledValue: value });
}
};
(0, Utilities_1.initializeComponentRef)(_this);
_this._async = new Utilities_1.Async(_this);
_this._fallbackId = (0, Utilities_1.getId)(COMPONENT_NAME);
_this._descriptionId = (0, Utilities_1.getId)(COMPONENT_NAME + 'Description');
_this._labelId = (0, Utilities_1.getId)(COMPONENT_NAME + 'Label');
_this._prefixId = (0, Utilities_1.getId)(COMPONENT_NAME + 'Prefix');
_this._suffixId = (0, Utilities_1.getId)(COMPONENT_NAME + 'Suffix');
_this._warnControlledUsage();
var _a = props.defaultValue, defaultValue = _a === void 0 ? DEFAULT_STATE_VALUE : _a;
if (typeof defaultValue === 'number') {
// This isn't allowed per the props, but happens anyway.
defaultValue = String(defaultValue);
}
_this.state = {
uncontrolledValue: _this._isControlled ? undefined : defaultValue,
isFocused: false,
errorMessage: '',
};
_this._delayedValidate = _this._async.debounce(_this._validate, _this.props.deferredValidationTime);
_this._lastValidation = 0;
return _this;
}
Object.defineProperty(TextFieldBase.prototype, "value", {
/**
* Gets the current value of the text field.
*/
get: function () {
return _getValue(this.props, this.state);
},
enumerable: false,
configurable: true
});
TextFieldBase.prototype.componentDidMount = function () {
this._adjustInputHeight();
if (this.props.validateOnLoad) {
this._validate(this.value);
}
};
TextFieldBase.prototype.componentWillUnmount = function () {
this._async.dispose();
};
TextFieldBase.prototype.getSnapshotBeforeUpdate = function (prevProps, prevState) {
return {
selection: [this.selectionStart, this.selectionEnd],
};
};
TextFieldBase.prototype.componentDidUpdate = function (prevProps, prevState, snapshot) {
var props = this.props;
var _a = (snapshot || {}).selection, selection = _a === void 0 ? [null, null] : _a;
var start = selection[0], end = selection[1];
if (!!prevProps.multiline !== !!props.multiline && prevState.isFocused) {
// The text field has just changed between single- and multi-line, so we need to reset focus
// and selection/cursor.
this.focus();
if (start !== null && end !== null && start >= 0 && end >= 0) {
this.setSelectionRange(start, end);
}
}
if (prevProps.value !== props.value) {
// Only if the value in props changed, reset the record of the last value seen by a
// change/input event (don't do this if the value in state changed, since at least in tests
// the state update may happen before the second event in a series)
this._lastChangeValue = undefined;
}
var prevValue = _getValue(prevProps, prevState);
var value = this.value;
if (prevValue !== value) {
// Handle controlled/uncontrolled warnings and status
this._warnControlledUsage(prevProps);
// Clear error message if needed
// TODO: is there any way to do this without an extra render?
if (this.state.errorMessage && !props.errorMessage) {
this.setState({ errorMessage: '' });
}
// Adjust height if needed based on new value
this._adjustInputHeight();
// TODO: #5875 added logic to trigger validation in componentWillReceiveProps and other places.
// This seems a bit odd and hard to integrate with the new approach.
// (Starting to think we should just put the validation logic in a separate wrapper component...?)
if (_shouldValidateAllChanges(props)) {
this._delayedValidate(value);
}
}
};
TextFieldBase.prototype.render = function () {
var _a = this.props, borderless = _a.borderless, className = _a.className, disabled = _a.disabled, invalid = _a.invalid, iconProps = _a.iconProps, inputClassName = _a.inputClassName, label = _a.label, multiline = _a.multiline, required = _a.required, underlined = _a.underlined, prefix = _a.prefix, resizable = _a.resizable, suffix = _a.suffix, theme = _a.theme, styles = _a.styles, autoAdjustHeight = _a.autoAdjustHeight, canRevealPassword = _a.canRevealPassword, revealPasswordAriaLabel = _a.revealPasswordAriaLabel, type = _a.type, _b = _a.onRenderPrefix, onRenderPrefix = _b === void 0 ? this._onRenderPrefix : _b, _c = _a.onRenderSuffix, onRenderSuffix = _c === void 0 ? this._onRenderSuffix : _c, _d = _a.onRenderLabel, onRenderLabel = _d === void 0 ? this._onRenderLabel : _d, _e = _a.onRenderDescription, onRenderDescription = _e === void 0 ? this._onRenderDescription : _e;
var _f = this.state, isFocused = _f.isFocused, isRevealingPassword = _f.isRevealingPassword;
var errorMessage = this._errorMessage;
var isInvalid = typeof invalid === 'boolean' ? invalid : !!errorMessage;
var hasRevealButton = !!canRevealPassword && type === 'password' && _browserNeedsRevealButton();
var classNames = (this._classNames = getClassNames(styles, {
theme: theme,
className: className,
disabled: disabled,
focused: isFocused,
required: required,
multiline: multiline,
hasLabel: !!label,
hasErrorMessage: isInvalid,
borderless: borderless,
resizable: resizable,
hasIcon: !!iconProps,
underlined: underlined,
inputClassName: inputClassName,
autoAdjustHeight: autoAdjustHeight,
hasRevealButton: hasRevealButton,
}));
return (
// eslint-disable-next-line @typescript-eslint/no-deprecated
React.createElement("div", { ref: this.props.elementRef, className: classNames.root },
React.createElement("div", { className: classNames.wrapper },
onRenderLabel(this.props, this._onRenderLabel),
React.createElement("div", { className: classNames.fieldGroup },
(prefix !== undefined || this.props.onRenderPrefix) && (React.createElement("div", { className: classNames.prefix, id: this._prefixId }, onRenderPrefix(this.props, this._onRenderPrefix))),
multiline ? this._renderTextArea() : this._renderInput(),
iconProps && React.createElement(Icon_1.Icon, tslib_1.__assign({ className: classNames.icon }, iconProps)),
hasRevealButton && (
// Explicitly set type="button" since the default button type within a form is "submit"
React.createElement("button", { "aria-label": revealPasswordAriaLabel, className: classNames.revealButton, onClick: this._onRevealButtonClick, "aria-pressed": !!isRevealingPassword, type: "button" },
React.createElement("span", { className: classNames.revealSpan },
React.createElement(Icon_1.Icon, { className: classNames.revealIcon, iconName: isRevealingPassword ? HIDE_ICON_NAME : REVEAL_ICON_NAME })))),
(suffix !== undefined || this.props.onRenderSuffix) && (React.createElement("div", { className: classNames.suffix, id: this._suffixId }, onRenderSuffix(this.props, this._onRenderSuffix))))),
this._isDescriptionAvailable && (React.createElement("span", { id: this._descriptionId },
onRenderDescription(this.props, this._onRenderDescription),
errorMessage && (React.createElement("div", { role: "alert" },
React.createElement(Utilities_1.DelayedRender, null, this._renderErrorMessage())))))));
};
/**
* Sets focus on the text field
*/
TextFieldBase.prototype.focus = function () {
if (this._textElement.current) {
this._textElement.current.focus();
}
};
/**
* Blurs the text field.
*/
TextFieldBase.prototype.blur = function () {
if (this._textElement.current) {
this._textElement.current.blur();
}
};
/**
* Selects the text field
*/
TextFieldBase.prototype.select = function () {
if (this._textElement.current) {
this._textElement.current.select();
}
};
/**
* Sets the selection start of the text field to a specified value
*/
TextFieldBase.prototype.setSelectionStart = function (value) {
if (this._textElement.current) {
this._textElement.current.selectionStart = value;
}
};
/**
* Sets the selection end of the text field to a specified value
*/
TextFieldBase.prototype.setSelectionEnd = function (value) {
if (this._textElement.current) {
this._textElement.current.selectionEnd = value;
}
};
Object.defineProperty(TextFieldBase.prototype, "selectionStart", {
/**
* Gets the selection start of the text field
*/
get: function () {
return this._textElement.current ? this._textElement.current.selectionStart : -1;
},
enumerable: false,
configurable: true
});
Object.defineProperty(TextFieldBase.prototype, "selectionEnd", {
/**
* Gets the selection end of the text field
*/
get: function () {
return this._textElement.current ? this._textElement.current.selectionEnd : -1;
},
enumerable: false,
configurable: true
});
/**
* Sets the start and end positions of a selection in a text field.
* @param start - Index of the start of the selection.
* @param end - Index of the end of the selection.
*/
TextFieldBase.prototype.setSelectionRange = function (start, end) {
if (this._textElement.current) {
this._textElement.current.setSelectionRange(start, end);
}
};
TextFieldBase.prototype._warnControlledUsage = function (prevProps) {
// Show warnings if props are being used in an invalid way
(0, Utilities_1.warnControlledUsage)({
componentId: this._id,
componentName: COMPONENT_NAME,
props: this.props,
oldProps: prevProps,
valueProp: 'value',
defaultValueProp: 'defaultValue',
onChangeProp: 'onChange',
readOnlyProp: 'readOnly',
});
if (this.props.value === null && !this._hasWarnedNullValue) {
this._hasWarnedNullValue = true;
(0, Utilities_1.warn)("Warning: 'value' prop on '".concat(COMPONENT_NAME, "' should not be null. Consider using an ") +
'empty string to clear the component or undefined to indicate an uncontrolled component.');
}
};
Object.defineProperty(TextFieldBase.prototype, "_id", {
/** Returns `props.id` if available, or a fallback if not. */
get: function () {
return this.props.id || this._fallbackId;
},
enumerable: false,
configurable: true
});
Object.defineProperty(TextFieldBase.prototype, "_isControlled", {
get: function () {
return (0, Utilities_1.isControlled)(this.props, 'value');
},
enumerable: false,
configurable: true
});
TextFieldBase.prototype._onRenderPrefix = function (props) {
var prefix = props.prefix;
return React.createElement("span", { style: { paddingBottom: '1px' } }, prefix);
};
TextFieldBase.prototype._onRenderSuffix = function (props) {
var suffix = props.suffix;
return React.createElement("span", { style: { paddingBottom: '1px' } }, suffix);
};
Object.defineProperty(TextFieldBase.prototype, "_errorMessage", {
/**
* Current error message from either `props.errorMessage` or the result of `props.onGetErrorMessage`.
*
* - If there is no validation error or we have not validated the input value, errorMessage is an empty string.
* - If we have done the validation and there is validation error, errorMessage is the validation error message.
*/
get: function () {
var _a = this.props.errorMessage, errorMessage = _a === void 0 ? this.state.errorMessage : _a;
return errorMessage || '';
},
enumerable: false,
configurable: true
});
/**
* Renders error message based on the type of the message.
*
* - If error message is string, it will render using the built in styles.
* - If error message is an element, user has full control over how it's rendered.
*/
TextFieldBase.prototype._renderErrorMessage = function () {
var errorMessage = this._errorMessage;
return errorMessage ? (typeof errorMessage === 'string' ? (React.createElement("p", { className: this._classNames.errorMessage },
React.createElement("span", { "data-automation-id": "error-message" }, errorMessage))) : (React.createElement("div", { className: this._classNames.errorMessage, "data-automation-id": "error-message" }, errorMessage))) : null;
};
Object.defineProperty(TextFieldBase.prototype, "_isDescriptionAvailable", {
/**
* If a custom description render function is supplied then treat description as always available.
* Otherwise defer to the presence of description or error message text.
*/
get: function () {
var props = this.props;
return !!(props.onRenderDescription || props.description || this._errorMessage);
},
enumerable: false,
configurable: true
});
TextFieldBase.prototype._renderTextArea = function () {
var _a = this.props.invalid, invalid = _a === void 0 ? !!this._errorMessage : _a;
var textAreaProps = (0, Utilities_1.getNativeProps)(this.props, Utilities_1.textAreaProperties, ['defaultValue']);
var ariaLabelledBy = this.props['aria-labelledby'] || (this.props.label ? this._labelId : undefined);
return (React.createElement("textarea", tslib_1.__assign({ id: this._id }, textAreaProps, { ref: this._textElement, value: this.value || '', onInput: this._onInputChange, onChange: this._onInputChange, className: this._classNames.field, "aria-labelledby": ariaLabelledBy, "aria-describedby": this._isDescriptionAvailable ? this._descriptionId : this.props['aria-describedby'], "aria-invalid": invalid, "aria-label": this.props.ariaLabel, readOnly: this.props.readOnly, onFocus: this._onFocus, onBlur: this._onBlur })));
};
TextFieldBase.prototype._renderInput = function () {
var _a = this.props, ariaLabel = _a.ariaLabel, _b = _a.invalid, invalid = _b === void 0 ? !!this._errorMessage : _b, onRenderPrefix = _a.onRenderPrefix, onRenderSuffix = _a.onRenderSuffix, prefix = _a.prefix, suffix = _a.suffix, _c = _a.type, type = _c === void 0 ? 'text' : _c, label = _a.label;
// build aria-labelledby list from label, prefix, and suffix
var labelIds = [];
label && labelIds.push(this._labelId);
(prefix !== undefined || onRenderPrefix) && labelIds.push(this._prefixId);
(suffix !== undefined || onRenderSuffix) && labelIds.push(this._suffixId);
var inputProps = tslib_1.__assign(tslib_1.__assign({ type: this.state.isRevealingPassword ? 'text' : type, id: this._id }, (0, Utilities_1.getNativeProps)(this.props, Utilities_1.inputProperties, ['defaultValue', 'type'])), { 'aria-labelledby': this.props['aria-labelledby'] || (labelIds.length > 0 ? labelIds.join(' ') : undefined), ref: this._textElement, value: this.value || '', onInput: this._onInputChange, onChange: this._onInputChange, className: this._classNames.field, 'aria-label': ariaLabel, 'aria-describedby': this._isDescriptionAvailable ? this._descriptionId : this.props['aria-describedby'], 'aria-invalid': invalid, onFocus: this._onFocus, onBlur: this._onBlur });
var defaultRender = function (updatedInputProps) {
return React.createElement("input", tslib_1.__assign({}, updatedInputProps));
};
var onRenderInput = this.props.onRenderInput || defaultRender;
return onRenderInput(inputProps, defaultRender);
};
TextFieldBase.prototype._validate = function (value) {
var _this = this;
// In case _validate is called again while validation promise is executing
if (this._latestValidateValue === value && _shouldValidateAllChanges(this.props)) {
return;
}
this._latestValidateValue = value;
var onGetErrorMessage = this.props.onGetErrorMessage;
var result = onGetErrorMessage && onGetErrorMessage(value || '');
if (result !== undefined) {
if (typeof result === 'string' || !('then' in result)) {
this.setState({ errorMessage: result });
this._notifyAfterValidate(value, result);
}
else {
var currentValidation_1 = ++this._lastValidation;
result.then(function (errorMessage) {
if (currentValidation_1 === _this._lastValidation) {
_this.setState({ errorMessage: errorMessage });
}
_this._notifyAfterValidate(value, errorMessage);
});
}
}
else {
this._notifyAfterValidate(value, '');
}
};
TextFieldBase.prototype._notifyAfterValidate = function (value, errorMessage) {
if (value === this.value && this.props.onNotifyValidationResult) {
this.props.onNotifyValidationResult(errorMessage, value);
}
};
TextFieldBase.prototype._adjustInputHeight = function () {
var _a, _b;
if (this._textElement.current && this.props.autoAdjustHeight && this.props.multiline) {
var scrollTop = (_b = (_a = this.props.scrollContainerRef) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.scrollTop;
var textField = this._textElement.current;
textField.style.height = '';
textField.style.height = textField.scrollHeight + 'px';
if (scrollTop) {
// Safe to assert not null, otherwise we wouldn't have a scrollTop;
this.props.scrollContainerRef.current.scrollTop = scrollTop;
}
}
};
TextFieldBase.defaultProps = {
resizable: true,
deferredValidationTime: 200,
validateOnLoad: true,
};
return TextFieldBase;
}(React.Component));
exports.TextFieldBase = TextFieldBase;
/** Get the value from the given state and props (converting from number to string if needed) */
function _getValue(props, state) {
var _a = props.value, value = _a === void 0 ? state.uncontrolledValue : _a;
if (typeof value === 'number') {
// not allowed per typings, but happens anyway
return String(value);
}
return value;
}
/**
* If `validateOnFocusIn` or `validateOnFocusOut` is true, validation should run **only** on that event.
* Otherwise, validation should run on every change.
*/
function _shouldValidateAllChanges(props) {
return !(props.validateOnFocusIn || props.validateOnFocusOut);
}
// Only calculate this once across all TextFields, since will stay the same
var __browserNeedsRevealButton;
function _browserNeedsRevealButton() {
if (typeof __browserNeedsRevealButton !== 'boolean') {
var win = (0, Utilities_1.getWindow)();
if (win === null || win === void 0 ? void 0 : win.navigator) {
// Edge, Chromium Edge
var isEdge = /Edg/.test(win.navigator.userAgent || '');
__browserNeedsRevealButton = !((0, Utilities_1.isIE11)() || isEdge);
}
else {
__browserNeedsRevealButton = true;
}
}
return __browserNeedsRevealButton;
}
});
//# sourceMappingURL=TextField.base.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
import * as React from 'react';
import type { ITextFieldProps } from './TextField.types';
export declare const TextField: React.FunctionComponent<ITextFieldProps>;
export type { ITextField } from './TextField.types';
@@ -0,0 +1,9 @@
define(["require", "exports", "../../Utilities", "./TextField.base", "./TextField.styles"], function (require, exports, Utilities_1, TextField_base_1, TextField_styles_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextField = void 0;
exports.TextField = (0, Utilities_1.styled)(TextField_base_1.TextFieldBase, TextField_styles_1.getStyles, undefined, {
scope: 'TextField',
});
});
//# sourceMappingURL=TextField.js.map
@@ -0,0 +1 @@
{"version":3,"file":"TextField.js","sourceRoot":"../src/","sources":["components/TextField/TextField.ts"],"names":[],"mappings":";;;;IAMa,QAAA,SAAS,GAA6C,IAAA,kBAAM,EAIvE,8BAAa,EAAE,4BAAS,EAAE,SAAS,EAAE;QACrC,KAAK,EAAE,WAAW;KACnB,CAAC,CAAC","sourcesContent":["import * as React from 'react';\nimport { styled } from '../../Utilities';\nimport { TextFieldBase } from './TextField.base';\nimport { getStyles } from './TextField.styles';\nimport type { ITextFieldProps, ITextFieldStyles, ITextFieldStyleProps } from './TextField.types';\n\nexport const TextField: React.FunctionComponent<ITextFieldProps> = styled<\n ITextFieldProps,\n ITextFieldStyleProps,\n ITextFieldStyles\n>(TextFieldBase, getStyles, undefined, {\n scope: 'TextField',\n});\n\nexport type { ITextField } from './TextField.types';\n"]}
@@ -0,0 +1,2 @@
import type { ITextFieldStyleProps, ITextFieldStyles } from './TextField.types';
export declare function getStyles(props: ITextFieldStyleProps): ITextFieldStyles;
@@ -0,0 +1,413 @@
define(["require", "exports", "tslib", "../../Styling"], function (require, exports, tslib_1, Styling_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStyles = getStyles;
var globalClassNames = {
root: 'ms-TextField',
description: 'ms-TextField-description',
errorMessage: 'ms-TextField-errorMessage',
field: 'ms-TextField-field',
fieldGroup: 'ms-TextField-fieldGroup',
prefix: 'ms-TextField-prefix',
suffix: 'ms-TextField-suffix',
wrapper: 'ms-TextField-wrapper',
revealButton: 'ms-TextField-reveal',
multiline: 'ms-TextField--multiline',
borderless: 'ms-TextField--borderless',
underlined: 'ms-TextField--underlined',
unresizable: 'ms-TextField--unresizable',
required: 'is-required',
disabled: 'is-disabled',
active: 'is-active',
};
function getLabelStyles(props) {
var underlined = props.underlined, disabled = props.disabled, focused = props.focused, theme = props.theme;
var palette = theme.palette, fonts = theme.fonts;
return function () {
var _a;
return ({
root: [
underlined &&
disabled && {
color: palette.neutralTertiary,
},
underlined && {
fontSize: fonts.medium.fontSize,
marginRight: 8,
paddingLeft: 12,
paddingRight: 0,
lineHeight: '22px',
height: 32,
},
underlined &&
focused && {
selectors: (_a = {},
_a[Styling_1.HighContrastSelector] = {
height: 31, // -1px to prevent jumpiness in HC with the increased border-width to 2px
},
_a),
},
],
});
};
}
function getStyles(props) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
var theme = props.theme, className = props.className, disabled = props.disabled, focused = props.focused, required = props.required, multiline = props.multiline, hasLabel = props.hasLabel, borderless = props.borderless, underlined = props.underlined, hasIcon = props.hasIcon, resizable = props.resizable, hasErrorMessage = props.hasErrorMessage, inputClassName = props.inputClassName, autoAdjustHeight = props.autoAdjustHeight, hasRevealButton = props.hasRevealButton;
var semanticColors = theme.semanticColors, effects = theme.effects, fonts = theme.fonts;
var classNames = (0, Styling_1.getGlobalClassNames)(globalClassNames, theme);
var fieldPrefixSuffix = {
// Suffix/Prefix are not editable so the disabled slot perfectly fits.
background: semanticColors.disabledBackground,
color: !disabled ? semanticColors.inputPlaceholderText : semanticColors.disabledText,
display: 'flex',
alignItems: 'center',
padding: '0 10px',
lineHeight: 1,
whiteSpace: 'nowrap',
flexShrink: 0,
selectors: (_a = {},
_a[Styling_1.HighContrastSelector] = {
background: 'Window',
color: disabled ? 'GrayText' : 'WindowText',
},
_a),
};
// placeholder style constants
var placeholderStyles = [
{
color: semanticColors.inputPlaceholderText,
opacity: 1,
selectors: (_b = {},
_b[Styling_1.HighContrastSelector] = {
color: 'GrayText',
},
_b),
},
];
var disabledPlaceholderStyles = {
color: semanticColors.disabledText,
selectors: (_c = {},
_c[Styling_1.HighContrastSelector] = {
color: 'GrayText',
},
_c),
};
return {
root: [
classNames.root,
fonts.medium,
required && classNames.required,
disabled && classNames.disabled,
focused && classNames.active,
multiline && classNames.multiline,
borderless && classNames.borderless,
underlined && classNames.underlined,
Styling_1.normalize,
{
position: 'relative',
},
className,
],
wrapper: [
classNames.wrapper,
underlined && [
{
display: 'flex',
borderBottom: "1px solid ".concat(!hasErrorMessage ? semanticColors.inputBorder : semanticColors.errorText),
width: '100%',
},
disabled && {
borderBottomColor: semanticColors.disabledBackground,
selectors: (_d = {},
_d[Styling_1.HighContrastSelector] = tslib_1.__assign({ borderColor: 'GrayText' }, (0, Styling_1.getHighContrastNoAdjustStyle)()),
_d),
},
!disabled && {
selectors: {
':hover': {
borderBottomColor: !hasErrorMessage ? semanticColors.inputBorderHovered : semanticColors.errorText,
selectors: (_e = {},
_e[Styling_1.HighContrastSelector] = tslib_1.__assign({ borderBottomColor: 'Highlight' }, (0, Styling_1.getHighContrastNoAdjustStyle)()),
_e),
},
},
},
focused && [
{
position: 'relative',
},
(0, Styling_1.getInputFocusStyle)(!hasErrorMessage ? semanticColors.inputFocusBorderAlt : semanticColors.errorText, 0, 'borderBottom'),
],
],
],
fieldGroup: [
classNames.fieldGroup,
Styling_1.normalize,
{
border: "1px solid ".concat(semanticColors.inputBorder),
borderRadius: effects.roundedCorner2,
background: semanticColors.inputBackground,
cursor: 'text',
height: 32,
display: 'flex',
flexDirection: 'row',
alignItems: 'stretch',
position: 'relative',
},
multiline && {
minHeight: '60px',
height: 'auto',
display: 'flex',
},
!focused &&
!disabled && {
selectors: {
':hover': {
borderColor: semanticColors.inputBorderHovered,
selectors: (_f = {},
_f[Styling_1.HighContrastSelector] = tslib_1.__assign({ borderColor: 'Highlight' }, (0, Styling_1.getHighContrastNoAdjustStyle)()),
_f),
},
},
},
focused &&
!underlined &&
(0, Styling_1.getInputFocusStyle)(!hasErrorMessage ? semanticColors.inputFocusBorderAlt : semanticColors.errorText, effects.roundedCorner2),
disabled && {
borderColor: semanticColors.disabledBackground,
selectors: (_g = {},
_g[Styling_1.HighContrastSelector] = tslib_1.__assign({ borderColor: 'GrayText' }, (0, Styling_1.getHighContrastNoAdjustStyle)()),
_g),
cursor: 'default',
},
borderless && {
border: 'none',
},
borderless &&
focused && {
border: 'none',
selectors: {
':after': {
border: 'none',
},
},
},
underlined && {
flex: '1 1 0px',
border: 'none',
textAlign: 'left',
},
underlined &&
disabled && {
backgroundColor: 'transparent',
},
hasErrorMessage &&
!underlined && {
borderColor: semanticColors.errorText,
selectors: {
'&:hover': {
borderColor: semanticColors.errorText,
},
},
},
!hasLabel &&
required && {
selectors: (_h = {
':before': {
content: "'*'",
color: semanticColors.errorText,
position: 'absolute',
top: -5,
right: -10,
}
},
_h[Styling_1.HighContrastSelector] = {
selectors: {
':before': {
color: 'WindowText',
right: -14, // moving the * 4 pixel to right to alleviate border clipping in HC mode.
},
},
},
_h),
},
],
field: [
fonts.medium,
classNames.field,
Styling_1.normalize,
{
borderRadius: 0,
border: 'none',
background: 'none',
backgroundColor: 'transparent',
color: semanticColors.inputText,
padding: '0 8px',
width: '100%',
minWidth: 0,
textOverflow: 'ellipsis',
outline: 0,
selectors: (_j = {
'&:active, &:focus, &:hover': { outline: 0 },
'::-ms-clear': {
display: 'none',
}
},
_j[Styling_1.HighContrastSelector] = {
background: 'Window',
color: disabled ? 'GrayText' : 'WindowText',
},
_j),
},
(0, Styling_1.getPlaceholderStyles)(placeholderStyles),
multiline &&
!resizable && [
classNames.unresizable,
{
resize: 'none',
},
],
multiline && {
minHeight: 'inherit',
lineHeight: 17,
flexGrow: 1,
paddingTop: 6,
paddingBottom: 6,
overflow: 'auto',
width: '100%',
},
multiline &&
autoAdjustHeight && {
overflow: 'hidden',
},
hasIcon &&
!hasRevealButton && {
paddingRight: 24,
},
multiline &&
hasIcon && {
paddingRight: 40,
},
disabled && [
{
backgroundColor: semanticColors.disabledBackground,
color: semanticColors.disabledText,
borderColor: semanticColors.disabledBackground,
},
(0, Styling_1.getPlaceholderStyles)(disabledPlaceholderStyles),
],
underlined && {
textAlign: 'left',
},
focused &&
!borderless && {
selectors: (_k = {},
_k[Styling_1.HighContrastSelector] = {
paddingLeft: 11,
paddingRight: 11,
},
_k),
},
focused &&
multiline &&
!borderless && {
selectors: (_l = {},
_l[Styling_1.HighContrastSelector] = {
paddingTop: 4, // take into consideration the 2px increased border-width (not when borderless).
},
_l),
},
inputClassName,
],
icon: [
multiline && {
paddingRight: 24,
alignItems: 'flex-end',
},
{
pointerEvents: 'none',
position: 'absolute',
bottom: 6,
right: 8,
top: 'auto',
fontSize: Styling_1.IconFontSizes.medium,
lineHeight: 18,
},
disabled && {
color: semanticColors.disabledText,
},
],
description: [
classNames.description,
{
color: semanticColors.bodySubtext,
fontSize: fonts.xSmall.fontSize,
},
],
errorMessage: [
classNames.errorMessage,
Styling_1.AnimationClassNames.slideDownIn20,
fonts.small,
{
color: semanticColors.errorText,
margin: 0,
paddingTop: 5,
display: 'flex',
alignItems: 'center',
},
],
prefix: [classNames.prefix, fieldPrefixSuffix],
suffix: [classNames.suffix, fieldPrefixSuffix],
revealButton: [
classNames.revealButton,
'ms-Button',
'ms-Button--icon',
(0, Styling_1.getFocusStyle)(theme, { inset: 1 }),
{
height: 30,
width: 32,
border: 'none',
padding: '0px 4px',
backgroundColor: 'transparent',
color: semanticColors.link,
selectors: {
':hover': {
outline: 0,
color: semanticColors.primaryButtonBackgroundHovered,
backgroundColor: semanticColors.buttonBackgroundHovered,
selectors: (_m = {},
_m[Styling_1.HighContrastSelector] = {
borderColor: 'Highlight',
color: 'Highlight',
},
_m),
},
':focus': { outline: 0 },
},
},
hasIcon && {
marginRight: 28,
},
],
revealSpan: {
display: 'flex',
height: '100%',
alignItems: 'center',
},
revealIcon: {
margin: '0px 4px',
pointerEvents: 'none',
bottom: 6,
right: 8,
top: 'auto',
fontSize: Styling_1.IconFontSizes.medium,
lineHeight: 18,
},
subComponentStyles: {
label: getLabelStyles(props),
},
};
}
});
//# sourceMappingURL=TextField.styles.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,376 @@
import * as React from 'react';
import type { IShadowDomStyle, IStyle, ITheme } from '../../Styling';
import type { IRefObject, IRenderFunction, IStyleFunctionOrObject } from '../../Utilities';
import type { IIconProps } from '../../Icon';
import type { JSXElement } from '@fluentui/utilities';
/**
* {@docCategory TextField}
*/
export interface ITextField {
/** Gets the current value of the input. */
value: string | undefined;
/** Sets focus to the input. */
focus: () => void;
/** Blurs the input */
blur: () => void;
/** Select the value of the text field. */
select: () => void;
/** Sets the selection start of the text field to a specified value. */
setSelectionStart: (value: number) => void;
/** Sets the selection end of the text field to a specified value. */
setSelectionEnd: (value: number) => void;
/**
* Sets the start and end positions of a selection in a text field.
* Call with start and end set to the same value to set the cursor position.
* @param start - Index of the start of the selection.
* @param end - Index of the end of the selection.
*/
setSelectionRange: (start: number, end: number) => void;
/** Gets the selection start of the text field. Returns -1 if there is no selection. */
selectionStart: number | null;
/** Gets the selection end of the text field. Returns -1 if there is no selection. */
selectionEnd: number | null;
}
/**
* TextField component props.
* {@docCategory TextField}
*/
export interface ITextFieldProps extends React.AllHTMLAttributes<HTMLInputElement | HTMLTextAreaElement> {
/**
* Optional callback to access the ITextField component. Use this instead of ref for accessing
* the public methods and properties of the component.
*/
componentRef?: IRefObject<ITextField>;
/**
* Optional callback to access the root DOM element.
* @deprecated Temporary solution which will be replaced with ref once TextField is converted to a function component.
*/
elementRef?: React.Ref<HTMLDivElement>;
/**
* Whether or not the text field is a multiline text field.
* @defaultvalue false
*/
multiline?: boolean;
/**
* For multiline text fields, whether or not the field is resizable.
* @defaultvalue true
*/
resizable?: boolean;
/**
* For multiline text fields, whether or not to auto adjust text field height.
* If inside a scrollable container, also set `scrollContainerRef`.
* @defaultvalue false
*/
autoAdjustHeight?: boolean;
/**
* A read-only ref to the scrollable element that contains this TextField, if any. Only needed if
* `autoAdjustHeight` is set. This allows TextField to maintain the scroll position when text is edited.
*/
scrollContainerRef?: React.RefObject<HTMLElement | null>;
/**
* Whether or not the text field is underlined.
* @defaultvalue false
*/
underlined?: boolean;
/**
* Whether or not the text field is borderless.
* @defaultvalue false
*/
borderless?: boolean;
/**
* Label displayed above the text field (and read by screen readers).
*/
label?: string;
/**
* Custom renderer for the label.
* If you don't call defaultRender, ensure that you give your custom-rendered label an id and that
* you set the textfield's aria-labelledby prop to that id.
*/
onRenderLabel?: IRenderFunction<ITextFieldProps>;
/**
* Description displayed below the text field to provide additional details about what text to enter.
*/
description?: string;
/**
* Custom renderer for the description.
*/
onRenderDescription?: IRenderFunction<ITextFieldProps>;
/**
* Custom renderer for the actual single-line input field (not used if `multiline` is true).
* This receives the processed props which would usually be passed to the `<input>` element
* and allows manually modifying them or rendering as a different element. (Use with care,
* since changes here could easily break the component.)
*/
onRenderInput?: IRenderFunction<React.InputHTMLAttributes<HTMLInputElement> & React.RefAttributes<HTMLInputElement>>;
/**
* Prefix displayed before the text field contents. This is not included in the value.
* Ensure a descriptive label is present to assist screen readers, as the value does not include the prefix.
*/
prefix?: string;
/**
* Suffix displayed after the text field contents. This is not included in the value.
* Ensure a descriptive label is present to assist screen readers, as the value does not include the suffix.
*/
suffix?: string;
/**
* Custom render function for prefix.
*/
onRenderPrefix?: IRenderFunction<ITextFieldProps>;
/**
* Custom render function for suffix.
*/
onRenderSuffix?: IRenderFunction<ITextFieldProps>;
/**
* Props for an optional icon, displayed in the far right end of the text field.
*/
iconProps?: IIconProps;
/**
* Default value of the text field. Only provide this if the text field is an uncontrolled component;
* otherwise, use the `value` property.
*/
defaultValue?: string;
/**
* Current value of the text field. Only provide this if the text field is a controlled component where you
* are maintaining its current state; otherwise, use the `defaultValue` property.
*/
value?: string;
/**
* Disabled state of the text field.
* @defaultvalue false
*/
disabled?: boolean;
/**
* If true, the text field is readonly.
* @defaultvalue false
*/
readOnly?: boolean;
/**
* If true, the text field is invalid. Will be auto-determined by errorMessage unless set.
* @defaultvalue false
*/
invalid?: boolean;
/**
* Static error message displayed below the text field. Use `onGetErrorMessage` to dynamically
* change the error message displayed (if any) based on the current value. `errorMessage` and
* `onGetErrorMessage` are mutually exclusive (`errorMessage` takes precedence).
*/
errorMessage?: string | JSXElement;
/**
* Callback for when the input value changes.
* This is called on both `input` and `change` events.
* (In a later version, this will probably only be called for the `change` event.)
*/
onChange?: (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => void;
/**
* Function called after validation completes.
*/
onNotifyValidationResult?: (errorMessage: string | JSXElement, value: string | undefined) => void;
/**
* Function used to determine whether the input value is valid and get an error message if not.
* Mutually exclusive with the static string `errorMessage` (it will take precedence over this).
*
* When it returns `string | JSXElement`:
* - If valid, it returns empty string.
* - If invalid, it returns the error message and the text field will
* show a red border and show an error message below the text field.
*
* When it returns `Promise<string | JSXElement>`:
* - The resolved value is displayed as the error message.
* - If rejected, the value is thrown away.
*/
onGetErrorMessage?: (value: string) => string | JSXElement | PromiseLike<string | JSXElement> | undefined;
/**
* Text field will start to validate after users stop typing for `deferredValidationTime` milliseconds.
* Updates to this prop will not be respected.
* @defaultvalue 200
*/
deferredValidationTime?: number;
/**
* Optional class name that is added to the container of the component.
*/
className?: string;
/**
* Optional class name that is added specifically to the input/textarea element.
*/
inputClassName?: string;
/**
* Aria label for the text field.
*/
ariaLabel?: string;
/**
* Run validation when focus moves into the input, and **do not** validate on change.
*
* (Unless this prop and/or `validateOnFocusOut` is set to true, validation will run on every change.)
* @defaultvalue false
*/
validateOnFocusIn?: boolean;
/**
* Run validation when focus moves out of the input, and **do not** validate on change.
*
* (Unless this prop and/or `validateOnFocusIn` is set to true, validation will run on every change.)
* @defaultvalue false
*/
validateOnFocusOut?: boolean;
/**
* Whether validation should run when the input is initially rendered.
* @defaultvalue true
*/
validateOnLoad?: boolean;
/**
* Theme (provided through customization).
*/
theme?: ITheme;
/**
* Call to provide customized styling that will layer on top of the variant rules.
*/
styles?: IStyleFunctionOrObject<ITextFieldStyleProps, ITextFieldStyles>;
/**
* Whether the input field should have autocomplete enabled.
* This tells the browser to display options based on earlier typed values.
* Common values are 'on' and 'off' but for all possible values see the following links:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#Values
* https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill
*/
autoComplete?: string;
/**
* Whether to show the reveal password button for input type `'password'`. This will be ignored
* if the `type` prop is not set to `'password'`, or if the browser is known to have a built-in
* reveal button for password inputs (Edge, IE).
*/
canRevealPassword?: boolean;
/**
* If `canRevealPassword` is true, aria label for the reveal password button (example: "Show
* password"). Note that this will NOT be used in browsers known to have a built-in reveal
* password button for password inputs (Edge, IE).
*/
revealPasswordAriaLabel?: string;
}
/**
* {@docCategory TextField}
*/
export type ITextFieldStyleProps = Required<Pick<ITextFieldProps, 'theme'>> & Pick<ITextFieldProps, 'className' | 'disabled' | 'inputClassName' | 'required' | 'multiline' | 'borderless' | 'resizable' | 'underlined' | 'autoAdjustHeight'> & {
/** Element has an error message. */
hasErrorMessage?: boolean;
/** Element has an icon. */
hasIcon?: boolean;
/** Element has a label. */
hasLabel?: boolean;
/** Element has focus. */
focused?: boolean;
/** Element has a peek button for passwords */
hasRevealButton?: boolean;
};
/**
* {@docCategory TextField}
*/
export interface ITextFieldSubComponentStyles {
/**
* Styling for Label child component.
*/
label: IStyleFunctionOrObject<any, any>;
}
/**
* {@docCategory TextField}
*/
export interface ITextFieldStyles extends IShadowDomStyle {
/**
* Style for root element.
*/
root: IStyle;
/**
* Style for field group encompassing entry area (prefix, field, icon and suffix).
*/
fieldGroup: IStyle;
/**
* Style for prefix element.
*/
prefix: IStyle;
/**
* Style for suffix element.
*/
suffix: IStyle;
/**
* Style for main field entry element.
*/
field: IStyle;
/**
* Style for icon prop element.
*/
icon: IStyle;
/**
* Style for description element.
*/
description: IStyle;
/**
* Style for TextField wrapper element.
* Mainly useful for overriding border styles for underlined fields.
*/
wrapper: IStyle;
/**
* Style for error message element.
*/
errorMessage: IStyle;
/**
* Styling for subcomponents.
*/
subComponentStyles: ITextFieldSubComponentStyles;
/**
* Styling for reveal password button
*/
revealButton: IStyle;
/**
* Styling for reveal password span
*/
revealSpan: IStyle;
/**
* Styling for reveal password icon
*/
revealIcon: IStyle;
}
/**
* {@docCategory TextField}
*/
export interface IMaskedTextField extends ITextField {
/**
* The value of all filled format characters, or undefined if not all format characters are filled.
*/
value: string | undefined;
}
/**
* MaskedTextField component props.
* {@docCategory TextField}
*/
export interface IMaskedTextFieldProps extends ITextFieldProps, React.RefAttributes<HTMLDivElement> {
/**
* Optional callback to access the IMaskedTextField interface. Use this instead of ref for accessing
* the public methods and properties of the component.
*/
componentRef?: IRefObject<IMaskedTextField>;
/**
* The masking string that defines the mask's behavior.
* A backslash will escape any character.
* Special format characters are:
* '9': [0-9]
* 'a': [a-zA-Z]
* '*': [a-zA-Z0-9]
*
* @example `Phone Number: (999) 999-9999`
*/
mask?: string;
/**
* The character to show in place of unfilled characters of the mask.
* @defaultvalue '_'
*/
maskChar?: string;
/**
* An object defining the format characters and corresponding regexp values.
* Default format characters: \{
* '9': /[0-9]/,
* 'a': /[a-zA-Z]/,
* '*': /[a-zA-Z0-9]/
* \}
*/
maskFormat?: {
[key: string]: RegExp;
};
}
@@ -0,0 +1,5 @@
define(["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
});
//# sourceMappingURL=TextField.types.js.map
File diff suppressed because one or more lines are too long
+5
View File
@@ -0,0 +1,5 @@
export * from './TextField';
export * from './TextField.base';
export { getStyles as getTextFieldStyles } from './TextField.styles';
export * from './TextField.types';
export * from './MaskedTextField/MaskedTextField';
+11
View File
@@ -0,0 +1,11 @@
define(["require", "exports", "tslib", "./TextField", "./TextField.base", "./TextField.styles", "./TextField.types", "./MaskedTextField/MaskedTextField"], function (require, exports, tslib_1, TextField_1, TextField_base_1, TextField_styles_1, TextField_types_1, MaskedTextField_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTextFieldStyles = void 0;
tslib_1.__exportStar(TextField_1, exports);
tslib_1.__exportStar(TextField_base_1, exports);
Object.defineProperty(exports, "getTextFieldStyles", { enumerable: true, get: function () { return TextField_styles_1.getStyles; } });
tslib_1.__exportStar(TextField_types_1, exports);
tslib_1.__exportStar(MaskedTextField_1, exports);
});
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"../src/","sources":["components/TextField/index.ts"],"names":[],"mappings":";;;;IAAA,2CAA4B;IAC5B,gDAAiC;IACxB,sHAAA,SAAS,OAAsB;IACxC,iDAAkC;IAClC,iDAAkD","sourcesContent":["export * from './TextField';\nexport * from './TextField.base';\nexport { getStyles as getTextFieldStyles } from './TextField.styles';\nexport * from './TextField.types';\nexport * from './MaskedTextField/MaskedTextField';\n"]}