Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/main/src/ComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ import {
COMBOBOX_AVAILABLE_OPTIONS,
COMBOBOX_DIALOG_OK_BUTTON,
COMBOBOX_DIALOG_CANCEL_BUTTON,
COMBOBOX_LOADING,
COMBOBOX_LOADED,
COMBOBOX_LOADED_ITEMS,
COMBOBOX_LOADED_ITEM,
SELECT_OPTIONS,
LIST_ITEM_POSITION,
LIST_ITEM_GROUP_HEADER,
Expand Down Expand Up @@ -501,6 +505,8 @@ class ComboBox extends UI5Element implements IFormInputElement {
icon!: Slot<IIcon>;

_initialRendering = true;
_prevLoading: boolean;
_announceLoading?: boolean;
_itemFocused = false;
// used only for Safari fix (check onAfterRendering)
_autocomplete = false;
Expand Down Expand Up @@ -546,6 +552,7 @@ class ComboBox extends UI5Element implements IFormInputElement {

// when an initial value is set it should be considered as a _lastValue
this._lastValue = this.getAttribute("value") || "";
this._prevLoading = this.loading;
}

onBeforeRendering() {
Expand Down Expand Up @@ -596,6 +603,16 @@ class ComboBox extends UI5Element implements IFormInputElement {
});

this._selectMatchingItem();

if (!this._initialRendering) {
if (!this._prevLoading && this.loading) {
this._announceLoading = true;
} else if (this._prevLoading && !this.loading) {
this._announceLoading = false;
}
}

this._prevLoading = this.loading;
this._initialRendering = false;

this.style.setProperty("--_ui5-input-icons-count", `${this.iconsCount}`);
Expand All @@ -616,6 +633,15 @@ class ComboBox extends UI5Element implements IFormInputElement {

this.storeResponsivePopoverWidth();

if (this._announceLoading) {
announce(ComboBox.i18nBundle.getText(COMBOBOX_LOADING), InvisibleMessageMode.Polite);
} else if (this._announceLoading === false) {
const count = this._getItems().filter(item => !item.isGroupItem && item._isVisible).length;
const itemsLoadedMessage = count === 1 ? ComboBox.i18nBundle.getText(COMBOBOX_LOADED_ITEM) : ComboBox.i18nBundle.getText(COMBOBOX_LOADED_ITEMS, count);
announce(`${ComboBox.i18nBundle.getText(COMBOBOX_LOADED)}. ${itemsLoadedMessage}`, InvisibleMessageMode.Polite);
}
this._announceLoading = undefined;

if (!arraysAreEqual(this._valueStateLinks, this.linksInAriaValueStateHiddenText)) {
this._removeLinksEventListeners();
this._addLinksEventListeners();
Expand Down
206 changes: 103 additions & 103 deletions packages/main/src/ComboBoxPopoverTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import generateHighlightedMarkupFirstMatch from "@ui5/webcomponents-base/dist/ut
import type ComboBox from "./ComboBox.js";

export default function ComboBoxPopoverTemplate(this: ComboBox) {
const loadingOnDesktopWithValueState = this.loading && !this._isPhone && this.hasValueState;
const loadingDelay = 100;
return (
<>
<ResponsivePopover
Expand All @@ -33,123 +35,121 @@ export default function ComboBoxPopoverTemplate(this: ComboBox) {
onKeyDown={this._handlePopoverKeydown}
onFocusOut={this._handlePopoverFocusout}
>
{this.loading &&
<BusyIndicator active={true} class="ui5-combobox-busy"/>
}
{this._isPhone &&
<>
<div slot="header" class="ui5-responsive-popover-header">
<div class="row">
<Title
level="H1"
wrappingType="None"
class="ui5-responsive-popover-header-text"
>
{this._headerTitleText}
</Title>
</div>

{!this.loading && this._isPhone &&
<>
<div slot="header" class="ui5-responsive-popover-header">
<div class="row">
<Title
level="H1"
wrappingType="None"
class="ui5-responsive-popover-header-text"
>
{this._headerTitleText}
</Title>
<div class="row">
<Input
open={this.openOnMobile}
placeholder={this.placeholder}
valueState={this.valueState}
showClearIcon={this.showClearIcon}
noTypeahead={this.noTypeahead}
onKeyDown={this._handleMobileKeydown}
onInput={this._handleMobileInput}
onChange={this._inputChange}
>
{!this.loading && this._filteredItems.flatMap(item => {
if (item.isGroupItem && item.items) {
// For group items, return all nested items
return item.items
.filter(nestedItem => !!nestedItem)
.map(nestedItem =>
<SuggestionItem text={nestedItem.text} additional-text={nestedItem.additionalText} markupText={generateHighlightedMarkupFirstMatch(nestedItem.text || "", this.filterValue)} />
);
}
// For regular items
return <SuggestionItem text={item.text} additional-text={item.additionalText} markupText={generateHighlightedMarkupFirstMatch(item.text || "", this.filterValue)} />;
})}
</Input>
</div>
</div>

<div class="row">
<Input
open={this.openOnMobile}
placeholder={this.placeholder}
valueState={this.valueState}
showClearIcon={this.showClearIcon}
noTypeahead={this.noTypeahead}
onKeyDown={this._handleMobileKeydown}
onInput={this._handleMobileInput}
onChange={this._inputChange}
>
{ this._filteredItems.flatMap(item => {
if (item.isGroupItem && item.items) {
// For group items, return all nested items
return item.items
.filter(nestedItem => !!nestedItem)
.map(nestedItem =>
<SuggestionItem text={nestedItem.text} additional-text={nestedItem.additionalText} markupText={generateHighlightedMarkupFirstMatch(nestedItem.text || "", this.filterValue)}/>
);
}
// For regular items
return <SuggestionItem text={item.text} additional-text={item.additionalText} markupText={generateHighlightedMarkupFirstMatch(item.text || "", this.filterValue)}/>;
})}
</Input>
</div>
</div>

{this.hasValueStateText &&
<div class={this.classes.popoverValueState} style={this.styles.popoverValueStateMessage}>
<Icon class="ui5-input-value-state-message-icon" name={this._valueStateMessageIcon}/>
{ this.open && valueStateMessage.call(this) }
</div>
}
</>
{this.hasValueStateText &&
<div class={this.classes.popoverValueState} style={this.styles.popoverValueStateMessage}>
<Icon class="ui5-input-value-state-message-icon" name={this._valueStateMessageIcon} />
{this.open && valueStateMessage.call(this)}
</div>
}
</>
}

{!this._isPhone && this.hasValueStateText &&
<div
slot="header"
class={{
"ui5-responsive-popover-header": true,
...this.classes.popoverValueState,
}}
style={this.styles.suggestionPopoverHeader}
>
<Icon class="ui5-input-value-state-message-icon" name={this._valueStateMessageIcon}/>
{ this.open && valueStateMessage.call(this) }
</div>
<div
slot="header"
class={{
"ui5-responsive-popover-header": true,
...this.classes.popoverValueState,
}}
style={this.styles.suggestionPopoverHeader}
>
<Icon class="ui5-input-value-state-message-icon" name={this._valueStateMessageIcon} />
{this.open && valueStateMessage.call(this)}
</div>
}

{!this.loading && !!this._filteredItems.length &&
<List
class="ui5-combobox-items-list"
separators="None"
accessibleRole="ListBox"
selectionMode="Single"
onItemClick={this._selectItem}
onItemFocused={this._onItemFocus}
onMouseDown={this._itemMousedown}
>
{ this._filteredItems.map(item => <slot name={item._individualSlot}></slot>)}
</List>
{(this._isPhone || !this.hasValueState) && this.loading && <BusyIndicator active={true} class="ui5-combobox-busy" delay={loadingDelay} />}
{((!this.loading && !!this._filteredItems.length) || loadingOnDesktopWithValueState) &&
<List
class="ui5-combobox-items-list"
separators="None"
accessibleRole="ListBox"
selectionMode="Single"
onItemClick={this._selectItem}
onItemFocused={this._onItemFocus}
onMouseDown={this._itemMousedown}
>
{loadingOnDesktopWithValueState && <BusyIndicator active={true} class="ui5-combobox-busy" delay={loadingDelay} />}
{!this.loading && this._filteredItems.map(item => <slot name={item._individualSlot}></slot>)}
</List>
}

{this._isPhone &&
<div slot="footer" class="ui5-responsive-popover-footer">
<Button
design="Emphasized"
onClick={this._closeRespPopover}
>{this._dialogOkButtonText}</Button>
<Button
class="ui5-responsive-popover-close-btn"
design="Transparent"
onClick={this._closeRespPopover}
>
{this._dialogCancelButtonText}
</Button>
</div>
<div slot="footer" class="ui5-responsive-popover-footer">
<Button
design="Emphasized"
onClick={this._closeRespPopover}
>{this._dialogOkButtonText}</Button>
<Button
class="ui5-responsive-popover-close-btn"
design="Transparent"
onClick={this._closeRespPopover}
>
{this._dialogCancelButtonText}
</Button>
</div>
}
</ResponsivePopover>

{this.shouldOpenValueStateMessagePopover &&
<Popover
preventFocusRestore={true}
preventInitialFocus={true}
hideArrow={true}
tabindex={-1}
class="ui5-valuestatemessage-popover"
horizontalAlign={PopoverHorizontalAlign.Start}
placement="Bottom"
opener={this}
open={this.valueStateOpen}
onClose={this._handleValueStatePopoverAfterClose}
onFocusOut={this._handleValueStatePopoverFocusout}
>
<div slot="header" class={this.classes.popoverValueState}>
<Icon class="ui5-input-value-state-message-icon" name={this._valueStateMessageIcon}/>
{ valueStateMessage.call(this) }
</div>
</Popover>
<Popover
preventFocusRestore={true}
preventInitialFocus={true}
hideArrow={true}
tabindex={-1}
class="ui5-valuestatemessage-popover"
horizontalAlign={PopoverHorizontalAlign.Start}
placement="Bottom"
opener={this}
open={this.valueStateOpen}
onClose={this._handleValueStatePopoverAfterClose}
onFocusOut={this._handleValueStatePopoverFocusout}
>
<div slot="header" class={this.classes.popoverValueState}>
<Icon class="ui5-input-value-state-message-icon" name={this._valueStateMessageIcon} />
{valueStateMessage.call(this)}
</div>
</Popover>
}
</>
);
Expand All @@ -158,7 +158,7 @@ export default function ComboBoxPopoverTemplate(this: ComboBox) {
function valueStateMessage(this: ComboBox) {
return (
<>
{ this.shouldDisplayDefaultValueStateMessage ? this.valueStateDefaultText : <slot name="valueStateMessage"></slot> }
{this.shouldDisplayDefaultValueStateMessage ? this.valueStateDefaultText : <slot name="valueStateMessage"></slot>}
</>
);
}
Loading
Loading