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
+68
View File
@@ -0,0 +1,68 @@
import * as React from 'react';
import { KeyCodes } from '../../Utilities';
import type { IAutofill, IAutofillProps } from './Autofill.types';
import type { JSXElement } from '@fluentui/utilities';
export interface IAutofillState {
inputValue: string;
isComposing: boolean;
}
interface ICursorLocation {
start: number;
end: number;
dir: 'forward' | 'backward' | 'none' | undefined;
}
/**
* {@docCategory Autofill}
*/
export declare class Autofill extends React.Component<IAutofillProps, IAutofillState> implements IAutofill {
static defaultProps: {
enableAutofillOnKeyPress: KeyCodes[];
};
static contextType: React.Context<import("@fluentui/react-window-provider").WindowProviderProps>;
context: any;
private _inputElement;
private _autoFillEnabled;
private _async;
static getDerivedStateFromProps(props: IAutofillProps, state: IAutofillState): IAutofillState | null;
constructor(props: IAutofillProps);
get cursorLocation(): number | null;
get isValueSelected(): boolean;
get value(): string;
get selectionStart(): number | null;
get selectionEnd(): number | null;
get inputElement(): HTMLInputElement | null;
componentDidUpdate(_: any, _1: any, cursor: ICursorLocation | null): void;
componentWillUnmount(): void;
render(): JSXElement;
focus(): void;
clear(): void;
getSnapshotBeforeUpdate(): ICursorLocation | null;
private _onCompositionStart;
private _onCompositionUpdate;
private _onCompositionEnd;
private _onClick;
private _onKeyDown;
private _onInputChanged;
private _onChanged;
private _getCurrentInputValue;
/**
* 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.
*/
private _tryEnableAutofill;
/**
* Updates the current input value as well as getting a new display value.
* @param newValue - The new value from the input
*/
private _updateValue;
private _getDisplayValue;
private _getControlledValue;
}
export {};
+343
View File
@@ -0,0 +1,343 @@
import { __assign, __extends } from "tslib";
import * as React from 'react';
import { Async, getDocument, getNativeProps, initializeComponentRef, inputProperties, isIE11, KeyCodes, } from '../../Utilities';
import { WindowContext } from '@fluentui/react-window-provider';
var SELECTION_FORWARD = 'forward';
var SELECTION_BACKWARD = 'backward';
/**
* {@docCategory Autofill}
*/
var Autofill = /** @class */ (function (_super) {
__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 (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 KeyCodes.backspace:
_this._autoFillEnabled = false;
break;
case KeyCodes.left:
case 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 (!(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); });
};
initializeComponentRef(_this);
_this._async = new 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 __assign(__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) || 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 = getNativeProps(this.props, inputProperties);
var style = __assign(__assign({}, this.props.style), { fontFamily: 'inherit' });
return (React.createElement("input", __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: [KeyCodes.down, KeyCodes.up],
};
// need to check WindowContext to get the provided document
Autofill.contextType = WindowContext;
return Autofill;
}(React.Component));
export { 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;
}
if (process.env.NODE_ENV !== 'production') {
for (var _i = 0, _a = [text, startWith]; _i < _a.length; _i++) {
var val = _a[_i];
if (typeof val !== 'string') {
throw new Error("".concat(Autofill.name
// eslint-disable-next-line @fluentui/max-len
, " received non-string value \"").concat(val, "\" of type ").concat(typeof val, " from either input's value or suggestedDisplayValue"));
}
}
}
return text.toLocaleLowerCase().indexOf(startWith.toLocaleLowerCase()) === 0;
}
//# sourceMappingURL=Autofill.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,110 @@
import * as React from 'react';
import { Autofill } from './Autofill';
import { KeyCodes } from '../../Utilities';
import type { IRefObject } from '../../Utilities';
/**
* {@docCategory Autofill}
*/
export interface IAutofill {
/**
* The current index of the cursor in the input area. Returns -1 if the input element
* is not ready.
*/
cursorLocation: number | null;
/**
* Whether there is a value selected in the input area.
*/
isValueSelected: boolean;
/**
* The current text value that the user has entered.
*/
value: string;
/**
* The current index of where the selection starts. Returns -1 if the input element
* is not ready.
*/
selectionStart: number | null;
/**
* The current index of where the selection ends. Returns -1 if the input element
* is not ready.
*/
selectionEnd: number | null;
/**
* The current input element.
*/
inputElement: HTMLInputElement | null;
/**
* Focus the input element.
*/
focus(): void;
/**
* Clear all text in the input. Sets value to `''`.
*/
clear(): void;
}
/**
* {@docCategory Autofill}
*/
export interface IAutofillProps extends React.InputHTMLAttributes<HTMLInputElement | Autofill> {
/**
* Gets the component ref.
*/
componentRef?: IRefObject<IAutofill>;
/**
* The suggested autofill value that will display.
*/
suggestedDisplayValue?: string;
/**
* A callback for when the current input value changes. Called after
* the state has been changed.
*
* @param composing - true if the change event was triggered while the
* inner input was in the middle of a multi-character composition.
* (for example, jp-hiragana IME input)
*/
onInputValueChange?: (newValue?: string, composing?: boolean) => void;
/**
* When the user uses left arrow, right arrow, clicks, or deletes text, autofill is disabled
* since the user has taken control. It is automatically re-enabled when the user enters text and the
* cursor is at the end of the text in the input box. This prop can be used to override the
* default list of key presses that will re-enable autofill.
* @defaultvalue [KeyCodes.down, KeyCodes.up]
*/
enableAutofillOnKeyPress?: KeyCodes[];
/**
* The default value to be visible. This is different from placeholder
* because it actually sets the current value of the picker.
* Note: Updates to this prop will not be respected.
*/
defaultVisibleValue?: string;
/**
* Handler for checking and updating the value if needed in `componentWillReceiveProps`.
* Returns the updated value to set, if needed.
*
* @deprecated use standard input `value` prop instead if the autofill should act like a controlled component
*/
updateValueInWillReceiveProps?: () => string | null;
/**
* Handler for checking if the full value of the input should be selected in `componentDidUpdate`.
* Returns whether the full value of the input should be selected.
*/
shouldSelectFullInputValueInComponentDidUpdate?: () => boolean;
/**
* A callback used to modify the input string. Will entirely override the default behavior if provided.
* If you just want to be notified of changes, use `onInputValueChange` instead.
* Called before the state has been updated.
*
* @param composing - true if the change event was triggered while the
* inner input was in the middle of a multi-character composition.
* (for example, jp-hiragana IME input)
* @deprecated To control the value, pass in `value` like in any other controlled component.
* To be notified of changes, use `onInputValueChange`.
*/
onInputChange?: (value: string, composing: boolean) => string | void;
/**
* Should the value of the input be selected? True if we're focused on our input, false otherwise.
* We need to explicitly not select the text in the autofill if we are no longer focused.
* In IE11, selecting an input will also focus the input, causing other element's focus to be stolen.
*/
preventValueSelection?: boolean;
}
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=Autofill.types.js.map
@@ -0,0 +1 @@
{"version":3,"file":"Autofill.types.js","sourceRoot":"../src/","sources":["components/Autofill/Autofill.types.ts"],"names":[],"mappings":"","sourcesContent":["import * as React from 'react';\nimport { Autofill } from './Autofill';\nimport { KeyCodes } from '../../Utilities';\nimport type { IRefObject } from '../../Utilities';\n\n/**\n * {@docCategory Autofill}\n */\nexport interface IAutofill {\n /**\n * The current index of the cursor in the input area. Returns -1 if the input element\n * is not ready.\n */\n cursorLocation: number | null;\n /**\n * Whether there is a value selected in the input area.\n */\n isValueSelected: boolean;\n /**\n * The current text value that the user has entered.\n */\n value: string;\n /**\n * The current index of where the selection starts. Returns -1 if the input element\n * is not ready.\n */\n selectionStart: number | null;\n /**\n * The current index of where the selection ends. Returns -1 if the input element\n * is not ready.\n */\n selectionEnd: number | null;\n /**\n * The current input element.\n */\n inputElement: HTMLInputElement | null;\n /**\n * Focus the input element.\n */\n focus(): void;\n /**\n * Clear all text in the input. Sets value to `''`.\n */\n clear(): void;\n}\n\n/**\n * {@docCategory Autofill}\n */\nexport interface IAutofillProps extends React.InputHTMLAttributes<HTMLInputElement | Autofill> {\n /**\n * Gets the component ref.\n */\n componentRef?: IRefObject<IAutofill>;\n\n /**\n * The suggested autofill value that will display.\n */\n suggestedDisplayValue?: string;\n\n /**\n * A callback for when the current input value changes. Called after\n * the state has been changed.\n *\n * @param composing - true if the change event was triggered while the\n * inner input was in the middle of a multi-character composition.\n * (for example, jp-hiragana IME input)\n */\n onInputValueChange?: (newValue?: string, composing?: boolean) => void;\n\n /**\n * When the user uses left arrow, right arrow, clicks, or deletes text, autofill is disabled\n * since the user has taken control. It is automatically re-enabled when the user enters text and the\n * cursor is at the end of the text in the input box. This prop can be used to override the\n * default list of key presses that will re-enable autofill.\n * @defaultvalue [KeyCodes.down, KeyCodes.up]\n */\n enableAutofillOnKeyPress?: KeyCodes[];\n\n /**\n * The default value to be visible. This is different from placeholder\n * because it actually sets the current value of the picker.\n * Note: Updates to this prop will not be respected.\n */\n defaultVisibleValue?: string;\n\n /**\n * Handler for checking and updating the value if needed in `componentWillReceiveProps`.\n * Returns the updated value to set, if needed.\n *\n * @deprecated use standard input `value` prop instead if the autofill should act like a controlled component\n */\n updateValueInWillReceiveProps?: () => string | null;\n\n /**\n * Handler for checking if the full value of the input should be selected in `componentDidUpdate`.\n * Returns whether the full value of the input should be selected.\n */\n shouldSelectFullInputValueInComponentDidUpdate?: () => boolean;\n\n /**\n * A callback used to modify the input string. Will entirely override the default behavior if provided.\n * If you just want to be notified of changes, use `onInputValueChange` instead.\n * Called before the state has been updated.\n *\n * @param composing - true if the change event was triggered while the\n * inner input was in the middle of a multi-character composition.\n * (for example, jp-hiragana IME input)\n * @deprecated To control the value, pass in `value` like in any other controlled component.\n * To be notified of changes, use `onInputValueChange`.\n */\n onInputChange?: (value: string, composing: boolean) => string | void;\n\n /**\n * Should the value of the input be selected? True if we're focused on our input, false otherwise.\n * We need to explicitly not select the text in the autofill if we are no longer focused.\n * In IE11, selecting an input will also focus the input, causing other element's focus to be stolen.\n */\n preventValueSelection?: boolean;\n}\n"]}
+2
View File
@@ -0,0 +1,2 @@
export * from './Autofill';
export * from './Autofill.types';
+3
View File
@@ -0,0 +1,3 @@
export * from './Autofill';
export * from './Autofill.types';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"../src/","sources":["components/Autofill/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC","sourcesContent":["export * from './Autofill';\nexport * from './Autofill.types';\n"]}