Skip to content

Commit d323d51

Browse files
authored
feat(ui5-textarea): add "valueStateMessage" slot (#1419)
- add "valueStateMessage" slot - rename "valueStateMessage" CSS classes from "ui5-input-valuestatemessage-success" to "ui5-valuestatemessage--success", because they should not belong to the input only and ad two dashes to show modification (by the BEM notation) - there is code duplication at places with the code that handles the valueStateMessage in Input, but also differences related to the fact that the TextArea has no suggestions and has this "exceeding" (like Warning) state. FIXES: #1401
1 parent fb500fc commit d323d51

File tree

10 files changed

+255
-46
lines changed

10 files changed

+255
-46
lines changed

packages/main/src/Input.js

+15-12
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,18 @@ const metadata = {
9494
},
9595

9696
/**
97-
* The slot is used in order to display a valueStateMessage.
97+
* Defines the value state message that will be displayed as pop up under the <code>ui5-input</code>.
9898
* <br><br>
99-
* <b>Note:</b> The valueStateMessage would be displayed only if the <code>ui5-input</code> has
100-
* a valueState of type <code>Information</code>, <code>Warning</code> or <code>Error</code>.
99+
*
100+
* <b>Note:</b> If not specified, a default text (in the respective language) will be displayed.
101+
* <br>
102+
* <b>Note:</b> The <code>valueStateMessage</code> would be displayed,
103+
* when the <code>ui5-input</code> is in <code>Information</code>, <code>Warning</code> or <code>Error</code> value state.
104+
* <br>
105+
* <b>Note:</b> If the <code>ui5-input</code> has <code>suggestionItems</code>,
106+
* the <code>valueStateMessage</code> would be displayed as part of the same popover, if used on desktop, or dialog - on phone.
101107
* @type {HTMLElement[]}
108+
* @since 1.0.0-rc.6
102109
* @slot
103110
* @public
104111
*/
@@ -808,21 +815,17 @@ class Input extends UI5Element {
808815
get classes() {
809816
return {
810817
popoverValueState: {
811-
"ui5-input-valuestatemessage-root": true,
812-
"ui5-input-valuestatemessage-success": this.valueState === ValueState.Success,
813-
"ui5-input-valuestatemessage-error": this.valueState === ValueState.Error,
814-
"ui5-input-valuestatemessage-warning": this.valueState === ValueState.Warning,
815-
"ui5-input-valuestatemessage-information": this.valueState === ValueState.Information,
818+
"ui5-valuestatemessage-root": true,
819+
"ui5-valuestatemessage--success": this.valueState === ValueState.Success,
820+
"ui5-valuestatemessage--error": this.valueState === ValueState.Error,
821+
"ui5-valuestatemessage--warning": this.valueState === ValueState.Warning,
822+
"ui5-valuestatemessage--information": this.valueState === ValueState.Information,
816823
},
817824
};
818825
}
819826

820827
get styles() {
821828
return {
822-
root: {
823-
"min-height": "1rem",
824-
"box-shadow": "none",
825-
},
826829
popoverHeader: {
827830
"width": `${this._inputWidth}px`,
828831
},

packages/main/src/InputPopover.hbs

+3-4
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,10 @@
7979
skip-registry-update
8080
no-padding
8181
no-arrow
82-
class="ui5-input-valuestatemessage-popover"
83-
placement-type="Bottom"
84-
style={{styles.root}}
82+
class="ui5-valuestatemessage-popover"
83+
placement-type="Bottom"
8584
>
86-
<div slot="header" class="ui5-responsive-popover-header {{classes.popoverValueState}}" style={{styles.popoverHeader}}>
85+
<div slot="header" class="ui5-responsive-popover-header {{classes.popoverValueState}}" style="{{styles.popoverHeader}}">
8786
{{> valueStateMessage}}
8887
</div>
8988
</ui5-popover>

packages/main/src/Popover.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,8 @@ class Popover extends UI5Element {
331331
}
332332

333333
isOpenerClicked(event) {
334-
return event.target === this._opener;
334+
const target = event.target;
335+
return target === this._opener || (target.getFocusDomRef && target.getFocusDomRef() === this._opener);
335336
}
336337

337338
/**

packages/main/src/TextArea.hbs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<textarea
2222
id="{{_id}}-inner"
2323
class="ui5-textarea-inner"
24-
placeholder="{{ placeholder }}"
24+
placeholder="{{placeholder}}"
2525
?disabled="{{disabled}}"
2626
?readonly="{{readonly}}"
2727
?required="{{required}}"

packages/main/src/TextArea.js

+152-1
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
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 Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
45
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
56
import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js";
67
import { isIE } from "@ui5/webcomponents-base/dist/Device.js";
78
import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
89

910
import TextAreaTemplate from "./generated/templates/TextAreaTemplate.lit.js";
10-
import { TEXTAREA_CHARACTERS_LEFT, TEXTAREA_CHARACTERS_EXCEEDED } from "./generated/i18n/i18n-defaults.js";
11+
import TextAreaPopoverTemplate from "./generated/templates/TextAreaPopoverTemplate.lit.js";
12+
13+
import {
14+
VALUE_STATE_INFORMATION,
15+
VALUE_STATE_ERROR,
16+
VALUE_STATE_WARNING,
17+
TEXTAREA_CHARACTERS_LEFT,
18+
TEXTAREA_CHARACTERS_EXCEEDED,
19+
} from "./generated/i18n/i18n-defaults.js";
1120

1221
// Styles
1322
import styles from "./generated/themes/TextArea.css.js";
23+
import valueStateMessageStyles from "./generated/themes/ValueStateMessage.css.js";
1424

1525
/**
1626
* @public
1727
*/
1828
const metadata = {
1929
tag: "ui5-textarea",
30+
managedSlots: true,
2031
properties: /** @lends sap.ui.webcomponents.main.TextArea.prototype */ {
2132
/**
2233
* Defines the value of the Web Component.
@@ -210,15 +221,49 @@ const metadata = {
210221
type: Boolean,
211222
},
212223

224+
/**
225+
* @private
226+
*/
213227
_mirrorText: {
214228
type: Object,
215229
multiple: true,
216230
defaultValue: "",
217231
},
232+
233+
/**
234+
* @private
235+
*/
218236
_maxHeight: {
219237
type: String,
220238
noAttribute: true,
221239
},
240+
241+
/**
242+
* @private
243+
*/
244+
_width: {
245+
type: Integer,
246+
},
247+
},
248+
slots: /** @lends sap.ui.webcomponents.main.TextArea.prototype */ {
249+
250+
/**
251+
* Defines the value state message that will be displayed as pop up under the <code>ui5-textarea</code>.
252+
*
253+
* <br><br>
254+
* <b>Note:</b> If not specified, a default text (in the respective language) will be displayed.
255+
*
256+
* <br><br>
257+
* <b>Note:</b> The <code>valueStateMessage</code> would be displayed if the <code>ui5-textarea</code> has
258+
* <code>valueState</code> of type <code>Information</code>, <code>Warning</code> or <code>Error</code>.
259+
* @type {HTMLElement[]}
260+
* @since 1.0.0-rc.7
261+
* @slot
262+
* @public
263+
*/
264+
valueStateMessage: {
265+
type: HTMLElement,
266+
},
222267
},
223268
events: /** @lends sap.ui.webcomponents.main.TextArea.prototype */ {
224269
/**
@@ -282,12 +327,31 @@ class TextArea extends UI5Element {
282327
return TextAreaTemplate;
283328
}
284329

330+
static get staticAreaTemplate() {
331+
return TextAreaPopoverTemplate;
332+
}
333+
334+
static get staticAreaStyles() {
335+
return valueStateMessageStyles;
336+
}
337+
285338
constructor() {
286339
super();
287340

341+
this._firstRendering = true;
342+
this._openValueStateMsgPopover = false;
343+
this._fnOnResize = this._onResize.bind(this);
288344
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
289345
}
290346

347+
onEnterDOM() {
348+
ResizeHandler.register(this, this._fnOnResize);
349+
}
350+
351+
onExitDOM() {
352+
ResizeHandler.deregister(this, this._fnOnResize);
353+
}
354+
291355
onBeforeRendering() {
292356
this._exceededTextProps = this._calcExceededText();
293357
this._mirrorText = this._tokenizeText(this.value);
@@ -307,6 +371,11 @@ class TextArea extends UI5Element {
307371
}
308372
}
309373

374+
onAfterRendering() {
375+
this.toggleValueStateMessage(this.openValueStateMsgPopover);
376+
this._firstRendering = false;
377+
}
378+
310379
getInputDomRef() {
311380
return this.getDomRef().querySelector("textarea");
312381
}
@@ -321,10 +390,12 @@ class TextArea extends UI5Element {
321390

322391
_onfocusin() {
323392
this.focused = true;
393+
this._openValueStateMsgPopover = true;
324394
}
325395

326396
_onfocusout() {
327397
this.focused = false;
398+
this._openValueStateMsgPopover = false;
328399
}
329400

330401
_onchange() {
@@ -355,6 +426,35 @@ class TextArea extends UI5Element {
355426
this.fireEvent("value-changed");
356427
}
357428

429+
_onResize() {
430+
if (this.displayValueStateMessage) {
431+
this._width = this.offsetWidth;
432+
}
433+
}
434+
435+
toggleValueStateMessage(toggle) {
436+
if (toggle) {
437+
this.openPopover();
438+
} else {
439+
this.closePopover();
440+
}
441+
}
442+
443+
async openPopover() {
444+
this.popover = await this._getPopover();
445+
this.popover && this.popover.openBy(this.shadowRoot.querySelector(".ui5-textarea-inner"));
446+
}
447+
448+
async closePopover() {
449+
this.popover = await this._getPopover();
450+
this.popover && this.popover.close();
451+
}
452+
453+
async _getPopover() {
454+
const staticAreaItem = await this.getStaticAreaItemDomRef();
455+
return staticAreaItem.querySelector("ui5-popover");
456+
}
457+
358458
_tokenizeText(value) {
359459
const tokenizedText = value.replace(/&/gm, "&amp;").replace(/"/gm, "&quot;").replace(/"/gm, "&#39;").replace(/</gm, "&lt;")
360460
.replace(/>/gm, "&gt;")
@@ -402,6 +502,16 @@ class TextArea extends UI5Element {
402502
};
403503
}
404504

505+
get classes() {
506+
return {
507+
valueStateMsg: {
508+
"ui5-valuestatemessage--error": this.valueState === ValueState.Error,
509+
"ui5-valuestatemessage--warning": this.valueState === ValueState.Warning || this.exceeding,
510+
"ui5-valuestatemessage--information": this.valueState === ValueState.Information,
511+
},
512+
};
513+
}
514+
405515
get styles() {
406516
const lineHeight = 1.4 * 16;
407517

@@ -417,6 +527,9 @@ class TextArea extends UI5Element {
417527
"height": (this.showExceededText ? "calc(100% - 26px)" : "100%"),
418528
"max-height": (this._maxHeight),
419529
},
530+
valueStateMsgPopover: {
531+
"max-width": `${this._width}px`,
532+
},
420533
};
421534
}
422535

@@ -432,6 +545,44 @@ class TextArea extends UI5Element {
432545
return this.valueState === "Error" ? "true" : undefined;
433546
}
434547

548+
get openValueStateMsgPopover() {
549+
return !this._firstRendering && this._openValueStateMsgPopover && this.displayValueStateMessage;
550+
}
551+
552+
get displayValueStateMessage() {
553+
return !!this.valueStateMessage.length || this.exceeding || (this.valueState !== ValueState.Success && this.valueState !== ValueState.None);
554+
}
555+
556+
get displayDefaultValueStateMessage() {
557+
if (this.valueStateMessage.length) {
558+
return false;
559+
}
560+
561+
return this.exceeding || (this.valueState !== ValueState.Success && this.valueState !== ValueState.None);
562+
}
563+
564+
get valueStateMessageText() {
565+
return this.valueStateMessage.map(x => x.cloneNode(true));
566+
}
567+
568+
get valueStateText() {
569+
if (this.valueState !== ValueState.Error && this.exceeding) {
570+
return this.valueStateTextMappings()[ValueState.Warning];
571+
}
572+
573+
return this.valueStateTextMappings()[this.valueState];
574+
}
575+
576+
valueStateTextMappings() {
577+
const i18nBundle = this.i18nBundle;
578+
579+
return {
580+
"Information": i18nBundle.getText(VALUE_STATE_INFORMATION),
581+
"Error": i18nBundle.getText(VALUE_STATE_ERROR),
582+
"Warning": i18nBundle.getText(VALUE_STATE_WARNING),
583+
};
584+
}
585+
435586
static async onDefine() {
436587
await fetchI18nBundle("@ui5/webcomponents");
437588
}

packages/main/src/TextAreaPopover.hbs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{{#if displayValueStateMessage}}
2+
<ui5-popover
3+
skip-registry-update
4+
no-padding
5+
no-arrow
6+
_disable-initial-focus
7+
class="ui5-valuestatemessage-popover"
8+
style="{{styles.valueStateMsgPopover}}"
9+
placement-type="Bottom"
10+
horizontal-align="Left"
11+
>
12+
<div slot="header" class="ui5-valuestatemessage-root {{classes.valueStateMsg}}">
13+
{{> valueStateMessage}}
14+
</div>
15+
</ui5-popover>
16+
{{/if}}
17+
18+
{{#*inline "valueStateMessage"}}
19+
{{#if displayDefaultValueStateMessage}}
20+
{{valueStateText}}
21+
{{else}}
22+
{{#each valueStateMessageText}}
23+
{{this}}
24+
{{/each}}
25+
{{/if}}
26+
{{/inline}}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
.ui5-input-valuestatemessage-root {
1+
.ui5-valuestatemessage-popover {
2+
min-height: 1rem;
3+
box-shadow: none;
4+
}
5+
6+
.ui5-valuestatemessage-root {
27
box-sizing: border-box;
38
display: inline-block;
49
color: var(--sapUiBaseColor);
@@ -7,20 +12,21 @@
712
padding: .3rem .625rem;
813
overflow: hidden;
914
text-overflow: ellipsis;
15+
min-width: 6.25rem;
1016
}
1117

12-
.ui5-input-valuestatemessage-success {
18+
.ui5-valuestatemessage--success {
1319
background: var(--sapSuccessBackground);
1420
}
1521

16-
.ui5-input-valuestatemessage-warning {
22+
.ui5-valuestatemessage--warning {
1723
background: var(--sapWarningBackground);
1824
}
1925

20-
.ui5-input-valuestatemessage-error {
26+
.ui5-valuestatemessage--error {
2127
background: var(--sapErrorBackground);
2228
}
2329

24-
.ui5-input-valuestatemessage-information {
30+
.ui5-valuestatemessage--information {
2531
background: var(--sapInformationBackground);
2632
}

0 commit comments

Comments
 (0)