diff --git a/packages/demo/src/app-routing.ts b/packages/demo/src/app-routing.ts
index 0549c194..b86f8d3d 100644
--- a/packages/demo/src/app-routing.ts
+++ b/packages/demo/src/app-routing.ts
@@ -69,6 +69,7 @@ import Options37 from './options/options37.js';
import Options38 from './options/options38.js';
import Options39 from './options/options39.js';
import Options40 from './options/options40.js';
+import Options41 from './options/options41.js';
export const navbarRouting = [
{ name: 'getting-started', view: '/src/getting-started.html', viewModel: GettingStarted, title: 'Getting Started' },
@@ -139,7 +140,8 @@ export const exampleRouting = [
{ name: 'options37', view: '/src/options/options37.html', viewModel: Options37, title: 'Navigation Highlight' },
{ name: 'options38', view: '/src/options/options38.html', viewModel: Options38, title: 'Dark Mode' },
{ name: 'options39', view: '/src/options/options39.html', viewModel: Options39, title: 'Label Id (aria-labelledby)' },
- { name: 'options40', view: '/src/options/options40.html', viewModel: Options40, title: 'Filter Data' },
+ { name: 'options40', view: '/src/options/options40.html', viewModel: Options40, title: 'Pre-Filter Data' },
+ { name: 'options41', view: '/src/options/options41.html', viewModel: Options41, title: 'Pre-Sort Data' },
],
},
{
diff --git a/packages/demo/src/options/options40.html b/packages/demo/src/options/options40.html
index f325bf7a..c17c35b4 100644
--- a/packages/demo/src/options/options40.html
+++ b/packages/demo/src/options/options40.html
@@ -1,7 +1,7 @@
- Filter Data
+ Pre-Filter Data
Code
@@ -17,7 +17,7 @@
-
Use preFilter(predicate)
to pre-filter the data collection before rendering the select dropdown in the UI.
+
Use preFilter: predicate
to pre-filter the data collection before rendering the select dropdown in the UI.
diff --git a/packages/demo/src/options/options41.html b/packages/demo/src/options/options41.html
new file mode 100644
index 00000000..18c2dc2b
--- /dev/null
+++ b/packages/demo/src/options/options41.html
@@ -0,0 +1,87 @@
+
+
+
+ Pre-Sort Data
+
+ Code
+
+ html
+ |
+ ts
+
+
+
+
Use preSort: comparer
to pre-sort the data collection before rendering the select dropdown in the UI.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/demo/src/options/options41.ts b/packages/demo/src/options/options41.ts
new file mode 100644
index 00000000..ffb70ddc
--- /dev/null
+++ b/packages/demo/src/options/options41.ts
@@ -0,0 +1,153 @@
+import { type MultipleSelectInstance, type OptGroupRowData, multipleSelect } from 'multiple-select-vanilla';
+
+export default class Example {
+ ms1?: MultipleSelectInstance;
+ ms2?: MultipleSelectInstance;
+ ms3?: MultipleSelectInstance;
+ ms4?: MultipleSelectInstance;
+
+ mount() {
+ this.ms1 = multipleSelect('select[data-test=select1]', {
+ filter: true,
+ preSort: (item1, item2) => {
+ // sort by value in reverse order
+ const direction = -1; // reverse order
+ return (+item1.value! - +item2.value!) * direction;
+ },
+ }) as MultipleSelectInstance;
+
+ this.ms2 = multipleSelect('select[data-test=select2]', {
+ filter: true,
+ preSort: (item1, item2) => {
+ // sort by value in reverse order
+ const direction = -1; // reverse order
+ // @ts-ignore
+ if (direction === 1) {
+ return (item1 as OptGroupRowData).label < (item2 as OptGroupRowData).label ? -1 : 1;
+ }
+ return (item1 as OptGroupRowData).label < (item2 as OptGroupRowData).label ? 1 : -1;
+ },
+ }) as MultipleSelectInstance;
+
+ this.ms3 = multipleSelect('select[data-test=select3]', {
+ data: {
+ 1: 'January',
+ 2: 'February',
+ 3: 'March',
+ 4: 'April',
+ 5: 'May',
+ 6: 'June',
+ 7: 'July',
+ 8: 'August',
+ 9: 'September',
+ 10: 'October',
+ 11: 'November',
+ 12: 'December',
+ },
+ preSort: (item1, item2) => {
+ // sort by value in reverse order
+ const direction = -1; // reverse order
+ return (+item1.value! - +item2.value!) * direction;
+ },
+ }) as MultipleSelectInstance;
+
+ this.ms4 = multipleSelect('select[data-test=select4]', {
+ data: [
+ {
+ type: 'optgroup',
+ label: 'Q1',
+ children: [
+ {
+ text: 'January',
+ value: 1,
+ selected: true,
+ },
+ {
+ text: 'February',
+ value: 2,
+ },
+ {
+ text: 'March',
+ value: 3,
+ },
+ ],
+ },
+ {
+ type: 'optgroup',
+ label: 'Q2',
+ children: [
+ {
+ text: 'April',
+ value: 4,
+ },
+ {
+ text: 'May',
+ value: 5,
+ },
+ {
+ text: 'June',
+ value: 6,
+ },
+ ],
+ },
+ {
+ type: 'optgroup',
+ label: 'Q3',
+ children: [
+ {
+ text: 'July',
+ value: 7,
+ },
+ {
+ text: 'August',
+ value: 8,
+ },
+ {
+ text: 'September',
+ value: 9,
+ },
+ ],
+ },
+ {
+ type: 'optgroup',
+ label: 'Q4',
+ children: [
+ {
+ text: 'October',
+ value: 10,
+ },
+ {
+ text: 'November',
+ value: 11,
+ },
+ {
+ text: 'December',
+ value: 12,
+ },
+ ],
+ },
+ ],
+ preSort: (item1, item2) => {
+ // sort by value in reverse order
+ const direction = -1; // reverse order
+ // @ts-ignore
+ if (direction === 1) {
+ return (item1 as OptGroupRowData).label < (item2 as OptGroupRowData).label ? -1 : 1;
+ }
+ return (item1 as OptGroupRowData).label < (item2 as OptGroupRowData).label ? 1 : -1;
+ },
+ }) as MultipleSelectInstance;
+ }
+
+ unmount() {
+ // destroy ms instance(s) to avoid DOM leaks
+ this.ms1?.destroy();
+ this.ms2?.destroy();
+ this.ms3?.destroy();
+ this.ms4?.destroy();
+ this.ms1 = undefined;
+ this.ms2 = undefined;
+ this.ms3 = undefined;
+ this.ms4 = undefined;
+ }
+}
diff --git a/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts b/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts
index d449976e..e23b8393 100644
--- a/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts
+++ b/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts
@@ -316,11 +316,16 @@ export class MultipleSelectInstance {
this.fromHtml = true;
}
- // user might filter its data prior to rendering the list
+ // user might pre-filter its data prior to rendering the list
if (this.data && this.options.preFilter) {
this.data = this.data.filter(this.options.preFilter);
}
+ // user might pre-sort its data prior to rendering the list
+ if (this.data && this.options.preSort) {
+ this.data = this.data.sort(this.options.preSort);
+ }
+
this.dataTotal = setDataKeys(this.data || []);
}
diff --git a/packages/multiple-select-vanilla/src/models/multipleSelectOption.interface.ts b/packages/multiple-select-vanilla/src/models/multipleSelectOption.interface.ts
index 50425612..4233228f 100644
--- a/packages/multiple-select-vanilla/src/models/multipleSelectOption.interface.ts
+++ b/packages/multiple-select-vanilla/src/models/multipleSelectOption.interface.ts
@@ -169,9 +169,18 @@ export interface MultipleSelectOption extends MultipleSelectLocale {
/** Defines the position of select dropdown, can only be bottom or top. By default this option is set to bottom. */
position: 'bottom' | 'top';
- /** Custom Filter predicate function to pre-filter the data collection before rendering the select dropdown. Executed before `preSort`. */
+ /**
+ * Custom Filter predicate function callback to pre-filter the data collection before rendering the select dropdown.
+ * Note that this is executed before `preSort` (when defined).
+ */
preFilter?: (dataItem: OptionRowData | OptGroupRowData) => boolean;
+ /**
+ * Custom Sort Comparer function callback to pre-sort the data collection before rendering the select dropdown.
+ * Note that this is executed after `preSort` (when defined).
+ */
+ preSort?: (item1: OptionRowData | OptGroupRowData, item2: OptionRowData | OptGroupRowData) => number;
+
/** Defaults to False, should we render option labels as html? */
renderOptionLabelAsHtml?: boolean;
diff --git a/playwright/e2e/options40.spec.ts b/playwright/e2e/options40.spec.ts
index 2b449230..119fc533 100644
--- a/playwright/e2e/options40.spec.ts
+++ b/playwright/e2e/options40.spec.ts
@@ -1,7 +1,7 @@
import { test, expect } from '@playwright/test';
-test.describe('Options 40 - Filter Data', () => {
- test('all select dropdown should have data filtered out', async ({ page }) => {
+test.describe('Options 40 - Pre-Filter Data', () => {
+ test('all select dropdown should have data pre-filtered', async ({ page }) => {
await page.goto('#/options40');
// 1st select
@@ -35,7 +35,7 @@ test.describe('Options 40 - Filter Data', () => {
await expect(await page.getByRole('option', { name: 'March' })).toHaveCount(1);
await expect(await page.getByRole('option', { name: 'April' })).toHaveCount(0);
await expect(await page.getByRole('option', { name: 'May' })).toHaveCount(1);
- const select3LiElms = await page.locator('div[data-test=select1] li[role="option"]');
+ const select3LiElms = await page.locator('div[data-test=select3] li[role="option"]');
await expect(select3LiElms).toHaveCount(10);
await page.locator('div[data-test=select3].ms-parent').click();
diff --git a/playwright/e2e/options41.spec.ts b/playwright/e2e/options41.spec.ts
new file mode 100644
index 00000000..dc7014fb
--- /dev/null
+++ b/playwright/e2e/options41.spec.ts
@@ -0,0 +1,51 @@
+import { test, expect } from '@playwright/test';
+
+test.describe('Options 41 - Pre-Sort Data', () => {
+ test('all select dropdown should have data pre-filtered', async ({ page }) => {
+ await page.goto('#/options41');
+
+ // 1st select
+ await page.locator('div[data-test=select1].ms-parent').click();
+ const select1LiElms = await page.locator('div[data-test=select1] li[role="option"]');
+ await expect(select1LiElms).toHaveCount(12);
+ const select1li1 = await page.locator('div[data-test=select1] .ms-drop li').nth(0);
+ await expect(await select1li1.locator('label')).toHaveText('December');
+ const select1li2 = await page.locator('div[data-test=select1] .ms-drop li').nth(1);
+ await expect(await select1li2.locator('label')).toHaveText('November');
+ const select1li3 = await page.locator('div[data-test=select1] .ms-drop li').nth(2);
+ await expect(await select1li3.locator('label')).toHaveText('October');
+ await page.locator('div[data-test=select1].ms-parent').click();
+
+ // 2nd select
+ await page.locator('div[data-test=select2].ms-parent').click();
+ const select2GroupElms = await page.locator('div[data-test=select2] li.group');
+ const select2li1 = await page.locator('div[data-test=select2] .ms-drop li.group').nth(0);
+ await expect(await select2li1.locator('label.optgroup')).toHaveText('Group 3');
+ const select2li2 = await page.locator('div[data-test=select2] .ms-drop li.group').nth(1);
+ await expect(await select2li2.locator('label.optgroup')).toHaveText('Group 2');
+ await expect(select2GroupElms).toHaveCount(3);
+ await page.locator('div[data-test=select2].ms-parent').click();
+
+ // 3rd select
+ await page.locator('div[data-test=select3].ms-parent').click();
+ const select3LiElms = await page.locator('div[data-test=select3] li[role="option"]');
+ await expect(select3LiElms).toHaveCount(12);
+ const select3li1 = await page.locator('div[data-test=select3] .ms-drop li').nth(0);
+ await expect(await select3li1.locator('label')).toHaveText('December');
+ const select3li2 = await page.locator('div[data-test=select3] .ms-drop li').nth(1);
+ await expect(await select3li2.locator('label')).toHaveText('November');
+ const select3li3 = await page.locator('div[data-test=select3] .ms-drop li').nth(2);
+ await expect(await select3li3.locator('label')).toHaveText('October');
+ await page.locator('div[data-test=select3].ms-parent').click();
+
+ // 4th select
+ await page.locator('div[data-test=select4].ms-parent').click();
+ const select4GroupElms = await page.locator('div[data-test=select4] li.group');
+ const select4li1 = await page.locator('div[data-test=select4] .ms-drop li.group').nth(0);
+ await expect(await select4li1.locator('label.optgroup')).toHaveText('Q4');
+ const select4li2 = await page.locator('div[data-test=select4] .ms-drop li.group').nth(1);
+ await expect(await select4li2.locator('label.optgroup')).toHaveText('Q3');
+ await expect(select4GroupElms).toHaveCount(4);
+ await page.locator('div[data-test=select4].ms-parent').click();
+ });
+});