Skip to content

Commit 82b3d41

Browse files
authored
feat(ui5-datepicker): implement valuestatemessage slot (#1476)
1 parent ec14719 commit 82b3d41

File tree

11 files changed

+130
-38
lines changed

11 files changed

+130
-38
lines changed

packages/main/src/DatePicker.hbs

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
data-sap-focus-ref
1919
._inputAccInfo ="{{accInfo}}"
2020
>
21+
22+
{{#if valueStateMessage.length}}
23+
<slot name="valueStateMessage" slot="valueStateMessage"></slot>
24+
{{/if}}
25+
2126
{{#unless readonly}}
2227
<ui5-icon
2328
slot="icon"

packages/main/src/DatePicker.js

+21
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import ResponsivePopoverCommonCss from "./generated/themes/ResponsivePopoverComm
3737
*/
3838
const metadata = {
3939
tag: "ui5-datepicker",
40+
managedSlots: true,
4041
properties: /** @lends sap.ui.webcomponents.main.DatePicker.prototype */ {
4142
/**
4243
* Defines a formatted date value.
@@ -199,6 +200,26 @@ const metadata = {
199200
type: Object,
200201
},
201202
},
203+
204+
slots: /** @lends sap.ui.webcomponents.main.DatePicker.prototype */ {
205+
/**
206+
* Defines the value state message that will be displayed as pop up under the <code>ui5-datepicker</code>.
207+
* <br><br>
208+
*
209+
* <b>Note:</b> If not specified, a default text (in the respective language) will be displayed.
210+
* <br>
211+
* <b>Note:</b> The <code>valueStateMessage</code> would be displayed,
212+
* when the <code>ui5-datepicker</code> is in <code>Information</code>, <code>Warning</code> or <code>Error</code> value state.
213+
* @type {HTMLElement}
214+
* @since 1.0.0-rc.7
215+
* @slot
216+
* @public
217+
*/
218+
valueStateMessage: {
219+
type: HTMLElement,
220+
},
221+
},
222+
202223
events: /** @lends sap.ui.webcomponents.main.DatePicker.prototype */ {
203224

204225
/**

packages/main/src/Input.js

+46-19
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,11 @@ const metadata = {
300300
type: Boolean,
301301
noAttribute: true,
302302
},
303+
304+
_inputIconFocused: {
305+
type: Boolean,
306+
noAttribute: true,
307+
},
303308
},
304309
events: /** @lends sap.ui.webcomponents.main.Input.prototype */ {
305310
/**
@@ -474,7 +479,7 @@ class Input extends UI5Element {
474479

475480
if (!isPhone() && shouldOpenSuggestions) {
476481
// Set initial focus to the native input
477-
this.getInputDOMRef().focus();
482+
this.inputDomRef.focus();
478483
}
479484
}
480485

@@ -535,15 +540,21 @@ class Input extends UI5Element {
535540
}
536541
}
537542

538-
_onfocusin(event) {
543+
async _onfocusin(event) {
539544
this.focused = true; // invalidating property
540545
this.previousValue = this.value;
546+
547+
await this.getInputDOMRef();
548+
this._inputIconFocused = event.target === this.querySelector("ui5-icon");
541549
}
542550

543551
_onfocusout(event) {
544-
// if focusout is triggered by pressing on suggestion item skip invalidation, because re-rendering
552+
const focusedOutToSuggestions = this.Suggestions && event.relatedTarget && event.relatedTarget.shadowRoot && event.relatedTarget.shadowRoot.contains(this.Suggestions.responsivePopover);
553+
const focusedOutToValueStateMessage = event.relatedTarget && event.relatedTarget.shadowRoot && event.relatedTarget.shadowRoot.querySelector(".ui5-valuestatemessage-root");
554+
555+
// if focusout is triggered by pressing on suggestion item or value state message popover, skip invalidation, because re-rendering
545556
// will happen before "itemPress" event, which will make item "active" state not visualized
546-
if (this.Suggestions && event.relatedTarget && event.relatedTarget.shadowRoot && event.relatedTarget.shadowRoot.contains(this.Suggestions.responsivePopover)) {
557+
if (focusedOutToSuggestions || focusedOutToValueStateMessage) {
547558
return;
548559
}
549560

@@ -567,8 +578,9 @@ class Input extends UI5Element {
567578
this.fireEvent(this.EVENT_CHANGE);
568579
}
569580

570-
_handleInput(event) {
571-
if (event.target === this.getInputDOMRef()) {
581+
async _handleInput(event) {
582+
await this.getInputDOMRef();
583+
if (event.target === this.inputDomRef) {
572584
// stop the native event, as the semantic "input" would be fired.
573585
event.stopImmediatePropagation();
574586
}
@@ -577,7 +589,7 @@ class Input extends UI5Element {
577589
- value of the host and the internal input should be differnt in case of actual input
578590
- input is called when a key is pressed => keyup should not be called yet
579591
*/
580-
const skipFiring = (this.getInputDOMRef().value === this.value) && isIE() && !this._keyDown && !!this.placeholder;
592+
const skipFiring = (this.inputDomRef.value === this.value) && isIE() && !this._keyDown && !!this.placeholder;
581593

582594
!skipFiring && this.fireEventByAction(this.ACTION_USER_INPUT);
583595

@@ -589,19 +601,18 @@ class Input extends UI5Element {
589601
}
590602

591603
_handleResize() {
592-
if (this.hasValueStateMessage) {
593-
this._inputWidth = this.offsetWidth;
594-
}
604+
this._inputWidth = this.offsetWidth;
595605
}
596606

597607
_closeRespPopover() {
598608
this.Suggestions.close();
599609
}
600610

601-
_afterOpenPopover() {
611+
async _afterOpenPopover() {
602612
// Set initial focus to the native input
603613
if (isPhone()) {
604-
this.getInputDOMRef().focus();
614+
await this.getInputDOMRef();
615+
this.inputDomRef.focus();
605616
}
606617
}
607618

@@ -692,7 +703,9 @@ class Input extends UI5Element {
692703
this.value = item.group ? "" : item.textContent;
693704
}
694705

695-
fireEventByAction(action) {
706+
async fireEventByAction(action) {
707+
await this.getInputDOMRef();
708+
696709
if (this.disabled || this.readonly) {
697710
return;
698711
}
@@ -724,23 +737,25 @@ class Input extends UI5Element {
724737
getInputValue() {
725738
const inputDOM = this.getDomRef();
726739
if (inputDOM) {
727-
return this.getInputDOMRef().value;
740+
return this.inputDomRef.value;
728741
}
729742
return "";
730743
}
731744

732-
getInputDOMRef() {
745+
async getInputDOMRef() {
733746
let inputDomRef;
734747

735-
if (isPhone()) {
748+
if (isPhone() && this.Suggestions) {
749+
await this.Suggestions._respPopover();
736750
inputDomRef = this.Suggestions && this.Suggestions.responsivePopover.querySelector(".ui5-input-inner-phone");
737751
}
738752

739753
if (!inputDomRef) {
740754
inputDomRef = this.getDomRef().querySelector(`#${this.getInputId()}`);
741755
}
742756

743-
return inputDomRef;
757+
this.inputDomRef = inputDomRef;
758+
return this.inputDomRef;
744759
}
745760

746761
getLabelableElementId() {
@@ -842,7 +857,17 @@ class Input extends UI5Element {
842857
}
843858

844859
get valueStateMessageText() {
845-
const valueStateMessage = this.valueStateMessage.map(x => x.cloneNode(true));
860+
const valueStateMessage = [];
861+
862+
this.valueStateMessage.forEach(el => {
863+
if (el.localName === "slot") {
864+
el.assignedNodes({ flatten: true }).forEach(assignedNode => {
865+
valueStateMessage.push(assignedNode.cloneNode(true));
866+
});
867+
} else {
868+
valueStateMessage.push(el.cloneNode(true));
869+
}
870+
});
846871

847872
return valueStateMessage;
848873
}
@@ -860,7 +885,9 @@ class Input extends UI5Element {
860885
}
861886

862887
get hasValueStateMessage() {
863-
return this.hasValueState && this.valueState !== ValueState.Success;
888+
return this.hasValueState && this.valueState !== ValueState.Success
889+
&& (!this._inputIconFocused // Handles the cases when valueStateMessage is forwarded (from datepicker e.g.)
890+
|| (this._isPhone && this.Suggestions)); // Handles Input with suggestions on mobile
864891
}
865892

866893
get valueStateText() {

packages/main/src/InputPopover.hbs

+5-5
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
{{#if hasValueStateMessage}}
3535
<div class="row {{classes.popoverValueState}}" style="{{styles.suggestionPopoverHeader}}">
3636
{{> valueStateMessage}}
37-
</div>
37+
</div>
3838
{{/if}}
3939
</div>
4040
{{/if}}
@@ -43,23 +43,23 @@
4343
{{#if hasValueStateMessage}}
4444
<div slot="header" class="ui5-responsive-popover-header {{classes.popoverValueState}}" style={{styles.suggestionPopoverHeader}}>
4545
{{> valueStateMessage}}
46-
</div>
46+
</div>
4747
{{/if}}
4848
{{/unless}}
4949

5050

5151
<ui5-list separators="Inner">
5252
{{#each suggestionsTexts}}
5353
{{#if group}}
54-
<ui5-li-groupheader>{{ this.text }}</ui5-groupheader>
54+
<ui5-li-groupheader>{{ this.text }}</ui5-li-groupheader>
5555
{{else}}
5656
<ui5-li
5757
image="{{this.image}}"
5858
icon="{{this.icon}}"
5959
description="{{this.description}}"
6060
info="{{this.info}}"
6161
info-state="{{this.infoState}}"
62-
@ui5-_itemPress="{{ fnOnSuggestionItemPress }}"
62+
@ui5-_itemPress="{{ fnOnSuggestionItemPress }}"
6363
>{{ this.text }}</ui5-li>
6464
{{/if}}
6565
{{/each}}
@@ -96,4 +96,4 @@
9696
{{this}}
9797
{{/each}}
9898
{{/if}}
99-
{{/inline}}
99+
{{/inline}}

packages/main/test/pageobjects/DatePickerTestPage.js

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class DatePickerTestPage {
3939
return browser.$(this._sut).shadow$("ui5-input").shadow$("input");
4040
}
4141

42+
get inputStaticAreaItem() {
43+
return browser.$(`.${this.input.getProperty("_id")}`);
44+
}
45+
4246
hasIcon() {
4347
return browser.execute(function(id) {
4448
return !!document.querySelector(id).shadowRoot.querySelector("ui5-icon");

packages/main/test/pages/DatePicker.html

+8
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@
4141
title='Delivery Date!'>
4242
</ui5-datepicker>
4343

44+
<ui5-datepicker id='ui5-datepicker-value-state-message'
45+
placeholder='Delivery Date...'
46+
value-state="Error"
47+
title='Delivery Date!'>
48+
<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>
49+
<div slot="valueStateMessage">Information message 2. 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>
50+
</ui5-datepicker>
51+
4452
<h3>placeholder + title + events</h3>
4553
<ui5-datepicker id='dp5'
4654
placeholder='Delivery Date...'

packages/main/test/pages/DatePicker_test_page.html

+9
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ <h3>Test placeholder</h3>
5656
<ui5-datepicker id="dp14" format-pattern="MMM d, y"></ui5-datepicker>
5757
<ui5-datepicker id="dp15" format-pattern="MMM d, y" placeholder="Delivery date"></ui5-datepicker>
5858

59+
<h3>DatePicker with valueStateMessage</h3>
60+
<ui5-datepicker
61+
id="dp17"
62+
value-state="Error">
63+
<div slot="valueStateMessage" id="coolValueStateMessage">
64+
This date is wrong
65+
</div>
66+
</ui5-datepicker>
67+
5968
<script>
6069
var originalGetDate = Date.prototype.getDate;
6170

packages/main/test/pages/Input.html

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ <h3>Input with valueState and Dynamic suggestions</h3>
103103
value-state="Error"
104104
placeholder="Search for a country ...">
105105
<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+
<div slot="valueStateMessage">Information message 2. 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-input>
107108

108109
<h3>Input suggestions with valueState and ui5-li</h3>

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

+7-3
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,18 @@ <h3>Basic DatePicker</h3>
1919
</section>
2020

2121
<section>
22-
<h3>DatePicker with Placeholder, Tooltip, Events, and ValueState</h3>
22+
<h3>DatePicker with Placeholder, Tooltip, Events, ValueState and valueStateMessage</h3>
2323
<div class="snippet">
2424
<div class="datepicker-width">
25-
<ui5-datepicker id='myDatepicker2' placeholder='Delivery Date...' title='Delivery Date!'></ui5-datepicker>
25+
<ui5-datepicker id='myDatepicker2' placeholder='Delivery Date...' title='Delivery Date!'>
26+
<div slot="valueStateMessage">The value is not valid. Please provide valid value</div>
27+
</ui5-datepicker>
2628
</div>
2729
</div>
2830
<pre class="prettyprint lang-html"><xmp>
29-
<ui5-datepicker id="myDatepicker2" placeholder='Delivery Date...' title='Delivery Date!'></ui5-datepicker>
31+
<ui5-datepicker id='myDatepicker2' placeholder='Delivery Date...' title='Delivery Date!'>
32+
<div slot="valueStateMessage">The value is not valid. Please provide valid value</div>
33+
</ui5-datepicker>
3034
<script>
3135
const dp = document.getElementById('myDatepicker2');
3236
dp.addEventListener('change', (e) => {

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

+23-10
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,26 @@ describe("Date Picker Tests", () => {
3939
assert.ok(contentWrapper.isDisplayedInViewport(), "content wrapper has error styles");
4040
});
4141

42+
it("Can focus the input after open", () => {
43+
datepicker.id = "#dp1";
44+
datepicker.openPicker({ focusInput: true });
45+
const a = datepicker.innerInput.isFocusedDeep();
46+
47+
console.log(datepicker.innerInput.isFocusedDeep());
48+
assert.ok(a, "inner input is focused");
49+
});
50+
51+
it("Value State Message", () => {
52+
datepicker.id = "#dp17"
53+
datepicker.root.click();
54+
55+
const inputStaticAreaItem = datepicker.inputStaticAreaItem;
56+
const popover = inputStaticAreaItem.shadow$("ui5-popover");
57+
58+
const slot = popover.$("#coolValueStateMessage");
59+
assert.notOk(slot.error, "Value State message slot is working");
60+
});
61+
4262
it("disabled", () => {
4363
datepicker.id = "#dp2";
4464
datepicker.root.setAttribute("disabled", "");
@@ -81,13 +101,6 @@ describe("Date Picker Tests", () => {
81101
assert.equal(datepicker.innerInput.getAttribute("value"), "Rab. I 6, 1440 AH", "input has correct Islamic value");
82102
});
83103

84-
it("Can focus the input after open", () => {
85-
datepicker.id = "#dp1";
86-
datepicker.openPicker({ focusInput: true });
87-
88-
assert.ok(datepicker.innerInput.isFocusedDeep(), "inner input is focused");
89-
});
90-
91104
it("Selected date from daypicker is the same as datepicker date", () => {
92105
datepicker.id = "#dp4";
93106

@@ -551,7 +564,7 @@ describe("Date Picker Tests", () => {
551564
while(datepicker.root.getValue() !== ""){
552565
datepicker.root.keys("Backspace");
553566
}
554-
567+
555568
datepicker.root.keys("May 5, 2100");
556569
datepicker.root.keys("Enter");
557570

@@ -568,7 +581,7 @@ describe("Date Picker Tests", () => {
568581
while(datepicker.root.getValue() !== ""){
569582
datepicker.root.keys("Backspace");
570583
}
571-
584+
572585
datepicker.root.keys("Jan 8, 2100");
573586
datepicker.root.keys("Enter");
574587

@@ -578,7 +591,7 @@ describe("Date Picker Tests", () => {
578591
while(datepicker.root.getValue() !== ""){
579592
datepicker.root.keys("Backspace");
580593
}
581-
594+
582595
datepicker.root.keys("Jan 1, 2000");
583596
datepicker.root.keys("Enter");
584597

0 commit comments

Comments
 (0)