Skip to content

Commit 8ef00a2

Browse files
authored
Merge pull request #4862 from m3ftwz/fix-multiselect-list
Fix multiselect list editor duplicate selections.
2 parents 3d6cd08 + 4abd01b commit 8ef00a2

2 files changed

Lines changed: 118 additions & 3 deletions

File tree

src/js/modules/Edit/List.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,11 @@ export default class Edit{
410410
this._resolveValue(true);
411411
}else{
412412
if(this.focusedItem){
413-
this._chooseItem(this.focusedItem);
413+
if(this.isFilter && !this.params.multiselect && this.focusedItem.selected){
414+
this._resolveValue();
415+
}else{
416+
this._chooseItem(this.focusedItem);
417+
}
414418
}
415419
}
416420
}
@@ -655,6 +659,10 @@ export default class Edit{
655659
this.lastAction = "typing";
656660
}
657661

662+
if(this.params.multiselect) {
663+
this.initialValues = null;
664+
}
665+
658666
this.data = data;
659667

660668
return data;
@@ -678,7 +686,19 @@ export default class Edit{
678686
original:option,
679687
};
680688

681-
if(this.initialValues && this.initialValues.indexOf(option.value) > -1){
689+
if(this.params.multiselect){
690+
var existingIndex = this.currentItems.findIndex(existing => existing.value === option.value);
691+
if(existingIndex > -1){
692+
if(this.focusedItem === this.currentItems[existingIndex]){
693+
this.focusedItem = item;
694+
}
695+
696+
this.currentItems[existingIndex] = item;
697+
item.selected = true;
698+
}else if(this.initialValues && this.initialValues.indexOf(option.value) > -1){
699+
this._chooseItem(item, true);
700+
}
701+
}else if(this.initialValues && this.initialValues.indexOf(option.value) > -1){
682702
this._chooseItem(item, true);
683703
}
684704
}
@@ -997,6 +1017,12 @@ export default class Edit{
9971017
this._styleItem(item);
9981018

9991019
}else{
1020+
if(this.isFilter && !silent && item.selected){
1021+
this._clearChoices();
1022+
this.input.value = "";
1023+
this._resolveValue();
1024+
return;
1025+
}
10001026
this.currentItems = [item];
10011027
item.selected = true;
10021028

@@ -1032,6 +1058,8 @@ export default class Edit{
10321058
}else{
10331059
if(this.currentItems[0]){
10341060
output = this.currentItems[0].value;
1061+
}else if(this.isFilter && this.focusedItem && this.focusedItem.selected){
1062+
output = this.focusedItem.value;
10351063
}else{
10361064
initialValue = Array.isArray(this.initialValues) ? this.initialValues[0] : this.initialValues;
10371065

test/unit/modules/Edit.spec.js

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import TabulatorFull from '../../../src/js/core/TabulatorFull.js';
22
import Edit from '../../../src/js/modules/Edit/Edit.js';
3+
import List from '../../../src/js/modules/Edit/List.js';
34
import { DateTime } from "luxon";
45

56
describe("Edit module", () => {
@@ -34,7 +35,38 @@ describe("Edit module", () => {
3435
return newTable;
3536
};
3637

38+
const setupList = async ({
39+
cellType = "header",
40+
cellValue = "Female",
41+
editorParams = { values: ["Male", "Female"] },
42+
success = jest.fn(),
43+
cancel = jest.fn(),
44+
} = {}) => {
45+
const element = document.createElement("div");
46+
const list = new List(
47+
{
48+
table: {},
49+
},
50+
{
51+
getType: jest.fn().mockReturnValue(cellType),
52+
getValue: jest.fn().mockReturnValue(cellValue),
53+
getElement: jest.fn().mockReturnValue(element),
54+
},
55+
jest.fn(),
56+
success,
57+
cancel,
58+
editorParams
59+
);
60+
61+
list.input.dispatchEvent(new Event("focus"));
62+
await new Promise(resolve => setTimeout(resolve, 0));
63+
64+
return {list, success, cancel, element};
65+
};
66+
3767
beforeEach(async () => {
68+
// jsdom does not implement scrollIntoView, which the list focus uses.
69+
Element.prototype.scrollIntoView = jest.fn();
3870
table = await setupTable();
3971
});
4072

@@ -126,6 +158,61 @@ describe("Edit module", () => {
126158
// Should have received the cellEdited event
127159
expect(cellEditedSpy).toHaveBeenCalled();
128160
});
161+
162+
it("should apply a focused header list filter item when Enter is pressed", async () => {
163+
const {list, success} = await setupList({
164+
cellValue: "",
165+
});
166+
167+
// Navigate down to "Female" (the second option) and apply it.
168+
list.listEl.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }));
169+
list.listEl.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }));
170+
list.listEl.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
171+
172+
expect(success).toHaveBeenCalledWith("Female");
173+
expect(list.input.value).toBe("Female");
174+
});
175+
176+
it("should keep a header list filter selected when Enter is pressed repeatedly", async () => {
177+
const {list, success} = await setupList();
178+
179+
list.listEl.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
180+
list.listEl.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
181+
182+
expect(success).toHaveBeenCalledTimes(2);
183+
expect(success).toHaveBeenNthCalledWith(1, "Female");
184+
expect(success).toHaveBeenNthCalledWith(2, "Female");
185+
});
186+
187+
it("should use currentItems instead of initialValues after the first multiselect parse", async () => {
188+
const {list} = await setupList({
189+
cellType: "cell",
190+
cellValue: ["red", "blue"],
191+
editorParams: {
192+
multiselect: true,
193+
values: ["red", "green", "blue"],
194+
},
195+
});
196+
197+
expect(list.initialValues).toBeNull();
198+
expect(list.currentItems.map(item => item.value)).toEqual(["red", "blue"]);
199+
200+
// Deselect "red" by clicking its rendered list item.
201+
list.data.find(item => item.value === "red").element.dispatchEvent(new MouseEvent("click", { bubbles: true }));
202+
203+
expect(list.currentItems.map(item => item.value)).toEqual(["blue"]);
204+
205+
list.input.dispatchEvent(new Event("focus"));
206+
await new Promise(resolve => setTimeout(resolve, 0));
207+
208+
const redItem = list.data.find(item => item.value === "red");
209+
const blueItem = list.data.find(item => item.value === "blue");
210+
211+
expect(list.initialValues).toBeNull();
212+
expect(redItem.selected).toBe(false);
213+
expect(blueItem.selected).toBe(true);
214+
expect(list.currentItems).toEqual([blueItem]);
215+
});
129216

130217
// Regression test for https://github.com/tabulator-tables/tabulator/issues/4421
131218
//
@@ -268,4 +355,4 @@ describe("Edit module - date editors with 'x' (epoch milliseconds) format", () =
268355

269356
expect(typeof cell.getValue()).toBe("number");
270357
});
271-
});
358+
});

0 commit comments

Comments
 (0)