svelte-5-select 1.0.0-beta.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Select.svelte +196 -391
- package/dist/Select.svelte.d.ts +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/tailwind.css +4 -4
- package/dist/types.d.ts +81 -10
- package/dist/use-hover.svelte.d.ts +13 -0
- package/dist/use-hover.svelte.js +97 -0
- package/dist/use-load-options.svelte.d.ts +4 -0
- package/dist/use-load-options.svelte.js +68 -0
- package/dist/use-value.svelte.d.ts +14 -0
- package/dist/use-value.svelte.js +175 -0
- package/dist/utils.d.ts +2 -2
- package/package.json +27 -28
package/dist/Select.svelte.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { SelectProps } from './types';
|
|
2
|
-
declare const Select: import("svelte").Component<SelectProps, {
|
|
2
|
+
declare const Select: import("svelte").Component<SelectProps, {
|
|
3
|
+
reset: () => void;
|
|
4
|
+
}, "loading" | "value" | "hoverItemIndex" | "listOpen" | "filterText" | "items" | "focused" | "justValue">;
|
|
3
5
|
type Select = ReturnType<typeof Select>;
|
|
4
6
|
export default Select;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export { default as LoadingIcon } from './LoadingIcon.svelte';
|
|
|
5
5
|
export { default as filter } from './filter';
|
|
6
6
|
export { useKeyboardNavigation } from './keyboard-navigation.svelte';
|
|
7
7
|
export { isStringArray, isCancelled, areItemsEqual } from './utils';
|
|
8
|
-
export type { SelectItem, SelectProps, FloatingConfig, FilterConfig, KeyboardNavigationContext, ErrorEvent, } from './types';
|
|
8
|
+
export type { SelectItem, SelectProps, SelectValue, JustValue, FloatingConfig, FilterConfig, KeyboardNavigationContext, ErrorEvent, } from './types';
|
package/dist/tailwind.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@import 'tailwindcss
|
|
1
|
+
@import 'tailwindcss';
|
|
2
2
|
|
|
3
3
|
.svelte-select {
|
|
4
4
|
@apply border rounded box-border h-10 relative flex items-center px-4 py-0 bg-white m-0 w-full
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
.list .list-group-title {
|
|
77
|
-
@apply text-slate-800 cursor-default text-sm font-medium h-10 leading-10 px-5
|
|
77
|
+
@apply text-slate-800 cursor-default text-sm font-medium h-10 leading-10 px-5 text-ellipsis whitespace-nowrap uppercase;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
.list .empty {
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
.item {
|
|
85
|
-
@apply cursor-default h-10 leading-10 px-5 text-gray-800
|
|
85
|
+
@apply cursor-default h-10 leading-10 px-5 text-gray-800 text-ellipsis overflow-hidden whitespace-nowrap;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
.item.group-item {
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
.multi-item {
|
|
117
|
-
@apply bg-gray-100 mt-1 mr-1 border border-gray-200 rounded-sm h-8 leading-8 flex cursor-default pr-1 pl-1 max-w-full items-center mr-1 overflow-hidden
|
|
117
|
+
@apply bg-gray-100 mt-1 mr-1 border border-gray-200 rounded-sm h-8 leading-8 flex cursor-default pr-1 pl-1 max-w-full items-center mr-1 overflow-hidden text-ellipsis whitespace-nowrap;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
.multi-item.disabled {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { ComputePositionConfig } from 'svelte-floating-ui/dom';
|
|
3
|
+
export type SelectValue = SelectItem | SelectItem[] | null;
|
|
3
4
|
export interface ErrorEvent {
|
|
4
5
|
type: string;
|
|
5
|
-
details:
|
|
6
|
+
details: unknown;
|
|
6
7
|
}
|
|
7
8
|
export interface FilterConfig {
|
|
8
9
|
loadOptions?: (filterText: string) => Promise<SelectItem[] | string[]>;
|
|
@@ -27,7 +28,7 @@ export interface KeyboardNavigationContext {
|
|
|
27
28
|
filteredItems: SelectItem[];
|
|
28
29
|
hoverItemIndex: number;
|
|
29
30
|
multiple: boolean;
|
|
30
|
-
value:
|
|
31
|
+
value: SelectProps['value'];
|
|
31
32
|
filterText: string;
|
|
32
33
|
activeValue: number | undefined;
|
|
33
34
|
itemId: string;
|
|
@@ -41,6 +42,75 @@ export interface KeyboardNavigationContext {
|
|
|
41
42
|
handleSelect: (item: SelectItem) => void;
|
|
42
43
|
handleMultiItemClear: (index: number) => Promise<void>;
|
|
43
44
|
}
|
|
45
|
+
export interface HoverContext {
|
|
46
|
+
getState: () => {
|
|
47
|
+
listOpen: boolean;
|
|
48
|
+
filteredItems: SelectItem[];
|
|
49
|
+
hoverItemIndex: number;
|
|
50
|
+
multiple: boolean;
|
|
51
|
+
value: SelectProps['value'];
|
|
52
|
+
isScrolling: boolean;
|
|
53
|
+
groupBy: ((item: SelectItem) => string) | undefined;
|
|
54
|
+
itemId: string;
|
|
55
|
+
};
|
|
56
|
+
setHoverItemIndex: (value: number) => void;
|
|
57
|
+
setIsScrolling: (value: boolean) => void;
|
|
58
|
+
onhoveritem: (index: number) => void;
|
|
59
|
+
}
|
|
60
|
+
export type JustValue = string | number | string[] | number[] | null;
|
|
61
|
+
export interface ValueContext {
|
|
62
|
+
getState: () => {
|
|
63
|
+
value: SelectProps['value'];
|
|
64
|
+
prevValue: SelectProps['value'];
|
|
65
|
+
items: SelectItem[] | string[] | null;
|
|
66
|
+
multiple: boolean;
|
|
67
|
+
itemId: string;
|
|
68
|
+
label: string;
|
|
69
|
+
hasValue: boolean;
|
|
70
|
+
normalizedValue: SelectItem | SelectItem[] | null;
|
|
71
|
+
useJustValue: boolean;
|
|
72
|
+
justValue: JustValue | undefined;
|
|
73
|
+
clearState: boolean;
|
|
74
|
+
closeListOnChange: boolean;
|
|
75
|
+
};
|
|
76
|
+
setValue: (value: SelectProps['value']) => void;
|
|
77
|
+
setJustValue: (value: JustValue) => void;
|
|
78
|
+
setPrevValue: (value: SelectProps['value']) => void;
|
|
79
|
+
setClearState: (value: boolean) => void;
|
|
80
|
+
setActiveValue: (value: number | undefined) => void;
|
|
81
|
+
setFilterText: (value: string) => void;
|
|
82
|
+
closeList: () => void;
|
|
83
|
+
oninput: (value: SelectProps['value']) => void;
|
|
84
|
+
onchange: (value: SelectProps['value']) => void;
|
|
85
|
+
onclear: (value: SelectProps['value'] | SelectItem) => void;
|
|
86
|
+
onselect: (selection: SelectItem) => void;
|
|
87
|
+
}
|
|
88
|
+
export interface LoadOptionsContext {
|
|
89
|
+
getState: () => {
|
|
90
|
+
filterText: string;
|
|
91
|
+
prevFilterText: string | undefined;
|
|
92
|
+
loadOptionsDeps: any[];
|
|
93
|
+
loadOptions: ((filterText: string) => Promise<SelectItem[] | string[]>) | undefined;
|
|
94
|
+
disabled: boolean;
|
|
95
|
+
multiple: boolean;
|
|
96
|
+
value: SelectProps['value'];
|
|
97
|
+
items: SelectItem[] | string[] | null;
|
|
98
|
+
itemId: string;
|
|
99
|
+
useJustValue: boolean;
|
|
100
|
+
justValue: JustValue | undefined;
|
|
101
|
+
listOpen: boolean;
|
|
102
|
+
debounceWait: number;
|
|
103
|
+
};
|
|
104
|
+
setItems: (items: SelectItem[] | string[] | null) => void;
|
|
105
|
+
setValue: (value: SelectProps['value']) => void;
|
|
106
|
+
setJustValue: (value: JustValue) => void;
|
|
107
|
+
setLoading: (value: boolean) => void;
|
|
108
|
+
setListOpen: (value: boolean) => void;
|
|
109
|
+
debounce: (fn: () => void, wait: number) => void;
|
|
110
|
+
convertStringItemsToObjects: (items: string[]) => SelectItem[];
|
|
111
|
+
onloaded: (options: SelectItem[]) => void;
|
|
112
|
+
onerror: (error: ErrorEvent) => void;
|
|
113
|
+
}
|
|
44
114
|
export interface ScrollActionParams {
|
|
45
115
|
scroll: boolean;
|
|
46
116
|
}
|
|
@@ -59,7 +129,7 @@ export interface SelectProps {
|
|
|
59
129
|
filterText?: string;
|
|
60
130
|
itemId?: string;
|
|
61
131
|
items?: SelectItem[] | string[] | null;
|
|
62
|
-
justValue?:
|
|
132
|
+
justValue?: JustValue;
|
|
63
133
|
label?: string;
|
|
64
134
|
value?: SelectItem | SelectItem[] | string | null;
|
|
65
135
|
disabled?: boolean;
|
|
@@ -100,26 +170,27 @@ export interface SelectProps {
|
|
|
100
170
|
getFilteredItems?: () => SelectItem[];
|
|
101
171
|
groupBy?: ((item: SelectItem) => string) | undefined;
|
|
102
172
|
groupFilter?: (groups: string[]) => string[];
|
|
103
|
-
itemFilter?: (label: string, filterText: string, option:
|
|
173
|
+
itemFilter?: (label: string, filterText: string, option: SelectItem) => boolean;
|
|
104
174
|
loadOptions?: (filterText: string) => Promise<SelectItem[] | string[]>;
|
|
105
175
|
ariaFocused?: () => string;
|
|
176
|
+
ariaLabel?: string;
|
|
106
177
|
ariaListOpen?: (label: string, count: number) => string;
|
|
107
178
|
ariaValues?: (values: string) => string;
|
|
108
179
|
handleClear?: () => void;
|
|
109
180
|
onblur?: (e: FocusEvent) => void;
|
|
110
|
-
onchange?: (value:
|
|
111
|
-
onclear?: (value:
|
|
181
|
+
onchange?: (value: SelectValue) => void;
|
|
182
|
+
onclear?: (value: SelectValue) => void;
|
|
112
183
|
onerror?: (error: ErrorEvent) => void;
|
|
113
184
|
onfilter?: (items: SelectItem[]) => void;
|
|
114
185
|
onfocus?: (e: FocusEvent) => void;
|
|
115
186
|
onhoveritem?: (index: number) => void;
|
|
116
|
-
oninput?: (value:
|
|
187
|
+
oninput?: (value: SelectValue) => void;
|
|
117
188
|
onloaded?: (options: SelectItem[]) => void;
|
|
118
189
|
onselect?: (selection: SelectItem) => void;
|
|
119
190
|
chevronIconSnippet?: Snippet<[boolean]>;
|
|
120
191
|
clearIconSnippet?: Snippet;
|
|
121
192
|
emptySnippet?: Snippet;
|
|
122
|
-
inputHiddenSnippet?: Snippet<[
|
|
193
|
+
inputHiddenSnippet?: Snippet<[SelectValue]>;
|
|
123
194
|
itemSnippet?: Snippet<[SelectItem, number]>;
|
|
124
195
|
listAppendSnippet?: Snippet;
|
|
125
196
|
listPrependSnippet?: Snippet;
|
|
@@ -127,8 +198,8 @@ export interface SelectProps {
|
|
|
127
198
|
loadingIconSnippet?: Snippet;
|
|
128
199
|
multiClearIconSnippet?: Snippet;
|
|
129
200
|
prependSnippet?: Snippet;
|
|
130
|
-
requiredSnippet?: Snippet<[
|
|
131
|
-
selectionSnippet?: Snippet<[
|
|
201
|
+
requiredSnippet?: Snippet<[SelectValue]>;
|
|
202
|
+
selectionSnippet?: Snippet<[SelectItem | SelectItem[], number?]>;
|
|
132
203
|
container?: HTMLDivElement;
|
|
133
204
|
input?: HTMLInputElement;
|
|
134
205
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HoverContext, SelectItem } from './types';
|
|
2
|
+
export declare function useHover(context: HoverContext): {
|
|
3
|
+
computeNextIndex: (filteredItems: SelectItem[], fromIndex: number, increment: number) => number;
|
|
4
|
+
setHoverIndex: (increment: number) => void;
|
|
5
|
+
setValueIndexAsHoverIndex: () => void;
|
|
6
|
+
checkHoverSelectable: (startingIndex?: number, ignoreGroup?: boolean) => void;
|
|
7
|
+
getFirstSelectableIndex: () => number;
|
|
8
|
+
isItemActive: (item: SelectItem, val: SelectItem | SelectItem[] | null | undefined, itemId: string) => boolean | undefined;
|
|
9
|
+
isItemSelectable: (item: SelectItem) => boolean;
|
|
10
|
+
handleHover: (i: number) => void;
|
|
11
|
+
handleListScroll: () => void;
|
|
12
|
+
handleListScrollEnd: () => void;
|
|
13
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { areItemsEqual, isItemSelectableCheck } from './utils';
|
|
2
|
+
export function useHover(context) {
|
|
3
|
+
function computeNextIndex(filteredItems, fromIndex, increment) {
|
|
4
|
+
let selectableFilteredItems = filteredItems.filter((item) => !Object.hasOwn(item, 'selectable') || item.selectable === true);
|
|
5
|
+
if (selectableFilteredItems.length === 0) {
|
|
6
|
+
return 0;
|
|
7
|
+
}
|
|
8
|
+
const currentItem = filteredItems[fromIndex];
|
|
9
|
+
const isCurrentSelectable = isItemSelectableCheck(currentItem);
|
|
10
|
+
let currentSelectableIndex;
|
|
11
|
+
if (isCurrentSelectable) {
|
|
12
|
+
currentSelectableIndex = selectableFilteredItems.findIndex(item => item === currentItem);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
currentSelectableIndex = increment > 0 ? -1 : selectableFilteredItems.length;
|
|
16
|
+
}
|
|
17
|
+
let newSelectableIndex;
|
|
18
|
+
if (increment > 0) {
|
|
19
|
+
newSelectableIndex = (currentSelectableIndex + 1) % selectableFilteredItems.length;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
newSelectableIndex = (currentSelectableIndex - 1 + selectableFilteredItems.length) % selectableFilteredItems.length;
|
|
23
|
+
}
|
|
24
|
+
const newItem = selectableFilteredItems[newSelectableIndex];
|
|
25
|
+
return filteredItems.findIndex(item => item === newItem);
|
|
26
|
+
}
|
|
27
|
+
function setHoverIndex(increment) {
|
|
28
|
+
const { filteredItems, hoverItemIndex } = context.getState();
|
|
29
|
+
context.setHoverItemIndex(computeNextIndex(filteredItems, hoverItemIndex, increment));
|
|
30
|
+
}
|
|
31
|
+
function setValueIndexAsHoverIndex() {
|
|
32
|
+
const { value, filteredItems, itemId } = context.getState();
|
|
33
|
+
const normalizedValue = !value ? null : typeof value === 'string' ? { value, label: value } : value;
|
|
34
|
+
if (!normalizedValue || Array.isArray(normalizedValue))
|
|
35
|
+
return;
|
|
36
|
+
const singleValue = normalizedValue;
|
|
37
|
+
const valueIndex = filteredItems.findIndex((i) => {
|
|
38
|
+
return i[itemId] === singleValue[itemId];
|
|
39
|
+
});
|
|
40
|
+
checkHoverSelectable(valueIndex, true);
|
|
41
|
+
}
|
|
42
|
+
function checkHoverSelectable(startingIndex = 0, ignoreGroup) {
|
|
43
|
+
const { groupBy, filteredItems } = context.getState();
|
|
44
|
+
const idx = startingIndex < 0 ? 0 : startingIndex;
|
|
45
|
+
if (!ignoreGroup && groupBy && filteredItems[idx] && !filteredItems[idx].selectable) {
|
|
46
|
+
// Compute the final index without intermediate state writes
|
|
47
|
+
context.setHoverItemIndex(computeNextIndex(filteredItems, idx, 1));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
context.setHoverItemIndex(idx);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function getFirstSelectableIndex() {
|
|
54
|
+
const { groupBy, filteredItems } = context.getState();
|
|
55
|
+
if (!groupBy || filteredItems.length === 0)
|
|
56
|
+
return 0;
|
|
57
|
+
if (!isItemSelectableCheck(filteredItems[0])) {
|
|
58
|
+
const firstSelectable = filteredItems.findIndex(isItemSelectableCheck);
|
|
59
|
+
return firstSelectable >= 0 ? firstSelectable : 0;
|
|
60
|
+
}
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
function isItemActive(item, val, itemId) {
|
|
64
|
+
const { multiple } = context.getState();
|
|
65
|
+
if (multiple)
|
|
66
|
+
return;
|
|
67
|
+
const normalized = !val ? null : typeof val === 'string' ? { value: val, label: val } : val;
|
|
68
|
+
return areItemsEqual(normalized, item, itemId);
|
|
69
|
+
}
|
|
70
|
+
function isItemSelectable(item) {
|
|
71
|
+
return (item.groupHeader && item.selectable) || isItemSelectableCheck(item);
|
|
72
|
+
}
|
|
73
|
+
function handleHover(i) {
|
|
74
|
+
const { isScrolling } = context.getState();
|
|
75
|
+
if (isScrolling)
|
|
76
|
+
return;
|
|
77
|
+
context.setHoverItemIndex(i);
|
|
78
|
+
}
|
|
79
|
+
function handleListScroll() {
|
|
80
|
+
context.setIsScrolling(true);
|
|
81
|
+
}
|
|
82
|
+
function handleListScrollEnd() {
|
|
83
|
+
context.setIsScrolling(false);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
computeNextIndex,
|
|
87
|
+
setHoverIndex,
|
|
88
|
+
setValueIndexAsHoverIndex,
|
|
89
|
+
checkHoverSelectable,
|
|
90
|
+
getFirstSelectableIndex,
|
|
91
|
+
isItemActive,
|
|
92
|
+
isItemSelectable,
|
|
93
|
+
handleHover,
|
|
94
|
+
handleListScroll,
|
|
95
|
+
handleListScrollEnd,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export function useLoadOptions(context) {
|
|
2
|
+
function handleLoadOptions(currentFilterText, currentDeps) {
|
|
3
|
+
const { loadOptions, disabled, multiple, value, itemId, useJustValue, justValue, prevFilterText, debounceWait, listOpen } = context.getState();
|
|
4
|
+
if (loadOptions && !disabled) {
|
|
5
|
+
context.setLoading(true);
|
|
6
|
+
const isFilterTextChange = currentFilterText !== prevFilterText;
|
|
7
|
+
const executeLoad = async () => {
|
|
8
|
+
try {
|
|
9
|
+
const result = await loadOptions(currentFilterText);
|
|
10
|
+
if (result && result.length > 0 && typeof result[0] === 'string') {
|
|
11
|
+
context.setItems(context.convertStringItemsToObjects(result));
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
context.setItems(result ? result.slice() : null);
|
|
15
|
+
}
|
|
16
|
+
// Re-read state after async operation
|
|
17
|
+
const state = context.getState();
|
|
18
|
+
if (state.value && state.items && state.items.length > 0) {
|
|
19
|
+
const items = state.items;
|
|
20
|
+
const valueExists = state.multiple
|
|
21
|
+
? Array.isArray(state.value) && state.value.every((v) => items.some((item) => (typeof item === 'string' ? item : item[state.itemId]) === (typeof v === 'string' ? v : v[state.itemId])))
|
|
22
|
+
: items.some((item) => (typeof item === 'string' ? item : item[state.itemId]) === (typeof state.value === 'string' ? state.value : state.value[state.itemId]));
|
|
23
|
+
if (!valueExists) {
|
|
24
|
+
context.setValue(state.multiple ? [] : undefined);
|
|
25
|
+
if (state.useJustValue) {
|
|
26
|
+
context.setJustValue(state.multiple ? [] : '');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (state.value && (!state.items || state.items.length === 0)) {
|
|
31
|
+
context.setValue(state.multiple ? [] : undefined);
|
|
32
|
+
}
|
|
33
|
+
context.setLoading(false);
|
|
34
|
+
const finalState = context.getState();
|
|
35
|
+
context.onloaded((finalState.items || []));
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.error('loadOptions error:', err);
|
|
39
|
+
context.onerror({ type: 'loadOptions', details: err });
|
|
40
|
+
context.setItems(null);
|
|
41
|
+
context.setLoading(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
if (isFilterTextChange) {
|
|
45
|
+
context.debounce(executeLoad, debounceWait);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
executeLoad();
|
|
49
|
+
}
|
|
50
|
+
if (currentFilterText.length > 0 && !listOpen) {
|
|
51
|
+
context.setListOpen(true);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (loadOptions && disabled) {
|
|
55
|
+
const state = context.getState();
|
|
56
|
+
if (state.value || (state.useJustValue && state.justValue)) {
|
|
57
|
+
context.setValue(state.multiple ? [] : undefined);
|
|
58
|
+
if (state.useJustValue) {
|
|
59
|
+
context.setJustValue(state.multiple ? [] : '');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
context.setItems(null);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
handleLoadOptions,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ValueContext, SelectItem, JustValue } from './types';
|
|
2
|
+
export declare function useValue(context: ValueContext): {
|
|
3
|
+
setValue: () => void;
|
|
4
|
+
updateValueDisplay: (items?: SelectItem[] | string[] | null) => void;
|
|
5
|
+
computeJustValue: () => JustValue | undefined;
|
|
6
|
+
checkValueForDuplicates: () => boolean;
|
|
7
|
+
findItem: (selection?: SelectItem) => SelectItem | undefined;
|
|
8
|
+
findItemByValue: (id: string | number) => SelectItem | undefined;
|
|
9
|
+
dispatchSelectedItem: () => void;
|
|
10
|
+
itemSelected: (selection: SelectItem) => void;
|
|
11
|
+
setupMulti: () => void;
|
|
12
|
+
handleMultiItemClear: (i: number) => Promise<void>;
|
|
13
|
+
convertStringItemsToObjects: (_items: string[]) => SelectItem[];
|
|
14
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { hasValueChanged } from './utils';
|
|
2
|
+
export function useValue(context) {
|
|
3
|
+
function findItemByValue(id) {
|
|
4
|
+
const { items, itemId } = context.getState();
|
|
5
|
+
return items?.find(item => item[itemId] === id);
|
|
6
|
+
}
|
|
7
|
+
function findItem(selection) {
|
|
8
|
+
const { normalizedValue, items, itemId } = context.getState();
|
|
9
|
+
let matchTo = selection ? selection[itemId] : normalizedValue[itemId];
|
|
10
|
+
return items?.find(item => item[itemId] === matchTo);
|
|
11
|
+
}
|
|
12
|
+
function setValue() {
|
|
13
|
+
const { value, multiple, itemId } = context.getState();
|
|
14
|
+
context.setPrevValue(value);
|
|
15
|
+
if (typeof value === 'string') {
|
|
16
|
+
let item = findItemByValue(value);
|
|
17
|
+
context.setValue(item || {
|
|
18
|
+
[itemId]: value,
|
|
19
|
+
label: value,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
else if (multiple && Array.isArray(value) && value.length > 0) {
|
|
23
|
+
context.setValue(value.map((val) => {
|
|
24
|
+
if (typeof val === 'string') {
|
|
25
|
+
let item = findItemByValue(val);
|
|
26
|
+
return item || { value: val, label: val };
|
|
27
|
+
}
|
|
28
|
+
return val;
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function updateValueDisplay(items) {
|
|
33
|
+
const { value, itemId } = context.getState();
|
|
34
|
+
if (!items || items.length === 0 || items.some((item) => typeof item !== 'object'))
|
|
35
|
+
return;
|
|
36
|
+
if (!value)
|
|
37
|
+
return;
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
if (value.some((selection) => !selection || !selection[itemId]))
|
|
40
|
+
return;
|
|
41
|
+
context.setValue(value.map((selection) => findItem(selection) || selection));
|
|
42
|
+
}
|
|
43
|
+
else if (typeof value === 'object') {
|
|
44
|
+
if (!value[itemId])
|
|
45
|
+
return;
|
|
46
|
+
context.setValue(findItem() || value);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function computeJustValue() {
|
|
50
|
+
const { multiple, value, itemId, useJustValue, justValue, clearState } = context.getState();
|
|
51
|
+
const hasJustValue = multiple
|
|
52
|
+
? (Array.isArray(justValue) && justValue.length > 0)
|
|
53
|
+
: (justValue !== '' && justValue != null);
|
|
54
|
+
if (useJustValue && !value && !clearState && hasJustValue) {
|
|
55
|
+
const { items } = context.getState();
|
|
56
|
+
const typedItems = items || [];
|
|
57
|
+
if (multiple && Array.isArray(justValue)) {
|
|
58
|
+
const justValueArr = justValue;
|
|
59
|
+
context.setValue(typedItems.filter((item) => justValueArr.includes(item[itemId])));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
context.setValue(typedItems.filter((item) => item[itemId] === justValue)[0]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const wasClearing = clearState;
|
|
66
|
+
context.setClearState(false);
|
|
67
|
+
if (useJustValue && !value && !wasClearing) {
|
|
68
|
+
return justValue;
|
|
69
|
+
}
|
|
70
|
+
if (multiple && Array.isArray(value)) {
|
|
71
|
+
return value.map((item) => item[itemId]);
|
|
72
|
+
}
|
|
73
|
+
if (!value || typeof value === 'string' || Array.isArray(value)) {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
return value[itemId];
|
|
77
|
+
}
|
|
78
|
+
function checkValueForDuplicates() {
|
|
79
|
+
const { value, itemId } = context.getState();
|
|
80
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
81
|
+
return true;
|
|
82
|
+
const seen = new Set();
|
|
83
|
+
const uniqueValues = value.filter((val) => {
|
|
84
|
+
const id = val[itemId];
|
|
85
|
+
if (seen.has(id))
|
|
86
|
+
return false;
|
|
87
|
+
seen.add(id);
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
const noDuplicates = uniqueValues.length === value.length;
|
|
91
|
+
if (!noDuplicates)
|
|
92
|
+
context.setValue(uniqueValues);
|
|
93
|
+
return noDuplicates;
|
|
94
|
+
}
|
|
95
|
+
function dispatchSelectedItem() {
|
|
96
|
+
const { multiple, value, prevValue, itemId } = context.getState();
|
|
97
|
+
if (multiple) {
|
|
98
|
+
if (hasValueChanged(value, prevValue)) {
|
|
99
|
+
if (checkValueForDuplicates()) {
|
|
100
|
+
context.oninput(value || []);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!prevValue || hasValueChanged(value[itemId], prevValue[itemId])) {
|
|
106
|
+
context.oninput(value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function setupMulti() {
|
|
110
|
+
const { value } = context.getState();
|
|
111
|
+
if (value) {
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
context.setValue([...value]);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
context.setValue([value]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function handleMultiItemClear(i) {
|
|
121
|
+
const { value } = context.getState();
|
|
122
|
+
if (!Array.isArray(value))
|
|
123
|
+
return;
|
|
124
|
+
const itemToRemove = value[i];
|
|
125
|
+
context.setClearState(true);
|
|
126
|
+
if (value.length === 1) {
|
|
127
|
+
context.setValue(undefined);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
context.setValue(value.filter((item) => {
|
|
131
|
+
return item !== itemToRemove;
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
context.onclear(itemToRemove);
|
|
135
|
+
}
|
|
136
|
+
function convertStringItemsToObjects(_items) {
|
|
137
|
+
return _items.map((item, index) => {
|
|
138
|
+
return {
|
|
139
|
+
index,
|
|
140
|
+
value: item,
|
|
141
|
+
label: `${item}`,
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function itemSelected(selection) {
|
|
146
|
+
const { multiple, value, closeListOnChange } = context.getState();
|
|
147
|
+
if (selection) {
|
|
148
|
+
context.setFilterText('');
|
|
149
|
+
const item = Object.assign({}, selection);
|
|
150
|
+
if (item.groupHeader && !item.selectable)
|
|
151
|
+
return;
|
|
152
|
+
setValue();
|
|
153
|
+
updateValueDisplay(context.getState().items);
|
|
154
|
+
context.setValue(multiple ? (value ? value.concat([item]) : [item]) : item);
|
|
155
|
+
if (closeListOnChange)
|
|
156
|
+
context.closeList();
|
|
157
|
+
context.setActiveValue(undefined);
|
|
158
|
+
context.onchange(context.getState().value);
|
|
159
|
+
context.onselect(selection);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
setValue,
|
|
164
|
+
updateValueDisplay,
|
|
165
|
+
computeJustValue,
|
|
166
|
+
checkValueForDuplicates,
|
|
167
|
+
findItem,
|
|
168
|
+
findItemByValue,
|
|
169
|
+
dispatchSelectedItem,
|
|
170
|
+
itemSelected,
|
|
171
|
+
setupMulti,
|
|
172
|
+
handleMultiItemClear,
|
|
173
|
+
convertStringItemsToObjects,
|
|
174
|
+
};
|
|
175
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { SelectItem } from './types';
|
|
2
2
|
export declare function getItemProperty<T>(item: T, key: keyof T | string): T[keyof T] | undefined;
|
|
3
|
-
export declare function areItemsEqual(a:
|
|
3
|
+
export declare function areItemsEqual(a: SelectItem | null | undefined, b: SelectItem | null | undefined, itemId: string): boolean;
|
|
4
4
|
export declare function isCancelled(res: any): res is {
|
|
5
5
|
cancelled: boolean;
|
|
6
6
|
};
|
|
7
7
|
export declare function isStringArray(arr: any[]): arr is string[];
|
|
8
8
|
export declare function isItemSelectableCheck(item: SelectItem | undefined): boolean;
|
|
9
|
-
export declare function hasValueChanged(newValue:
|
|
9
|
+
export declare function hasValueChanged(newValue: unknown, oldValue: unknown): boolean;
|
|
10
10
|
export declare function createGroupHeaderItem(groupValue: string, item: SelectItem, labelKey?: string): SelectItem;
|