diff --git a/packages/main/cypress/specs/MultiComboBox.cy.tsx b/packages/main/cypress/specs/MultiComboBox.cy.tsx
index ae9197edbed4..57bb41f3f7c9 100644
--- a/packages/main/cypress/specs/MultiComboBox.cy.tsx
+++ b/packages/main/cypress/specs/MultiComboBox.cy.tsx
@@ -704,6 +704,140 @@ describe("General", () => {
}));
});
+ it("Select All checkbox state with grouped items", () => {
+ cy.mount(
+
+
+
+
+
+
+
+
+
+
+ );
+
+ cy.get("[ui5-multi-combobox]")
+ .as("mcb")
+ .realClick();
+
+ cy.get("@mcb")
+ .shadow()
+ .find(".inputIcon")
+ .realClick();
+
+ cy.get("@mcb")
+ .shadow()
+ .find("ui5-responsive-popover")
+ .as("popover")
+ .ui5ResponsivePopoverOpened();
+
+ // Verify checkbox is initially unchecked
+ cy.get("@popover")
+ .find(".ui5-mcb-select-all-checkbox")
+ .as("checkbox")
+ .should("not.have.attr", "checked");
+
+ // Click Select All
+ cy.get("@checkbox")
+ .realClick();
+
+ // Verify checkbox is now checked
+ cy.get("@checkbox")
+ .should("have.attr", "checked");
+
+ // Verify all 4 items are selected (not 2 groups)
+ cy.get("@selectionChangeEvent")
+ .should("have.been.calledWithMatch", Cypress.sinon.match(event => {
+ return event.detail.items.length === 4;
+ }));
+
+ // Click Select All again to deselect
+ cy.get("@checkbox")
+ .realClick();
+
+ // Verify checkbox is unchecked
+ cy.get("@checkbox")
+ .should("not.have.attr", "checked");
+
+ // Verify all items are deselected
+ cy.get("@selectionChangeEvent")
+ .should("have.been.calledWithMatch", Cypress.sinon.match(event => {
+ return event.detail.items.length === 0;
+ }));
+ });
+
+ it("Select All checkbox with partial selection in groups", () => {
+ cy.mount(
+
+
+
+
+
+
+
+
+
+
+ );
+
+ cy.get("[ui5-multi-combobox]")
+ .as("mcb")
+ .realClick();
+
+ cy.get("@mcb")
+ .shadow()
+ .find(".inputIcon")
+ .realClick();
+
+ cy.get("@mcb")
+ .shadow()
+ .find("ui5-responsive-popover")
+ .as("popover")
+ .ui5ResponsivePopoverOpened();
+
+ // Verify checkbox is unchecked (1 out of 4 items selected)
+ cy.get("@popover")
+ .find(".ui5-mcb-select-all-checkbox")
+ .should("not.have.attr", "checked");
+ });
+
+ it("Select All checkbox checked when all grouped items selected initially", () => {
+ cy.mount(
+
+
+
+
+
+
+
+
+
+
+ );
+
+ cy.get("[ui5-multi-combobox]")
+ .as("mcb")
+ .realClick();
+
+ cy.get("@mcb")
+ .shadow()
+ .find(".inputIcon")
+ .realClick();
+
+ cy.get("@mcb")
+ .shadow()
+ .find("ui5-responsive-popover")
+ .as("popover")
+ .ui5ResponsivePopoverOpened();
+
+ // Verify checkbox is checked (all 4 items selected)
+ cy.get("@popover")
+ .find(".ui5-mcb-select-all-checkbox")
+ .should("have.attr", "checked");
+ });
+
it("Tokenizer expansion on dynamically added tokens", () => {
const addTokens = () => {
const mcb = document.getElementById("mcb");
diff --git a/packages/main/src/MultiComboBox.ts b/packages/main/src/MultiComboBox.ts
index 534eeb34c311..841cf48cfd38 100644
--- a/packages/main/src/MultiComboBox.ts
+++ b/packages/main/src/MultiComboBox.ts
@@ -733,14 +733,20 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
this._showMorePressed = true;
this._tokenizer._scrollToEndOnExpand = true;
+ this._applySelectedItemsFilter();
+
this._toggleTokenizerPopover();
}
filterSelectedItems(e: UI5CustomEvent) {
this.filterSelected = (e.target as ToggleButton).pressed;
- const selectedItems = this._filteredItems.filter(item => item.selected);
- this.selectedItems = this._getItems().filter((item, idx, allItems) => MultiComboBox._groupItemFilter(item, ++idx, allItems, selectedItems) || selectedItems.indexOf(item) !== -1);
+ if (this.filterSelected) {
+ this._applySelectedItemsFilter();
+ } else {
+ // Reset to show all items
+ this._updateItemsVisibility();
+ }
}
get _showAllItemsButtonPressed(): boolean {
@@ -1199,7 +1205,8 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
this.filterSelected = false;
} else {
this._previouslySelectedItems = this._getSelectedItems();
- this.selectedItems?.filter(item => !item.isGroupItem).forEach(item => {
+ // In n-more mode, use _filteredItems instead of selectedItems
+ this._filteredItems?.filter(item => !item.isGroupItem).forEach(item => {
item.selected = (e.target as CheckBox).checked;
});
@@ -1212,6 +1219,11 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
if (changePrevented) {
this._revertSelection();
}
+
+ // In n-more mode, update Select All checkbox state after selection changes
+ if (this.filterSelected) {
+ this._updateGroupsVisibility();
+ }
}
}
@@ -1630,6 +1642,71 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
.filter((v): v is string => !!v);
}
+ /**
+ * Filters items to show only selected items and their group headers,
+ * and updates the _isVisible property accordingly.
+ * Used in n-more mode.
+ * @private
+ */
+ _applySelectedItemsFilter() {
+ const allItems = this._getItems();
+
+ // Set _isVisible for all items based on selection
+ allItems.forEach(item => {
+ if (isInstanceOfMultiComboBoxItem(item)) {
+ item._isVisible = item.selected;
+ }
+ });
+
+ // Hide unselected items and empty groups using CSS
+ allItems.forEach(item => {
+ const shouldBeVisible = item.isGroupItem ? item._isVisible : item.selected;
+ (item as HTMLElement).style.display = shouldBeVisible ? "" : "none";
+ });
+
+ // Filter to only include selected items and non-empty groups
+ const filtered = allItems.filter(item => {
+ if (item.isGroupItem) {
+ return item._isVisible; // Group's _isVisible getter checks if any children are visible
+ }
+ return item.selected;
+ });
+
+ this._filteredItems = [...filtered];
+ }
+
+ /**
+ * Updates the _isVisible property for all items.
+ * If visibleItems is provided, only those items will be visible.
+ * If not provided, all items will be visible.
+ * @private
+ */
+ _updateItemsVisibility(visibleItems?: IMultiComboBoxItem[]) {
+ this._getItems().forEach(item => {
+ if (isInstanceOfMultiComboBoxItem(item)) {
+ item._isVisible = visibleItems ? visibleItems.includes(item) : true;
+ }
+ // Reset display style
+ (item as HTMLElement).style.display = "";
+ });
+ }
+
+ /**
+ * Updates Select All checkbox state in n-more mode.
+ * Items and groups stay visible - they're only filtered on reopen.
+ * Used when selections change while the n-more popup is open.
+ * @private
+ */
+ _updateGroupsVisibility() {
+ // In n-more mode, when selections change, we only update the Select All checkbox state
+ // We DON'T hide items or groups - they stay visible until popup closes and reopens
+
+ // Recalculate Select All checkbox state based on currently visible items
+ const visibleSelectableItems = this._filteredItems.filter(item => !item.isGroupItem);
+ const selectedVisibleItems = visibleSelectableItems.filter(item => item.selected);
+ this._allSelected = selectedVisibleItems.length > 0 && selectedVisibleItems.length === visibleSelectableItems.length;
+ }
+
_listSelectionChange(e: CustomEvent) {
let changePrevented;
@@ -1649,6 +1726,11 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
this.selectedValues = this._getSelectedValues();
}
+ // In n-more mode, update Select All checkbox state when selections change
+ if (this.filterSelected) {
+ this._updateGroupsVisibility();
+ }
+
// don't call selection change right after selection as user can cancel it on phone
if (!isPhone()) {
changePrevented = this.fireSelectionChange();
@@ -1740,6 +1822,9 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
this._iconPressed = false;
this._preventTokenizerToggle = false;
this.filterSelected = false;
+
+ // Reset _isVisible for all items when closing
+ this._updateItemsVisibility();
}
_beforeOpen() {
@@ -1758,8 +1843,7 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
this._innerInput.value = this.value;
if (this.filterSelected) {
- const selectedItems = this._filteredItems.filter(item => item.selected);
- this.selectedItems = this._getItems().filter((item, idx, allItems) => MultiComboBox._groupItemFilter(item, ++idx, allItems, selectedItems) || selectedItems.indexOf(item) !== -1);
+ this._applySelectedItemsFilter();
}
}
@@ -1861,16 +1945,28 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
const value = input && input.value;
if (this.open) {
- const list = this._getList();
- const selectedListItemsCount = this.items.filter(item => item.selected).length;
- this._allSelected = selectedListItemsCount > 0 && ((selectedListItemsCount === this.items.length) || (list?.getSlottedNodes("items").length === selectedListItemsCount));
+ // When in n-more mode, Select All is checked only if ALL visible items are selected
+ // In normal mode, Select All is checked only if ALL selectable items are selected
+ if (this.filterSelected) {
+ const visibleSelectableItems = this._filteredItems.filter(item => !item.isGroupItem);
+ const selectedVisibleItems = visibleSelectableItems.filter(item => item.selected);
+ this._allSelected = selectedVisibleItems.length > 0 && selectedVisibleItems.length === visibleSelectableItems.length;
+ } else {
+ // Normal mode: Select All is checked only if ALL selectable items are selected
+ const selectableItems = this._getItems().filter(item => !item.isGroupItem && item._isVisible);
+ const selectedCount = selectableItems.filter(item => item.selected).length;
+ this._allSelected = selectedCount > 0 && selectedCount === selectableItems.length;
+ }
}
this._effectiveShowClearIcon = (this.showClearIcon && !!this.value && !this.readonly && !this.disabled);
if (input && !input.value) {
this.valueBeforeAutoComplete = "";
- this._filteredItems = this._getItems();
+ // Don't reset _filteredItems in n-more mode - it's controlled by _applySelectedItemsFilter
+ if (!this.filterSelected) {
+ this._filteredItems = this._getItems();
+ }
}
if (this.selectedValues) {
@@ -1881,9 +1977,13 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
this.style.setProperty("--_ui5-input-icons-count", `${this.iconsCount}`);
if (!input || !value) {
+ // Don't reset visibility in n-more mode - it's controlled by _beforeOpen and _showFilteredItems
+ if (!this.filterSelected) {
+ this._updateItemsVisibility();
+ }
+ // Update readonly state for all items
this._getItems().forEach(item => {
if (isInstanceOfMultiComboBoxItem(item)) {
- item._isVisible = true;
item._readonly = this.readonly;
}
});
@@ -1902,10 +2002,13 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
}
}
- if (this._shouldFilterItems) {
- this._filteredItems = this._filterItems(autoCompletedChars ? this.valueBeforeAutoComplete : value);
- } else {
- this._filteredItems = this._getItems();
+ // Don't reset _filteredItems in n-more mode - it's controlled by _applySelectedItemsFilter
+ if (!this.filterSelected) {
+ if (this._shouldFilterItems) {
+ this._filteredItems = this._filterItems(autoCompletedChars ? this.valueBeforeAutoComplete : value);
+ } else {
+ this._filteredItems = this._getItems();
+ }
}
}
@@ -2341,7 +2444,16 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
}
get selectAllCheckboxLabel() {
- const items = this._getItems().filter(item => !item.isGroupItem);
+ // In n-more mode, show count of selected vs. total visible items
+ // In normal mode, show selected vs. total count
+ if (this.filterSelected) {
+ const visibleItems = this._filteredItems.filter(item => !item.isGroupItem);
+ const selectedCount = visibleItems.filter(item => item.selected).length;
+ return MultiComboBox.i18nBundle.getText(MCB_SELECTED_ITEMS, selectedCount, visibleItems.length);
+ }
+
+ // Normal mode: count only visible items
+ const items = this._getItems().filter(item => !item.isGroupItem && item._isVisible);
const selected = items.filter(item => item.selected);
return MultiComboBox.i18nBundle.getText(MCB_SELECTED_ITEMS, selected.length, items.length);
diff --git a/packages/main/src/MultiComboBoxPopoverTemplate.tsx b/packages/main/src/MultiComboBoxPopoverTemplate.tsx
index 0393b075a1f3..3d5a51a7c931 100644
--- a/packages/main/src/MultiComboBoxPopoverTemplate.tsx
+++ b/packages/main/src/MultiComboBoxPopoverTemplate.tsx
@@ -95,7 +95,7 @@ export default function MultiComboBoxPopoverTemplate(this: MultiComboBox) {
{!this.loading && this.filterSelected ?
- {this.selectedItems.map(item => )}
+ {this._filteredItems.map(item => )}
: !this.loading &&
diff --git a/packages/main/src/themes/MultiComboBoxPopover.css b/packages/main/src/themes/MultiComboBoxPopover.css
index 21115cc5c4a9..9ce322eeaf3b 100644
--- a/packages/main/src/themes/MultiComboBoxPopover.css
+++ b/packages/main/src/themes/MultiComboBoxPopover.css
@@ -16,13 +16,14 @@
font-family: var(--sapFontBoldFamily);
}
+.ui5-mcb-select-all-checkbox::part(root) {
+ width: 100%;
+}
+
.ui5-mcb-select-all-checkbox::part(root):focus::before {
border: var(--sapContent_FocusWidth) var(--sapContent_FocusStyle) var(--sapContent_FocusColor);
border-radius: 0;
- right: 2px;
- left: 2px;
- bottom: 0;
- top: 0;
+ inset: 0 2px;
}
.ui5-mcb-select-all-checkbox::part(label) {