Skip to content

Commit 793a29e

Browse files
authored
feat(ui5-multi-combobox): Implement valueStateMessage (#2258)
Implement valueStateMessage support for MultiComboBox component. FIXES: #1086
1 parent f52e0b1 commit 793a29e

File tree

5 files changed

+241
-39
lines changed

5 files changed

+241
-39
lines changed

packages/main/src/MultiComboBox.hbs

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@
6363
input-icon
6464
slot="icon"
6565
tabindex="-1"
66-
@click={{togglePopover}}
66+
@click="{{togglePopover}}"
67+
@mousedown="{{_onIconMousedown}}"
6768
?pressed="{{open}}"
6869
dir="{{effectiveDir}}"
6970
accessible-name="{{_iconAccessibleNameText}}"

packages/main/src/MultiComboBox.js

+148-7
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 ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
45
import {
56
isShow,
@@ -9,6 +10,7 @@ import {
910
isLeft,
1011
isRight,
1112
} from "@ui5/webcomponents-base/dist/Keys.js";
13+
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
1214
import "@ui5/webcomponents-icons/dist/icons/slim-arrow-down.js";
1315
import { isIE, isPhone } from "@ui5/webcomponents-base/dist/Device.js";
1416
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
@@ -32,6 +34,7 @@ import {
3234
TOKENIZER_ARIA_CONTAIN_SEVERAL_TOKENS,
3335
INPUT_SUGGESTIONS_TITLE,
3436
ICON_ACCESSIBLE_NAME,
37+
MULTICOMBOBOX_DIALOG_OK_BUTTON,
3538
} from "./generated/i18n/i18n-defaults.js";
3639

3740
// Templates
@@ -41,6 +44,7 @@ import MultiComboBoxPopoverTemplate from "./generated/templates/MultiComboBoxPop
4144
// Styles
4245
import styles from "./generated/themes/MultiComboBox.css.js";
4346
import ResponsivePopoverCommonCss from "./generated/themes/ResponsivePopoverCommon.css.js";
47+
import ValueStateMessageCss from "./generated/themes/ValueStateMessage.css.js";
4448

4549
/**
4650
* @public
@@ -82,6 +86,22 @@ const metadata = {
8286
type: HTMLElement,
8387
},
8488

89+
/**
90+
* Defines the value state message that will be displayed as pop up under the <code>ui5-multicombobox</code>.
91+
* <br><br>
92+
*
93+
* <b>Note:</b> If not specified, a default text (in the respective language) will be displayed.
94+
* <br>
95+
* <b>Note:</b> The <code>valueStateMessage</code> would be displayed,
96+
* when the <code>ui5-select</code> is in <code>Information</code>, <code>Warning</code> or <code>Error</code> value state.
97+
* @type {HTMLElement[]}
98+
* @since 1.0.0-rc.9
99+
* @slot
100+
* @public
101+
*/
102+
valueStateMessage: {
103+
type: HTMLElement,
104+
},
85105
},
86106
properties: /** @lends sap.ui.webcomponents.main.MultiComboBox.prototype */ {
87107
/**
@@ -205,6 +225,22 @@ const metadata = {
205225
_rootFocused: {
206226
type: Boolean,
207227
},
228+
229+
_iconPressed: {
230+
type: Boolean,
231+
noAttribute: true,
232+
},
233+
234+
_inputWidth: {
235+
type: Integer,
236+
noAttribute: true,
237+
},
238+
239+
_listWidth: {
240+
type: Integer,
241+
defaultValue: 0,
242+
noAttribute: true,
243+
},
208244
},
209245
events: /** @lends sap.ui.webcomponents.main.MultiComboBox.prototype */ {
210246
/**
@@ -319,7 +355,7 @@ class MultiComboBox extends UI5Element {
319355
}
320356

321357
static get staticAreaStyles() {
322-
return ResponsivePopoverCommonCss;
358+
return [ResponsivePopoverCommonCss, ValueStateMessageCss];
323359
}
324360

325361
static get dependencies() {
@@ -344,6 +380,19 @@ class MultiComboBox extends UI5Element {
344380
this._deleting = false;
345381
this._validationTimeout = null;
346382
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
383+
this._handleResizeBound = this._handleResize.bind(this);
384+
}
385+
386+
onEnterDOM() {
387+
ResizeHandler.register(this, this._handleResizeBound);
388+
}
389+
390+
onExitDOM() {
391+
ResizeHandler.deregister(this, this._handleResizeBound);
392+
}
393+
394+
_handleResize() {
395+
this._inputWidth = this.offsetWidth;
347396
}
348397

349398
_inputChange() {
@@ -605,6 +654,7 @@ class MultiComboBox extends UI5Element {
605654
this.blur();
606655
}
607656

657+
this._iconPressed = false;
608658
this.filterSelected = false;
609659
}
610660

@@ -622,14 +672,51 @@ class MultiComboBox extends UI5Element {
622672

623673
async onAfterRendering() {
624674
await this._getRespPopover();
675+
await this._getList();
676+
677+
this.toggle(this.shouldDisplayOnlyValueStateMessage);
678+
this.storeResponsivePopoverWidth();
625679
}
626680

627-
get valueStateTextMappings() {
628-
return {
629-
"Success": this.i18nBundle.getText(VALUE_STATE_SUCCESS),
630-
"Error": this.i18nBundle.getText(VALUE_STATE_ERROR),
631-
"Warning": this.i18nBundle.getText(VALUE_STATE_WARNING),
632-
};
681+
get _isPhone() {
682+
return isPhone();
683+
}
684+
685+
_onIconMousedown() {
686+
this._iconPressed = true;
687+
}
688+
689+
storeResponsivePopoverWidth() {
690+
if (this.open && !this._listWidth) {
691+
this._listWidth = this.list.offsetWidth;
692+
}
693+
}
694+
695+
toggle(isToggled) {
696+
if (isToggled && !this.open) {
697+
this.openPopover();
698+
} else {
699+
this.closePopover();
700+
}
701+
}
702+
703+
async openPopover() {
704+
const popover = await this._getPopover();
705+
706+
if (popover) {
707+
popover.openBy(this);
708+
}
709+
}
710+
711+
async closePopover() {
712+
const popover = await this._getPopover();
713+
714+
popover && popover.close();
715+
}
716+
717+
async _getPopover() {
718+
const staticAreaItem = await this.getStaticAreaItemDomRef();
719+
return staticAreaItem.querySelector("[ui5-popover]");
633720
}
634721

635722
get _tokenizer() {
@@ -674,6 +761,10 @@ class MultiComboBox extends UI5Element {
674761
return this.valueState !== ValueState.None;
675762
}
676763

764+
get hasValueStateMessage() {
765+
return this.hasValueState && this.valueState !== ValueState.Success;
766+
}
767+
677768
get valueStateText() {
678769
return this.valueStateTextMappings[this.valueState];
679770
}
@@ -682,6 +773,26 @@ class MultiComboBox extends UI5Element {
682773
return this.hasValueState ? `${this._id}-valueStateDesc` : undefined;
683774
}
684775

776+
get valueStateMessageText() {
777+
return this.getSlottedNodes("valueStateMessage").map(el => el.cloneNode(true));
778+
}
779+
780+
get shouldDisplayDefaultValueStateMessage() {
781+
return !this.valueStateMessage.length && this.hasValueStateMessage;
782+
}
783+
784+
get shouldDisplayOnlyValueStateMessage() {
785+
return this._rootFocused && this.hasValueStateMessage && !this._iconPressed;
786+
}
787+
788+
get valueStateTextMappings() {
789+
return {
790+
"Success": this.i18nBundle.getText(VALUE_STATE_SUCCESS),
791+
"Error": this.i18nBundle.getText(VALUE_STATE_ERROR),
792+
"Warning": this.i18nBundle.getText(VALUE_STATE_WARNING),
793+
};
794+
}
795+
685796
get _innerInput() {
686797
if (isPhone()) {
687798
if (this.allItemsPopover.opened) {
@@ -700,10 +811,40 @@ class MultiComboBox extends UI5Element {
700811
return this.i18nBundle.getText(ICON_ACCESSIBLE_NAME);
701812
}
702813

814+
get _dialogOkButton() {
815+
return this.i18nBundle.getText(MULTICOMBOBOX_DIALOG_OK_BUTTON);
816+
}
817+
703818
get _tokenizerExpanded() {
704819
return this._rootFocused || this.open;
705820
}
706821

822+
get classes() {
823+
return {
824+
popoverValueState: {
825+
"ui5-valuestatemessage-root": true,
826+
"ui5-valuestatemessage--success": this.valueState === ValueState.Success,
827+
"ui5-valuestatemessage--error": this.valueState === ValueState.Error,
828+
"ui5-valuestatemessage--warning": this.valueState === ValueState.Warning,
829+
"ui5-valuestatemessage--information": this.valueState === ValueState.Information,
830+
},
831+
};
832+
}
833+
834+
get styles() {
835+
return {
836+
popoverValueStateMessage: {
837+
"width": `${this._listWidth}px`,
838+
"min-height": "2.5rem",
839+
"padding": "0.5625rem 1rem",
840+
"display": this._listWidth === 0 ? "none" : "inline-block",
841+
},
842+
popoverHeader: {
843+
"width": `${this._inputWidth}px`,
844+
},
845+
};
846+
}
847+
707848
static async onDefine() {
708849
await fetchI18nBundle("@ui5/webcomponents");
709850
}

packages/main/src/MultiComboBoxPopover.hbs

+74-31
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@
44
class="ui5-multi-combobox-all-items-responsive-popover"
55
no-arrow
66
_disable-initial-focus
7-
content-only-on-desktop
87
@ui5-selection-change={{_listSelectionChange}}
98
@ui5-after-close={{_toggle}}
109
@ui5-after-open={{_toggle}}
1110
>
12-
<div slot="header" class="ui5-responsive-popover-header">
13-
<div class="row">
14-
<span>{{_headerTitleText}}</span>
15-
<ui5-button
16-
class="ui5-responsive-popover-close-btn"
17-
icon="decline"
18-
design="Transparent"
19-
@click="{{togglePopover}}"
20-
>
21-
</ui5-button>
22-
</div>
23-
<div class="row">
24-
<div
11+
{{#if _isPhone}}
12+
<div slot="header" class="ui5-responsive-popover-header" style="{{styles.popoverHeader}}">
13+
<div class="row">
14+
<span>{{_headerTitleText}}</span>
15+
<ui5-button
16+
class="ui5-responsive-popover-close-btn"
17+
icon="decline"
18+
design="Transparent"
19+
@click="{{togglePopover}}"
20+
>
21+
</ui5-button>
22+
</div>
23+
<div class="row">
24+
<div
2525
slot="header"
2626
class="input-root-phone"
2727
value-state="{{valueState}}"
@@ -37,30 +37,73 @@
3737
aria-autocomplete="both"
3838
aria-labelledby="{{_id}}-hiddenText-nMore"
3939
aria-describedby="{{_id}}-valueStateDesc"
40-
/>
40+
/>
41+
</div>
42+
<ui5-togglebutton
43+
slot="header"
44+
class="ui5-multi-combobox-toggle-button"
45+
icon="multiselect-all"
46+
design="Transparent"
47+
?pressed={{_showAllItemsButtonPressed}}
48+
?disabled={{allItemsSelected}}
49+
@click="{{filterSelectedItems}}"
50+
></ui5-togglebutton>
4151
</div>
42-
<ui5-togglebutton
43-
slot="header"
44-
class="ui5-multi-combobox-toggle-button"
45-
icon="multiselect-all"
46-
design="Transparent"
47-
?pressed={{_showAllItemsButtonPressed}}
48-
?disabled={{allItemsSelected}}
49-
@click="{{filterSelectedItems}}"
50-
></ui5-togglebutton>
52+
{{#if hasValueStateMessage}}
53+
<div class="{{classes.popoverValueState}}" style="{{styles.popoverValueStateMessage}}">
54+
{{> valueStateMessage}}
55+
</div>
56+
{{/if}}
57+
</div>
5158
</div>
52-
</div>
59+
{{/if}}
60+
61+
{{#unless _isPhone}}
62+
{{#if hasValueStateMessage}}
63+
<div slot="header" class="ui5-responsive-popover-header {{classes.popoverValueState}}" style={{styles.popoverValueStateMessage}}>
64+
{{> valueStateMessage}}
65+
</div>
66+
{{/if}}
67+
{{/unless}}
5368

5469
<ui5-list separators="None" mode="MultiSelect" class="ui5-multi-combobox-all-items-list">
5570
{{#each _filteredItems}}
5671
<ui5-li type="Active" ?selected={{this.selected}} data-ui5-token-id="{{this._id}}">{{this.text}}</ui5-li>
5772
{{/each}}
5873
</ui5-list>
5974

60-
<div slot="footer" class="ui5-responsive-popover-footer">
61-
<ui5-button
62-
design="Transparent"
63-
@click="{{togglePopover}}"
64-
>OK</ui5-button>
65-
</div>
75+
{{#if _isPhone}}
76+
<div slot="footer" class="ui5-responsive-popover-footer">
77+
<ui5-button
78+
design="Transparent"
79+
@click="{{togglePopover}}"
80+
>{{_dialogOkButton}}</ui5-button>
81+
</div>
82+
{{/if}}
6683
</ui5-responsive-popover>
84+
85+
{{#if hasValueStateMessage}}
86+
<ui5-popover
87+
skip-registry-update
88+
_disable-initial-focus
89+
prevent-focus-restore
90+
no-padding
91+
no-arrow
92+
class="ui5-valuestatemessage-popover"
93+
placement-type="Bottom"
94+
>
95+
<div slot="header" class="{{classes.popoverValueState}}" style="{{styles.popoverHeader}}">
96+
{{> valueStateMessage}}
97+
</div>
98+
</ui5-popover>
99+
{{/if}}
100+
101+
{{#*inline "valueStateMessage"}}
102+
{{#if shouldDisplayDefaultValueStateMessage}}
103+
{{valueStateText}}
104+
{{else}}
105+
{{#each valueStateMessageText}}
106+
{{this}}
107+
{{/each}}
108+
{{/if}}
109+
{{/inline}}

0 commit comments

Comments
 (0)