From 25515e38925f802cff0cd84abf29c4e7069e07b0 Mon Sep 17 00:00:00 2001 From: Ivanova Terzieva Date: Tue, 14 Apr 2026 15:46:12 +0300 Subject: [PATCH 1/7] chore(ui5-combo-box): lazy loading and acc --- packages/main/test/pages/MultiComboBox.html | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/main/test/pages/MultiComboBox.html b/packages/main/test/pages/MultiComboBox.html index f7fbd7899ab2..21c89a5dd824 100644 --- a/packages/main/test/pages/MultiComboBox.html +++ b/packages/main/test/pages/MultiComboBox.html @@ -580,9 +580,9 @@

MultiComboBox in Compact


- +

@@ -891,8 +891,17 @@

MultiComboBox in Compact

document.getElementById("toggle-loading-btn").addEventListener("click", function () { const mcb = document.getElementById("toggle-loading-mcb"); - mcb.loading = !mcb.loading; + mcb.loading = true; + setTimeout(() => { + mcb.appendChild(document.createElement("ui5-mcb-item")).text = "New Item"; + mcb.appendChild(document.createElement("ui5-mcb-item")).text = "New Item 2"; + mcb.loading = false; + const invisibleMessage = window["sap-ui-webcomponents-bundle"].invisibleMessage; + invisibleMessage.announce("2 items available", "Assertive"); + }, 2000); }); + + From adb2070322a1fc05d8eaf44e6b2510319f4c3f83 Mon Sep 17 00:00:00 2001 From: Ivanova Terzieva Date: Tue, 21 Apr 2026 10:36:49 +0300 Subject: [PATCH 2/7] chore(ui5-combobox): change sample of lazy loaded itrems --- packages/main/test/pages/ComboBox.html | 35 ++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/packages/main/test/pages/ComboBox.html b/packages/main/test/pages/ComboBox.html index e278ffc8c04d..d3d24aa4202f 100644 --- a/packages/main/test/pages/ComboBox.html +++ b/packages/main/test/pages/ComboBox.html @@ -197,11 +197,8 @@
- - - - Toggle Loading + Clear items
@@ -637,9 +634,33 @@

ComboBox Composition

document.getElementById("combo").focus(); }); - document.getElementById("toggle-loading-btn").addEventListener("click", function () { - const combo = document.getElementById("toggle-loading-cb"); - combo.loading = !combo.loading; + const cbLazyLoad = document.getElementById("toggle-loading-cb"); + + document.getElementById("btn-clear").addEventListener("click", () => { + while (cbLazyLoad.firstChild) { + cbLazyLoad.removeChild(cbLazyLoad.firstChild); + } + }); + + const createItem = (cb, text) => { + const item = document.createElement("ui5-cb-item"); + item.text = text; + cb.appendChild(item); + }; + + cbLazyLoad.addEventListener("open", (e) => { + if(!cbLazyLoad.items.length){ + const invisibleMessage = window["sap-ui-webcomponents-bundle"].invisibleMessage; + cbLazyLoad.loading = true; + invisibleMessage.announce("Loading data", "Assertive"); + createItem(cbLazyLoad, `Item 1`); + createItem(cbLazyLoad, `Item 2`); + createItem(cbLazyLoad, `Item 3`); + setTimeout(() => { + cbLazyLoad.loading = false; + invisibleMessage.announce("Data loaded. 3 results are available", "Assertive"); + }, 3000); + } }); From 8615f18433ed422264d8f9c07cb4dba9dea5ac10 Mon Sep 17 00:00:00 2001 From: Ivanova Terzieva Date: Tue, 12 May 2026 09:34:08 +0300 Subject: [PATCH 3/7] chore(ui5-combobox): lazy loading --- packages/main/src/ComboBox.ts | 54 +++++++-- packages/main/src/ComboBoxPopoverTemplate.tsx | 107 +++++++++--------- .../main/src/i18n/messagebundle.properties | 11 ++ packages/main/test/pages/ComboBox.html | 42 +++---- 4 files changed, 130 insertions(+), 84 deletions(-) diff --git a/packages/main/src/ComboBox.ts b/packages/main/src/ComboBox.ts index 1152c6b5e14b..68c0ad011a2d 100644 --- a/packages/main/src/ComboBox.ts +++ b/packages/main/src/ComboBox.ts @@ -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, @@ -499,6 +503,8 @@ class ComboBox extends UI5Element implements IFormInputElement { icon!: Slot; _initialRendering = true; + _prevLoading = false; + _announceLoading?: boolean | undefined; _itemFocused = false; // used only for Safari fix (check onAfterRendering) _autocomplete = false; @@ -594,6 +600,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}`); @@ -614,6 +630,23 @@ class ComboBox extends UI5Element implements IFormInputElement { this.storeResponsivePopoverWidth(); + if (this._announceLoading) { + announce(ComboBox.i18nBundle.getText(COMBOBOX_LOADING), InvisibleMessageMode.Polite); + } else if (this._announceLoading === false) { + let count = 0; + this._filteredItems.forEach(item => { + if (isInstanceOfComboBoxItemGroup(item)) { + count += item.items?.filter(i => i._isVisible).length || 0; + } else { + count++; + } + }); + + 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(); @@ -967,17 +1000,20 @@ class ComboBox extends UI5Element implements IFormInputElement { } const allItems = this._getItems(); - const currentItem = allItems[indexOfItem]; - const isLastItem = indexOfItem === allItems.length - 1; - // We don't want to navigate further if the current item is the last one and either is already focused or the popover is closed - if (isLastItem && ((isOpen && currentItem.focused) || !isOpen)) { - return; - } + if(allItems.length){ + const currentItem = allItems[indexOfItem]; + const isLastItem = indexOfItem === allItems.length - 1; - const itemIndexToBeFocused = isLastItem ? indexOfItem : indexOfItem + 1; + // We don't want to navigate further if the current item is the last one and either is already focused or the popover is closed + if (isLastItem && ((isOpen && currentItem.focused) || !isOpen)) { + return; + } - this._handleItemNavigation(e, itemIndexToBeFocused, true /* isForward */); + const itemIndexToBeFocused = isLastItem ? indexOfItem : indexOfItem + 1; + + this._handleItemNavigation(e, itemIndexToBeFocused, true /* isForward */); + } } _handleArrowUp(e: KeyboardEvent, indexOfItem: number) { @@ -1040,7 +1076,7 @@ class ComboBox extends UI5Element implements IFormInputElement { this._autocomplete = !(isBackSpace(e) || isDelete(e)); this._isKeyNavigation = false; - if (isNavKey && !this.readonly && this._filteredItems.length) { + if (isNavKey && !this.readonly) { this.handleNavKeyPress(e); } diff --git a/packages/main/src/ComboBoxPopoverTemplate.tsx b/packages/main/src/ComboBoxPopoverTemplate.tsx index b4dd0ebcc93d..03e88f927f20 100644 --- a/packages/main/src/ComboBoxPopoverTemplate.tsx +++ b/packages/main/src/ComboBoxPopoverTemplate.tsx @@ -33,11 +33,7 @@ export default function ComboBoxPopoverTemplate(this: ComboBox) { onKeyDown={this._handlePopoverKeydown} onFocusOut={this._handlePopoverFocusout} > - {this.loading && - - } - - {!this.loading && this._isPhone && + {this._isPhone && <>
@@ -61,7 +57,7 @@ export default function ComboBoxPopoverTemplate(this: ComboBox) { onInput={this._handleMobileInput} onChange={this._inputChange} > - { this._filteredItems.flatMap(item => { + {!this.loading && this._filteredItems.flatMap(item => { if (item.isGroupItem && item.items) { // For group items, return all nested items return item.items @@ -86,65 +82,66 @@ export default function ComboBoxPopoverTemplate(this: ComboBox) { } - {!this._isPhone && this.hasValueStateText && -
- - { this.open && valueStateMessage.call(this) } -
+ {!this._isPhone && !this.loading && this.hasValueStateText && +
+ + { this.open && valueStateMessage.call(this) } +
} + {this.loading && } {!this.loading && !!this._filteredItems.length && - - { this._filteredItems.map(item => )} - + + { this._filteredItems.map(item => )} + } {this._isPhone && - + } {this.shouldOpenValueStateMessagePopover && - +
{ valueStateMessage.call(this) } diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index d6f9bc6b3c89..097bf4d9ba18 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -444,6 +444,17 @@ MULTICOMBOBOX_DIALOG_CANCEL_BUTTON=Cancel #XACT: ARIA announcement for Combo Box and Multi Combo Box available options COMBOBOX_AVAILABLE_OPTIONS=Available Options +#XACT: ARIA announcement when ComboBox starts loading items +COMBOBOX_LOADING=Loading data + +#XACT: ARIA announcement when ComboBox ends loading items +COMBOBOX_LOADED=Data loaded + +#XACT: ARIA announcement when ComboBox finishes loading, {0} is the number of loaded items +COMBOBOX_LOADED_ITEMS={0} results are available + +COMBOBOX_LOADED_ITEM=1 result is available + #XBUT: Combobox Dialog OK button on mobile devices COMBOBOX_DIALOG_OK_BUTTON=OK diff --git a/packages/main/test/pages/ComboBox.html b/packages/main/test/pages/ComboBox.html index a780e443f5a6..39a6f8263d59 100644 --- a/packages/main/test/pages/ComboBox.html +++ b/packages/main/test/pages/ComboBox.html @@ -515,7 +515,7 @@

ComboBox Composition