From 564669aaaad5e4f30208c2849651781bcc2d92f2 Mon Sep 17 00:00:00 2001 From: Milen Karmidzhanov Date: Tue, 24 Feb 2026 19:05:59 +0200 Subject: [PATCH 1/7] fix(ui5-input): preserve case when input is used with suggestions --- packages/main/cypress/specs/Input.cy.tsx | 203 +++++++++++++++++- packages/main/cypress/specs/MultiInput.cy.tsx | 2 +- packages/main/src/Input.ts | 59 ++++- packages/main/src/Token.ts | 4 +- .../main/src/i18n/messagebundle.properties | 3 - 5 files changed, 256 insertions(+), 15 deletions(-) diff --git a/packages/main/cypress/specs/Input.cy.tsx b/packages/main/cypress/specs/Input.cy.tsx index ee14e9829d84..c3d3a2245921 100644 --- a/packages/main/cypress/specs/Input.cy.tsx +++ b/packages/main/cypress/specs/Input.cy.tsx @@ -1964,7 +1964,7 @@ describe("Input general interaction", () => { .should("be.focused"); cy.get("@inputEl") - .realType("a"); + .realType("A"); cy.get("@inputEl") .should("have.value", "Adam D"); @@ -1985,7 +1985,7 @@ describe("Input general interaction", () => { ); cy.get("#input-custom-flat").shadow().find("input").as("input"); - cy.get("@input").click().realType("a"); + cy.get("@input").click().realType("A"); cy.get("@input").should("have.value", "Albania"); cy.get("@input").then($input => { @@ -3227,4 +3227,203 @@ describe("Input built-in filtering", () => { .eq(1) .should("have.attr", "hidden"); }); + + describe("Case-preserving suggestions", () => { + it("should preserve user's typed case during typeahead but use original suggestion case when accepted", () => { + cy.mount( + + + + + + ); + + cy.get("#case-test") + .shadow() + .find("input") + .as("input"); + + // Type lowercase 'a' - should show 'apple' with user's lowercase 'a' + cy.get("@input") + .realClick() + .realType("a"); + + cy.get("@input") + .should("have.value", "apple"); + + // Verify text selection (typeahead highlighting) + cy.get("@input") + .then($input => { + const input = $input[0] as HTMLInputElement; + expect(input.selectionStart).to.equal(1); + expect(input.selectionEnd).to.equal(5); + }); + + // Press Enter to accept - should use original suggestion case "Apple" + cy.realPress("Enter"); + + cy.get("@input") + .should("have.value", "Apple"); + }); + + it("should preserve uppercase typed characters during typeahead", () => { + cy.mount( + + + + + ); + + cy.get("#case-test-upper") + .shadow() + .find("input") + .as("input"); + + // Type uppercase 'A' - should show 'Apple' with user's uppercase 'A' + cy.get("@input") + .realClick() + .realType("A"); + + cy.get("@input") + .should("have.value", "Apple"); + + // Press Enter to accept - should use original suggestion case "apple" + cy.realPress("Enter"); + + cy.get("@input") + .should("have.value", "apple"); + }); + + it("should handle exact match with different case on Enter", () => { + cy.mount( + + + + + ); + + cy.get("#exact-match-test") + .shadow() + .find("input") + .as("input"); + + // Type "Ap" matching suggestion "ap" + cy.get("@input") + .realClick() + .realType("Ap"); + + // During typing, user's case is preserved + cy.get("@input") + .should("have.value", "Ap"); + + // Press Enter - should use original suggestion case "ap" + cy.realPress("Enter"); + + cy.get("@input") + .should("have.value", "ap"); + }); + + it("should preserve original case through multiple characters typed", () => { + cy.mount( + + + + + ); + + cy.get("#multi-char-test") + .shadow() + .find("input") + .as("input"); + + // Type "ban" with mixed case + cy.get("@input") + .realClick() + .realType("bAn"); + + // Should show suggestion with user's typed case + cy.get("@input") + .should("have.value", "bAnANA"); + + // Press Enter - should use original suggestion case "BANANA" + cy.realPress("Enter"); + + cy.get("@input") + .should("have.value", "BANANA"); + }); + + it("should work with selection-change event and preserve original case", () => { + const onChangeSpy = cy.spy().as("onChange"); + const onSelectionChangeSpy = cy.spy().as("onSelectionChange"); + + cy.mount( + + + + + ); + + cy.get("#selection-change-test") + .shadow() + .find("input") + .as("input"); + + // Type lowercase 'o' + cy.get("@input") + .realClick() + .realType("o"); + + cy.get("@input") + .should("have.value", "orange"); + + // Press Enter to trigger selection-change + cy.realPress("Enter"); + + // Value should be original suggestion case + cy.get("@input") + .should("have.value", "Orange"); + + // Verify both events were called + cy.get("@onChange").should("have.been.calledOnce"); + cy.get("@onSelectionChange").should("have.been.calledOnce"); + }); + + it("should clear matched item on Escape and restore typed value", () => { + cy.mount( + + + + ); + + cy.get("#escape-test") + .shadow() + .find("input") + .as("input"); + + // Type 'a' to trigger typeahead + cy.get("@input") + .realClick() + .realType("a"); + + cy.get("@input") + .should("have.value", "apple"); + + // Press Escape to cancel autocomplete + cy.realPress("Escape"); + + cy.get("@input") + .should("have.value", "a"); + + // Now press Enter - should not select anything + cy.realPress("Enter"); + + cy.get("@input") + .should("have.value", "a"); + }); + }); }); diff --git a/packages/main/cypress/specs/MultiInput.cy.tsx b/packages/main/cypress/specs/MultiInput.cy.tsx index 277705c8672e..8591ab208a7c 100644 --- a/packages/main/cypress/specs/MultiInput.cy.tsx +++ b/packages/main/cypress/specs/MultiInput.cy.tsx @@ -575,7 +575,7 @@ describe("MultiInput tokens", () => { .realClick(); cy.get("@input") - .type("b"); + .type("B"); cy.get("[ui5-multi-input]") .should("have.attr", "value", "Bulgaria"); diff --git a/packages/main/src/Input.ts b/packages/main/src/Input.ts index 9eac8282caa2..b56297736ab0 100644 --- a/packages/main/src/Input.ts +++ b/packages/main/src/Input.ts @@ -637,6 +637,7 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement _clearIconClicked?: boolean; _focusedAfterClear: boolean; _changeToBeFired?: boolean; // used to wait change event firing after suggestion item selection + _matchedSuggestionItem?: IInputSuggestionItemSelectable; // stores the original matched suggestion for preserving case _performTextSelection?: boolean; _isLatestValueFromSuggestions: boolean; _isChangeTriggeredBySuggestion: boolean; @@ -801,6 +802,9 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement this._handleTypeAhead(item); } this._selectMatchingItem(item); + } else { + // Clear matched item when there's no match + this._matchedSuggestionItem = undefined; } } } @@ -1027,9 +1031,15 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement // if a group item is focused, this is false const suggestionItemPressed = !!(this.Suggestions?.onEnter(e)); const innerInput = this.getInputDOMRefSync()!; - const matchingItem = this._selectableItems.find(item => { - return item.text === this.value; - }); + + // Try to find matching item - use stored matched item first (preserves original case), + // then fall back to case-insensitive search + let matchingItem = this._matchedSuggestionItem; + if (!matchingItem) { + matchingItem = this._selectableItems.find(item => { + return item.text?.toLowerCase() === this.value.toLowerCase(); + }); + } if (matchingItem) { const itemText = matchingItem.text || ""; @@ -1090,6 +1100,8 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement const isAutoCompleted = innerInput.selectionEnd! - innerInput.selectionStart! > 0; this.isTyping = false; + // Clear matched suggestion item when user escapes + this._matchedSuggestionItem = undefined; if (this.value !== this.previousValue && this.value !== this.lastConfirmedValue && !this.open) { this.value = this.lastConfirmedValue ? this.lastConfirmedValue : this.previousValue; @@ -1325,6 +1337,8 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement _selectMatchingItem(item: IInputSuggestionItemSelectable) { item.selected = true; + // Store the matched item to preserve original case when accepting + this._matchedSuggestionItem = item; } _filterItems(value: string) { @@ -1374,11 +1388,21 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement } _handleTypeAhead(item: IInputSuggestionItemSelectable) { - const value = item.text ? item.text : ""; + const suggestionText = item.text ? item.text : ""; + const typedValue = this.typedInValue; - this.value = value; - this._performTextSelection = true; + // Store the original matched item for later use when accepting the suggestion + this._matchedSuggestionItem = item; + // Preserve the user's typed input case during typing + // Example: user types "a", suggestion is "Apple" → show "apple" (with "pple" highlighted) + if (suggestionText.toLowerCase().startsWith(typedValue.toLowerCase())) { + this.value = typedValue + suggestionText.substring(typedValue.length); + } else { + this.value = suggestionText; + } + + this._performTextSelection = true; this._shouldAutocomplete = false; } @@ -1527,7 +1551,21 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement return; } - const itemText = item.text || ""; + // Use the original matched suggestion item's text if: + // 1. It's keyboard navigation AND matched item exists, OR + // 2. The passed item matches the matched item (case-insensitive) + // Otherwise, use the passed item (e.g., when clicking a different suggestion) + let originalItem = item; + if (this._matchedSuggestionItem) { + const matchedText = this._matchedSuggestionItem.text?.toLowerCase() || ""; + const itemText = item.text?.toLowerCase() || ""; + // Only use matched item if keyboard navigation or if it's the same item (case-insensitive) + if (keyboardUsed || matchedText === itemText) { + originalItem = this._matchedSuggestionItem; + } + } + + const itemText = originalItem.text || ""; const fireChange = keyboardUsed ? this.valueBeforeItemSelection !== itemText : this.previousValue !== itemText; @@ -1549,6 +1587,8 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement } this.valueBeforeSelectionStart = ""; + // Clear the matched item after accepting + this._matchedSuggestionItem = undefined; this.isTyping = false; this.open = false; @@ -1563,6 +1603,11 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement this.value = itemValue || ""; this._performTextSelection = true; + + // Update the matched item when navigating with arrows to preserve correct case on Enter + if (!this._isGroupItem(item)) { + this._matchedSuggestionItem = item as IInputSuggestionItemSelectable; + } } fireEventByAction(action: INPUT_ACTIONS, e: InputEvent) { diff --git a/packages/main/src/Token.ts b/packages/main/src/Token.ts index 352119238981..b1aec3faf762 100644 --- a/packages/main/src/Token.ts +++ b/packages/main/src/Token.ts @@ -13,7 +13,7 @@ import { } from "@ui5/webcomponents-base/dist/Keys.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; -import { TOKEN_ARIA_DELETABLE, TOKEN_ARIA_LABEL, TOKEN_ARIA_REMOVE } from "./generated/i18n/i18n-defaults.js"; +import { DELETE, TOKEN_ARIA_DELETABLE, TOKEN_ARIA_LABEL } from "./generated/i18n/i18n-defaults.js"; import type { IIcon } from "./Icon.js"; import type { IToken } from "./MultiInput.js"; @@ -195,7 +195,7 @@ class Token extends UI5Element implements IToken { } get tokenDeletableText() { - return Token.i18nBundle.getText(TOKEN_ARIA_REMOVE); + return Token.i18nBundle.getText(DELETE); } get textDom() { diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index bb11ba379fd7..35962c02de10 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -613,9 +613,6 @@ DATETIME_PICKER_TIME_BUTTON=Time #XACT: ARIA announcement for token deletable TOKEN_ARIA_DELETABLE=Deletable -#XACT: ARIA announcement for token removal -TOKEN_ARIA_REMOVE=Remove - #XACT: ARIA announcement for token label TOKEN_ARIA_LABEL=Token From 2e75557cd4357e04f3aca3a4c0737036d3a244b5 Mon Sep 17 00:00:00 2001 From: Milen Karmidzhanov Date: Tue, 24 Feb 2026 21:03:24 +0200 Subject: [PATCH 2/7] fix(ui5-input): preserve case when input is used with suggestions --- packages/main/cypress/specs/Input.mobile.cy.tsx | 4 ++-- packages/main/cypress/specs/MultiComboBox.mobile.cy.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/main/cypress/specs/Input.mobile.cy.tsx b/packages/main/cypress/specs/Input.mobile.cy.tsx index 60c97f9e96da..31be35f278d8 100644 --- a/packages/main/cypress/specs/Input.mobile.cy.tsx +++ b/packages/main/cypress/specs/Input.mobile.cy.tsx @@ -272,7 +272,7 @@ describe("Typeahead", () => { .ui5ResponsivePopoverOpened(); cy.get("#myInput2").shadow().find(".ui5-input-inner-phone").should("be.focused"); - cy.get("#myInput2").shadow().find(".ui5-input-inner-phone").realType("c"); + cy.get("#myInput2").shadow().find(".ui5-input-inner-phone").realType("C"); cy.get("#myInput2").shadow().find(".ui5-input-inner-phone").should("have.value", "Cozy"); }); @@ -313,7 +313,7 @@ describe("Typeahead", () => { .ui5ResponsivePopoverOpened(); cy.get("#input-custom-flat").shadow().find(".ui5-input-inner-phone").should("be.focused"); - cy.get("#input-custom-flat").shadow().find(".ui5-input-inner-phone").realType("a"); + cy.get("#input-custom-flat").shadow().find(".ui5-input-inner-phone").realType("A"); cy.get("#input-custom-flat").shadow().find(".ui5-input-inner-phone").should("have.value", "Albania"); }); }); diff --git a/packages/main/cypress/specs/MultiComboBox.mobile.cy.tsx b/packages/main/cypress/specs/MultiComboBox.mobile.cy.tsx index 67726bb4707d..e6503a754606 100644 --- a/packages/main/cypress/specs/MultiComboBox.mobile.cy.tsx +++ b/packages/main/cypress/specs/MultiComboBox.mobile.cy.tsx @@ -197,7 +197,7 @@ describe("Typeahead", () => { .find("[ui5-input]") .as("respPopoverInput") .realClick() - .realType("c"); + .realType("C"); cy.get("@respPopoverInput") .should("have.value", "Cosy"); From 6d8c1591346a4c66eaacde30533de35da9116133 Mon Sep 17 00:00:00 2001 From: Milen Karmidzhanov Date: Thu, 26 Feb 2026 14:25:57 +0200 Subject: [PATCH 3/7] fix(ui5-input): preserve case when input is used with suggestions --- packages/main/src/Input.ts | 16 ---------------- packages/main/src/Token.ts | 4 ++-- packages/main/src/i18n/messagebundle.properties | 3 +++ packages/main/test/pages/MultiInput.html | 2 +- 4 files changed, 6 insertions(+), 19 deletions(-) diff --git a/packages/main/src/Input.ts b/packages/main/src/Input.ts index b56297736ab0..5c4334aea204 100644 --- a/packages/main/src/Input.ts +++ b/packages/main/src/Input.ts @@ -803,7 +803,6 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement } this._selectMatchingItem(item); } else { - // Clear matched item when there's no match this._matchedSuggestionItem = undefined; } } @@ -1032,8 +1031,6 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement const suggestionItemPressed = !!(this.Suggestions?.onEnter(e)); const innerInput = this.getInputDOMRefSync()!; - // Try to find matching item - use stored matched item first (preserves original case), - // then fall back to case-insensitive search let matchingItem = this._matchedSuggestionItem; if (!matchingItem) { matchingItem = this._selectableItems.find(item => { @@ -1100,7 +1097,6 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement const isAutoCompleted = innerInput.selectionEnd! - innerInput.selectionStart! > 0; this.isTyping = false; - // Clear matched suggestion item when user escapes this._matchedSuggestionItem = undefined; if (this.value !== this.previousValue && this.value !== this.lastConfirmedValue && !this.open) { @@ -1337,7 +1333,6 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement _selectMatchingItem(item: IInputSuggestionItemSelectable) { item.selected = true; - // Store the matched item to preserve original case when accepting this._matchedSuggestionItem = item; } @@ -1391,15 +1386,9 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement const suggestionText = item.text ? item.text : ""; const typedValue = this.typedInValue; - // Store the original matched item for later use when accepting the suggestion - this._matchedSuggestionItem = item; - // Preserve the user's typed input case during typing - // Example: user types "a", suggestion is "Apple" → show "apple" (with "pple" highlighted) if (suggestionText.toLowerCase().startsWith(typedValue.toLowerCase())) { this.value = typedValue + suggestionText.substring(typedValue.length); - } else { - this.value = suggestionText; } this._performTextSelection = true; @@ -1551,10 +1540,6 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement return; } - // Use the original matched suggestion item's text if: - // 1. It's keyboard navigation AND matched item exists, OR - // 2. The passed item matches the matched item (case-insensitive) - // Otherwise, use the passed item (e.g., when clicking a different suggestion) let originalItem = item; if (this._matchedSuggestionItem) { const matchedText = this._matchedSuggestionItem.text?.toLowerCase() || ""; @@ -1587,7 +1572,6 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement } this.valueBeforeSelectionStart = ""; - // Clear the matched item after accepting this._matchedSuggestionItem = undefined; this.isTyping = false; diff --git a/packages/main/src/Token.ts b/packages/main/src/Token.ts index b1aec3faf762..fca069913e6d 100644 --- a/packages/main/src/Token.ts +++ b/packages/main/src/Token.ts @@ -13,7 +13,7 @@ import { } from "@ui5/webcomponents-base/dist/Keys.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; -import { DELETE, TOKEN_ARIA_DELETABLE, TOKEN_ARIA_LABEL } from "./generated/i18n/i18n-defaults.js"; +import { TOKEN_ARIA_DELETE, TOKEN_ARIA_DELETABLE, TOKEN_ARIA_LABEL } from "./generated/i18n/i18n-defaults.js"; import type { IIcon } from "./Icon.js"; import type { IToken } from "./MultiInput.js"; @@ -195,7 +195,7 @@ class Token extends UI5Element implements IToken { } get tokenDeletableText() { - return Token.i18nBundle.getText(DELETE); + return Token.i18nBundle.getText(TOKEN_ARIA_DELETE); } get textDom() { diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index 35962c02de10..913b45577269 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -613,6 +613,9 @@ DATETIME_PICKER_TIME_BUTTON=Time #XACT: ARIA announcement for token deletable TOKEN_ARIA_DELETABLE=Deletable +#XACT: ARIA announcement for token removal +TOKEN_ARIA_DELETE=Delete + #XACT: ARIA announcement for token label TOKEN_ARIA_LABEL=Token diff --git a/packages/main/test/pages/MultiInput.html b/packages/main/test/pages/MultiInput.html index 80bcff883259..a55cdc7aab8a 100644 --- a/packages/main/test/pages/MultiInput.html +++ b/packages/main/test/pages/MultiInput.html @@ -333,7 +333,7 @@

Composition

Suggestions + showing wrapping

- + From a6e80eb14038eaccc9544aa8f61b0fd30c965e6a Mon Sep 17 00:00:00 2001 From: Milen Karmidzhanov Date: Mon, 2 Mar 2026 08:55:34 +0200 Subject: [PATCH 4/7] fix(ui5-input): preserve case when input is used with suggestions --- packages/main/cypress/specs/Input.cy.tsx | 36 ++++++++++++------------ packages/main/test/pages/Input.html | 11 +++++++- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/packages/main/cypress/specs/Input.cy.tsx b/packages/main/cypress/specs/Input.cy.tsx index c3d3a2245921..2e0fe702a76c 100644 --- a/packages/main/cypress/specs/Input.cy.tsx +++ b/packages/main/cypress/specs/Input.cy.tsx @@ -3228,17 +3228,17 @@ describe("Input built-in filtering", () => { .should("have.attr", "hidden"); }); - describe("Case-preserving suggestions", () => { - it("should preserve user's typed case during typeahead but use original suggestion case when accepted", () => { + describe("Typeahead capitalization handling", () => { + it("should preserve user's typed capitalization during typeahead and use original suggestion capitalization when accepted", () => { cy.mount( - + ); - cy.get("#case-test") + cy.get("#capitalization-test") .shadow() .find("input") .as("input"); @@ -3259,22 +3259,22 @@ describe("Input built-in filtering", () => { expect(input.selectionEnd).to.equal(5); }); - // Press Enter to accept - should use original suggestion case "Apple" + // Press Enter to accept - should use original suggestion capitalization "Apple" cy.realPress("Enter"); cy.get("@input") .should("have.value", "Apple"); }); - it("should preserve uppercase typed characters during typeahead", () => { + it("should preserve uppercase typed letters during typeahead", () => { cy.mount( - + ); - cy.get("#case-test-upper") + cy.get("#capitalization-test-upper") .shadow() .find("input") .as("input"); @@ -3287,14 +3287,14 @@ describe("Input built-in filtering", () => { cy.get("@input") .should("have.value", "Apple"); - // Press Enter to accept - should use original suggestion case "apple" + // Press Enter to accept - should use original suggestion capitalization "apple" cy.realPress("Enter"); cy.get("@input") .should("have.value", "apple"); }); - it("should handle exact match with different case on Enter", () => { + it("should match suggestions regardless of capitalization and use original on Enter", () => { cy.mount( @@ -3312,18 +3312,18 @@ describe("Input built-in filtering", () => { .realClick() .realType("Ap"); - // During typing, user's case is preserved + // During typing, user's capitalization is preserved cy.get("@input") .should("have.value", "Ap"); - // Press Enter - should use original suggestion case "ap" + // Press Enter - should use original suggestion capitalization "ap" cy.realPress("Enter"); cy.get("@input") .should("have.value", "ap"); }); - it("should preserve original case through multiple characters typed", () => { + it("should preserve user's typed capitalization through multiple characters", () => { cy.mount( @@ -3336,23 +3336,23 @@ describe("Input built-in filtering", () => { .find("input") .as("input"); - // Type "ban" with mixed case + // Type "bAn" with mixed capitalization cy.get("@input") .realClick() .realType("bAn"); - // Should show suggestion with user's typed case + // Should show suggestion with user's typed capitalization cy.get("@input") .should("have.value", "bAnANA"); - // Press Enter - should use original suggestion case "BANANA" + // Press Enter - should use original suggestion capitalization "BANANA" cy.realPress("Enter"); cy.get("@input") .should("have.value", "BANANA"); }); - it("should work with selection-change event and preserve original case", () => { + it("should work with selection-change event and preserve original capitalization", () => { const onChangeSpy = cy.spy().as("onChange"); const onSelectionChangeSpy = cy.spy().as("onSelectionChange"); @@ -3384,7 +3384,7 @@ describe("Input built-in filtering", () => { // Press Enter to trigger selection-change cy.realPress("Enter"); - // Value should be original suggestion case + // Value should be original suggestion capitalization cy.get("@input") .should("have.value", "Orange"); diff --git a/packages/main/test/pages/Input.html b/packages/main/test/pages/Input.html index 12b97241c8c0..e0c9cca883a2 100644 --- a/packages/main/test/pages/Input.html +++ b/packages/main/test/pages/Input.html @@ -609,8 +609,17 @@

Input Composition

Check Validity -

+

Capitalization suggestions

+ + + + + + + +

+