Skip to content

Commit ff6d49b

Browse files
authored
fix(ui5-input): prevent double opening of VSM (#12715)
1 parent d046cb3 commit ff6d49b

3 files changed

Lines changed: 155 additions & 103 deletions

File tree

packages/main/cypress/specs/Input.cy.tsx

Lines changed: 100 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -491,19 +491,19 @@ describe("Input general interaction", () => {
491491
);
492492

493493
cy.get("#input-equal-click")
494-
.shadow()
495-
.find("input")
496-
.click()
497-
.realType("Cozy");
494+
.shadow()
495+
.find("input")
496+
.click()
497+
.realType("Cozy");
498498

499499
cy.get("#input-equal-click")
500-
.shadow()
501-
.find<ResponsivePopover>("[ui5-responsive-popover]")
502-
.ui5ResponsivePopoverOpened();
500+
.shadow()
501+
.find<ResponsivePopover>("[ui5-responsive-popover]")
502+
.ui5ResponsivePopoverOpened();
503503

504504
cy.get('#input-equal-click')
505-
.find('ui5-suggestion-item[text="Cozy"]')
506-
.click();
505+
.find('ui5-suggestion-item[text="Cozy"]')
506+
.click();
507507

508508
cy.get("#input-equal-click").should("have.value", "Cozy");
509509
cy.get("@onChange").should("have.been.calledOnce");
@@ -527,15 +527,15 @@ describe("Input general interaction", () => {
527527
);
528528

529529
cy.get("#input-equal-keyboard")
530-
.shadow()
531-
.find("input")
532-
.click()
533-
.realType("Cozy");
530+
.shadow()
531+
.find("input")
532+
.click()
533+
.realType("Cozy");
534534

535535
cy.get("#input-equal-keyboard")
536-
.shadow()
537-
.find<ResponsivePopover>("[ui5-responsive-popover]")
538-
.ui5ResponsivePopoverOpened();
536+
.shadow()
537+
.find<ResponsivePopover>("[ui5-responsive-popover]")
538+
.ui5ResponsivePopoverOpened();
539539

540540
cy.realPress("ArrowDown");
541541
cy.realPress("Enter");
@@ -894,7 +894,7 @@ describe("Input Ctrl + Alt + F8 navigation", () => {
894894
.should("have.focus");
895895

896896
cy.get("@secondLink")
897-
.realPress("Tab");
897+
.realPress("Tab");
898898

899899
cy.get("@secondInput")
900900
.should("have.focus");
@@ -1655,9 +1655,9 @@ describe("Input general interaction", () => {
16551655
// Mount Input with suggestions and a change event spy
16561656
cy.mount(
16571657
<Input
1658-
id="myInput"
1659-
showSuggestions={true}
1660-
onChange={cy.spy().as("changeSpy")}
1658+
id="myInput"
1659+
showSuggestions={true}
1660+
onChange={cy.spy().as("changeSpy")}
16611661
>
16621662
<SuggestionItem text="Canada"></SuggestionItem>
16631663
<SuggestionItem text="Cuba"></SuggestionItem>
@@ -1871,7 +1871,7 @@ describe("Input general interaction", () => {
18711871
.find("input")
18721872
.should("not.have.attr", "aria-describedby");
18731873
});
1874-
1874+
18751875
it("Checks if aria-label is reflected in the shadow DOM", () => {
18761876
const accessibleName = "New cool text";
18771877
cy.mount(<Input accessibleName={accessibleName} />);
@@ -2034,7 +2034,7 @@ describe("Input general interaction", () => {
20342034
cy.get("#input-disabled-autocomplete").shadow().find("input").click().realType("c");
20352035
cy.get("#input-disabled-autocomplete").shadow().find("input").should("have.value", "c");
20362036
});
2037-
2037+
20382038
it("Tests disabled autocomplete(type-ahead)", () => {
20392039
cy.mount(
20402040
<Input id="input-disabled-autocomplete" showSuggestions noTypeahead>
@@ -2072,9 +2072,9 @@ describe("Input general interaction", () => {
20722072

20732073
cy.get("#inputPreview2").shadow().find("input").click().realType("c");
20742074
cy.get("#inputPreview2")
2075-
.shadow()
2076-
.find<ResponsivePopover>("[ui5-responsive-popover]")
2077-
.ui5ResponsivePopoverOpened();
2075+
.shadow()
2076+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2077+
.ui5ResponsivePopoverOpened();
20782078

20792079
cy.get("#inputPreview2").shadow().find("input").realPress("ArrowDown");
20802080

@@ -2093,9 +2093,9 @@ describe("Input general interaction", () => {
20932093

20942094
cy.get("#myInput").shadow().find("input").click().realType("a");
20952095
cy.get("#myInput")
2096-
.shadow()
2097-
.find<ResponsivePopover>("[ui5-responsive-popover]")
2098-
.ui5ResponsivePopoverOpened();
2096+
.shadow()
2097+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2098+
.ui5ResponsivePopoverOpened();
20992099

21002100
cy.get("#myInput").shadow().find<ResponsivePopover>("ui5-responsive-popover").ui5ResponsivePopoverOpened();
21012101
cy.get("@onOpen").should("have.been.calledOnce");
@@ -2130,17 +2130,17 @@ describe("Input general interaction", () => {
21302130

21312131
cy.get("#inputInDialog").shadow().find("input").realType("c");
21322132
cy.get("#inputInDialog")
2133-
.shadow()
2134-
.find<ResponsivePopover>("[ui5-responsive-popover]")
2135-
.ui5ResponsivePopoverOpened();
2133+
.shadow()
2134+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2135+
.ui5ResponsivePopoverOpened();
21362136
cy.get("#inputInDialog").shadow().find<ResponsivePopover>("ui5-responsive-popover").ui5ResponsivePopoverOpened();
21372137

21382138
cy.get("#inputInDialog").shadow().find("input").realPress("ArrowDown");
21392139
cy.get("#inputInDialog").shadow().find("input").realPress("Escape");
21402140
cy.get("#inputInDialog")
2141-
.shadow()
2142-
.find<ResponsivePopover>("[ui5-responsive-popover]")
2143-
.ui5ResponsivePopoverClosed();
2141+
.shadow()
2142+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2143+
.ui5ResponsivePopoverClosed();
21442144
cy.get("#dialog").should("have.attr", "open");
21452145
});
21462146

@@ -2294,7 +2294,7 @@ describe("Input general interaction", () => {
22942294
.as("popover")
22952295
.ui5ResponsivePopoverOpened();
22962296

2297-
cy.get("#openPickerInput")
2297+
cy.get("#openPickerInput")
22982298
.shadow()
22992299
.find<ResponsivePopover>("[ui5-responsive-popover]")
23002300
.should("have.attr", "open");
@@ -2368,7 +2368,7 @@ describe("Input general interaction", () => {
23682368
});
23692369

23702370
it("Tests prevented input event", () => {
2371-
cy.mount(<Input id="prevent-input-event" onInput={e => (e.target as Input).value.length > 3 ? e.preventDefault() : null}/>);
2371+
cy.mount(<Input id="prevent-input-event" onInput={e => (e.target as Input).value.length > 3 ? e.preventDefault() : null} />);
23722372

23732373
cy.get("#prevent-input-event").shadow().find("input").click().realType("abcd");
23742374
cy.get("#prevent-input-event").should("have.value", "abc");
@@ -2571,9 +2571,9 @@ describe("Lazy loading", () => {
25712571

25722572
cy.get("#field").shadow().find("input").click().realType("a");
25732573
cy.get("#field")
2574-
.shadow()
2575-
.find<ResponsivePopover>("[ui5-responsive-popover]")
2576-
.ui5ResponsivePopoverOpened();
2574+
.shadow()
2575+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2576+
.ui5ResponsivePopoverOpened();
25772577
});
25782578

25792579
it("Does not reopen picker on focus in", () => {
@@ -2589,9 +2589,9 @@ describe("Lazy loading", () => {
25892589
cy.get("#field").shadow().find("input").realPress("Tab");
25902590
cy.wait(3000);
25912591
cy.get("#field")
2592-
.shadow()
2593-
.find<ResponsivePopover>("[ui5-responsive-popover]")
2594-
.ui5ResponsivePopoverClosed();
2592+
.shadow()
2593+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2594+
.ui5ResponsivePopoverClosed();
25952595
});
25962596

25972597
it("Should not close picker when items are updated", () => {
@@ -2604,14 +2604,14 @@ describe("Lazy loading", () => {
26042604

26052605
cy.get("#field1").shadow().find("input").click().realType("S");
26062606
cy.get("#field1")
2607-
.shadow()
2608-
.find<ResponsivePopover>("[ui5-responsive-popover]")
2609-
.ui5ResponsivePopoverOpened();
2607+
.shadow()
2608+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2609+
.ui5ResponsivePopoverOpened();
26102610
cy.get("#field1").shadow().find("input").realType("b");
26112611
cy.get("#field1")
2612-
.shadow()
2613-
.find<ResponsivePopover>("[ui5-responsive-popover]")
2614-
.ui5ResponsivePopoverOpened();
2612+
.shadow()
2613+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2614+
.ui5ResponsivePopoverOpened();
26152615
});
26162616
});
26172617

@@ -2682,9 +2682,9 @@ describe("Property open", () => {
26822682
);
26832683

26842684
cy.get("#input-suggestions-open")
2685-
.shadow()
2686-
.find<ResponsivePopover>("[ui5-responsive-popover]")
2687-
.ui5ResponsivePopoverOpened();
2685+
.shadow()
2686+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2687+
.ui5ResponsivePopoverOpened();
26882688
cy.get("#input-suggestions-open").find("ui5-suggestion-item").should("have.length", 3);
26892689
});
26902690

@@ -2698,9 +2698,52 @@ describe("Property open", () => {
26982698
);
26992699

27002700
cy.get("#input-suggestions-open")
2701-
.shadow()
2702-
.find<ResponsivePopover>("[ui5-responsive-popover]")
2703-
.ui5ResponsivePopoverClosed();
2701+
.shadow()
2702+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2703+
.ui5ResponsivePopoverClosed();
2704+
});
2705+
2706+
it("should show only one valuestate popover when valueState changes during typing", () => {
2707+
cy.mount(
2708+
<Input
2709+
id="input-valuestate-dynamic"
2710+
valueState="None"
2711+
showSuggestions={true}
2712+
onInput={(e) => {
2713+
(e.target as Input).valueState = "Negative";
2714+
}}
2715+
>
2716+
<SuggestionItem text="Apple"></SuggestionItem>
2717+
</Input>
2718+
);
2719+
2720+
cy.get("#input-valuestate-dynamic")
2721+
.as("input")
2722+
.realClick();
2723+
2724+
cy.get("@input")
2725+
.should("be.focused");
2726+
2727+
cy.get("@input")
2728+
.realType("a");
2729+
2730+
cy.get("@input")
2731+
.should("have.attr", "value-state", "Negative");
2732+
2733+
cy.get("@input")
2734+
.shadow()
2735+
.find("[ui5-responsive-popover]")
2736+
.as("suggestionsPopover")
2737+
.should("have.attr", "open");
2738+
2739+
cy.get("@input")
2740+
.realPress("Escape");
2741+
2742+
cy.get("@input")
2743+
.shadow()
2744+
.find("[ui5-popover]")
2745+
.as("valueStatePopover")
2746+
.should("have.attr", "open");
27042747
});
27052748
});
27062749

@@ -2738,7 +2781,7 @@ describe("Input Composition", () => {
27382781
cy.get("@input").should("have.prop", "_isComposing", true);
27392782

27402783
cy.get("@nativeInput").trigger("compositionend", { data: "사랑" });
2741-
2784+
27422785
cy.get("@nativeInput")
27432786
.invoke("val", "사랑")
27442787
.trigger("input", { inputType: "insertCompositionText" });
@@ -2793,7 +2836,7 @@ describe("Input Composition", () => {
27932836
cy.get("@input").should("have.prop", "_isComposing", true);
27942837

27952838
cy.get("@nativeInput").trigger("compositionend", { data: "ありがとう" });
2796-
2839+
27972840
cy.get("@nativeInput")
27982841
.invoke("val", "ありがとう")
27992842
.trigger("input", { inputType: "insertCompositionText" });
@@ -2848,7 +2891,7 @@ describe("Input Composition", () => {
28482891
cy.get("@input").should("have.prop", "_isComposing", true);
28492892

28502893
cy.get("@nativeInput").trigger("compositionend", { data: "谢谢" });
2851-
2894+
28522895
cy.get("@nativeInput")
28532896
.invoke("val", "谢谢")
28542897
.trigger("input", { inputType: "insertCompositionText" });

packages/main/src/Input.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -762,20 +762,21 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
762762
const hasItems = !!this._flattenItems.length;
763763
const hasValue = !!this.value;
764764
const isFocused = this.shadowRoot!.querySelector("input") === getActiveElement();
765-
if (this.shouldDisplayOnlyValueStateMessage) {
766-
this.openValueStatePopover();
767-
} else {
768-
this.closeValueStatePopover();
769-
}
770-
771765
const preventOpenPicker = this.disabled || this.readonly;
766+
const shouldOpenSuggestions = !preventOpenPicker && !this._isPhone && hasItems && (this.open || (hasValue && isFocused && this.isTyping));
772767

773768
if (preventOpenPicker) {
774769
this.open = false;
775770
} else if (!this._isPhone) {
776771
this.open = hasItems && (this.open || (hasValue && isFocused && this.isTyping));
777772
}
778773

774+
if (this.shouldDisplayOnlyValueStateMessage && !shouldOpenSuggestions) {
775+
this.openValueStatePopover();
776+
} else {
777+
this.closeValueStatePopover();
778+
}
779+
779780
const value = this.value;
780781
const innerInput = this.getInputDOMRefSync();
781782

@@ -980,9 +981,9 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
980981
});
981982
link.addEventListener("keydown", this._linksListenersArray[index]);
982983
});
983-
}
984+
}
984985

985-
_removeLinksEventListeners() {
986+
_removeLinksEventListeners() {
986987
const links = this.linksInAriaValueStateHiddenText;
987988

988989
links.forEach((link, index) => {
@@ -1105,7 +1106,7 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
11051106
* Called on "focusin" of the native input HTML Element.
11061107
* **Note:** implemented in MultiInput, but used in the Input template.
11071108
*/
1108-
innerFocusIn(): void | undefined {}
1109+
innerFocusIn(): void | undefined { }
11091110

11101111
_onfocusout(e: FocusEvent) {
11111112
const toBeFocused = e.relatedTarget as HTMLElement;
@@ -1443,7 +1444,7 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
14431444
// If the feature is preloaded (the user manually imported InputSuggestions.js), it is already available on the constructor
14441445
if (Input.SuggestionsClass) {
14451446
setup(Input.SuggestionsClass);
1446-
// If feature is not preloaded, load it dynamically
1447+
// If feature is not preloaded, load it dynamically
14471448
} else {
14481449
import("./features/InputSuggestions.js").then(SuggestionsModule => {
14491450
setup(SuggestionsModule.default);
@@ -1808,7 +1809,7 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
18081809
const links: Array<HTMLElement> = [];
18091810
if (this.valueStateMessage) {
18101811
this.valueStateMessage.forEach(element => {
1811-
if (element.children.length) {
1812+
if (element.children.length) {
18121813
element.querySelectorAll("ui5-link").forEach(link => {
18131814
links.push(link as HTMLElement);
18141815
});

0 commit comments

Comments
 (0)