UNPKG

@fluentui/react

Version:

Reusable React components for building web experiences.

335 lines (334 loc) 17.9 kB
define(["require", "exports", "tslib", "react", "../../Utilities", "@fluentui/react-window-provider"], function (require, exports, tslib_1, React, Utilities_1, react_window_provider_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Autofill = void 0; var SELECTION_FORWARD = 'forward'; var SELECTION_BACKWARD = 'backward'; /** * {@docCategory Autofill} */ var Autofill = /** @class */ (function (_super) { tslib_1.__extends(Autofill, _super); function Autofill(props) { var _this = _super.call(this, props) || this; _this._inputElement = React.createRef(); _this._autoFillEnabled = true; // Composition events are used when the character/text requires several keystrokes to be completed. // Some examples of this are mobile text input and languages like Japanese or Arabic. // Find out more at https://developer.mozilla.org/en-US/docs/Web/Events/compositionstart _this._onCompositionStart = function (ev) { _this.setState({ isComposing: true }); _this._autoFillEnabled = false; }; // Composition events are used when the character/text requires several keystrokes to be completed. // Some examples of this are mobile text input and languages like Japanese or Arabic. // Find out more at https://developer.mozilla.org/en-US/docs/Web/Events/compositionstart _this._onCompositionUpdate = function () { if ((0, Utilities_1.isIE11)()) { _this._updateValue(_this._getCurrentInputValue(), true); } }; // Composition events are used when the character/text requires several keystrokes to be completed. // Some examples of this are mobile text input and languages like Japanese or Arabic. // Find out more at https://developer.mozilla.org/en-US/docs/Web/Events/compositionstart _this._onCompositionEnd = function (ev) { var inputValue = _this._getCurrentInputValue(); _this._tryEnableAutofill(inputValue, _this.value, false, true); _this.setState({ isComposing: false }); // Due to timing, this needs to be async, otherwise no text will be selected. _this._async.setTimeout(function () { // it's technically possible that the value of isComposing is reset during this timeout, // so explicitly trigger this with composing=true here, since it is supposed to be the // update for composition end _this._updateValue(_this._getCurrentInputValue(), false); }, 0); }; _this._onClick = function () { if (_this.value && _this.value !== '' && _this._autoFillEnabled) { _this._autoFillEnabled = false; } }; _this._onKeyDown = function (ev) { if (_this.props.onKeyDown) { _this.props.onKeyDown(ev); } // If the event is actively being composed, then don't alert autofill. // Right now typing does not have isComposing, once that has been fixed any should be removed. if (!ev.nativeEvent.isComposing) { // eslint-disable-next-line @typescript-eslint/no-deprecated switch (ev.which) { case Utilities_1.KeyCodes.backspace: _this._autoFillEnabled = false; break; case Utilities_1.KeyCodes.left: case Utilities_1.KeyCodes.right: if (_this._autoFillEnabled) { _this.setState(function (prev) { return ({ inputValue: _this.props.suggestedDisplayValue || prev.inputValue, }); }); _this._autoFillEnabled = false; } break; default: if (!_this._autoFillEnabled) { // eslint-disable-next-line @typescript-eslint/no-deprecated if (_this.props.enableAutofillOnKeyPress.indexOf(ev.which) !== -1) { _this._autoFillEnabled = true; } } break; } } }; _this._onInputChanged = function (ev) { var value = _this._getCurrentInputValue(ev); if (!_this.state.isComposing) { _this._tryEnableAutofill(value, _this.value, ev.nativeEvent.isComposing); } // If it is not IE11 and currently composing, update the value if (!((0, Utilities_1.isIE11)() && _this.state.isComposing)) { var nativeEventComposing = ev.nativeEvent.isComposing; var isComposing = nativeEventComposing === undefined ? _this.state.isComposing : nativeEventComposing; _this._updateValue(value, isComposing); } }; _this._onChanged = function () { // Swallow this event, we don't care about it // We must provide it because React PropTypes marks it as required, but onInput serves the correct purpose return; }; /** * Updates the current input value as well as getting a new display value. * @param newValue - The new value from the input */ _this._updateValue = function (newValue, composing) { // Only proceed if the value is nonempty and is different from the old value // This is to work around the fact that, in IE 11, inputs with a placeholder fire an onInput event on focus if (!newValue && newValue === _this.value) { return; } // eslint-disable-next-line @typescript-eslint/no-deprecated var _a = _this.props, onInputChange = _a.onInputChange, onInputValueChange = _a.onInputValueChange; if (onInputChange) { newValue = (onInputChange === null || onInputChange === void 0 ? void 0 : onInputChange(newValue, composing)) || ''; } _this.setState({ inputValue: newValue }, function () { return onInputValueChange === null || onInputValueChange === void 0 ? void 0 : onInputValueChange(newValue, composing); }); }; (0, Utilities_1.initializeComponentRef)(_this); _this._async = new Utilities_1.Async(_this); _this.state = { inputValue: props.defaultVisibleValue || '', isComposing: false, }; return _this; } Autofill.getDerivedStateFromProps = function (props, state) { // eslint-disable-next-line @typescript-eslint/no-deprecated if (props.updateValueInWillReceiveProps) { // eslint-disable-next-line @typescript-eslint/no-deprecated var updatedInputValue = props.updateValueInWillReceiveProps(); // Don't update if we have a null value or the value isn't changing // the value should still update if an empty string is passed in if (updatedInputValue !== null && updatedInputValue !== state.inputValue && !state.isComposing) { return tslib_1.__assign(tslib_1.__assign({}, state), { inputValue: updatedInputValue }); } } return null; }; Object.defineProperty(Autofill.prototype, "cursorLocation", { get: function () { if (this._inputElement.current) { var inputElement = this._inputElement.current; if (inputElement.selectionDirection !== SELECTION_FORWARD) { return inputElement.selectionEnd; } else { return inputElement.selectionStart; } } else { return -1; } }, enumerable: false, configurable: true }); Object.defineProperty(Autofill.prototype, "isValueSelected", { get: function () { return Boolean(this.inputElement && this.inputElement.selectionStart !== this.inputElement.selectionEnd); }, enumerable: false, configurable: true }); Object.defineProperty(Autofill.prototype, "value", { get: function () { return this._getControlledValue() || this.state.inputValue || ''; }, enumerable: false, configurable: true }); Object.defineProperty(Autofill.prototype, "selectionStart", { get: function () { return this._inputElement.current ? this._inputElement.current.selectionStart : -1; }, enumerable: false, configurable: true }); Object.defineProperty(Autofill.prototype, "selectionEnd", { get: function () { return this._inputElement.current ? this._inputElement.current.selectionEnd : -1; }, enumerable: false, configurable: true }); Object.defineProperty(Autofill.prototype, "inputElement", { get: function () { return this._inputElement.current; }, enumerable: false, configurable: true }); Autofill.prototype.componentDidUpdate = function (_, _1, cursor) { var _a; var _b = this.props, suggestedDisplayValue = _b.suggestedDisplayValue, shouldSelectFullInputValueInComponentDidUpdate = _b.shouldSelectFullInputValueInComponentDidUpdate, preventValueSelection = _b.preventValueSelection; var differenceIndex = 0; if (preventValueSelection) { return; } var document = ((_a = this.context) === null || _a === void 0 ? void 0 : _a.window.document) || (0, Utilities_1.getDocument)(this._inputElement.current); var isFocused = this._inputElement.current && this._inputElement.current === (document === null || document === void 0 ? void 0 : document.activeElement); if (isFocused && this._autoFillEnabled && this.value && suggestedDisplayValue && _doesTextStartWith(suggestedDisplayValue, this.value)) { var shouldSelectFullRange = false; if (shouldSelectFullInputValueInComponentDidUpdate) { shouldSelectFullRange = shouldSelectFullInputValueInComponentDidUpdate(); } if (shouldSelectFullRange) { this._inputElement.current.setSelectionRange(0, suggestedDisplayValue.length, SELECTION_BACKWARD); } else { while (differenceIndex < this.value.length && this.value[differenceIndex].toLocaleLowerCase() === suggestedDisplayValue[differenceIndex].toLocaleLowerCase()) { differenceIndex++; } if (differenceIndex > 0) { this._inputElement.current.setSelectionRange(differenceIndex, suggestedDisplayValue.length, SELECTION_BACKWARD); } } } else if (this._inputElement.current) { if (cursor !== null && !this._autoFillEnabled && !this.state.isComposing) { this._inputElement.current.setSelectionRange(cursor.start, cursor.end, cursor.dir); } } }; Autofill.prototype.componentWillUnmount = function () { this._async.dispose(); }; Autofill.prototype.render = function () { var nativeProps = (0, Utilities_1.getNativeProps)(this.props, Utilities_1.inputProperties); var style = tslib_1.__assign(tslib_1.__assign({}, this.props.style), { fontFamily: 'inherit' }); return (React.createElement("input", tslib_1.__assign({ autoCapitalize: "off", autoComplete: "off", "aria-autocomplete": 'both' }, nativeProps, { style: style, ref: this._inputElement, value: this._getDisplayValue(), onCompositionStart: this._onCompositionStart, onCompositionUpdate: this._onCompositionUpdate, onCompositionEnd: this._onCompositionEnd, // TODO (Fabric 8?) - switch to calling only onChange. See notes in TextField._onInputChange. onChange: this._onChanged, onInput: this._onInputChanged, onKeyDown: this._onKeyDown, onClick: this.props.onClick ? this.props.onClick : this._onClick, "data-lpignore": true }))); }; Autofill.prototype.focus = function () { this._inputElement.current && this._inputElement.current.focus(); }; Autofill.prototype.clear = function () { this._autoFillEnabled = true; this._updateValue('', false); this._inputElement.current && this._inputElement.current.setSelectionRange(0, 0); }; Autofill.prototype.getSnapshotBeforeUpdate = function () { var _a, _b; var inel = this._inputElement.current; if (inel && inel.selectionStart !== this.value.length) { return { start: (_a = inel.selectionStart) !== null && _a !== void 0 ? _a : inel.value.length, end: (_b = inel.selectionEnd) !== null && _b !== void 0 ? _b : inel.value.length, dir: inel.selectionDirection || 'backward' || 'none', }; } return null; }; Autofill.prototype._getCurrentInputValue = function (ev) { if (ev && ev.target && ev.target.value) { return ev.target.value; } else if (this.inputElement && this.inputElement.value) { return this.inputElement.value; } else { return ''; } }; /** * Attempts to enable autofill. Whether or not autofill is enabled depends on the input value, * whether or not any text is selected, and only if the new input value is longer than the old input value. * Autofill should never be set to true if the value is composing. Once compositionEnd is called, then * it should be completed. * See https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent for more information on composition. * @param newValue - new input value * @param oldValue - old input value * @param isComposing - if true then the text is actively being composed and it has not completed. * @param isComposed - if the text is a composed text value. */ Autofill.prototype._tryEnableAutofill = function (newValue, oldValue, isComposing, isComposed) { if (!isComposing && newValue && this._inputElement.current && this._inputElement.current.selectionStart === newValue.length && !this._autoFillEnabled && (newValue.length > oldValue.length || isComposed)) { this._autoFillEnabled = true; } }; Autofill.prototype._getDisplayValue = function () { if (this._autoFillEnabled) { return _getDisplayValue(this.value, this.props.suggestedDisplayValue); } return this.value; }; Autofill.prototype._getControlledValue = function () { var value = this.props.value; if (value === undefined || typeof value === 'string') { return value; } // eslint-disable-next-line no-console console.warn("props.value of Autofill should be a string, but it is ".concat(value, " with type of ").concat(typeof value)); return value.toString(); }; Autofill.defaultProps = { enableAutofillOnKeyPress: [Utilities_1.KeyCodes.down, Utilities_1.KeyCodes.up], }; // need to check WindowContext to get the provided document Autofill.contextType = react_window_provider_1.WindowContext; return Autofill; }(React.Component)); exports.Autofill = Autofill; /** * Returns a string that should be used as the display value. * It evaluates this based on whether or not the suggested value starts with the input value * and whether or not autofill is enabled. * @param inputValue - the value that the input currently has. * @param suggestedDisplayValue - the possible full value */ function _getDisplayValue(inputValue, suggestedDisplayValue) { var displayValue = inputValue; if (suggestedDisplayValue && inputValue && _doesTextStartWith(suggestedDisplayValue, displayValue)) { displayValue = suggestedDisplayValue; } return displayValue; } function _doesTextStartWith(text, startWith) { if (!text || !startWith) { return false; } return text.toLocaleLowerCase().indexOf(startWith.toLocaleLowerCase()) === 0; } }); //# sourceMappingURL=Autofill.js.map