svelte-multiselect
Version:
Svelte multi-select component
317 lines (316 loc) • 9.5 kB
TypeScript
import type { Snippet } from 'svelte';
import type { FlipParams } from 'svelte/animate';
import type { HTMLAttributes, HTMLInputAttributes } from 'svelte/elements';
export type Option = string | number | ObjectOption;
export type OptionStyle = string | {
option?: string;
selected?: string;
};
export type ObjectOption = {
label: string | number;
value?: unknown;
title?: string;
disabled?: boolean;
preselected?: boolean;
disabledTitle?: string;
selectedTitle?: string;
style?: OptionStyle;
group?: string;
[key: string]: unknown;
};
export type PlaceholderConfig = {
text: string;
persistent?: boolean;
};
export interface MultiSelectEvents<T extends Option = Option> {
onadd?: (data: {
option: T;
selected: T[];
}) => unknown;
oncreate?: (data: {
option: T;
}) => false | T | undefined | Promise<false | T | undefined>;
onremove?: (data: {
option: T;
selected: T[];
}) => unknown;
onremoveAll?: (data: {
options: T[];
}) => unknown;
onselectAll?: (data: {
options: T[];
}) => unknown;
onreorder?: (data: {
options: T[];
previous: T[];
}) => unknown;
onchange?: (data: {
option?: T;
options?: T[];
type: `add` | `remove` | `removeAll` | `selectAll` | `reorder`;
}) => unknown;
onopen?: (data: {
event: Event;
}) => unknown;
onclose?: (data: {
event: Event;
}) => unknown;
ongroupToggle?: (data: {
group: string;
collapsed: boolean;
}) => unknown;
oncollapseAll?: (data: {
groups: string[];
}) => unknown;
onexpandAll?: (data: {
groups: string[];
}) => unknown;
onsearch?: (data: {
searchText: string;
matchingOptions: T[];
}) => unknown;
onmaxreached?: (data: {
selected: T[];
maxSelect: number;
attemptedOption: T;
}) => unknown;
onduplicate?: (data: {
option: T;
}) => unknown;
onparsed_paste?: (data: {
added: T[];
rejected: T[];
overflow: T[];
raw_text: string;
}) => unknown;
onactivate?: (data: {
option: T | null;
index: number | null;
}) => unknown;
onundo?: (data: {
previous: T[];
current: T[];
}) => unknown;
onredo?: (data: {
previous: T[];
current: T[];
}) => unknown;
}
export interface LoadOptionsParams {
search: string;
offset: number;
limit: number;
}
export interface LoadOptionsResult<T extends Option = Option> {
options: T[];
hasMore: boolean;
}
export type LoadOptionsFn<T extends Option = Option> = (params: LoadOptionsParams) => Promise<LoadOptionsResult<T>>;
export interface LoadOptionsConfig<T extends Option = Option> {
fetch: LoadOptionsFn<T>;
debounceMs?: number;
batchSize?: number;
onOpen?: boolean;
}
export type LoadOptions<T extends Option = Option> = LoadOptionsFn<T> | LoadOptionsConfig<T>;
export type FormSerialize<T extends Option = Option> = (selected: T[]) => string | null;
type InputSnippetProps<T extends Option = Option> = Pick<MultiSelectProps<T>, `selected` | `disabled` | `invalid` | `id` | `open` | `required` | `searchText`> & {
placeholder: string | null;
};
type UserMsgProps = {
searchText: string;
msgType: false | `dupe` | `create` | `no-match`;
msg: null | string;
};
export type GroupHeaderProps<T extends Option = Option> = {
group: string;
options: T[];
collapsed: boolean;
};
export type GroupedOptions<T extends Option = Option> = {
group: string | null;
options: T[];
collapsed: boolean;
};
export interface MultiSelectSnippets<T extends Option = Option> {
expandIcon?: Snippet<[{
open: boolean;
disabled: boolean;
}]>;
selectedItem?: Snippet<[{
option: T;
idx: number;
}]>;
children?: Snippet<[{
option: T;
idx: number;
type: `option` | `selected`;
}]>;
removeIcon?: Snippet<[
{
option: T;
isRemoveAll: false;
} | {
option?: undefined;
isRemoveAll: true;
}
]>;
beforeInput?: Snippet<[InputSnippetProps<T>]>;
afterInput?: Snippet<[InputSnippetProps<T>]>;
spinner?: Snippet;
disabledIcon?: Snippet;
option?: Snippet<[
{
option: T;
idx: number;
selected: boolean;
active: boolean;
disabled: boolean;
}
]>;
userMsg?: Snippet<[UserMsgProps]>;
groupHeader?: Snippet<[GroupHeaderProps<T>]>;
}
export interface PortalParams {
target_node?: HTMLElement | null;
active?: boolean;
placement?: `auto` | `bottom` | `top`;
}
type InputEventProp = Extract<keyof HTMLInputAttributes, `on${string}`>;
export type InputProps = Omit<HTMLInputAttributes, InputEventProp>;
export interface MultiSelectProps<T extends Option = Option> extends MultiSelectEvents<T>, MultiSelectSnippets<T>, Omit<HTMLAttributes<HTMLDivElement>, `children` | `onchange` | `onclose` | `placeholder`> {
activeIndex?: number | null;
activeOption?: T | null;
createOptionMsg?: string | ((state: {
searchText: string;
selected: T[];
options: T[];
matchingOptions: T[];
}) => string) | null;
allowUserOptions?: boolean | `append`;
allowEmpty?: boolean;
autocomplete?: HTMLInputAttributes[`autocomplete`];
autoScroll?: boolean;
breakpoint?: number;
defaultDisabledTitle?: string;
disabled?: boolean;
disabledInputTitle?: string;
duplicateOptionMsg?: string;
duplicates?: boolean | `case-insensitive`;
expandIconPosition?: `left` | `right` | `none`;
keepSelectedInDropdown?: false | `plain` | `checkboxes`;
key?: (opt: T) => unknown;
filterFunc?: (opt: T, searchText: string) => boolean;
fuzzy?: boolean;
closeDropdownOnSelect?: boolean | `if-mobile` | `retain-focus`;
form_input?: HTMLInputElement | null;
formSerialize?: FormSerialize<T>;
highlightMatches?: boolean;
id?: string | null;
input?: HTMLInputElement | null;
inputClass?: string;
inputProps?: InputProps;
inputStyle?: string | null;
inputmode?: HTMLInputAttributes[`inputmode`] | null;
invalid?: boolean;
liActiveOptionClass?: string;
liActiveUserMsgClass?: string;
liOptionClass?: string;
liOptionStyle?: string | null;
liSelectedClass?: string;
liSelectedStyle?: string | null;
liUserMsgClass?: string;
loading?: boolean;
matchingOptions?: T[];
maxOptions?: number | undefined;
virtualList?: boolean | {
itemHeight?: number;
overscan?: number;
};
maxSelect?: number | null;
maxSelectMsg?: ((current: number, max: number) => string) | null;
maxSelectMsgClass?: string;
maxVisibleChips?: number | null;
name?: string | null;
noMatchingOptionsMsg?: string;
open?: boolean;
options?: T[];
outerDiv?: HTMLDivElement | null;
outerDivClass?: string;
parseLabelsAsHtml?: boolean;
pattern?: string | null;
placeholder?: string | PlaceholderConfig | null;
removeAllTitle?: string;
removeBtnTitle?: string;
minSelect?: number | null;
required?: boolean | number;
resetFilterOnAdd?: boolean;
parse_paste?: (text: string) => T[];
searchText?: string;
selected?: T[];
selectedDisplay?: `chips` | `input`;
sortSelected?: boolean | ((op1: T, op2: T) => number);
selectedOptionsDraggable?: boolean;
style?: string | null;
ulOptionsClass?: string;
ulSelectedClass?: string;
ulSelectedStyle?: string | null;
ulOptionsStyle?: string | null;
value?: T | T[] | null;
portal?: PortalParams;
selectAllOption?: boolean | string;
selectAllDisabledTitle?: string | ((state: {
max_reached: boolean;
maxSelect: number | null;
selected_count: number;
}) => string) | null;
liSelectAllClass?: string;
loadOptions?: LoadOptions<T>;
selectedFlipParams?: FlipParams;
collapsibleGroups?: boolean;
collapsedGroups?: Set<string>;
groupSelectAll?: boolean;
ungroupedPosition?: `first` | `last`;
groupSortOrder?: `none` | `asc` | `desc` | ((a: string, b: string) => number);
searchExpandsCollapsedGroups?: boolean;
searchMatchesGroups?: boolean;
keyboardExpandsCollapsedGroups?: boolean;
stickyGroupHeaders?: boolean;
liGroupHeaderClass?: string;
liGroupHeaderStyle?: string | null;
collapseAllGroups?: () => void;
expandAllGroups?: () => void;
shortcuts?: Partial<KeyboardShortcuts>;
history?: boolean | number;
undo?: () => boolean;
redo?: () => boolean;
canUndo?: boolean;
canRedo?: boolean;
}
export interface KeyboardShortcuts {
select_all?: string | null;
clear_all?: string | null;
open?: string | null;
close?: string | null;
undo?: string | null;
redo?: string | null;
}
export type NavRouteObject = {
label?: string;
children?: string[];
disabled?: boolean | string;
separator?: boolean;
align?: `left` | `right`;
external?: boolean;
tooltip?: string;
class?: string;
style?: string;
[key: string]: unknown;
} & ({
href: string;
} | {
separator: true;
href?: string;
});
export type NavRoute = string | [string, string] | [string, string[]] | NavRouteObject;
export {};