Skip to content
Merged
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
19 changes: 15 additions & 4 deletions packages/multiple-select-vanilla/src/MultipleSelectInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ export class MultipleSelectInstance {
const selectName = this.elm.getAttribute('name') || this.options.name || '';
this.selectAllParentElm = createDomElement('div', { className: 'ms-select-all', dataset: { key: 'select_all' } });
const saLabelElm = document.createElement('label');
const saIconClass = this.isAllSelected ? 'ms-icon-check' : this.isPartiallyAllSelected ? 'ms-icon-minus' : 'ms-icon-uncheck';
const saIconClass = this.isAllSelected ? 'ms-icon-check' : `ms-icon-${this.isPartiallyAllSelected ? 'partial-all' : 'uncheck'}`;
const selectAllIconClass = `ms-icon ${saIconClass}`;
const saIconContainerElm = createDomElement('div', { className: 'icon-checkbox-container' }, saLabelElm);
createDomElement(
Expand Down Expand Up @@ -635,6 +635,12 @@ export class MultipleSelectInstance {
if (isSingleWithoutRadioIcon) {
itemOrGroupBlock = inputCheckboxStruct;
} else {
// determine if it's an optgroup and the group has a partial selection
let hasPartialGroupSelected = false;
if ('children' in dataRow && (dataRow as OptGroupRowData).children.some(child => child?.selected)) {
hasPartialGroupSelected = true;
}

itemOrGroupBlock = {
tagName: 'div',
props: {
Expand All @@ -645,7 +651,7 @@ export class MultipleSelectInstance {
{
tagName: 'div',
props: {
className: `ms-icon ${isChecked ? (type === 'radio' ? 'ms-icon-radio' : 'ms-icon-check') : 'ms-icon-uncheck'}`,
className: `ms-icon ${isChecked ? (type === 'radio' ? 'ms-icon-radio' : 'ms-icon-check') : `ms-icon-${hasPartialGroupSelected ? 'partial-group' : 'uncheck'}`}`,
},
},
],
Expand Down Expand Up @@ -1575,6 +1581,11 @@ export class MultipleSelectInstance {
const closestLiElm = inputElm.closest('li');
const iconDivElm = closestLiElm?.querySelector('.icon-checkbox-container div');
if (closestLiElm) {
// determine if it's an optgroup and the group has a partial selection
let hasPartialGroupSelected = false;
if ('children' in row && (row as OptGroupRowData).children.some(child => child?.selected)) {
hasPartialGroupSelected = true;
}
if (row.selected && !closestLiElm.classList.contains('selected')) {
closestLiElm.classList.add('selected');
closestLiElm.ariaSelected = 'true';
Expand All @@ -1585,7 +1596,7 @@ export class MultipleSelectInstance {
closestLiElm.classList.remove('selected');
closestLiElm.ariaSelected = 'false';
if (iconDivElm) {
iconDivElm.className = 'ms-icon ms-icon-uncheck';
iconDivElm.className = `ms-icon ms-icon-${hasPartialGroupSelected ? 'partial-group' : 'uncheck'}`;
}
}
}
Expand All @@ -1602,7 +1613,7 @@ export class MultipleSelectInstance {
if (this.isAllSelected) {
iconClass = 'ms-icon-check';
} else if (this.isPartiallyAllSelected) {
iconClass = 'ms-icon-minus';
iconClass = 'ms-icon-partial-all';
} else {
iconClass = 'ms-icon-uncheck';
}
Expand Down
5 changes: 3 additions & 2 deletions packages/multiple-select-vanilla/src/styles/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

@use 'sass:color';

// this is the only variable without $ms prefix and exists so that user could use
// this is the only variable without $ms prefix and exists so that user could use
// the same Bootstrap primary color without declaring $ms-primary-color variable (which also exists)
$primary-color: #149085 !default;
$ms-primary-color: $primary-color !default;
Expand All @@ -30,7 +30,8 @@ $ms-icon-caret-svg-path: "M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8
$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;
$ms-icon-loading-svg-path: "M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" !default;
$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;
$ms-icon-minus-svg-path: "M20 14H4V10H20" !default;
$ms-icon-partial-all-svg-path: "M20 14H4V10H20" !default;
$ms-icon-partial-group-svg-path: "M19,13H5V11H19V13Z" !default;
$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;
$ms-icon-color: #444 !default;
$ms-icon-color-hover: #303030 !default;
Expand Down
13 changes: 7 additions & 6 deletions packages/multiple-select-vanilla/src/styles/multiple-select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
@include m.createSvgClass("ms-icon-caret", v.$ms-icon-caret-svg-path);
@include m.createSvgClass("ms-icon-close", v.$ms-icon-close-svg-path);
@include m.createSvgClass("ms-icon-check", v.$ms-icon-check-svg-path);
@include m.createSvgClass("ms-icon-minus", v.$ms-icon-minus-svg-path);
@include m.createSvgClass("ms-icon-partial-all", v.$ms-icon-partial-all-svg-path);
@include m.createSvgClass("ms-icon-partial-group", v.$ms-icon-partial-group-svg-path);
@include m.createSvgClass("ms-icon-radio", v.$ms-icon-radio-svg-path);
@include m.createSvgClass("ms-icon-loading", v.$ms-icon-loading-svg-path);

Expand Down Expand Up @@ -56,14 +57,14 @@
width: var(--ms-checkbox-icon-container-width, v.$ms-checkbox-icon-container-width);
border: var(--ms-checkbox-icon-container-border, v.$ms-checkbox-icon-container-border);
border-radius: var(--ms-checkbox-icon-container-border-radius, v.$ms-checkbox-icon-container-border-radius);

div {
font-size: 14px;
color: var(--ms-checkbox-color, v.$ms-checkbox-color);
&:hover {
color: var(--ms-checkbox-hover-color, v.$ms-checkbox-hover-color);
}
// since we use the div container with a border, we don't actually need an icon for unchecked
// since we use the div container with a border, we don't actually need an icon for unchecked
// BUT since we want to keep the same size, we can simply hide the mask to keep the same size
&.ms-icon-uncheck {
visibility: hidden;
Expand Down Expand Up @@ -386,7 +387,7 @@
margin-top: var(--ms-drop-input-margin-top, v.$ms-drop-input-margin-top);
accent-color: var(--ms-checkbox-color, v.$ms-checkbox-color);
}
&:focus {
&:focus {
outline: var(--ms-input-focus-outline, v.$ms-input-focus-outline);
}
}
Expand All @@ -398,13 +399,13 @@
.ms-loading {
display: flex;
align-items: center;
gap: var(--ms-loading-gap, v.$ms-loading-gap);
gap: var(--ms-loading-gap, v.$ms-loading-gap);
padding: var(--ms-loading-padding, v.$ms-loading-padding);
.ms-icon-loading {
font-size: var(--ms-loading-icon-size, v.$ms-loading-icon-size);
height: var(--ms-loading-icon-size, v.$ms-loading-icon-size);
width: var(--ms-loading-icon-size, v.$ms-loading-icon-size);
}
}
}

.ms-infinite-option {
Expand Down
8 changes: 7 additions & 1 deletion playwright/e2e/example03.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ test.describe('Example 03 - Multiple Width', () => {
test('second select and expect optgroup selection to select the entire group when optgroup is selected', async ({ page }) => {
await page.goto('#/example03');
await page.locator('div[data-test=select2].ms-parent').click();
let group1SelectAll = await page.locator('[data-key="group_0"] .icon-checkbox-container div');
await expect(group1SelectAll).toHaveClass('ms-icon ms-icon-uncheck');
await page.getByText('Group 1').click();
group1SelectAll = await page.locator('[data-key="group_0"] .icon-checkbox-container div');
await expect(group1SelectAll).toHaveClass('ms-icon ms-icon-check');
await page.getByRole('button', { name: '5 of 15 selected' }).click();
await page.getByRole('button', { name: '5 of 15 selected' }).click();
await page.getByRole('option', { name: '3', exact: true }).click();
group1SelectAll = await page.locator('[data-key="group_0"] .icon-checkbox-container div');
await expect(group1SelectAll).toHaveClass('ms-icon ms-icon-partial-group');
expect(await page.getByRole('option', { name: '3', exact: true }).locator('div').nth(1)).toHaveClass('ms-icon ms-icon-uncheck');
await page.getByRole('button', { name: '4 of 15 selected' }).click();
await page.getByRole('button', { name: '4 of 15 selected' }).click();
Expand All @@ -42,7 +48,7 @@ test.describe('Example 03 - Multiple Width', () => {
await page.getByRole('button', { name: '14 of 15 selected' }).click();
await page.getByRole('button', { name: '14 of 15 selected' }).click();
const selectAll2 = await page.locator('[data-test=select2] .ms-select-all .icon-checkbox-container div');
await expect(selectAll2).toHaveClass('ms-icon ms-icon-minus');
await expect(selectAll2).toHaveClass('ms-icon ms-icon-partial-all');
await page.getByRole('option', { name: '3', exact: true }).click();
expect(await page.getByRole('option', { name: '3', exact: true }).locator('div').nth(1)).toHaveClass('ms-icon ms-icon-check');
await expect(selectAll2).toHaveClass('ms-icon ms-icon-check');
Expand Down
10 changes: 9 additions & 1 deletion playwright/e2e/example06.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test } from '@playwright/test';
import { expect, test } from '@playwright/test';

test.describe('Example 06 - Disabled items', () => {
test('first select disabled selection February, March are shown but not clickable', async ({ page }) => {
Expand All @@ -14,11 +14,19 @@ test.describe('Example 06 - Disabled items', () => {
test('second select disabled group selection Group1, Option1,2,3 should not be toggable', async ({ page }) => {
await page.goto('#/example06');
await page.getByRole('button', { name: '[Group 1: Option 1], [Group 2: Option 5]' }).click();
const group1SelectAll = await page.locator('[data-key="group_0"] .icon-checkbox-container div');
await expect(group1SelectAll).toHaveClass('ms-icon ms-icon-partial-group');
let group2SelectAll = await page.locator('[data-key="group_1"] .icon-checkbox-container div');
await expect(group2SelectAll).toHaveClass('ms-icon ms-icon-partial-group');
await page.getByRole('option', { name: 'Group 2' }).click();
group2SelectAll = await page.locator('[data-key="group_1"] .icon-checkbox-container div');
await expect(group2SelectAll).toHaveClass('ms-icon ms-icon-check');
await page.getByRole('button', { name: '[Group 1: Option 1], [Group 2: Option 5]' });
await page.getByRole('button', { name: '4 of 9 selected' }).click();
await page.getByRole('button', { name: '4 of 9 selected' }).click();
await page.locator('label').filter({ hasText: 'Option 4' }).click();
await page.getByRole('button', { name: '[Group 1: Option 1], [Group 2: Option 5, Option 6]' }).click();
const group3SelectAll = await page.locator('[data-key="group_2"] .icon-checkbox-container div');
await expect(group3SelectAll).toHaveClass('ms-icon ms-icon-uncheck');
});
});
Loading