diff --git a/doc_src/pages/docs/api.md b/doc_src/pages/docs/api.md index ffbffd32..4bbab749 100644 --- a/doc_src/pages/docs/api.md +++ b/doc_src/pages/docs/api.md @@ -43,7 +43,7 @@ control.addItem('test'); Retrieves the dom element for the option identified by the given value. - getAdjacent(dom_element, direction) + getAdjacent(HTMLElement, direction) Retrieves the dom element for the previous or next option, relative to the currently highlighted option. The direction argument should be 1 for "next" or -1 for "previous". @@ -73,7 +73,7 @@ control.addItem('test'); "Selects" an item. Adds it to the list at the current caret position. If silent is truthy, no change event will be fired on the original input. - removeItem(value, silent) + removeItem(value|HTMLElement, silent) Removes the selected item matching the provided value. If silent is truthy, no change event will be fired on the original input. diff --git a/src/tom-select.ts b/src/tom-select.ts index 4cf52336..de7f02cc 100644 --- a/src/tom-select.ts +++ b/src/tom-select.ts @@ -4,7 +4,7 @@ import MicroPlugin from './contrib/microplugin.js'; import Sifter from '@orchidjs/sifter/dist/esm/sifter.js'; import { escape_regex } from '@orchidjs/sifter/dist/esm/utils.js'; import { TomSettings } from './types/settings'; -import { TomInput, TomArgObject, TomOption, TomOptions, TomCreateFilter, TomCreateCallback } from './types/index'; +import { TomInput, TomArgObject, TomOption, TomOptions, TomCreateFilter, TomCreateCallback, TomItem } from './types/index'; import {highlight, removeHighlight} from './contrib/highlight.js'; import * as constants from './constants.js'; import getSettings from './getSettings.js'; @@ -77,7 +77,7 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ public loadedSearches : { [key: string]: boolean } = {}; public activeOption : null|HTMLElement = null; - public activeItems : HTMLElement[] = []; + public activeItems : TomItem[] = []; public optgroups : TomOptions = {}; public options : TomOptions = {}; @@ -296,7 +296,7 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ addEvent(control,'click', (evt) => { var target_match = parentMatch( evt.target as HTMLElement, '.'+self.settings.itemClass, control); - if( target_match && self.onItemSelect(evt as MouseEvent, target_match) ){ + if( target_match && self.onItemSelect(evt as MouseEvent, target_match as TomItem) ){ preventDefault(evt,true); return; } @@ -822,7 +822,7 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ * that has been selected. * */ - onItemSelect( evt?:MouseEvent, item?:HTMLElement ):boolean{ + onItemSelect( evt?:MouseEvent, item?:TomItem ):boolean{ var self = this; if( !self.isLocked && self.settings.mode === 'multi' ){ @@ -953,7 +953,7 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ * Sets the selected item. * */ - setActiveItem( item?:HTMLElement, e?:MouseEvent|KeyboardEvent ){ + setActiveItem( item?:TomItem, e?:MouseEvent|KeyboardEvent ){ var self = this; var eventName; var i, begin, end, swap; @@ -984,7 +984,7 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ end = swap; } for (i = begin; i <= end; i++) { - item = self.control.children[i] as HTMLElement; + item = self.control.children[i] as TomItem; if (self.activeItems.indexOf(item) === -1) { self.setActiveItemClass(item); } @@ -1012,7 +1012,7 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ * Set the active and last-active classes * */ - setActiveItemClass( item:HTMLElement ){ + setActiveItemClass( item:TomItem ){ var last_active = this.control.querySelector('.last-active'); if( last_active ) removeClasses(last_active as HTMLElement,'last-active'); @@ -1027,7 +1027,7 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ * Remove active item * */ - removeActiveItem( item:HTMLElement ){ + removeActiveItem( item:TomItem ){ var idx = this.activeItems.indexOf(item); this.activeItems.splice(idx, 1); removeClasses(item,'active'); @@ -1736,10 +1736,15 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ * matching the given value. * */ - getItem(value:string):null|HTMLElement { - var hashed = hash_key(value); - return hashed - ? this.control.querySelector(`[data-value="${addSlashes(hashed)}"]`) + getItem(item:string|TomItem|null):null|TomItem { + + if( typeof item == 'object' ){ + return item; + } + + var value = hash_key(item); + return value + ? this.control.querySelector(`[data-value="${addSlashes(value)}"]`) : null; } @@ -1852,43 +1857,38 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ * the provided value. * */ - removeItem( value?:string, silent?:boolean ){ - var i, idx; - + removeItem( item:string|TomItem|null=null, silent?:boolean ){ const self = this; - const hashed = hash_key(value); - - if( !hashed ) return; - - const item = self.getItem(hashed); + item = self.getItem(item); if( !item ) return; - i = self.items.indexOf(hashed); - - if (i !== -1) { - item.remove(); - if( item.classList.contains('active') ){ - idx = self.activeItems.indexOf(item); - self.activeItems.splice(idx, 1); - removeClasses(item,'active'); - } + var i,idx; + const value = item.dataset.value; + i = nodeIndex(item); - self.items.splice(i, 1); - self.lastQuery = null; - if (!self.settings.persist && self.userOptions.hasOwnProperty(hashed)) { - self.removeOption(hashed, silent); - } + item.remove(); + if( item.classList.contains('active') ){ + idx = self.activeItems.indexOf(item); + self.activeItems.splice(idx, 1); + removeClasses(item,'active'); + } - if (i < self.caretPos) { - self.setCaret(self.caretPos - 1); - } + self.items.splice(i, 1); + self.lastQuery = null; + if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) { + self.removeOption(value, silent); + } - self.updateOriginalInput({silent: silent}); - self.refreshState(); - self.positionDropdown(); - self.trigger('item_remove', hashed, item); + if (i < self.caretPos) { + self.setCaret(self.caretPos - 1); } + + self.updateOriginalInput({silent: silent}); + self.refreshState(); + self.positionDropdown(); + self.trigger('item_remove', value, item); + } /** @@ -2222,7 +2222,7 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ * */ deleteSelection(e:KeyboardEvent):boolean { - var direction, selection, values, caret, tail; + var direction, selection, caret, tail; var self = this; direction = (e && e.keyCode === constants.KEY_BACKSPACE) ? -1 : 1; @@ -2230,7 +2230,7 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ // determine items that will be removed - values = []; + const rm_items:TomItem[] = []; if (self.activeItems.length) { @@ -2240,17 +2240,21 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ if (direction > 0) { caret++; } for( const item of self.activeItems ){ - values.push( item.dataset.value ); + rm_items.push( item ); } } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) { + const items = self.controlChildren(); if (direction < 0 && selection.start === 0 && selection.length === 0) { - values.push(self.items[self.caretPos - 1]); + rm_items.push( items[self.caretPos - 1]); + } else if (direction > 0 && selection.start === self.inputValue().length) { - values.push(self.items[self.caretPos]); + rm_items.push(items[self.caretPos]); } } + const values = rm_items.map(item => item.dataset.value); + // allow the callback to abort if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.call(self,values,e) === false)) { return false; @@ -2262,8 +2266,9 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ if (typeof caret !== 'undefined') { self.setCaret(caret); } - while (values.length) { - self.removeItem(values.pop()); + + while( rm_items.length ){ + self.removeItem(rm_items.pop()); } self.showInput(); @@ -2386,8 +2391,8 @@ export default class TomSelect extends MicroPlugin(MicroEvent){ * Return list of item dom elements * */ - controlChildren():HTMLElement[]{ - return Array.from( this.control.getElementsByClassName(this.settings.itemClass) ) as HTMLElement[]; + controlChildren():TomItem[]{ + return Array.from( this.control.getElementsByClassName(this.settings.itemClass) ) as TomItem[]; } /** diff --git a/src/types/index.d.ts b/src/types/index.d.ts index a2d246f1..2a18be3a 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -18,3 +18,9 @@ export type TomCreateFilter = (input:string) => boolean; export type TomCreateCallback = (data?:TomOption)=>void; export type TomCreate = (input:string,create:TomCreateCallback) => boolean; + +export interface TomItem extends HTMLElement{ + dataset:{ + value: string; + } +} diff --git a/test/tests/config-duplicates.js b/test/tests/config-duplicates.js index 63e78252..75f48264 100644 --- a/test/tests/config-duplicates.js +++ b/test/tests/config-duplicates.js @@ -16,13 +16,18 @@ describe('duplicates', function() { assert.equal(test.instance.items.length,3,'should have all three'); - // remove - test.instance.removeItem('a'); - assert.equal(test.instance.items.length,2,'should remove first'); - test.instance.removeItem('a'); - assert.equal(test.instance.items.length,1,'should remove second'); - test.instance.removeItem('a'); - assert.equal(0,test.instance.items.length,0,'should remove last'); + + + // remove items in order + const items = test.instance.controlChildren(); + await asyncClick(test.instance.control_input); + + while( items.length ){ + items.pop(); + await asyncType('\b',test.instance.control_input); + const items_after = test.instance.controlChildren(); + assert.deepEqual( items, items_after); + } });