Skip to content

Commit 39068b3

Browse files
authored
feat(ui5-input): implement valueStateMessage with suggestions (#1390)
display value state msg together with suggestion items both on desktop and phone
1 parent 98f5573 commit 39068b3

File tree

6 files changed

+165
-31
lines changed

6 files changed

+165
-31
lines changed

packages/main/src/Input.js

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
22
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
33
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
4+
import RenderScheduler from "@ui5/webcomponents-base/dist/RenderScheduler.js";
45
import { isIE, isPhone } from "@ui5/webcomponents-base/dist/Device.js";
56
import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
67
import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js";
@@ -280,6 +281,10 @@ const metadata = {
280281
type: Integer,
281282
},
282283

284+
_listWidth: {
285+
type: Integer,
286+
},
287+
283288
_isPopoverOpen: {
284289
type: Boolean,
285290
noAttribute: true,
@@ -452,13 +457,17 @@ class Input extends UI5Element {
452457
this.updateStaticAreaItemContentDensity();
453458
this.Suggestions.toggle(shouldOpenSuggestions);
454459

460+
RenderScheduler.whenFinished().then(async () => {
461+
this._listWidth = await this.Suggestions._getListWidth();
462+
});
463+
455464
if (!isPhone() && shouldOpenSuggestions) {
456465
// Set initial focus to the native input
457466
this.getInputDOMRef().focus();
458467
}
459468
}
460469

461-
if (!this.firstRendering && !this.Suggestions && this.hasValueStateMessage) {
470+
if (!this.firstRendering && this.hasValueStateMessage) {
462471
this.toggle(this.shouldDisplayOnlyValueStateMessage);
463472
}
464473

@@ -539,6 +548,7 @@ class Input extends UI5Element {
539548
if (isPhone() && !this.readonly && this.Suggestions) {
540549
this.updateStaticAreaItemContentDensity();
541550
this.Suggestions.open(this);
551+
this.isRespPopoverOpen = true;
542552
}
543553
}
544554

@@ -592,7 +602,7 @@ class Input extends UI5Element {
592602
}
593603

594604
toggle(isToggled) {
595-
if (isToggled) {
605+
if (isToggled && !this.isRespPopoverOpen) {
596606
this.openPopover();
597607
} else {
598608
this.closePopover();
@@ -813,9 +823,14 @@ class Input extends UI5Element {
813823
"min-height": "1rem",
814824
"box-shadow": "none",
815825
},
816-
header: {
826+
popoverHeader: {
817827
"width": `${this._inputWidth}px`,
818828
},
829+
suggestionPopoverHeader: {
830+
"display": this._listWidth === 0 ? "none" : "inline-block",
831+
"width": `${this._listWidth}px`,
832+
"padding": "0.5625rem 1rem",
833+
},
819834
};
820835
}
821836

@@ -826,7 +841,7 @@ class Input extends UI5Element {
826841
}
827842

828843
get shouldDisplayOnlyValueStateMessage() {
829-
return this.hasValueStateMessage && !this.suggestionItems.length && this.focused;
844+
return this.hasValueStateMessage && !this.shouldOpenSuggestions() && this.focused;
830845
}
831846

832847
get shouldDisplayDefaultValueStateMessage() {
@@ -849,6 +864,10 @@ class Input extends UI5Element {
849864
return this.i18nBundle.getText(INPUT_SUGGESTIONS);
850865
}
851866

867+
get _isPhone() {
868+
return isPhone();
869+
}
870+
852871
static async onDefine() {
853872
await Promise.all([
854873
Popover.define(),

packages/main/src/InputPopover.hbs

+48-21
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{{#if showSuggestions}}
22
<ui5-responsive-popover
3-
content-only-on-desktop
43
no-arrow
54
_disable-initial-focus
65
placement-type="Bottom"
76
horizontal-align="Left"
8-
@ui5-afterOpen={{_afterOpenPopover}}
9-
@ui5-afterClose={{_afterClosePopover}}
7+
@ui5-afterOpen="{{_afterOpenPopover}}"
8+
@ui5-afterClose="{{_afterClosePopover}}"
109
>
10+
{{#if _isPhone}}
1111
<div slot="header" class="ui5-responsive-popover-header">
1212
<div class="row">
1313
<span>{{_headerTitleText}}</span>
@@ -31,7 +31,22 @@
3131
/>
3232
</div>
3333
</div>
34+
{{#if hasValueStateMessage}}
35+
<div class="row {{classes.popoverValueState}}" style="{{styles.suggestionPopoverHeader}}">
36+
{{> valueStateMessage}}
37+
</div>
38+
{{/if}}
3439
</div>
40+
{{/if}}
41+
42+
{{#unless _isPhone}}
43+
{{#if hasValueStateMessage}}
44+
<div slot="header" class="ui5-responsive-popover-header {{classes.popoverValueState}}" style={{styles.suggestionPopoverHeader}}>
45+
{{> valueStateMessage}}
46+
</div>
47+
{{/if}}
48+
{{/unless}}
49+
3550

3651
<ui5-list separators="Inner">
3752
{{#each suggestionsTexts}}
@@ -49,25 +64,37 @@
4964
{{/if}}
5065
{{/each}}
5166
</ui5-list>
52-
53-
<div slot="footer" class="ui5-responsive-popover-footer">
54-
<ui5-button
55-
design="Transparent"
56-
@click="{{_closeRespPopover}}"
57-
>OK</ui5-button>
58-
</div>
67+
{{#if _isPhone}}
68+
<div slot="footer" class="ui5-responsive-popover-footer">
69+
<ui5-button
70+
design="Transparent"
71+
@click="{{_closeRespPopover}}"
72+
>OK</ui5-button>
73+
</div>
74+
{{/if}}
5975
</ui5-responsive-popover>
6076
{{/if}}
6177
{{#if hasValueStateMessage}}
62-
<ui5-popover class="ui5-input-valuestatemessage-popover" skip-registry-update placement-type="Bottom" no-padding no-arrow style={{styles.root}}>
63-
<div slot="header" class="ui5-responsive-popover-header {{classes.popoverValueState}}" style={{styles.header}}>
64-
{{#if shouldDisplayDefaultValueStateMessage}}
65-
{{valueStateText}}
66-
{{else}}
67-
{{#each valueStateMessageText}}
68-
{{this}}
69-
{{/each}}
70-
{{/if}}
71-
</div>
72-
</ui5-popover>
78+
<ui5-popover
79+
skip-registry-update
80+
no-padding
81+
no-arrow
82+
class="ui5-input-valuestatemessage-popover"
83+
placement-type="Bottom"
84+
style={{styles.root}}
85+
>
86+
<div slot="header" class="ui5-responsive-popover-header {{classes.popoverValueState}}" style={{styles.popoverHeader}}>
87+
{{> valueStateMessage}}
88+
</div>
89+
</ui5-popover>
7390
{{/if}}
91+
92+
{{#*inline "valueStateMessage"}}
93+
{{#if shouldDisplayDefaultValueStateMessage}}
94+
{{valueStateText}}
95+
{{else}}
96+
{{#each valueStateMessageText}}
97+
{{this}}
98+
{{/each}}
99+
{{/if}}
100+
{{/inline}}

packages/main/src/features/InputSuggestions.js

+5
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,11 @@ class Suggestions {
273273
return this.responsivePopover.querySelector("ui5-list");
274274
}
275275

276+
async _getListWidth() {
277+
const list = await this._getList();
278+
return list.offsetWidth;
279+
}
280+
276281
_getRealItems() {
277282
return this._getComponent().getSlottedNodes(this.slotName);
278283
}

packages/main/test/pages/Input.html

+24-2
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,26 @@ <h3> Test 'change' event</h3>
9595
<ui5-input id="input1" value-state="Warning" placeholder="Warning state ...">
9696
<ui5-icon slot="icon" name="message-warning"></ui5-icon>
9797
</ui5-input>
98+
99+
<h3>Input with valueState and Dynamic suggestions</h3>
100+
<ui5-input id="inputError"
101+
style="width: 500px"
102+
show-suggestions
103+
value-state="Error"
104+
placeholder="Search for a country ...">
105+
<div slot="valueStateMessage">Information message. This is a <a href="#">Link</a>. Extra long text used as an information message. Extra long text used as an information message - 2. Extra long text used as an information message - 3.</div>
106+
</ui5-input>
107+
108+
<h3>Input suggestions with valueState and ui5-li</h3>
109+
<ui5-input show-suggestions style="width: 500px" value-state="Information">
110+
<ui5-li>Cozy</ui5-li>
111+
<ui5-li>Extra long text used as a list item. Extra long text used as a list item -2.</ui5-li>
112+
<ui5-li>Condensed</ui5-li>
113+
<ui5-li>Cozy</ui5-li>
114+
<ui5-li>Compact</ui5-li>
115+
<ui5-li>Condensed</ui5-li>
116+
</ui5-input>
117+
98118
<h3> 'change' event result</h3>
99119
<ui5-input id="inputResult"></ui5-input>
100120

@@ -192,6 +212,7 @@ <h3> Input with multiple icons</h3>
192212
var input = document.getElementById('myInput');
193213
var inputGrouping = document.getElementById('myInputGrouping');
194214
var myInput2 = document.getElementById('myInput2');
215+
var inputError = document.getElementById("inputError");
195216
var input1 = document.getElementById('input1');
196217
var input2 = document.getElementById('input2');
197218
var inputResult = document.getElementById('inputResult');
@@ -215,8 +236,8 @@ <h3> Input with multiple icons</h3>
215236
});
216237
}
217238

218-
while (input.firstChild) {
219-
input.removeChild(input.firstChild);
239+
while (input.lastChild && input.lastChild !== input.valueStateMessage[0]) {
240+
input.removeChild(input.lastChild);
220241
}
221242

222243
suggestionItems.forEach(function(item, idx) {
@@ -236,6 +257,7 @@ <h3> Input with multiple icons</h3>
236257

237258
input.addEventListener("ui5-input", suggest);
238259
inputCompact.addEventListener("ui5-input", suggest);
260+
inputError.addEventListener("ui5-input", suggest);
239261

240262
input.addEventListener("ui5-suggestionItemSelect", function (event) {
241263
var item = event.detail.item;

packages/main/test/samples/Input.sample.html

+57-1
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,66 @@ <h3>Input with Value State</h3>
126126
<ui5-input value="Success" value-state="Success"></ui5-input>
127127
<ui5-input value="Warning" value-state="Warning"></ui5-input>
128128
<ui5-input value="Error" value-state="Error"></ui5-input>
129-
<ui5-input class="samples-margin samples-responsive-margin-bottom" value="Information" value-state="Information"></ui5-input>
129+
<ui5-input value="Information" value-state="Information"></ui5-input>
130130
</xmp></pre>
131131
</section>
132132

133+
<section>
134+
<h3>Input with Suggestions and Value State message</h3>
135+
<div class="snippet">
136+
<ui5-input class="samples-margin samples-responsive-margin-bottom" id="value-state-suggestions" class="samples-responsive-padding-bottom" placeholder="Start typing country name" show-suggestions value-state="Warning">
137+
</ui5-input>
138+
139+
<ui5-input class="samples-margin samples-responsive-margin-bottom" placeholder="Choose content density" show-suggestions value-state="Error">
140+
<div slot="valueStateMessage">This is an error message. Extra long text used as an error message.</div>
141+
<ui5-li>Cozy</ui5-li>
142+
<ui5-li>Compact</ui5-li>
143+
<ui5-li>Condensed</ui5-li>
144+
</ui5-input>
145+
146+
<script>
147+
var ui5_database_entries = ["Argentina", "Albania", "Algeria", "Angola", "Austria", "Australia", "Bulgaria", "Canada", "Columbia", "Croatia", "Denmark",
148+
"England", "Finland", "France", "Germany", "Hungary", "Ireland", "Italy", "Kuwait", "Luxembourg", "Mexico", "Morocco", "Norway", "Paraguay", "Philippines", "Portugal", "Spain", "Sweden", "Sri Lanka", "Senegal", "United Kingdom", "USA" ];
149+
150+
var oInput = document.getElementById("value-state-suggestions");
151+
152+
oInput.addEventListener("input", function(event) {
153+
var value = event.target.value;
154+
var suggestionItems = [];
155+
156+
if (value) {
157+
suggestionItems = ui5_database_entries.filter(function (item) {
158+
return item.toUpperCase().indexOf(value.toUpperCase()) === 0;
159+
});
160+
}
161+
162+
[].slice.call(oInput.children).forEach(function(child) {
163+
oInput.removeChild(child);
164+
});
165+
166+
suggestionItems.forEach(function(item) {
167+
var li = document.createElement("ui5-suggestion-item");
168+
li.icon = "world";
169+
li.id = item;
170+
li.text = item;
171+
oInput.appendChild(li);
172+
});
173+
});
174+
</script>
175+
</div>
176+
177+
<pre class="prettyprint lang-html"><xmp>
178+
<ui5-input id="value-state-suggestions" class="samples-responsive-padding-bottom" placeholder="Start typing country name" show-suggestions value-state="Warning">
179+
</ui5-input>
180+
181+
<ui5-input placeholder="Choose content density" show-suggestions value-state="Error">
182+
<div slot="valueStateMessage">This is an error message. Extra long text used as an error message.</div>
183+
<ui5-li>Cozy</ui5-li>
184+
<ui5-li>Compact</ui5-li>
185+
<ui5-li>Condensed</ui5-li>
186+
</ui5-input>
187+
</xmp></pre>
188+
133189
<section>
134190
<h3>Input as Search Field</h3>
135191
<div class="snippet">

packages/main/test/specs/Input.spec.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,17 @@ describe("Input general interaction", () => {
213213
});
214214

215215
it("Checks if valueStateMessage is shown", () => {
216-
let inputShadowRef = browser.$("#input2").shadow$("input");
217-
let staticAreaItemClassName = browser.getStaticAreaItemClassName("#input2");
218-
let popover = browser.$(`.${staticAreaItemClassName}`).shadow$("ui5-popover");
216+
const inputShadowRef = browser.$("#inputError").shadow$("input");
217+
const staticAreaItemClassName = browser.getStaticAreaItemClassName("#inputError");
218+
const popover = browser.$(`.${staticAreaItemClassName}`).shadow$("ui5-popover");
219+
const respPopover = browser.$(`.${staticAreaItemClassName}`).shadow$("ui5-responsive-popover .ui5-responsive-popover-header");
219220

220221
inputShadowRef.click();
221222

222223
assert.ok(popover.getProperty("opened"), "Popover with valueStateMessage should be opened.");
224+
225+
inputShadowRef.keys("a");
226+
227+
assert.ok(respPopover, "Responsive popover with valueStateMessage should be opened.");
223228
});
224229
});

0 commit comments

Comments
 (0)