Skip to content

Commit 538a79a

Browse files
authored
feat(ui5-input): implement valueStateMessage (#1297)
- Feature A popover with a message that appears right below the input field, when the Input is in "Error", "Warning" or ""Information" value state. - Usage To inform the user about the reason for that state.
1 parent 0af6c3d commit 538a79a

File tree

7 files changed

+190
-8
lines changed

7 files changed

+190
-8
lines changed

packages/main/src/Input.js

+133-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
22
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
3+
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
34
import { isIE, isPhone } from "@ui5/webcomponents-base/dist/Device.js";
45
import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
56
import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js";
@@ -13,12 +14,14 @@ import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
1314
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
1415
import "@ui5/webcomponents-icons/dist/icons/decline.js";
1516
import InputType from "./types/InputType.js";
17+
import Popover from "./Popover.js";
1618
// Templates
1719
import InputTemplate from "./generated/templates/InputTemplate.lit.js";
1820
import InputPopoverTemplate from "./generated/templates/InputPopoverTemplate.lit.js";
1921

2022
import {
2123
VALUE_STATE_SUCCESS,
24+
VALUE_STATE_INFORMATION,
2225
VALUE_STATE_ERROR,
2326
VALUE_STATE_WARNING,
2427
INPUT_SUGGESTIONS,
@@ -28,6 +31,7 @@ import {
2831
// Styles
2932
import styles from "./generated/themes/Input.css.js";
3033
import ResponsivePopoverCommonCss from "./generated/themes/ResponsivePopoverCommon.css.js";
34+
import ValueStateMessageCss from "./generated/themes/ValueStateMessage.css.js";
3135

3236
/**
3337
* @public
@@ -87,6 +91,19 @@ const metadata = {
8791
formSupport: {
8892
type: HTMLElement,
8993
},
94+
95+
/**
96+
* The slot is used in order to display a valueStateMessage.
97+
* <br><br>
98+
* <b>Note:</b> The valueStateMessage would be displayed only if the <code>ui5-input</code> has
99+
* a valueState of type <code>Information</code>, <code>Warning</code> or <code>Error</code>.
100+
* @type {HTMLElement[]}
101+
* @slot
102+
* @public
103+
*/
104+
valueStateMessage: {
105+
type: HTMLElement,
106+
},
90107
},
91108
properties: /** @lends sap.ui.webcomponents.main.Input.prototype */ {
92109

@@ -248,6 +265,15 @@ const metadata = {
248265
_wrapperAccInfo: {
249266
type: Object,
250267
},
268+
269+
_inputWidth: {
270+
type: Integer,
271+
},
272+
273+
_isPopoverOpen: {
274+
type: Boolean,
275+
noAttribute: true,
276+
},
251277
},
252278
events: /** @lends sap.ui.webcomponents.main.Input.prototype */ {
253279
/**
@@ -350,7 +376,7 @@ class Input extends UI5Element {
350376
}
351377

352378
static get staticAreaStyles() {
353-
return ResponsivePopoverCommonCss;
379+
return [ResponsivePopoverCommonCss, ValueStateMessageCss];
354380
}
355381

356382
constructor() {
@@ -383,6 +409,16 @@ class Input extends UI5Element {
383409
this.suggestionsTexts = [];
384410

385411
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
412+
413+
this._handleResizeBound = this._handleResize.bind(this);
414+
}
415+
416+
onEnterDOM() {
417+
ResizeHandler.register(this, this._handleResizeBound);
418+
}
419+
420+
onExitDOM() {
421+
ResizeHandler.deregister(this, this._handleResizeBound);
386422
}
387423

388424
onBeforeRendering() {
@@ -412,6 +448,10 @@ class Input extends UI5Element {
412448
}
413449
}
414450

451+
if (!this.firstRendering && !this.Suggestions && this.hasValueStateMessage) {
452+
this.toggle(this.shouldDisplayOnlyValueStateMessage);
453+
}
454+
415455
this.firstRendering = false;
416456
}
417457

@@ -477,6 +517,10 @@ class Input extends UI5Element {
477517
return;
478518
}
479519

520+
if (this.popover) {
521+
this.popover.close(false, false, true);
522+
}
523+
480524
this.previousValue = "";
481525
this.focused = false; // invalidating property
482526
}
@@ -513,6 +557,12 @@ class Input extends UI5Element {
513557
}
514558
}
515559

560+
_handleResize() {
561+
if (this.hasValueStateMessage) {
562+
this._inputWidth = this.offsetWidth;
563+
}
564+
}
565+
516566
_closeRespPopover() {
517567
this.Suggestions.close();
518568
}
@@ -531,6 +581,41 @@ class Input extends UI5Element {
531581
}
532582
}
533583

584+
toggle(isToggled) {
585+
if (isToggled) {
586+
this.openPopover();
587+
} else {
588+
this.closePopover();
589+
}
590+
}
591+
592+
/**
593+
* Checks if the popover is open.
594+
* @returns {Boolean} true if the popover is open, false otherwise
595+
* @public
596+
*/
597+
isOpen() {
598+
return !!this._isPopoverOpen;
599+
}
600+
601+
async openPopover() {
602+
this._isPopoverOpen = true;
603+
this.popover = await this._getPopover();
604+
this.popover.openBy(this);
605+
}
606+
607+
closePopover() {
608+
if (this.isOpen()) {
609+
this._isPopoverOpen = false;
610+
this.popover.close();
611+
}
612+
}
613+
614+
async _getPopover() {
615+
const staticAreaItem = await this.getStaticAreaItemDomRef();
616+
return staticAreaItem.querySelector("ui5-popover");
617+
}
618+
534619
enableSuggestions() {
535620
if (this.Suggestions) {
536621
return;
@@ -655,6 +740,7 @@ class Input extends UI5Element {
655740

656741
return {
657742
"Success": i18nBundle.getText(VALUE_STATE_SUCCESS),
743+
"Information": i18nBundle.getText(VALUE_STATE_INFORMATION),
658744
"Error": i18nBundle.getText(VALUE_STATE_ERROR),
659745
"Warning": i18nBundle.getText(VALUE_STATE_WARNING),
660746
};
@@ -699,10 +785,52 @@ class Input extends UI5Element {
699785
};
700786
}
701787

788+
get classes() {
789+
return {
790+
popoverValueState: {
791+
"ui5-input-valuestatemessage-root": true,
792+
"ui5-input-valuestatemessage-success": this.valueState === ValueState.Success,
793+
"ui5-input-valuestatemessage-error": this.valueState === ValueState.Error,
794+
"ui5-input-valuestatemessage-warning": this.valueState === ValueState.Warning,
795+
"ui5-input-valuestatemessage-information": this.valueState === ValueState.Information,
796+
},
797+
};
798+
}
799+
800+
get styles() {
801+
return {
802+
root: {
803+
"min-height": "1rem",
804+
"box-shadow": "none",
805+
},
806+
header: {
807+
"width": `${this._inputWidth}px`,
808+
},
809+
};
810+
}
811+
812+
get valueStateMessageText() {
813+
const valueStateMessage = this.valueStateMessage.map(x => x.cloneNode(true));
814+
815+
return valueStateMessage;
816+
}
817+
818+
get shouldDisplayOnlyValueStateMessage() {
819+
return this.hasValueStateMessage && !this.suggestionItems.length && this.focused;
820+
}
821+
822+
get shouldDisplayDefaultValueStateMessage() {
823+
return !this.valueStateMessage.length && this.hasValueStateMessage;
824+
}
825+
702826
get hasValueState() {
703827
return this.valueState !== ValueState.None;
704828
}
705829

830+
get hasValueStateMessage() {
831+
return this.hasValueState && this.valueState !== ValueState.Success;
832+
}
833+
706834
get valueStateText() {
707835
return this.valueStateTextMappings()[this.valueState];
708836
}
@@ -712,7 +840,10 @@ class Input extends UI5Element {
712840
}
713841

714842
static async onDefine() {
715-
await fetchI18nBundle("@ui5/webcomponents");
843+
await Promise.all([
844+
Popover.define(),
845+
fetchI18nBundle("@ui5/webcomponents"),
846+
]);
716847
}
717848
}
718849

packages/main/src/InputPopover.hbs

+14-1
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,17 @@
5757
>OK</ui5-button>
5858
</div>
5959
</ui5-responsive-popover>
60-
{{/if}}
60+
{{/if}}
61+
{{#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>
73+
{{/if}}

packages/main/src/Popover.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ class Popover extends UI5Element {
337337
* Closes the popover.
338338
* @public
339339
*/
340-
close(escPressed = false, preventRegitryUpdate = false) {
340+
close(escPressed = false, preventRegitryUpdate = false, preventFocusRestore = false) {
341341
if (!this.opened) {
342342
return;
343343
}
@@ -353,7 +353,7 @@ class Popover extends UI5Element {
353353
removeOpenedPopover(this);
354354
}
355355

356-
if (!this._prevetFocusRestore) {
356+
if (!preventFocusRestore) {
357357
this.resetFocus();
358358
}
359359

packages/main/src/i18n/messagebundle.properties

+3
Original file line numberDiff line numberDiff line change
@@ -303,5 +303,8 @@ VALUE_STATE_ERROR=Invalid entry
303303
#XTOL: text that is appended to the tooltips of input fields etc. which are marked to have a warning
304304
VALUE_STATE_WARNING=Warning issued
305305

306+
#XTOL: text that is appended to the tooltips of input fields etc. which are marked to be informative
307+
VALUE_STATE_INFORMATION=Informative entry
308+
306309
#XTOL: text that is appended to the tooltips of input fields etc. which are marked to be in success state
307310
VALUE_STATE_SUCCESS=Entry successfully validated
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.ui5-input-valuestatemessage-root {
2+
box-sizing: border-box;
3+
display: inline-block;
4+
color: var(--sapUiBaseColor);
5+
font-size: var(--sapFontSmallSize);
6+
font-family: var(--sapFontFamily);
7+
padding: .3rem .625rem;
8+
overflow: hidden;
9+
text-overflow: ellipsis;
10+
}
11+
12+
.ui5-input-valuestatemessage-success {
13+
background: var(--sapSuccessBackground);
14+
}
15+
16+
.ui5-input-valuestatemessage-warning {
17+
background: var(--sapWarningBackground);
18+
}
19+
20+
.ui5-input-valuestatemessage-error {
21+
background: var(--sapErrorBackground);
22+
}
23+
24+
.ui5-input-valuestatemessage-information {
25+
background: var(--sapInformationBackground);
26+
}

packages/main/test/pages/Input.html

+2-3
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,12 @@ <h3> 'suggestionItemSelect' event result on group item</h3>
103103

104104
<h3> Test 'input' event</h3>
105105
<ui5-input id="input2" value-state="Error" placeholder="Error state ...">
106+
<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>
106107
<ui5-icon slot="icon" name="message-error"></ui5-icon>
107108
</ui5-input>
108109
<h3> 'input' test result</h3>
109110
<ui5-input id="inputLiveChangeResult"></ui5-input>
110111

111-
112-
113112
<h3> Input test change</h3>
114113
<ui5-input id="inputChange"> </ui5-input>
115114
<h3> Input test change result</h3>
@@ -161,7 +160,7 @@ <h3> Inputs alignment</h3>
161160
<ui5-option selected>Compact</ui5-option>
162161
<ui5-option>Condensed</ui5-option>
163162
</ui5-select>
164-
163+
165164
<ui5-input value="input" value-state="Error">
166165
<ui5-icon slot="icon" name="message-warning"></ui5-icon>
167166
</ui5-input>

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

+10
Original file line numberDiff line numberDiff line change
@@ -211,4 +211,14 @@ describe("Input general interaction", () => {
211211
assert.ok(input5.getProperty("maxlength"), "Input's maxlength property should be applied.");
212212
assert.strictEqual(inputShadowRef.getAttribute("maxlength"), "10", "Input's maxlength attribute should be applied.");
213213
});
214+
215+
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");
219+
220+
inputShadowRef.click();
221+
222+
assert.ok(popover.getProperty("opened"), "Popover with valueStateMessage should be opened.");
223+
});
214224
});

0 commit comments

Comments
 (0)