From 4b30212ecfe23b92b21d0304a580a421dbd6808b Mon Sep 17 00:00:00 2001 From: Jon Schectman Date: Tue, 23 Jan 2018 13:55:24 -0800 Subject: [PATCH] BaseAutoFill: Fix a bug where BaseAutoFill would not work with composed languages (#3713) * enable the base auto fill to work with composed languages like Japanese. Also made some small improvements to structure * adding change file * removing unecessary import * fix lint * Update BasePicker Snap * Fixing comments and minor bug in firefox * Make onchange better and update snaps * adding in comments * fixing comments * small code cleanup --- ...utofill-fixime-input_2018-01-12-02-56.json | 11 ++ .../__snapshots__/ComboBox.test.tsx.snap | 2 + .../pickers/AutoFill/BaseAutoFill.tsx | 130 +++++++++++++----- .../__snapshots__/BasePicker.test.tsx.snap | 4 + 4 files changed, 114 insertions(+), 33 deletions(-) create mode 100644 common/changes/office-ui-fabric-react/baseautofill-fixime-input_2018-01-12-02-56.json diff --git a/common/changes/office-ui-fabric-react/baseautofill-fixime-input_2018-01-12-02-56.json b/common/changes/office-ui-fabric-react/baseautofill-fixime-input_2018-01-12-02-56.json new file mode 100644 index 0000000000000..33b5d94c697d8 --- /dev/null +++ b/common/changes/office-ui-fabric-react/baseautofill-fixime-input_2018-01-12-02-56.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "BaseAutoFill: Fixed a bug where baseautofill would not work with composed languages like Japanese", + "type": "patch" + } + ], + "packageName": "office-ui-fabric-react", + "email": "joschect@microsoft.com" +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/__snapshots__/ComboBox.test.tsx.snap b/packages/office-ui-fabric-react/src/components/ComboBox/__snapshots__/ComboBox.test.tsx.snap index c3fb997882aee..a9e542b214e9a 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/__snapshots__/ComboBox.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/ComboBox/__snapshots__/ComboBox.test.tsx.snap @@ -106,6 +106,8 @@ exports[`ComboBox Renders ComboBox correctly 1`] = ` onBlur={[Function]} onChange={[Function]} onClick={[Function]} + onCompositionEnd={[Function]} + onCompositionStart={[Function]} onFocus={[Function]} onKeyDown={[Function]} onKeyUp={[Function]} diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx index 0448b991c61f9..72e5b83f51a1e 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx @@ -73,9 +73,7 @@ export class BaseAutoFill extends BaseComponent) { + 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 langauges like Japanese or Arabic. + // Find out more at https://developer.mozilla.org/en-US/docs/Web/Events/compositionstart + @autobind + private _onCompositionEnd(ev: React.CompositionEvent) { + let inputValue = this._getCurrentInputValue(); + this._tryEnableAutofill(inputValue, this.value, false, true); + // Due to timing, this needs to be async, otherwise no text will be selected. + this._async.setTimeout(() => this._updateValue(inputValue), 0); + } + @autobind private _onClick() { if (this._value && this._value !== '' && this._autoFillEnabled) { @@ -154,37 +173,66 @@ export class BaseAutoFill extends BaseComponent) { - let value: string = (ev.target as HTMLInputElement).value; - if (value && (ev.target as HTMLInputElement).selectionStart === value.length && !this._autoFillEnabled && value.length > this._value.length) { + let value: string = this._getCurrentInputValue(ev); + // Right now typing does not have isComposing, once that has been fixed any should be removed. + this._tryEnableAutofill(value, this._value, (ev.nativeEvent as any).isComposing); + this._updateValue(value); + } + + private _getCurrentInputValue(ev?: React.FormEvent): string { + if (ev && ev.target && (ev.target as any).value) { + return (ev.target as any).value; + } else { + return this._inputElement.value; + } + } + + /** + * 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 + * @param oldValue + * @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(newValue: string, oldValue: string, isComposing?: boolean, isComposed?: boolean) { + if (!isComposing + && newValue + && this._inputElement.selectionStart === newValue.length + && !this._autoFillEnabled + && (newValue.length > oldValue.length || isComposed)) { this._autoFillEnabled = true; } - this._updateValue(value); } private _notifyInputChange(newValue: string) { @@ -193,20 +241,36 @@ export class BaseAutoFill extends BaseComponent this._notifyInputChange(this._value)); } + /** + * 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 + */ + private _getDisplayValue(inputValue: string, suggestedDisplayValue?: string) { + let displayValue = inputValue; + if (suggestedDisplayValue + && inputValue + && this._doesTextStartWith(suggestedDisplayValue, displayValue) + && this._autoFillEnabled) { + displayValue = suggestedDisplayValue; + } + return displayValue; + } + private _doesTextStartWith(text: string, startWith: string) { if (!text || !startWith) { return false; diff --git a/packages/office-ui-fabric-react/src/components/pickers/__snapshots__/BasePicker.test.tsx.snap b/packages/office-ui-fabric-react/src/components/pickers/__snapshots__/BasePicker.test.tsx.snap index f4c1a74dea981..6df55e9308d6f 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/__snapshots__/BasePicker.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/pickers/__snapshots__/BasePicker.test.tsx.snap @@ -54,6 +54,8 @@ exports[`Pickers BasePicker renders BasePicker correctly 1`] = ` disabled={undefined} onChange={[Function]} onClick={[Function]} + onCompositionEnd={[Function]} + onCompositionStart={[Function]} onFocus={[Function]} onKeyDown={[Function]} role="combobox" @@ -119,6 +121,8 @@ exports[`Pickers TagPicker renders TagPicker correctly 1`] = ` disabled={undefined} onChange={[Function]} onClick={[Function]} + onCompositionEnd={[Function]} + onCompositionStart={[Function]} onFocus={[Function]} onKeyDown={[Function]} role="combobox"