Skip to content

Commit 7b7b2f0

Browse files
authored
fix!: partial option group should show the minus icon (#462)
* fix!: partial option group should show the minus icon
1 parent 106dc0a commit 7b7b2f0

File tree

5 files changed

+41
-14
lines changed

5 files changed

+41
-14
lines changed

packages/multiple-select-vanilla/src/MultipleSelectInstance.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ export class MultipleSelectInstance {
444444
const selectName = this.elm.getAttribute('name') || this.options.name || '';
445445
this.selectAllParentElm = createDomElement('div', { className: 'ms-select-all', dataset: { key: 'select_all' } });
446446
const saLabelElm = document.createElement('label');
447-
const saIconClass = this.isAllSelected ? 'ms-icon-check' : this.isPartiallyAllSelected ? 'ms-icon-minus' : 'ms-icon-uncheck';
447+
const saIconClass = this.isAllSelected ? 'ms-icon-check' : `ms-icon-${this.isPartiallyAllSelected ? 'partial-all' : 'uncheck'}`;
448448
const selectAllIconClass = `ms-icon ${saIconClass}`;
449449
const saIconContainerElm = createDomElement('div', { className: 'icon-checkbox-container' }, saLabelElm);
450450
createDomElement(
@@ -635,6 +635,12 @@ export class MultipleSelectInstance {
635635
if (isSingleWithoutRadioIcon) {
636636
itemOrGroupBlock = inputCheckboxStruct;
637637
} else {
638+
// determine if it's an optgroup and the group has a partial selection
639+
let hasPartialGroupSelected = false;
640+
if ('children' in dataRow && (dataRow as OptGroupRowData).children.some(child => child?.selected)) {
641+
hasPartialGroupSelected = true;
642+
}
643+
638644
itemOrGroupBlock = {
639645
tagName: 'div',
640646
props: {
@@ -645,7 +651,7 @@ export class MultipleSelectInstance {
645651
{
646652
tagName: 'div',
647653
props: {
648-
className: `ms-icon ${isChecked ? (type === 'radio' ? 'ms-icon-radio' : 'ms-icon-check') : 'ms-icon-uncheck'}`,
654+
className: `ms-icon ${isChecked ? (type === 'radio' ? 'ms-icon-radio' : 'ms-icon-check') : `ms-icon-${hasPartialGroupSelected ? 'partial-group' : 'uncheck'}`}`,
649655
},
650656
},
651657
],
@@ -1575,6 +1581,11 @@ export class MultipleSelectInstance {
15751581
const closestLiElm = inputElm.closest('li');
15761582
const iconDivElm = closestLiElm?.querySelector('.icon-checkbox-container div');
15771583
if (closestLiElm) {
1584+
// determine if it's an optgroup and the group has a partial selection
1585+
let hasPartialGroupSelected = false;
1586+
if ('children' in row && (row as OptGroupRowData).children.some(child => child?.selected)) {
1587+
hasPartialGroupSelected = true;
1588+
}
15781589
if (row.selected && !closestLiElm.classList.contains('selected')) {
15791590
closestLiElm.classList.add('selected');
15801591
closestLiElm.ariaSelected = 'true';
@@ -1585,7 +1596,7 @@ export class MultipleSelectInstance {
15851596
closestLiElm.classList.remove('selected');
15861597
closestLiElm.ariaSelected = 'false';
15871598
if (iconDivElm) {
1588-
iconDivElm.className = 'ms-icon ms-icon-uncheck';
1599+
iconDivElm.className = `ms-icon ms-icon-${hasPartialGroupSelected ? 'partial-group' : 'uncheck'}`;
15891600
}
15901601
}
15911602
}
@@ -1602,7 +1613,7 @@ export class MultipleSelectInstance {
16021613
if (this.isAllSelected) {
16031614
iconClass = 'ms-icon-check';
16041615
} else if (this.isPartiallyAllSelected) {
1605-
iconClass = 'ms-icon-minus';
1616+
iconClass = 'ms-icon-partial-all';
16061617
} else {
16071618
iconClass = 'ms-icon-uncheck';
16081619
}

packages/multiple-select-vanilla/src/styles/_variables.scss

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
@use 'sass:color';
77

8-
// this is the only variable without $ms prefix and exists so that user could use
8+
// this is the only variable without $ms prefix and exists so that user could use
99
// the same Bootstrap primary color without declaring $ms-primary-color variable (which also exists)
1010
$primary-color: #149085 !default;
1111
$ms-primary-color: $primary-color !default;
@@ -30,7 +30,8 @@ $ms-icon-caret-svg-path: "M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8
3030
$ms-icon-close-svg-path: "M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" !default;
3131
$ms-icon-loading-svg-path: "M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" !default;
3232
$ms-icon-check-svg-path: "M9,20.42L2.79,14.21L5.62,11.38L9,14.77L18.88,4.88L21.71,7.71L9,20.42Z" !default;
33-
$ms-icon-minus-svg-path: "M20 14H4V10H20" !default;
33+
$ms-icon-partial-all-svg-path: "M20 14H4V10H20" !default;
34+
$ms-icon-partial-group-svg-path: "M19,13H5V11H19V13Z" !default;
3435
$ms-icon-radio-svg-path: "M12 3.7c4.6 0 8.3 3.7 8.3 8.3s-3.7 8.3-8.3 8.3-8.3-3.7-8.3-8.3S7.4 3.7 12 3.7z" !default;
3536
$ms-icon-color: #444 !default;
3637
$ms-icon-color-hover: #303030 !default;

packages/multiple-select-vanilla/src/styles/multiple-select.scss

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
@include m.createSvgClass("ms-icon-caret", v.$ms-icon-caret-svg-path);
1515
@include m.createSvgClass("ms-icon-close", v.$ms-icon-close-svg-path);
1616
@include m.createSvgClass("ms-icon-check", v.$ms-icon-check-svg-path);
17-
@include m.createSvgClass("ms-icon-minus", v.$ms-icon-minus-svg-path);
17+
@include m.createSvgClass("ms-icon-partial-all", v.$ms-icon-partial-all-svg-path);
18+
@include m.createSvgClass("ms-icon-partial-group", v.$ms-icon-partial-group-svg-path);
1819
@include m.createSvgClass("ms-icon-radio", v.$ms-icon-radio-svg-path);
1920
@include m.createSvgClass("ms-icon-loading", v.$ms-icon-loading-svg-path);
2021

@@ -56,14 +57,14 @@
5657
width: var(--ms-checkbox-icon-container-width, v.$ms-checkbox-icon-container-width);
5758
border: var(--ms-checkbox-icon-container-border, v.$ms-checkbox-icon-container-border);
5859
border-radius: var(--ms-checkbox-icon-container-border-radius, v.$ms-checkbox-icon-container-border-radius);
59-
60+
6061
div {
6162
font-size: 14px;
6263
color: var(--ms-checkbox-color, v.$ms-checkbox-color);
6364
&:hover {
6465
color: var(--ms-checkbox-hover-color, v.$ms-checkbox-hover-color);
6566
}
66-
// since we use the div container with a border, we don't actually need an icon for unchecked
67+
// since we use the div container with a border, we don't actually need an icon for unchecked
6768
// BUT since we want to keep the same size, we can simply hide the mask to keep the same size
6869
&.ms-icon-uncheck {
6970
visibility: hidden;
@@ -386,7 +387,7 @@
386387
margin-top: var(--ms-drop-input-margin-top, v.$ms-drop-input-margin-top);
387388
accent-color: var(--ms-checkbox-color, v.$ms-checkbox-color);
388389
}
389-
&:focus {
390+
&:focus {
390391
outline: var(--ms-input-focus-outline, v.$ms-input-focus-outline);
391392
}
392393
}
@@ -398,13 +399,13 @@
398399
.ms-loading {
399400
display: flex;
400401
align-items: center;
401-
gap: var(--ms-loading-gap, v.$ms-loading-gap);
402+
gap: var(--ms-loading-gap, v.$ms-loading-gap);
402403
padding: var(--ms-loading-padding, v.$ms-loading-padding);
403404
.ms-icon-loading {
404405
font-size: var(--ms-loading-icon-size, v.$ms-loading-icon-size);
405406
height: var(--ms-loading-icon-size, v.$ms-loading-icon-size);
406407
width: var(--ms-loading-icon-size, v.$ms-loading-icon-size);
407-
}
408+
}
408409
}
409410

410411
.ms-infinite-option {

playwright/e2e/example03.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,16 @@ test.describe('Example 03 - Multiple Width', () => {
2626
test('second select and expect optgroup selection to select the entire group when optgroup is selected', async ({ page }) => {
2727
await page.goto('#/example03');
2828
await page.locator('div[data-test=select2].ms-parent').click();
29+
let group1SelectAll = await page.locator('[data-key="group_0"] .icon-checkbox-container div');
30+
await expect(group1SelectAll).toHaveClass('ms-icon ms-icon-uncheck');
2931
await page.getByText('Group 1').click();
32+
group1SelectAll = await page.locator('[data-key="group_0"] .icon-checkbox-container div');
33+
await expect(group1SelectAll).toHaveClass('ms-icon ms-icon-check');
3034
await page.getByRole('button', { name: '5 of 15 selected' }).click();
3135
await page.getByRole('button', { name: '5 of 15 selected' }).click();
3236
await page.getByRole('option', { name: '3', exact: true }).click();
37+
group1SelectAll = await page.locator('[data-key="group_0"] .icon-checkbox-container div');
38+
await expect(group1SelectAll).toHaveClass('ms-icon ms-icon-partial-group');
3339
expect(await page.getByRole('option', { name: '3', exact: true }).locator('div').nth(1)).toHaveClass('ms-icon ms-icon-uncheck');
3440
await page.getByRole('button', { name: '4 of 15 selected' }).click();
3541
await page.getByRole('button', { name: '4 of 15 selected' }).click();
@@ -42,7 +48,7 @@ test.describe('Example 03 - Multiple Width', () => {
4248
await page.getByRole('button', { name: '14 of 15 selected' }).click();
4349
await page.getByRole('button', { name: '14 of 15 selected' }).click();
4450
const selectAll2 = await page.locator('[data-test=select2] .ms-select-all .icon-checkbox-container div');
45-
await expect(selectAll2).toHaveClass('ms-icon ms-icon-minus');
51+
await expect(selectAll2).toHaveClass('ms-icon ms-icon-partial-all');
4652
await page.getByRole('option', { name: '3', exact: true }).click();
4753
expect(await page.getByRole('option', { name: '3', exact: true }).locator('div').nth(1)).toHaveClass('ms-icon ms-icon-check');
4854
await expect(selectAll2).toHaveClass('ms-icon ms-icon-check');

playwright/e2e/example06.spec.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test } from '@playwright/test';
1+
import { expect, test } from '@playwright/test';
22

33
test.describe('Example 06 - Disabled items', () => {
44
test('first select disabled selection February, March are shown but not clickable', async ({ page }) => {
@@ -14,11 +14,19 @@ test.describe('Example 06 - Disabled items', () => {
1414
test('second select disabled group selection Group1, Option1,2,3 should not be toggable', async ({ page }) => {
1515
await page.goto('#/example06');
1616
await page.getByRole('button', { name: '[Group 1: Option 1], [Group 2: Option 5]' }).click();
17+
const group1SelectAll = await page.locator('[data-key="group_0"] .icon-checkbox-container div');
18+
await expect(group1SelectAll).toHaveClass('ms-icon ms-icon-partial-group');
19+
let group2SelectAll = await page.locator('[data-key="group_1"] .icon-checkbox-container div');
20+
await expect(group2SelectAll).toHaveClass('ms-icon ms-icon-partial-group');
1721
await page.getByRole('option', { name: 'Group 2' }).click();
22+
group2SelectAll = await page.locator('[data-key="group_1"] .icon-checkbox-container div');
23+
await expect(group2SelectAll).toHaveClass('ms-icon ms-icon-check');
1824
await page.getByRole('button', { name: '[Group 1: Option 1], [Group 2: Option 5]' });
1925
await page.getByRole('button', { name: '4 of 9 selected' }).click();
2026
await page.getByRole('button', { name: '4 of 9 selected' }).click();
2127
await page.locator('label').filter({ hasText: 'Option 4' }).click();
2228
await page.getByRole('button', { name: '[Group 1: Option 1], [Group 2: Option 5, Option 6]' }).click();
29+
const group3SelectAll = await page.locator('[data-key="group_2"] .icon-checkbox-container div');
30+
await expect(group3SelectAll).toHaveClass('ms-icon ms-icon-uncheck');
2331
});
2432
});

0 commit comments

Comments
 (0)