Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
30253a3
added option for selecting an optgroup
lkitzberger May 26, 2025
ca72405
deactivated options of a group should not be added when selecting a g…
lkitzberger May 26, 2025
50b5bde
Fixed: docment.mousedown event prevented default als fallback. If ano…
lkitzberger May 26, 2025
71a919f
dropdown would open on mousedown und close after mouseup(click).
lkitzberger May 26, 2025
92a447c
- do not preventDefault from document-mousedown-event when the target…
lkitzberger May 26, 2025
0ece2c3
- onBlur should not consider the option focusInputOnOpen
lkitzberger May 26, 2025
c2fcb1f
Scroll to active Option on dropdown open.
lkitzberger May 26, 2025
d5d0a5f
fixed doc_mousedown event handling when clicked on slotted element in…
lkitzberger May 26, 2025
d95913b
fixed change of scroll position on option select by setting an except…
lkitzberger May 26, 2025
7d5aa98
changed type of searchField options
lkitzberger May 26, 2025
7267561
removed old placeholder logic
lkitzberger May 26, 2025
84a883f
- drag_drop plugin: added plugin option for setting a drag&drop icon
lkitzberger May 26, 2025
9102e61
- single select: order of native options should not be changed, unles…
lkitzberger May 26, 2025
5275ce3
added touch support for drag&drop plugin.
lkitzberger May 26, 2025
a7130a0
drag_drop: order change with arrow keys should set active item
lkitzberger May 26, 2025
c856e24
blur should not be called if drag_drop plugin is active, so that the …
lkitzberger May 26, 2025
b215ecb
change addEvent method to listener
lkitzberger May 26, 2025
3e4af3d
screen reader should announce focused option even if dropdown_input i…
lkitzberger May 26, 2025
9cbae8f
screen reader should give information about opened select, even if dr…
lkitzberger May 26, 2025
b9b3ddb
add ID to optgroup-header
lkitzberger May 26, 2025
f7dfb68
removed restore_on_backspace changes
lkitzberger May 27, 2025
ffcb768
order of existing items is not changed on select - adjust tests accor…
lkitzberger May 27, 2025
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
2 changes: 2 additions & 0 deletions src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export default {
loadThrottle: 300,
loadingClass: 'loading',

allowOptgroupSelection: false,

dataAttr: null, //'data-data',
optgroupField: 'optgroup',
valueField: 'value',
Expand Down
95 changes: 70 additions & 25 deletions src/plugins/drag_drop/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@

import type TomSelect from '../../tom-select.ts';
import { TomOption, TomItem } from '../../types/index.ts';
import { escape_html, preventDefault, addEvent } from '../../utils.ts';
import { escape_html, preventDefault } from '../../utils.ts';
import { getDom, setAttr } from '../../vanilla.ts';
import {DIOptions} from './types.ts';


const insertAfter = (referenceNode:Element, newNode:Element) => {
Expand All @@ -41,25 +42,42 @@ const isBefore = (referenceNode:Element|undefined|null, newNode:Element|undefine
return false;
}

export default function(this:TomSelect) {
export default function(this:TomSelect, options?: DIOptions) {
var self = this;
if (self.settings.mode !== 'multi') return;

var orig_lock = self.lock;
var orig_unlock = self.unlock;
let sortable = true;
let drag_item:TomItem|undefined;
let drag_item:TomItem|undefined;

const updateValuesByItemsOrder = () => {
const values:string[] = [];
self.control.querySelectorAll(`[data-value]`).forEach((el:Element)=> {
if( (<HTMLOptionElement>el).dataset.value ){
let value = (<HTMLOptionElement>el).dataset.value;
if( value ){
values.push(value);
}
}
});

self.setValue(values);
}

/**
* Add draggable attribute to item
*/
self.hook('after','setupTemplates',() => {

var orig_render_item = self.settings.render.item;

self.settings.render.item = (data:TomOption, escape:typeof escape_html) => {
const item = getDom(orig_render_item.call(self, data, escape)) as TomItem;

if (options?.dragDropIcon) {
item.insertAdjacentHTML('afterbegin', options.dragDropIcon);
}

setAttr(item,{'draggable':'true'});


Expand All @@ -69,17 +87,24 @@ export default function(this:TomSelect) {
evt.stopPropagation();
}

const dragStart = (evt:Event) => {
const dragStart = (evt: DragEvent) => {
drag_item = item;
if (evt.dataTransfer) {
evt.dataTransfer.effectAllowed = 'move';
evt.dataTransfer.setData("text", data.value)
}

setTimeout(() => {
item.classList.add('ts-dragging');
}, 0);

}

const dragOver = (evt:Event) =>{
const dragOver = (evt: DragEvent) =>{
evt.preventDefault();
if (evt.dataTransfer) {
evt.dataTransfer.dropEffect = 'move';
}
item.classList.add('ts-drag-over');
moveitem(item,drag_item);
}
Expand All @@ -103,27 +128,16 @@ export default function(this:TomSelect) {
drag_item?.classList.remove('ts-dragging');
drag_item = undefined;

var values:string[] = [];
self.control.querySelectorAll(`[data-value]`).forEach((el:Element)=> {
if( (<HTMLOptionElement>el).dataset.value ){
let value = (<HTMLOptionElement>el).dataset.value;
if( value ){
values.push(value);
}
}
});

self.setValue(values);
}
updateValuesByItemsOrder();
}

item.addEventListener('mousedown', mousedown);
item.addEventListener('dragstart', dragStart);
item.addEventListener('dragenter', dragOver)
item.addEventListener('dragover', dragOver);
item.addEventListener('dragleave', dragLeave);
item.addEventListener('dragend', dragend);

addEvent(item,'mousedown', mousedown);
addEvent(item,'dragstart', dragStart);
addEvent(item,'dragenter', dragOver)
addEvent(item,'dragover', dragOver);
addEvent(item,'dragleave', dragLeave);
addEvent(item,'dragend', dragend);

return item;
}
});
Expand All @@ -140,4 +154,35 @@ export default function(this:TomSelect) {
return orig_unlock.call(self);
});

self.on('initialize', () => {
// prevent browser from default drop action
self.control.addEventListener('drop', evt => evt.preventDefault());

self.control.addEventListener('keydown', (evt: KeyboardEvent) => {
const activeItem: TomItem | undefined = self.activeItems[0];
let targetItem: Element | null = null;

if (activeItem) {
if (evt.key === 'ArrowLeft') {
targetItem = activeItem.previousElementSibling;
} else if (evt.key === 'ArrowRight') {
targetItem = activeItem.nextElementSibling;
}

if (targetItem && targetItem.hasAttribute('draggable')) {
if (isBefore(activeItem, targetItem)) {
insertAfter(targetItem, activeItem);
} else {
insertBefore(targetItem, activeItem);
}

updateValuesByItemsOrder();

const dragItemDataValue = activeItem.getAttribute('data-value');
const currentActiveItem = self.control.querySelector(`[data-value="${dragItemDataValue}"]`);
self.setActiveItem(currentActiveItem as TomItem);
}
}
});
});
};
3 changes: 3 additions & 0 deletions src/plugins/drag_drop/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type DIOptions = {
dragDropIcon?: string;
}
11 changes: 11 additions & 0 deletions src/plugins/dropdown_input/plugin.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
.plugin-dropdown_input{
&.has-items {
&.hide-placeholder {
.#{$select-ns}-control {
> input {
opacity: 0;
position: absolute;
left: -10000px;
}
}
}
}

&.focus.dropdown-active .#{$select-ns}-control{
box-shadow: none;
Expand Down
39 changes: 32 additions & 7 deletions src/plugins/dropdown_input/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@

import type TomSelect from '../../tom-select.ts';
import * as constants from '../../constants.ts';
import { getDom, addClasses } from '../../vanilla.ts';
import { addEvent, preventDefault } from '../../utils.ts';
import {getDom, addClasses, setAttr} from '../../vanilla.ts';
import {preventDefault} from '../../utils.ts';
import {DIOptions} from './types.ts';


export default function(this:TomSelect) {
export default function(this:TomSelect, options?: DIOptions) {
const self = this;

self.settings.shouldOpen = true; // make sure the input is shown even if there are no options to display in the dropdown
Expand All @@ -37,11 +38,21 @@ export default function(this:TomSelect) {
const placeholder = getDom('<input class="items-placeholder" tabindex="-1" />') as HTMLInputElement;
placeholder.placeholder = self.settings.placeholder ||'';
self.control.append(placeholder);

});


self.on('initialize',()=>{
if (options?.searchPlaceholder) {
self.control_input.placeholder = options.searchPlaceholder;
}

setAttr(self.control_input, {
role: 'combobox',
'aria-haspopup': 'listbox',
'aria-expanded': 'true',
'aria-controls': self.dropdown_content.id,
'aria-labelledby': self.settings.labelId
});

// set tabIndex on control to -1, otherwise [shift+tab] will put focus right back on control_input
self.control_input.addEventListener('keydown',(evt:KeyboardEvent) =>{
Expand All @@ -67,8 +78,10 @@ export default function(this:TomSelect) {


// give the control_input focus when the dropdown is open
self.on('dropdown_open',() =>{
self.control_input.focus();
self.on('dropdown_open',() => {
if (self.settings.focusInputOnOpen !== false) {
self.control_input.focus();
}
});

// prevent onBlur from closing when focus is on the control_input
Expand All @@ -78,7 +91,13 @@ export default function(this:TomSelect) {
return orig_onBlur.call(self);
});

addEvent(self.control_input,'blur', () => self.onBlur() );
self.control_input.addEventListener('keydown', (evt: KeyboardEvent) => {
switch (evt.keyCode) {
case constants.KEY_TAB:
self.onBlur();
break;
}
});

// return focus to control to allow further keyboard input
self.hook('before','close',() =>{
Expand All @@ -87,6 +106,12 @@ export default function(this:TomSelect) {
self.focus_node.focus({preventScroll: true});
});

self.hook('before', 'setActiveOption', ( option:null|HTMLElement) => {
if (option) {
setAttr(self.control_input,{'aria-activedescendant':option.getAttribute('id')});
}
});

});

};
5 changes: 5 additions & 0 deletions src/plugins/dropdown_input/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type DIOptions = {
// placeholder for the dropdown input field
showControlPlaceholder?: boolean;
searchPlaceholder?: string;
};
1 change: 1 addition & 0 deletions src/plugins/remove_button/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export default function(this:TomSelect, userOptions:RBOptions) {
self.removeItem(item);
self.refreshOptions(false);
self.inputState();
self.positionDropdown();
});

return item;
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/restore_on_backspace/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function(this:TomSelect, userOptions:TPluginOptions) {
return;
}

if( self.control_input.value.trim() === '' ){
if( self.control_input.value.trim() === ''){
var option = self.options[value];
if( option ){
self.setTextboxValue(options.text.call(self, option));
Expand Down
10 changes: 8 additions & 2 deletions src/plugins/virtual_scroll/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@
import type TomSelect from '../../tom-select.ts';
import { TomOption } from '../../types/index.ts';
import { addClasses } from '../../vanilla.ts';
import {VSOptions} from './types.ts';

export default function(this:TomSelect) {
export default function(this:TomSelect, userOptions: VSOptions) {
const self = this;

const pluginOptions = Object.assign<VSOptions, VSOptions>({
clearDropdownBeforeLoadCallback: true,
}, userOptions);

const orig_canLoad = self.canLoad;
const orig_clearActiveOption = self.clearActiveOption;
const orig_loadCallback = self.loadCallback;
Expand Down Expand Up @@ -136,7 +142,7 @@ export default function(this:TomSelect) {
// wrap the load
self.hook('instead','loadCallback',( options:TomOption[], optgroups:TomOption[])=>{

if( !loading_more ){
if(pluginOptions.clearDropdownBeforeLoadCallback && !loading_more ){
self.clearOptions(clearFilter);
}else if( load_more_opt ){
const first_option = options[0];
Expand Down
3 changes: 3 additions & 0 deletions src/plugins/virtual_scroll/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type VSOptions = {
clearDropdownBeforeLoadCallback?: boolean;
};
Loading