Skip to content

Commit 73ebf49

Browse files
hawacodesHavva Beyza OZLUOGLUdanielleroux
authored
feat(core/select): auto-convert individual indices to ALL chip selector when all selected (#1979)
Co-authored-by: Havva Beyza OZLUOGLU <[email protected]> Co-authored-by: Daniel Leroux <[email protected]>
1 parent fb14af0 commit 73ebf49

File tree

15 files changed

+324
-26
lines changed

15 files changed

+324
-26
lines changed

.changeset/true-geckos-smash.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@siemens/ix-angular': minor
3+
'@siemens/ix-react': minor
4+
'@siemens/ix': minor
5+
'@siemens/ix-vue': minor
6+
---
7+
8+
`ix-select` automatically showing an 'All' chip when all items are selected in multiple mode and `collapse-multiple-selection=true` is provided

packages/angular/src/components.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,15 +2306,15 @@ export declare interface IxRow extends Components.IxRow {}
23062306

23072307

23082308
@ProxyCmp({
2309-
inputs: ['allowClear', 'ariaLabelChevronDownIconButton', 'ariaLabelClearIconButton', 'disabled', 'dropdownMaxWidth', 'dropdownWidth', 'editable', 'helperText', 'hideListHeader', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
2309+
inputs: ['allowClear', 'ariaLabelChevronDownIconButton', 'ariaLabelClearIconButton', 'collapseMultipleSelection', 'disabled', 'dropdownMaxWidth', 'dropdownWidth', 'editable', 'helperText', 'hideListHeader', 'i18nAllSelected', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
23102310
methods: ['getNativeInputElement', 'focusInput']
23112311
})
23122312
@Component({
23132313
selector: 'ix-select',
23142314
changeDetection: ChangeDetectionStrategy.OnPush,
23152315
template: '<ng-content></ng-content>',
23162316
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2317-
inputs: ['allowClear', 'ariaLabelChevronDownIconButton', 'ariaLabelClearIconButton', 'disabled', 'dropdownMaxWidth', 'dropdownWidth', 'editable', 'helperText', 'hideListHeader', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
2317+
inputs: ['allowClear', 'ariaLabelChevronDownIconButton', 'ariaLabelClearIconButton', 'collapseMultipleSelection', 'disabled', 'dropdownMaxWidth', 'dropdownWidth', 'editable', 'helperText', 'hideListHeader', 'i18nAllSelected', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
23182318
outputs: ['valueChange', 'inputChange', 'addItem', 'ixBlur'],
23192319
standalone: false
23202320
})

packages/angular/standalone/src/components.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2407,15 +2407,15 @@ export declare interface IxRow extends Components.IxRow {}
24072407

24082408
@ProxyCmp({
24092409
defineCustomElementFn: defineIxSelect,
2410-
inputs: ['allowClear', 'ariaLabelChevronDownIconButton', 'ariaLabelClearIconButton', 'disabled', 'dropdownMaxWidth', 'dropdownWidth', 'editable', 'helperText', 'hideListHeader', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
2410+
inputs: ['allowClear', 'ariaLabelChevronDownIconButton', 'ariaLabelClearIconButton', 'collapseMultipleSelection', 'disabled', 'dropdownMaxWidth', 'dropdownWidth', 'editable', 'helperText', 'hideListHeader', 'i18nAllSelected', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
24112411
methods: ['getNativeInputElement', 'focusInput']
24122412
})
24132413
@Component({
24142414
selector: 'ix-select',
24152415
changeDetection: ChangeDetectionStrategy.OnPush,
24162416
template: '<ng-content></ng-content>',
24172417
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2418-
inputs: ['allowClear', 'ariaLabelChevronDownIconButton', 'ariaLabelClearIconButton', 'disabled', 'dropdownMaxWidth', 'dropdownWidth', 'editable', 'helperText', 'hideListHeader', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
2418+
inputs: ['allowClear', 'ariaLabelChevronDownIconButton', 'ariaLabelClearIconButton', 'collapseMultipleSelection', 'disabled', 'dropdownMaxWidth', 'dropdownWidth', 'editable', 'helperText', 'hideListHeader', 'i18nAllSelected', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
24192419
outputs: ['valueChange', 'inputChange', 'addItem', 'ixBlur'],
24202420
})
24212421
export class IxSelect {

packages/core/src/components.d.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2980,13 +2980,19 @@ export namespace Components {
29802980
/**
29812981
* ARIA label for the chevron down icon button Will be set as aria-label on the nested HTML button element
29822982
* @since 3.2.0
2983+
* @default 'Open select dropdown'
29832984
*/
29842985
"ariaLabelChevronDownIconButton"?: string;
29852986
/**
29862987
* ARIA label for the clear icon button Will be set as aria-label on the nested HTML button element
29872988
* @since 3.2.0
29882989
*/
29892990
"ariaLabelClearIconButton"?: string;
2991+
/**
2992+
* Show "all" chip when all items are selected in multiple mode
2993+
* @default false
2994+
*/
2995+
"collapseMultipleSelection": boolean;
29902996
/**
29912997
* If true the select will be in disabled state
29922998
* @default false
@@ -3024,6 +3030,11 @@ export namespace Components {
30243030
* @default false
30253031
*/
30263032
"hideListHeader": boolean;
3033+
/**
3034+
* Chip label for all selected items in multiple mode.
3035+
* @default 'All'
3036+
*/
3037+
"i18nAllSelected": string;
30273038
/**
30283039
* Information inside of dropdown if no items where found with current filter text
30293040
* @default 'No matches'
@@ -8919,13 +8930,19 @@ declare namespace LocalJSX {
89198930
/**
89208931
* ARIA label for the chevron down icon button Will be set as aria-label on the nested HTML button element
89218932
* @since 3.2.0
8933+
* @default 'Open select dropdown'
89228934
*/
89238935
"ariaLabelChevronDownIconButton"?: string;
89248936
/**
89258937
* ARIA label for the clear icon button Will be set as aria-label on the nested HTML button element
89268938
* @since 3.2.0
89278939
*/
89288940
"ariaLabelClearIconButton"?: string;
8941+
/**
8942+
* Show "all" chip when all items are selected in multiple mode
8943+
* @default false
8944+
*/
8945+
"collapseMultipleSelection"?: boolean;
89298946
/**
89308947
* If true the select will be in disabled state
89318948
* @default false
@@ -8953,6 +8970,11 @@ declare namespace LocalJSX {
89538970
* @default false
89548971
*/
89558972
"hideListHeader"?: boolean;
8973+
/**
8974+
* Chip label for all selected items in multiple mode.
8975+
* @default 'All'
8976+
*/
8977+
"i18nAllSelected"?: string;
89568978
/**
89578979
* Information inside of dropdown if no items where found with current filter text
89588980
* @default 'No matches'

packages/core/src/components/select/select.tsx

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class Select implements IxInputFieldComponent<string | string[]> {
7575
*
7676
* @since 3.2.0
7777
*/
78-
@Prop() ariaLabelChevronDownIconButton?: string;
78+
@Prop() ariaLabelChevronDownIconButton?: string = 'Open select dropdown';
7979

8080
/**
8181
* ARIA label for the clear icon button
@@ -168,6 +168,11 @@ export class Select implements IxInputFieldComponent<string | string[]> {
168168
*/
169169
@Prop({ attribute: 'i18n-no-matches' }) i18nNoMatches = 'No matches';
170170

171+
/**
172+
* Chip label for all selected items in multiple mode.
173+
*/
174+
@Prop({ attribute: 'i18n-all-selected' }) i18nAllSelected = 'All';
175+
171176
/**
172177
* Hide list header
173178
*/
@@ -184,6 +189,11 @@ export class Select implements IxInputFieldComponent<string | string[]> {
184189
*/
185190
@Prop() dropdownMaxWidth?: string;
186191

192+
/**
193+
* Show "all" chip when all items are selected in multiple mode
194+
*/
195+
@Prop() collapseMultipleSelection = false;
196+
187197
/**
188198
* Value changed
189199
*/
@@ -775,6 +785,45 @@ export class Select implements IxInputFieldComponent<string | string[]> {
775785
);
776786
}
777787

788+
private shouldDisplayAllChip(): boolean {
789+
return (
790+
this.selectedItems.length === this.items.length &&
791+
this.collapseMultipleSelection
792+
);
793+
}
794+
795+
private renderAllChip() {
796+
return (
797+
<ix-filter-chip
798+
disabled={this.disabled || this.readonly}
799+
ariaLabelCloseIconButton={this.i18nAllSelected}
800+
onCloseClick={(e) => {
801+
e.preventDefault();
802+
e.stopPropagation();
803+
this.clear();
804+
}}
805+
>
806+
{`${this.i18nAllSelected} (${this.selectedItems.length})`}
807+
</ix-filter-chip>
808+
);
809+
}
810+
811+
private renderChip(item: HTMLIxSelectItemElement) {
812+
return (
813+
<ix-filter-chip
814+
disabled={this.disabled || this.readonly}
815+
key={item.value}
816+
onCloseClick={(e) => {
817+
e.preventDefault();
818+
e.stopPropagation();
819+
this.itemClick(item.value);
820+
}}
821+
>
822+
{item.label}
823+
</ix-filter-chip>
824+
);
825+
}
826+
778827
@HookValidationLifecycle()
779828
onValidationChange({
780829
isInvalid,
@@ -863,21 +912,11 @@ export class Select implements IxInputFieldComponent<string | string[]> {
863912
>
864913
<div class="input-container">
865914
<div class="chips">
866-
{this.isMultipleMode
867-
? this.selectedItems?.map((item) => (
868-
<ix-filter-chip
869-
disabled={this.disabled || this.readonly}
870-
key={item.value}
871-
onCloseClick={(e) => {
872-
e.preventDefault();
873-
e.stopPropagation();
874-
this.itemClick(item.value);
875-
}}
876-
>
877-
{item.label}
878-
</ix-filter-chip>
879-
))
880-
: ''}
915+
{this.isMultipleMode &&
916+
this.items.length !== 0 &&
917+
(this.shouldDisplayAllChip()
918+
? this.renderAllChip()
919+
: this.selectedItems?.map((item) => this.renderChip(item)))}
881920
<div class="trigger">
882921
<input
883922
autocomplete="off"

packages/core/src/components/select/test/select.ct.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,3 +900,151 @@ test('last select item can be accessed via scrolling when select placed at cente
900900
});
901901
await expect(lastItem).toBeVisible();
902902
});
903+
904+
test('should display "All" chip when all items are selected in multiple mode', async ({
905+
mount,
906+
page,
907+
}) => {
908+
await mount(`
909+
<ix-select mode="multiple" collapse-multiple-selection>
910+
<ix-select-item value="1" label="Item 1">Test</ix-select-item>
911+
<ix-select-item value="2" label="Item 2">Test</ix-select-item>
912+
<ix-select-item value="3" label="Item 3">Test</ix-select-item>
913+
</ix-select>
914+
`);
915+
916+
const selectElement = page.locator('ix-select');
917+
const chipsContainer = selectElement.locator('.chips');
918+
919+
await page.locator('[data-select-dropdown]').click();
920+
921+
const item1 = selectElement.locator('ix-select-item').nth(0);
922+
const item2 = selectElement.locator('ix-select-item').nth(1);
923+
const item3 = selectElement.locator('ix-select-item').nth(2);
924+
await item1.click();
925+
await item2.click();
926+
await item3.click();
927+
928+
const allChip = chipsContainer
929+
.locator('ix-filter-chip')
930+
.filter({ hasText: 'All (3)' });
931+
932+
await expect(allChip).toBeVisible();
933+
934+
const chip1 = chipsContainer
935+
.locator('ix-filter-chip')
936+
.filter({ hasText: 'Item 1' });
937+
938+
const chip2 = chipsContainer
939+
.locator('ix-filter-chip')
940+
.filter({ hasText: 'Item 2' });
941+
942+
const chip3 = chipsContainer
943+
.locator('ix-filter-chip')
944+
.filter({ hasText: 'Item 3' });
945+
946+
await expect(chip1).not.toBeVisible();
947+
await expect(chip2).not.toBeVisible();
948+
await expect(chip3).not.toBeVisible();
949+
});
950+
951+
test('should clear items if "All" chip is clicked', async ({ mount, page }) => {
952+
await mount(`
953+
<ix-select mode="multiple" collapse-multiple-selection>
954+
<ix-select-item value="1" label="Item 1">Test</ix-select-item>
955+
<ix-select-item value="2" label="Item 2">Test</ix-select-item>
956+
<ix-select-item value="3" label="Item 3">Test</ix-select-item>
957+
</ix-select>
958+
`);
959+
960+
const selectElement = page.locator('ix-select');
961+
const chipsContainer = selectElement.locator('.chips');
962+
963+
await page.locator('[data-select-dropdown]').click();
964+
965+
const item1 = selectElement.locator('ix-select-item').nth(0);
966+
const item2 = selectElement.locator('ix-select-item').nth(1);
967+
const item3 = selectElement.locator('ix-select-item').nth(2);
968+
await item1.click();
969+
await item2.click();
970+
await item3.click();
971+
972+
const allChip = chipsContainer
973+
.locator('ix-filter-chip')
974+
.filter({ hasText: 'All (3)' });
975+
976+
await expect(allChip).toBeVisible();
977+
978+
const chip1 = chipsContainer
979+
.locator('ix-filter-chip')
980+
.filter({ hasText: 'Item 1' });
981+
982+
const chip2 = chipsContainer
983+
.locator('ix-filter-chip')
984+
.filter({ hasText: 'Item 2' });
985+
986+
const chip3 = chipsContainer
987+
.locator('ix-filter-chip')
988+
.filter({ hasText: 'Item 3' });
989+
990+
await expect(chip1).not.toBeVisible();
991+
await expect(chip2).not.toBeVisible();
992+
await expect(chip3).not.toBeVisible();
993+
994+
await allChip.getByRole('button', { name: 'All' }).click();
995+
await expect(allChip).not.toBeVisible();
996+
});
997+
998+
test('should not show "All" chip of de-selected a item', async ({
999+
mount,
1000+
page,
1001+
}) => {
1002+
await mount(`
1003+
<ix-select mode="multiple" collapse-multiple-selection>
1004+
<ix-select-item value="1" label="Item 1">Test</ix-select-item>
1005+
<ix-select-item value="2" label="Item 2">Test</ix-select-item>
1006+
<ix-select-item value="3" label="Item 3">Test</ix-select-item>
1007+
</ix-select>
1008+
`);
1009+
1010+
const selectElement = page.locator('ix-select');
1011+
const chipsContainer = selectElement.locator('.chips');
1012+
1013+
await page.locator('[data-select-dropdown]').click();
1014+
1015+
const item1 = selectElement.locator('ix-select-item').nth(0);
1016+
const item2 = selectElement.locator('ix-select-item').nth(1);
1017+
const item3 = selectElement.locator('ix-select-item').nth(2);
1018+
await item1.click();
1019+
await item2.click();
1020+
await item3.click();
1021+
1022+
const allChip = chipsContainer
1023+
.locator('ix-filter-chip')
1024+
.filter({ hasText: 'All (3)' });
1025+
1026+
await expect(allChip).toBeVisible();
1027+
1028+
const chip1 = chipsContainer
1029+
.locator('ix-filter-chip')
1030+
.filter({ hasText: 'Item 1' });
1031+
1032+
const chip2 = chipsContainer
1033+
.locator('ix-filter-chip')
1034+
.filter({ hasText: 'Item 2' });
1035+
1036+
const chip3 = chipsContainer
1037+
.locator('ix-filter-chip')
1038+
.filter({ hasText: 'Item 3' });
1039+
1040+
await expect(chip1).not.toBeVisible();
1041+
await expect(chip2).not.toBeVisible();
1042+
await expect(chip3).not.toBeVisible();
1043+
1044+
await item3.click();
1045+
await expect(chip1).toBeVisible();
1046+
await expect(chip2).toBeVisible();
1047+
await expect(chip3).not.toBeVisible();
1048+
1049+
await expect(allChip).not.toBeVisible();
1050+
});

packages/react/src/components.server.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1594,9 +1594,11 @@ export const IxSelect: StencilReactComponent<IxSelectElement, IxSelectEvents> =
15941594
i18nPlaceholderEditable: 'i18n-placeholder-editable',
15951595
i18nSelectListHeader: 'i18n-select-list-header',
15961596
i18nNoMatches: 'i18n-no-matches',
1597+
i18nAllSelected: 'i18n-all-selected',
15971598
hideListHeader: 'hide-list-header',
15981599
dropdownWidth: 'dropdown-width',
1599-
dropdownMaxWidth: 'dropdown-max-width'
1600+
dropdownMaxWidth: 'dropdown-max-width',
1601+
collapseMultipleSelection: 'collapse-multiple-selection'
16001602
},
16011603
hydrateModule: import('@siemens/ix/hydrate') as Promise<HydrateModule>,
16021604
clientModule: clientComponents.IxSelect as ReactWebComponent<IxSelectElement, IxSelectEvents>,

0 commit comments

Comments
 (0)