diff --git a/packages/demo/src/examples/example10.html b/packages/demo/src/examples/example10.html
index 30f5bae5..dd74de0c 100644
--- a/packages/demo/src/examples/example10.html
+++ b/packages/demo/src/examples/example10.html
@@ -19,17 +19,24 @@
Virtual scroll will be used with a large set of data
-
-
-
+
+
+
\ No newline at end of file
diff --git a/packages/demo/src/examples/example10.ts b/packages/demo/src/examples/example10.ts
index ae622eb7..b9a91a47 100644
--- a/packages/demo/src/examples/example10.ts
+++ b/packages/demo/src/examples/example10.ts
@@ -2,23 +2,37 @@ import { multipleSelect, MultipleSelectInstance } from 'multiple-select-vanilla'
export default class Example {
ms1?: MultipleSelectInstance;
+ ms2?: MultipleSelectInstance;
mount() {
- const data = [];
+ const data1 = [];
+ const data2 = [];
for (let i = 0; i < 10000; i++) {
- data.push(i);
+ data1.push(i);
}
+ for (let i = 0; i < 10000; i++) {
+ data2.push({ text: ` Task ${i}`, value: i });
+ }
+
+ this.ms1 = multipleSelect('#select1', {
+ filter: true,
+ data: data1,
+ showSearchClear: true,
+ }) as MultipleSelectInstance;
- this.ms1 = multipleSelect('#select', {
+ this.ms2 = multipleSelect('#select2', {
filter: true,
- data,
+ data: data2,
showSearchClear: true,
+ useSelectOptionLabelToHtml: true,
}) as MultipleSelectInstance;
}
unmount() {
// destroy ms instance(s) to avoid DOM leaks
this.ms1?.destroy();
+ this.ms2?.destroy();
this.ms1 = undefined;
+ this.ms2 = undefined;
}
}
diff --git a/packages/demo/src/main.html b/packages/demo/src/main.html
index 685f721c..60d7fa4d 100644
--- a/packages/demo/src/main.html
+++ b/packages/demo/src/main.html
@@ -23,7 +23,6 @@
diff --git a/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts b/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts
index caae32cf..80592204 100644
--- a/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts
+++ b/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts
@@ -503,47 +503,47 @@ export class MultipleSelectInstance {
protected getListRows(): HtmlStruct[] {
const rows: HtmlStruct[] = [];
this.updateData = [];
- this.data?.forEach((row) => rows.push(...this.initListItem(row)));
+ this.data?.forEach((dataRow) => rows.push(...this.initListItem(dataRow)));
rows.push({ tagName: 'li', props: { className: 'ms-no-results', textContent: this.formatNoMatchesFound(), tabIndex: 0 } });
return rows;
}
- protected initListItem(row: OptionRowData | OptGroupRowData, level = 0): HtmlStruct[] {
- const title = row?.title || '';
+ protected initListItem(dataRow: OptionRowData | OptGroupRowData, level = 0): HtmlStruct[] {
+ const title = dataRow?.title || '';
const multiple = this.options.multiple ? 'multiple' : '';
const type = this.options.single ? 'radio' : 'checkbox';
let classes = '';
- if (!row?.visible) {
+ if (!dataRow?.visible) {
return [];
}
- this.updateData.push(row);
+ this.updateData.push(dataRow);
if (this.options.single && !this.options.singleRadio) {
classes = 'hide-radio ';
}
- if (row.selected) {
+ if (dataRow.selected) {
classes += 'selected ';
}
- if (row.type === 'optgroup') {
+ if (dataRow.type === 'optgroup') {
// - group option row -
const htmlBlocks: HtmlStruct[] = [];
const itemOrGroupBlock: HtmlStruct =
this.options.hideOptgroupCheckboxes || this.options.single
- ? { tagName: 'span', props: { dataset: { name: this.selectGroupName, key: row._key } } }
+ ? { tagName: 'span', props: { dataset: { name: this.selectGroupName, key: dataRow._key } } }
: {
tagName: 'input',
props: {
type: 'checkbox',
- dataset: { name: this.selectGroupName, key: row._key },
- ariaChecked: String(row.selected || false),
- checked: !!row.selected,
- disabled: row.disabled,
+ dataset: { name: this.selectGroupName, key: dataRow._key },
+ ariaChecked: String(dataRow.selected || false),
+ checked: !!dataRow.selected,
+ disabled: dataRow.disabled,
tabIndex: -1,
},
};
@@ -553,25 +553,24 @@ export class MultipleSelectInstance {
}
const spanLabelBlock: HtmlStruct = { tagName: 'span', props: {} };
- this.applyAsTextOrHtmlWhenEnabled(spanLabelBlock.props, (row as OptGroupRowData).label);
-
+ this.applyAsTextOrHtmlWhenEnabled(spanLabelBlock.props, (dataRow as OptGroupRowData).label);
const liBlock: HtmlStruct = {
tagName: 'li',
props: {
className: `group ${classes}`.trim(),
- tabIndex: classes.includes('hide-radio') || row.disabled ? -1 : 0,
+ tabIndex: classes.includes('hide-radio') || dataRow.disabled ? -1 : 0,
},
children: [
{
tagName: 'label',
- props: { className: `optgroup${this.options.single || row.disabled ? ' disabled' : ''}` },
+ props: { className: `optgroup${this.options.single || dataRow.disabled ? ' disabled' : ''}` },
children: [itemOrGroupBlock, spanLabelBlock],
},
],
};
- const customStyleRules = this.options.cssStyler(row);
- const customStylerStr = String(this.options.styler(row) || ''); // deprecated
+ const customStyleRules = this.options.cssStyler(dataRow);
+ const customStylerStr = String(this.options.styler(dataRow) || ''); // deprecated
if (customStylerStr) {
liBlock.props.style = convertStringStyleToElementStyle(customStylerStr);
}
@@ -580,52 +579,51 @@ export class MultipleSelectInstance {
}
htmlBlocks.push(liBlock);
- (row as OptGroupRowData).children.forEach((child) => htmlBlocks.push(...this.initListItem(child, 1)));
+ (dataRow as OptGroupRowData).children.forEach((child) => htmlBlocks.push(...this.initListItem(child, 1)));
return htmlBlocks;
}
// - regular row -
- classes += row.classes || '';
+ classes += dataRow.classes || '';
if (level && this.options.single) {
classes += `option-level-${level} `;
}
- if (row.divider) {
+ if (dataRow.divider) {
return [{ tagName: 'li', props: { className: 'option-divider' } } as HtmlStruct];
}
const liClasses = multiple || classes ? (multiple + classes).trim() : '';
- const labelClasses = `${row.disabled ? 'disabled' : ''}`;
+ const labelClasses = `${dataRow.disabled ? 'disabled' : ''}`;
const spanLabelBlock: HtmlStruct = { tagName: 'span', props: {} };
- this.applyAsTextOrHtmlWhenEnabled(spanLabelBlock.props, (row as OptionRowData).text);
-
+ this.applyAsTextOrHtmlWhenEnabled(spanLabelBlock.props, (dataRow as OptionRowData).text);
const inputBlock: HtmlStruct = {
tagName: 'input',
props: {
type,
- value: encodeURI(row.value as string),
- dataset: { key: row._key, name: this.selectItemName },
- ariaChecked: String(row.selected || false),
- checked: !!row.selected,
- disabled: !!row.disabled,
+ value: encodeURI(dataRow.value as string),
+ dataset: { key: dataRow._key, name: this.selectItemName },
+ ariaChecked: String(dataRow.selected || false),
+ checked: !!dataRow.selected,
+ disabled: !!dataRow.disabled,
tabIndex: -1,
},
};
- if (row.selected) {
+ if (dataRow.selected) {
inputBlock.attrs = { checked: 'checked' };
}
const liBlock: HtmlStruct = {
tagName: 'li',
- props: { className: liClasses, title, tabIndex: row.disabled ? -1 : 0, dataset: { key: row._key } },
+ props: { className: liClasses, title, tabIndex: dataRow.disabled ? -1 : 0, dataset: { key: dataRow._key } },
children: [{ tagName: 'label', props: { className: labelClasses }, children: [inputBlock, spanLabelBlock] }],
};
- const customStyleRules = this.options.cssStyler(row);
- const customStylerStr = String(this.options.styler(row) || ''); // deprecated
+ const customStyleRules = this.options.cssStyler(dataRow);
+ const customStylerStr = String(this.options.styler(dataRow) || ''); // deprecated
if (customStylerStr) {
liBlock.props.style = convertStringStyleToElementStyle(customStylerStr);
}
diff --git a/packages/multiple-select-vanilla/src/utils/domUtils.ts b/packages/multiple-select-vanilla/src/utils/domUtils.ts
index c6d02d6d..9e21bafb 100644
--- a/packages/multiple-select-vanilla/src/utils/domUtils.ts
+++ b/packages/multiple-select-vanilla/src/utils/domUtils.ts
@@ -92,21 +92,21 @@ export function createDomElement {
test('select should use virtual scroll', async ({ page }) => {
await page.goto('#/example10');
- await page.locator('[data-test="select10"].ms-parent').click();
-
- const ulElm = await page.locator('.ms-drop ul');
- const liElms = await page.locator('.ms-drop ul li');
- await expect(liElms.nth(0)).toContainText('0');
- await liElms.nth(0).click();
- await expect(liElms.nth(1)).toContainText('1');
- await liElms.nth(1).click();
+
+ // -- 1st Select
+ await page.locator('[data-test="select10-1"].ms-parent').click();
+
+ const ulElm1 = await page.locator('[data-test="select10-1"] .ms-drop ul');
+ const liElms1 = await page.locator('[data-test="select10-1"] .ms-drop ul li');
+ await expect(liElms1.nth(0)).toContainText('0');
+ await liElms1.nth(0).click();
+ await expect(liElms1.nth(1)).toContainText('1');
+ await liElms1.nth(1).click();
await page.getByRole('button', { name: '0, 1' }).click();
// scroll to the middle and click 5001
- await page.locator('[data-test="select10"].ms-parent').click();
- await ulElm.evaluate((e) => (e.scrollTop = e.scrollHeight / 2));
- await page.locator('label').filter({ hasText: '5001' }).click();
+ await page.locator('[data-test="select10-1"].ms-parent').click();
+ await ulElm1.evaluate((e) => (e.scrollTop = e.scrollHeight / 2));
+ await page.locator('[data-test="select10-1"] .ms-drop label').filter({ hasText: '5001' }).click();
await page.getByRole('button', { name: '0, 1, 5001' });
// scroll to the end and select last 2 labels
- await ulElm.evaluate((e) => (e.scrollTop = e.scrollHeight));
+ await ulElm1.evaluate((e) => (e.scrollTop = e.scrollHeight));
await page.locator('label').filter({ hasText: '9998' }).click();
await page.locator('label').filter({ hasText: '9999' }).click();
await page.getByRole('button', { name: '5 of 10000 selected' });
// filter with text 999 and expect 9998 & 9999 to show up
- await page.getByPlaceholder('🔎︎').click();
- await page.getByPlaceholder('🔎︎').fill('999');
+ await page.getByRole('textbox', { name: '🔎︎' }).fill('999');
await page.locator('label').filter({ hasText: '9998' }).click();
await page.locator('label').filter({ hasText: '9999' }).click();
await page.getByRole('button', { name: '0, 1, 5001' }).click();
// clear filter, scroll back to top and expect 0,1 to still be checked
- await page.locator('[data-test="select10"].ms-parent').click();
- await page.locator('[data-test="select10"] .ms-search .icon-close').click();
- await ulElm.evaluate((e) => (e.scrollTop = 0));
- await expect(liElms.nth(0)).toContainText('0');
- await expect(liElms.nth(1)).toContainText('1');
- expect(await liElms.nth(0).locator('input[type=checkbox][data-key=option_0]').isChecked()).toBeTruthy();
- expect(await liElms.nth(1).locator('input[type=checkbox][data-key=option_1]').isChecked()).toBeTruthy();
- expect(await liElms.nth(2).locator('input[type=checkbox][data-key=option_2]').isChecked()).toBeFalsy();
+ await page.locator('[data-test="select10-1"].ms-parent').click();
+ await page.locator('[data-test="select10-1"] .ms-search .icon-close').click();
+ await ulElm1.evaluate((e) => (e.scrollTop = 0));
+ await expect(liElms1.nth(0)).toContainText('0');
+ await expect(liElms1.nth(1)).toContainText('1');
+ expect(await liElms1.nth(0).locator('input[type=checkbox][data-key=option_0]').isChecked()).toBeTruthy();
+ expect(await liElms1.nth(1).locator('input[type=checkbox][data-key=option_1]').isChecked()).toBeTruthy();
+ expect(await liElms1.nth(2).locator('input[type=checkbox][data-key=option_2]').isChecked()).toBeFalsy();
// scroll back to middle and expect 5001 to still be checked
- await ulElm.evaluate((e) => (e.scrollTop = e.scrollHeight / 2));
+ await ulElm1.evaluate((e) => (e.scrollTop = e.scrollHeight / 2));
expect(await page.locator('label').filter({ hasText: '5001' })).toBeVisible();
- expect(await liElms.locator('input[type=checkbox][data-key=option_5001]').isChecked()).toBeTruthy();
+ expect(await liElms1.locator('input[type=checkbox][data-key=option_5001]').isChecked()).toBeTruthy();
+ await page.locator('[data-test=select10-1].ms-parent').click(); // close drop
+
+ // -- 2nd Select
+ await page.locator('[data-test=select10-2].ms-parent').click();
+ const ulElm2 = await page.locator('[data-test="select10-2"] .ms-drop ul');
+ const liElms2 = await page.locator('[data-test="select10-2"] .ms-drop ul li');
+ await expect(await liElms2.nth(4).locator('span').innerHTML()).toBe(' Task 4');
+ await liElms2.nth(4).click();
+ await expect(await liElms2.nth(5).locator('span').innerHTML()).toBe(' Task 5');
+ await liElms2.nth(5).click();
+ await page.getByRole('button', { name: '4, 5' }).click();
+
+ // scroll to the middle and click 5003
+ await page.locator('[data-test="select10-2"].ms-parent').click();
+ await ulElm2.evaluate((e) => (e.scrollTop = e.scrollHeight / 2));
+ await page.locator('[data-test="select10-2"] .ms-drop label').filter({ hasText: '5003' }).click();
+ await page.getByRole('button', { name: '4, 5, 5003' });
+
+ // scroll to the end and select last 2 labels
+ await ulElm2.evaluate((e) => (e.scrollTop = e.scrollHeight));
+ await expect(await page.locator('[data-test="select10-2"] .ms-drop li[data-key=option_9995] label span').innerHTML()).toBe(
+ ' Task 9995'
+ );
+ await expect(await page.locator('[data-test="select10-2"] .ms-drop li[data-key=option_9996] label span').innerHTML()).toBe(
+ ' Task 9996'
+ );
+ await page.locator('[data-test="select10-2"] .ms-drop label').filter({ hasText: '9995' }).click();
+ await page.locator('[data-test="select10-2"] .ms-drop label').filter({ hasText: '9996' }).click();
+ await page.getByRole('button', { name: '5 of 10000 selected' });
+
+ // filter with text 999 and expect 9995 & 9996 to show up
+ await page.getByRole('textbox', { name: '🔎︎' }).fill('999');
+ await page.locator('[data-test="select10-2"] .ms-drop label').filter({ hasText: '9995' }).click();
+ await page.locator('[data-test="select10-2"] .ms-drop label').filter({ hasText: '9996' }).click();
+ await page.getByRole('button', { name: '4, 5, 5003' }).click();
+
+ // clear filter, scroll back to top and expect 0,1 to still be checked
+ await page.locator('[data-test="select10-2"].ms-parent').click();
+ await page.locator('[data-test="select10-2"] .ms-search .icon-close').click();
+ await ulElm2.evaluate((e) => (e.scrollTop = 0));
+ await expect(await liElms2.nth(4).locator('span').innerHTML()).toBe(' Task 4');
+ await expect(await liElms2.nth(5).locator('span').innerHTML()).toBe(' Task 5');
+ expect(await liElms2.nth(4).locator('input[type=checkbox][data-key=option_4]').isChecked()).toBeTruthy();
+ expect(await liElms2.nth(5).locator('input[type=checkbox][data-key=option_5]').isChecked()).toBeTruthy();
+ expect(await liElms2.nth(6).locator('input[type=checkbox][data-key=option_6]').isChecked()).toBeFalsy();
+
+ // scroll back to middle and expect 5003 to still be checked
+ await ulElm2.evaluate((e) => (e.scrollTop = e.scrollHeight / 2));
+ expect(await page.locator('[data-test="select10-2"] .ms-drop label').filter({ hasText: '5003' })).toBeVisible();
+ expect(await liElms2.locator('input[type=checkbox][data-key=option_5003]').isChecked()).toBeTruthy();
+ await page.locator('[data-test=select10-2].ms-parent').click(); // close drop
});
});