@pushwoosh/dumb-components 1.1.141 → 1.1.143
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/Combobox/Combobox.js +62 -18
- package/Combobox/index.d.ts +1 -1
- package/Combobox/types.d.ts +14 -5
- package/ComboboxMulti/ComboboxMulti.d.ts +1 -1
- package/ComboboxMulti/ComboboxMulti.js +24 -6
- package/ComboboxMulti/types.d.ts +6 -3
- package/index.d.ts +1 -1
- package/package.json +2 -2
- package/shared/dropdownField/FieldShell.d.ts +4 -1
- package/shared/dropdownField/FieldShell.js +18 -0
- package/shared/dropdownField/SuggestionsDropdown.d.ts +5 -1
- package/shared/dropdownField/SuggestionsDropdown.js +23 -5
- package/shared/dropdownField/index.d.ts +3 -2
- package/shared/dropdownField/index.js +2 -1
- package/shared/dropdownField/shared.d.ts +3 -1
- package/shared/dropdownField/shared.js +3 -0
- package/shared/dropdownField/styles.d.ts +1 -0
- package/shared/dropdownField/styles.js +5 -1
- package/shared/dropdownField/types.d.ts +9 -1
- package/shared/dropdownField/useAsyncItems.d.ts +7 -2
- package/shared/dropdownField/useAsyncItems.js +52 -20
- package/shared/dropdownField/useDropdownItems.d.ts +7 -2
- package/shared/dropdownField/useDropdownItems.js +9 -3
- package/shared/dropdownField/useDropdownPosition.d.ts +1 -0
- package/shared/dropdownField/useDropdownPosition.js +1 -0
- package/shared/dropdownField/useFieldOpenEffects.d.ts +10 -0
- package/shared/dropdownField/useFieldOpenEffects.js +25 -0
- package/shared/dropdownField/useListNavigation.d.ts +2 -1
- package/shared/dropdownField/useListNavigation.js +11 -2
package/Combobox/Combobox.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useId, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { DEFAULT_DEBOUNCE_MS, FieldInput, FieldShell, defaultGetKey, useActiveIndex, useDropdownItems, useListNavigation, useShellMouseDown } from '../shared/dropdownField';
|
|
3
|
+
import { DEFAULT_DEBOUNCE_MS, FieldInput, FieldShell, defaultGetKey, resolveDropdownText, useActiveIndex, useDropdownItems, useFieldOpenEffects, useListNavigation, useShellMouseDown } from '../shared/dropdownField';
|
|
4
4
|
function FreeCombobox({
|
|
5
5
|
items,
|
|
6
6
|
loadItems,
|
|
7
7
|
debounceMs = DEFAULT_DEBOUNCE_MS,
|
|
8
|
+
minChars,
|
|
8
9
|
value,
|
|
9
10
|
onChange,
|
|
11
|
+
onClose,
|
|
12
|
+
autoFocus,
|
|
10
13
|
getLabel: getLabelProp,
|
|
11
14
|
filter,
|
|
12
15
|
renderItem,
|
|
13
|
-
|
|
16
|
+
emptyText,
|
|
17
|
+
loadingText,
|
|
14
18
|
placeholder,
|
|
15
19
|
disabled,
|
|
16
20
|
loading,
|
|
17
21
|
clearable,
|
|
18
22
|
autosize,
|
|
19
|
-
|
|
23
|
+
hasError,
|
|
20
24
|
width,
|
|
21
25
|
minWidth
|
|
22
26
|
}) {
|
|
@@ -27,11 +31,14 @@ function FreeCombobox({
|
|
|
27
31
|
const [isOpen, setIsOpen] = useState(false);
|
|
28
32
|
const {
|
|
29
33
|
filteredItems,
|
|
30
|
-
loading: asyncLoading
|
|
34
|
+
loading: asyncLoading,
|
|
35
|
+
loadingMore,
|
|
36
|
+
loadMore
|
|
31
37
|
} = useDropdownItems({
|
|
32
38
|
items,
|
|
33
39
|
loadItems,
|
|
34
40
|
debounceMs,
|
|
41
|
+
minChars,
|
|
35
42
|
getLabel,
|
|
36
43
|
filter,
|
|
37
44
|
query: value,
|
|
@@ -44,7 +51,9 @@ function FreeCombobox({
|
|
|
44
51
|
setActiveIndex(-1);
|
|
45
52
|
}, [setActiveIndex]);
|
|
46
53
|
const pick = useCallback(item => {
|
|
47
|
-
onChange(getLabel(item)
|
|
54
|
+
onChange(getLabel(item), {
|
|
55
|
+
source: 'select'
|
|
56
|
+
});
|
|
48
57
|
setIsOpen(false);
|
|
49
58
|
setActiveIndex(-1);
|
|
50
59
|
}, [onChange, getLabel, setActiveIndex]);
|
|
@@ -55,9 +64,17 @@ function FreeCombobox({
|
|
|
55
64
|
activeIndex,
|
|
56
65
|
setActiveIndex,
|
|
57
66
|
onSelectIndex: index => pick(filteredItems[index]),
|
|
58
|
-
onEscape: close
|
|
67
|
+
onEscape: close,
|
|
68
|
+
onReachEnd: loadMore
|
|
59
69
|
});
|
|
60
70
|
const onShellMouseDown = useShellMouseDown(inputRef, isOpen, open);
|
|
71
|
+
useFieldOpenEffects({
|
|
72
|
+
isOpen,
|
|
73
|
+
open,
|
|
74
|
+
onClose,
|
|
75
|
+
autoFocus,
|
|
76
|
+
inputRef
|
|
77
|
+
});
|
|
61
78
|
const dropdownItems = useMemo(() => filteredItems.map((item, idx) => {
|
|
62
79
|
const selected = getLabel(item) === value;
|
|
63
80
|
const state = {
|
|
@@ -73,18 +90,22 @@ function FreeCombobox({
|
|
|
73
90
|
onSelect: () => pick(item)
|
|
74
91
|
};
|
|
75
92
|
}), [filteredItems, activeIndex, value, getLabel, renderItem, pick, baseId]);
|
|
76
|
-
const
|
|
93
|
+
const isLoading = loading || asyncLoading;
|
|
94
|
+
const hasSource = items !== undefined || loadItems !== undefined;
|
|
95
|
+
const dropdownVisible = isOpen && (filteredItems.length > 0 || isLoading || hasSource);
|
|
77
96
|
const activeDescendant = activeIndex >= 0 && activeIndex < filteredItems.length ? `${baseId}-opt-${filteredItems[activeIndex]}` : undefined;
|
|
78
97
|
const onInputChange = event => {
|
|
79
|
-
onChange(event.target.value
|
|
98
|
+
onChange(event.target.value, {
|
|
99
|
+
source: 'input'
|
|
100
|
+
});
|
|
80
101
|
if (!isOpen) open();
|
|
81
102
|
};
|
|
82
103
|
return _jsx(FieldShell, {
|
|
83
104
|
isOpen: isOpen,
|
|
84
105
|
dropdownOpen: dropdownVisible,
|
|
85
|
-
isErrored:
|
|
106
|
+
isErrored: hasError,
|
|
86
107
|
disabled: disabled,
|
|
87
|
-
loading:
|
|
108
|
+
loading: isLoading,
|
|
88
109
|
width: width,
|
|
89
110
|
minWidth: minWidth,
|
|
90
111
|
autosize: autosize,
|
|
@@ -93,11 +114,16 @@ function FreeCombobox({
|
|
|
93
114
|
showClear: !!(clearable && value),
|
|
94
115
|
onClear: () => {
|
|
95
116
|
var _inputRef$current;
|
|
96
|
-
onChange(''
|
|
117
|
+
onChange('', {
|
|
118
|
+
source: 'input'
|
|
119
|
+
});
|
|
97
120
|
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
|
|
98
121
|
},
|
|
99
122
|
dropdownItems: dropdownItems,
|
|
100
|
-
emptyText:
|
|
123
|
+
emptyText: resolveDropdownText(emptyText, value),
|
|
124
|
+
loadingText: resolveDropdownText(loadingText, value),
|
|
125
|
+
loadingMore: loadingMore,
|
|
126
|
+
onLoadMore: loadMore,
|
|
101
127
|
listboxId: listboxId,
|
|
102
128
|
children: _jsx(FieldInput, {
|
|
103
129
|
ref: inputRef,
|
|
@@ -120,19 +146,23 @@ function SelectCombobox({
|
|
|
120
146
|
items,
|
|
121
147
|
loadItems,
|
|
122
148
|
debounceMs = DEFAULT_DEBOUNCE_MS,
|
|
149
|
+
minChars,
|
|
123
150
|
value,
|
|
124
151
|
onChange,
|
|
152
|
+
onClose,
|
|
153
|
+
autoFocus,
|
|
125
154
|
getLabel,
|
|
126
155
|
getKey: getKeyProp,
|
|
127
156
|
filter,
|
|
128
157
|
renderItem,
|
|
129
|
-
|
|
158
|
+
emptyText,
|
|
159
|
+
loadingText,
|
|
130
160
|
placeholder,
|
|
131
161
|
disabled,
|
|
132
162
|
loading,
|
|
133
163
|
clearable,
|
|
134
164
|
autosize,
|
|
135
|
-
|
|
165
|
+
hasError,
|
|
136
166
|
width,
|
|
137
167
|
minWidth
|
|
138
168
|
}) {
|
|
@@ -146,11 +176,14 @@ function SelectCombobox({
|
|
|
146
176
|
const {
|
|
147
177
|
sourceItems,
|
|
148
178
|
filteredItems,
|
|
149
|
-
loading: asyncLoading
|
|
179
|
+
loading: asyncLoading,
|
|
180
|
+
loadingMore,
|
|
181
|
+
loadMore
|
|
150
182
|
} = useDropdownItems({
|
|
151
183
|
items,
|
|
152
184
|
loadItems,
|
|
153
185
|
debounceMs,
|
|
186
|
+
minChars,
|
|
154
187
|
getLabel,
|
|
155
188
|
filter,
|
|
156
189
|
query,
|
|
@@ -187,9 +220,17 @@ function SelectCombobox({
|
|
|
187
220
|
activeIndex,
|
|
188
221
|
setActiveIndex,
|
|
189
222
|
onSelectIndex: index => commit(filteredItems[index]),
|
|
190
|
-
onEscape: close
|
|
223
|
+
onEscape: close,
|
|
224
|
+
onReachEnd: loadMore
|
|
191
225
|
});
|
|
192
226
|
const onShellMouseDown = useShellMouseDown(inputRef, isOpen, open);
|
|
227
|
+
useFieldOpenEffects({
|
|
228
|
+
isOpen,
|
|
229
|
+
open,
|
|
230
|
+
onClose,
|
|
231
|
+
autoFocus,
|
|
232
|
+
inputRef
|
|
233
|
+
});
|
|
193
234
|
const dropdownItems = useMemo(() => filteredItems.map((item, idx) => {
|
|
194
235
|
const key = getKey(item);
|
|
195
236
|
const selected = key === selectedKey;
|
|
@@ -210,7 +251,7 @@ function SelectCombobox({
|
|
|
210
251
|
return _jsx(FieldShell, {
|
|
211
252
|
isOpen: isOpen,
|
|
212
253
|
dropdownOpen: isOpen,
|
|
213
|
-
isErrored:
|
|
254
|
+
isErrored: hasError,
|
|
214
255
|
disabled: disabled,
|
|
215
256
|
loading: loading || asyncLoading,
|
|
216
257
|
width: width,
|
|
@@ -225,7 +266,10 @@ function SelectCombobox({
|
|
|
225
266
|
(_inputRef$current3 = inputRef.current) === null || _inputRef$current3 === void 0 || _inputRef$current3.focus();
|
|
226
267
|
},
|
|
227
268
|
dropdownItems: dropdownItems,
|
|
228
|
-
emptyText:
|
|
269
|
+
emptyText: resolveDropdownText(emptyText, query),
|
|
270
|
+
loadingText: resolveDropdownText(loadingText, query),
|
|
271
|
+
loadingMore: loadingMore,
|
|
272
|
+
onLoadMore: loadMore,
|
|
229
273
|
listboxId: listboxId,
|
|
230
274
|
children: _jsx(FieldInput, {
|
|
231
275
|
ref: inputRef,
|
package/Combobox/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Combobox } from './Combobox';
|
|
2
|
-
export type { ComboboxProps, FreeComboboxProps, SelectComboboxProps, } from './types';
|
|
2
|
+
export type { ComboboxProps, ComboboxChangeSource, ComboboxChangeMeta, FreeComboboxProps, SelectComboboxProps, } from './types';
|
package/Combobox/types.d.ts
CHANGED
|
@@ -1,31 +1,40 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { GetKeyProp, ItemState } from '../shared/dropdownField';
|
|
2
|
+
import type { DropdownText, GetKeyProp, ItemState, LoadItems } from '../shared/dropdownField';
|
|
3
3
|
type ComboboxSource<T> = {
|
|
4
4
|
items?: T[];
|
|
5
5
|
loadItems?: never;
|
|
6
6
|
debounceMs?: never;
|
|
7
|
+
minChars?: never;
|
|
7
8
|
} | {
|
|
8
9
|
items?: never;
|
|
9
|
-
loadItems:
|
|
10
|
+
loadItems: LoadItems<T>;
|
|
10
11
|
debounceMs?: number;
|
|
12
|
+
minChars?: number;
|
|
13
|
+
};
|
|
14
|
+
export type ComboboxChangeSource = 'input' | 'select';
|
|
15
|
+
export type ComboboxChangeMeta = {
|
|
16
|
+
source: ComboboxChangeSource;
|
|
11
17
|
};
|
|
12
18
|
type CommonProps<T> = {
|
|
13
19
|
filter?: (item: T, query: string) => boolean;
|
|
14
20
|
renderItem?: (item: T, state: ItemState) => ReactNode;
|
|
15
|
-
|
|
21
|
+
emptyText?: DropdownText;
|
|
22
|
+
loadingText?: DropdownText;
|
|
23
|
+
onClose?: () => void;
|
|
24
|
+
autoFocus?: boolean;
|
|
16
25
|
placeholder?: string;
|
|
17
26
|
disabled?: boolean;
|
|
18
27
|
loading?: boolean;
|
|
19
28
|
clearable?: boolean;
|
|
20
29
|
autosize?: boolean;
|
|
21
|
-
|
|
30
|
+
hasError?: boolean;
|
|
22
31
|
width?: string;
|
|
23
32
|
minWidth?: string;
|
|
24
33
|
};
|
|
25
34
|
export type FreeComboboxProps = ComboboxSource<string> & CommonProps<string> & {
|
|
26
35
|
select?: false;
|
|
27
36
|
value: string;
|
|
28
|
-
onChange: (value: string) => void;
|
|
37
|
+
onChange: (value: string, meta: ComboboxChangeMeta) => void;
|
|
29
38
|
getLabel?: (item: string) => string;
|
|
30
39
|
};
|
|
31
40
|
export type SelectComboboxProps<T> = ComboboxSource<T> & GetKeyProp<T> & CommonProps<T> & {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type ReactElement } from 'react';
|
|
2
2
|
import { type ComboboxMultiProps } from './types';
|
|
3
|
-
export declare function ComboboxMulti<T>({ items, loadItems, debounceMs, value, onChange, getLabel, getKey: getKeyProp, filter, renderItem,
|
|
3
|
+
export declare function ComboboxMulti<T>({ items, loadItems, debounceMs, minChars, value, onChange, onClose, autoFocus, getLabel, getKey: getKeyProp, filter, renderItem, emptyText, loadingText, creatable, onCreate, canCreate, renderCreateLabel, maxValues, placeholder, disabled, loading, clearable, autosize, hasError, width, minWidth, }: ComboboxMultiProps<T>): ReactElement;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useId, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { Chip } from '../Chip';
|
|
4
|
-
import { DEFAULT_DEBOUNCE_MS, FieldInput, FieldShell, defaultGetKey, useActiveIndex, useDropdownItems, useListNavigation, useShellMouseDown } from '../shared/dropdownField';
|
|
4
|
+
import { DEFAULT_DEBOUNCE_MS, FieldInput, FieldShell, defaultGetKey, resolveDropdownText, useActiveIndex, useDropdownItems, useFieldOpenEffects, useListNavigation, useShellMouseDown } from '../shared/dropdownField';
|
|
5
5
|
function defaultCanCreate(query, items, getLabel) {
|
|
6
6
|
const trimmed = query.trim();
|
|
7
7
|
if (!trimmed) return false;
|
|
@@ -12,13 +12,17 @@ export function ComboboxMulti({
|
|
|
12
12
|
items,
|
|
13
13
|
loadItems,
|
|
14
14
|
debounceMs = DEFAULT_DEBOUNCE_MS,
|
|
15
|
+
minChars,
|
|
15
16
|
value,
|
|
16
17
|
onChange,
|
|
18
|
+
onClose,
|
|
19
|
+
autoFocus,
|
|
17
20
|
getLabel,
|
|
18
21
|
getKey: getKeyProp,
|
|
19
22
|
filter,
|
|
20
23
|
renderItem,
|
|
21
|
-
|
|
24
|
+
emptyText,
|
|
25
|
+
loadingText,
|
|
22
26
|
creatable,
|
|
23
27
|
onCreate,
|
|
24
28
|
canCreate,
|
|
@@ -29,7 +33,7 @@ export function ComboboxMulti({
|
|
|
29
33
|
loading,
|
|
30
34
|
clearable,
|
|
31
35
|
autosize,
|
|
32
|
-
|
|
36
|
+
hasError,
|
|
33
37
|
width,
|
|
34
38
|
minWidth
|
|
35
39
|
}) {
|
|
@@ -46,12 +50,15 @@ export function ComboboxMulti({
|
|
|
46
50
|
const {
|
|
47
51
|
sourceItems,
|
|
48
52
|
filteredItems,
|
|
49
|
-
loading: asyncLoading
|
|
53
|
+
loading: asyncLoading,
|
|
54
|
+
loadingMore,
|
|
55
|
+
loadMore
|
|
50
56
|
} = useDropdownItems({
|
|
51
57
|
items,
|
|
52
58
|
loadItems,
|
|
53
59
|
localItems: createdItems,
|
|
54
60
|
debounceMs,
|
|
61
|
+
minChars,
|
|
55
62
|
getLabel,
|
|
56
63
|
filter,
|
|
57
64
|
query,
|
|
@@ -101,12 +108,20 @@ export function ComboboxMulti({
|
|
|
101
108
|
setActiveIndex,
|
|
102
109
|
onSelectIndex: index => toggle(visibleItems[index]),
|
|
103
110
|
onEscape: close,
|
|
111
|
+
onReachEnd: loadMore,
|
|
104
112
|
hasExtraRow: canCreateNow,
|
|
105
113
|
onSelectExtra: create,
|
|
106
114
|
isQueryEmpty: !query && value.length > 0,
|
|
107
115
|
onBackspaceEmpty: () => onChange(value.slice(0, -1))
|
|
108
116
|
});
|
|
109
117
|
const onShellMouseDown = useShellMouseDown(inputRef, isOpen, open);
|
|
118
|
+
useFieldOpenEffects({
|
|
119
|
+
isOpen,
|
|
120
|
+
open,
|
|
121
|
+
onClose,
|
|
122
|
+
autoFocus,
|
|
123
|
+
inputRef
|
|
124
|
+
});
|
|
110
125
|
const toggleOpen = useCallback(() => {
|
|
111
126
|
if (isOpen) {
|
|
112
127
|
var _inputRef$current2;
|
|
@@ -146,7 +161,7 @@ export function ComboboxMulti({
|
|
|
146
161
|
return _jsxs(FieldShell, {
|
|
147
162
|
isOpen: isOpen,
|
|
148
163
|
dropdownOpen: isOpen,
|
|
149
|
-
isErrored:
|
|
164
|
+
isErrored: hasError,
|
|
150
165
|
disabled: disabled,
|
|
151
166
|
loading: loading || asyncLoading,
|
|
152
167
|
width: width,
|
|
@@ -161,7 +176,10 @@ export function ComboboxMulti({
|
|
|
161
176
|
showChevron: true,
|
|
162
177
|
onToggle: toggleOpen,
|
|
163
178
|
dropdownItems: dropdownItems,
|
|
164
|
-
emptyText:
|
|
179
|
+
emptyText: resolveDropdownText(emptyText, query),
|
|
180
|
+
loadingText: resolveDropdownText(loadingText, query),
|
|
181
|
+
loadingMore: loadingMore,
|
|
182
|
+
onLoadMore: loadMore,
|
|
165
183
|
createLabel: createLabel,
|
|
166
184
|
createActive: createActive,
|
|
167
185
|
onCreate: create,
|
package/ComboboxMulti/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { GetKeyProp, ItemState, ItemsSource } from '../shared/dropdownField';
|
|
2
|
+
import type { DropdownText, GetKeyProp, ItemState, ItemsSource } from '../shared/dropdownField';
|
|
3
3
|
type CreatableProp<T> = [T] extends [string] ? {
|
|
4
4
|
creatable?: boolean;
|
|
5
5
|
onCreate?: (query: string) => T;
|
|
@@ -13,10 +13,13 @@ type CreatableProp<T> = [T] extends [string] ? {
|
|
|
13
13
|
export type ComboboxMultiProps<T> = ItemsSource<T> & GetKeyProp<T> & CreatableProp<T> & {
|
|
14
14
|
value: T[];
|
|
15
15
|
onChange: (value: T[]) => void;
|
|
16
|
+
onClose?: () => void;
|
|
17
|
+
autoFocus?: boolean;
|
|
16
18
|
getLabel: (item: T) => string;
|
|
17
19
|
filter?: (item: T, query: string) => boolean;
|
|
18
20
|
renderItem?: (item: T, state: ItemState) => ReactNode;
|
|
19
|
-
|
|
21
|
+
emptyText?: DropdownText;
|
|
22
|
+
loadingText?: DropdownText;
|
|
20
23
|
canCreate?: (query: string, items: T[]) => boolean;
|
|
21
24
|
renderCreateLabel?: (query: string) => ReactNode;
|
|
22
25
|
maxValues?: number;
|
|
@@ -25,7 +28,7 @@ export type ComboboxMultiProps<T> = ItemsSource<T> & GetKeyProp<T> & CreatablePr
|
|
|
25
28
|
loading?: boolean;
|
|
26
29
|
clearable?: boolean;
|
|
27
30
|
autosize?: boolean;
|
|
28
|
-
|
|
31
|
+
hasError?: boolean;
|
|
29
32
|
width?: string;
|
|
30
33
|
minWidth?: string;
|
|
31
34
|
};
|
package/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export { ReCaptcha, useRecaptcha, asyncScriptLoad } from './ReCaptcha';
|
|
|
22
22
|
export { Spinner } from './Spinner';
|
|
23
23
|
export { SortButton, type SortButtonParams } from './SortButton';
|
|
24
24
|
export { Select, SelectAsync, CreatableSelect, SelectComponents, CLASS_NAME_PREFIX, type SelectBase, type SelectOption, type MultiSelectOptions, type SingleValue, type MultiValue, } from './Select';
|
|
25
|
-
export { Combobox, type ComboboxProps, type FreeComboboxProps, type SelectComboboxProps, } from './Combobox';
|
|
25
|
+
export { Combobox, type ComboboxProps, type ComboboxChangeSource, type ComboboxChangeMeta, type FreeComboboxProps, type SelectComboboxProps, } from './Combobox';
|
|
26
26
|
export { ComboboxMulti, type ComboboxMultiProps } from './ComboboxMulti';
|
|
27
27
|
export { Switch } from './Switch';
|
|
28
28
|
export { TextSkeleton, RectangularSkeleton, CircularSkeleton } from './Skeleton';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushwoosh/dumb-components",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.143",
|
|
4
4
|
"description": "React components to build Pushwoosh products",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@types/react": "^18.2.69",
|
|
32
32
|
"@types/react-dom": "^18.2.22",
|
|
33
33
|
"@types/react-transition-group": "^4.4.12",
|
|
34
|
-
"@types/styled-components": "^5.1.
|
|
34
|
+
"@types/styled-components": "^5.1.36",
|
|
35
35
|
"glob": "^11.0.3",
|
|
36
36
|
"jest": "^30.2.0",
|
|
37
37
|
"jest-environment-jsdom": "^30.2.0",
|
|
@@ -20,11 +20,14 @@ type FieldShellProps = {
|
|
|
20
20
|
onToggle?: () => void;
|
|
21
21
|
dropdownItems: DropdownItemDescriptor[];
|
|
22
22
|
emptyText?: ReactNode;
|
|
23
|
+
loadingText?: ReactNode;
|
|
24
|
+
loadingMore?: boolean;
|
|
25
|
+
onLoadMore?: () => void;
|
|
23
26
|
createLabel?: ReactNode;
|
|
24
27
|
createActive?: boolean;
|
|
25
28
|
onCreate?: () => void;
|
|
26
29
|
listboxId?: string;
|
|
27
30
|
createId?: string;
|
|
28
31
|
};
|
|
29
|
-
export declare function FieldShell({ children, isOpen, dropdownOpen, isErrored, disabled, loading, width, minWidth, autosize, tightLeft, onShellMouseDown, onClose, showClear, onClear, clearLabel, showChevron, onToggle, dropdownItems, emptyText, createLabel, createActive, onCreate, listboxId, createId, }: FieldShellProps): ReactElement;
|
|
32
|
+
export declare function FieldShell({ children, isOpen, dropdownOpen, isErrored, disabled, loading, width, minWidth, autosize, tightLeft, onShellMouseDown, onClose, showClear, onClear, clearLabel, showChevron, onToggle, dropdownItems, emptyText, loadingText, loadingMore, onLoadMore, createLabel, createActive, onCreate, listboxId, createId, }: FieldShellProps): ReactElement;
|
|
30
33
|
export {};
|
|
@@ -24,6 +24,9 @@ export function FieldShell({
|
|
|
24
24
|
onToggle,
|
|
25
25
|
dropdownItems,
|
|
26
26
|
emptyText,
|
|
27
|
+
loadingText,
|
|
28
|
+
loadingMore,
|
|
29
|
+
onLoadMore,
|
|
27
30
|
createLabel,
|
|
28
31
|
createActive,
|
|
29
32
|
onCreate,
|
|
@@ -33,6 +36,7 @@ export function FieldShell({
|
|
|
33
36
|
const {
|
|
34
37
|
setReference,
|
|
35
38
|
setFloating,
|
|
39
|
+
floatingRef,
|
|
36
40
|
floatingStyles
|
|
37
41
|
} = useDropdownPosition({
|
|
38
42
|
isOpen: dropdownOpen,
|
|
@@ -44,6 +48,15 @@ export function FieldShell({
|
|
|
44
48
|
isOpen: !!dropdownOpen,
|
|
45
49
|
onClose
|
|
46
50
|
});
|
|
51
|
+
const onShellBlur = event => {
|
|
52
|
+
var _floatingRef$current;
|
|
53
|
+
if (!isOpen) return;
|
|
54
|
+
const next = event.relatedTarget;
|
|
55
|
+
if (!next) return;
|
|
56
|
+
if (event.currentTarget.contains(next)) return;
|
|
57
|
+
if ((_floatingRef$current = floatingRef.current) !== null && _floatingRef$current !== void 0 && _floatingRef$current.contains(next)) return;
|
|
58
|
+
onClose === null || onClose === void 0 || onClose();
|
|
59
|
+
};
|
|
47
60
|
return _jsxs(_Fragment, {
|
|
48
61
|
children: [_jsxs(Shell, {
|
|
49
62
|
ref: setReference,
|
|
@@ -56,6 +69,7 @@ export function FieldShell({
|
|
|
56
69
|
"$autosize": autosize,
|
|
57
70
|
"$tightLeft": tightLeft,
|
|
58
71
|
onMouseDown: onShellMouseDown,
|
|
72
|
+
onBlur: onShellBlur,
|
|
59
73
|
children: [_jsx(ShellInner, {
|
|
60
74
|
children: children
|
|
61
75
|
}), _jsxs(Indicators, {
|
|
@@ -88,6 +102,10 @@ export function FieldShell({
|
|
|
88
102
|
floatingStyles: floatingStyles,
|
|
89
103
|
items: dropdownItems,
|
|
90
104
|
emptyText: emptyText,
|
|
105
|
+
loading: loading,
|
|
106
|
+
loadingText: loadingText,
|
|
107
|
+
loadingMore: loadingMore,
|
|
108
|
+
onLoadMore: onLoadMore,
|
|
91
109
|
createLabel: createLabel,
|
|
92
110
|
createActive: createActive,
|
|
93
111
|
onCreate: onCreate,
|
|
@@ -5,6 +5,10 @@ type SuggestionsDropdownProps = {
|
|
|
5
5
|
floatingStyles: CSSProperties;
|
|
6
6
|
items: DropdownItemDescriptor[];
|
|
7
7
|
emptyText?: ReactNode;
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
loadingText?: ReactNode;
|
|
10
|
+
loadingMore?: boolean;
|
|
11
|
+
onLoadMore?: () => void;
|
|
8
12
|
createLabel?: ReactNode;
|
|
9
13
|
createActive?: boolean;
|
|
10
14
|
onCreate?: () => void;
|
|
@@ -12,5 +16,5 @@ type SuggestionsDropdownProps = {
|
|
|
12
16
|
createId?: string;
|
|
13
17
|
zIndex?: number;
|
|
14
18
|
};
|
|
15
|
-
export declare function SuggestionsDropdown({ floatingRef, floatingStyles, items, emptyText, createLabel, createActive, onCreate, listboxId, createId, zIndex, }: SuggestionsDropdownProps): ReactElement | null;
|
|
19
|
+
export declare function SuggestionsDropdown({ floatingRef, floatingStyles, items, emptyText, loading, loadingText, loadingMore, onLoadMore, createLabel, createActive, onCreate, listboxId, createId, zIndex, }: SuggestionsDropdownProps): ReactElement | null;
|
|
16
20
|
export {};
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
|
-
import { CreateItemLabel, Dropdown, DropdownEmpty, DropdownItem } from './styles';
|
|
4
|
+
import { CreateItemLabel, Dropdown, DropdownEmpty, DropdownItem, DropdownLoadingMore } from './styles';
|
|
5
|
+
import { Spinner } from '../../Spinner';
|
|
6
|
+
const LOAD_MORE_THRESHOLD = 24;
|
|
5
7
|
const CREATE_KEY = '__create__';
|
|
6
8
|
export function SuggestionsDropdown({
|
|
7
9
|
floatingRef,
|
|
8
10
|
floatingStyles,
|
|
9
11
|
items,
|
|
10
12
|
emptyText = 'No options',
|
|
13
|
+
loading,
|
|
14
|
+
loadingText = 'Loading…',
|
|
15
|
+
loadingMore,
|
|
16
|
+
onLoadMore,
|
|
11
17
|
createLabel,
|
|
12
18
|
createActive,
|
|
13
19
|
onCreate,
|
|
@@ -16,6 +22,11 @@ export function SuggestionsDropdown({
|
|
|
16
22
|
zIndex
|
|
17
23
|
}) {
|
|
18
24
|
const activeItemRef = useRef(null);
|
|
25
|
+
const onScroll = event => {
|
|
26
|
+
if (!onLoadMore) return;
|
|
27
|
+
const el = event.currentTarget;
|
|
28
|
+
if (el.scrollHeight - el.scrollTop - el.clientHeight <= LOAD_MORE_THRESHOLD) onLoadMore();
|
|
29
|
+
};
|
|
19
30
|
const activeItem = items.find(item => item.active);
|
|
20
31
|
const activeKey = createActive ? CREATE_KEY : (activeItem === null || activeItem === void 0 ? void 0 : activeItem.key) ?? null;
|
|
21
32
|
useEffect(() => {
|
|
@@ -26,7 +37,7 @@ export function SuggestionsDropdown({
|
|
|
26
37
|
});
|
|
27
38
|
}, [activeKey]);
|
|
28
39
|
if (typeof document === 'undefined') return null;
|
|
29
|
-
const isEmpty = items.length === 0 && !createLabel;
|
|
40
|
+
const isEmpty = !loading && items.length === 0 && !createLabel;
|
|
30
41
|
return createPortal(_jsxs(Dropdown, {
|
|
31
42
|
ref: floatingRef,
|
|
32
43
|
style: {
|
|
@@ -35,7 +46,10 @@ export function SuggestionsDropdown({
|
|
|
35
46
|
},
|
|
36
47
|
id: listboxId,
|
|
37
48
|
role: "listbox",
|
|
38
|
-
|
|
49
|
+
onScroll: onScroll,
|
|
50
|
+
children: [loading && _jsx(DropdownEmpty, {
|
|
51
|
+
children: loadingText
|
|
52
|
+
}), !loading && createLabel && _jsx(DropdownItem, {
|
|
39
53
|
ref: createActive ? activeItemRef : undefined,
|
|
40
54
|
id: createId,
|
|
41
55
|
role: "option",
|
|
@@ -48,7 +62,7 @@ export function SuggestionsDropdown({
|
|
|
48
62
|
children: _jsx(CreateItemLabel, {
|
|
49
63
|
children: createLabel
|
|
50
64
|
})
|
|
51
|
-
}), items.map(item => _jsx(DropdownItem, {
|
|
65
|
+
}), !loading && items.map(item => _jsx(DropdownItem, {
|
|
52
66
|
ref: item.active ? activeItemRef : undefined,
|
|
53
67
|
id: item.id,
|
|
54
68
|
role: "option",
|
|
@@ -61,7 +75,11 @@ export function SuggestionsDropdown({
|
|
|
61
75
|
(_item$onSelect = item.onSelect) === null || _item$onSelect === void 0 || _item$onSelect.call(item);
|
|
62
76
|
},
|
|
63
77
|
children: item.label
|
|
64
|
-
}, item.key)),
|
|
78
|
+
}, item.key)), !loading && loadingMore && _jsx(DropdownLoadingMore, {
|
|
79
|
+
children: _jsx(Spinner, {
|
|
80
|
+
size: "small"
|
|
81
|
+
})
|
|
82
|
+
}), isEmpty && _jsx(DropdownEmpty, {
|
|
65
83
|
children: emptyText
|
|
66
84
|
})]
|
|
67
85
|
}), document.body);
|
|
@@ -6,7 +6,8 @@ export { useDebouncedValue } from './useDebouncedValue';
|
|
|
6
6
|
export { useAsyncItems } from './useAsyncItems';
|
|
7
7
|
export { useDropdownItems } from './useDropdownItems';
|
|
8
8
|
export { useActiveIndex } from './useActiveIndex';
|
|
9
|
+
export { useFieldOpenEffects } from './useFieldOpenEffects';
|
|
9
10
|
export { useListNavigation } from './useListNavigation';
|
|
10
|
-
export { defaultGetKey, defaultLabelFilter, useShellMouseDown } from './shared';
|
|
11
|
+
export { defaultGetKey, defaultLabelFilter, resolveDropdownText, useShellMouseDown, } from './shared';
|
|
11
12
|
export { DEFAULT_DEBOUNCE_MS } from './constants';
|
|
12
|
-
export type { ItemState, DropdownItemDescriptor, GetKeyProp, ItemsSource, } from './types';
|
|
13
|
+
export type { ItemState, DropdownItemDescriptor, DropdownText, GetKeyProp, ItemsSource, LoadItems, LoadItemsResult, } from './types';
|
|
@@ -6,6 +6,7 @@ export { useDebouncedValue } from './useDebouncedValue';
|
|
|
6
6
|
export { useAsyncItems } from './useAsyncItems';
|
|
7
7
|
export { useDropdownItems } from './useDropdownItems';
|
|
8
8
|
export { useActiveIndex } from './useActiveIndex';
|
|
9
|
+
export { useFieldOpenEffects } from './useFieldOpenEffects';
|
|
9
10
|
export { useListNavigation } from './useListNavigation';
|
|
10
|
-
export { defaultGetKey, defaultLabelFilter, useShellMouseDown } from './shared';
|
|
11
|
+
export { defaultGetKey, defaultLabelFilter, resolveDropdownText, useShellMouseDown } from './shared';
|
|
11
12
|
export { DEFAULT_DEBOUNCE_MS } from './constants';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { type MouseEvent, type RefObject } from 'react';
|
|
1
|
+
import { type MouseEvent, type ReactNode, type RefObject } from 'react';
|
|
2
|
+
import { type DropdownText } from './types';
|
|
3
|
+
export declare function resolveDropdownText(text: DropdownText, query: string): ReactNode;
|
|
2
4
|
export declare function defaultGetKey<T>(item: T): string | number;
|
|
3
5
|
export declare function defaultLabelFilter<T>(item: T, query: string, getLabel: (item: T) => string): boolean;
|
|
4
6
|
export declare function useShellMouseDown(inputRef: RefObject<HTMLInputElement>, isOpen: boolean, open: () => void): (event: MouseEvent<HTMLDivElement>) => void;
|
|
@@ -21,4 +21,5 @@ export declare const DropdownItem: import("styled-components").StyledComponent<"
|
|
|
21
21
|
$selected?: boolean;
|
|
22
22
|
}, never>;
|
|
23
23
|
export declare const DropdownEmpty: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
24
|
+
export declare const DropdownLoadingMore: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
24
25
|
export declare const CreateItemLabel: import("styled-components").StyledComponent<"span", any, {}, never>;
|
|
@@ -75,7 +75,11 @@ export const DropdownEmpty = styled.div.withConfig({
|
|
|
75
75
|
displayName: "DropdownEmpty",
|
|
76
76
|
componentId: "sc-1pvwhhw-8"
|
|
77
77
|
})(["padding:6px 12px;", ";color:", ";"], textBase, Color.PHANTOM);
|
|
78
|
+
export const DropdownLoadingMore = styled.div.withConfig({
|
|
79
|
+
displayName: "DropdownLoadingMore",
|
|
80
|
+
componentId: "sc-1pvwhhw-9"
|
|
81
|
+
})(["display:flex;align-items:center;justify-content:center;padding:8px 12px;"]);
|
|
78
82
|
export const CreateItemLabel = styled.span.withConfig({
|
|
79
83
|
displayName: "CreateItemLabel",
|
|
80
|
-
componentId: "sc-1pvwhhw-
|
|
84
|
+
componentId: "sc-1pvwhhw-10"
|
|
81
85
|
})(["color:", ";"], Color.PRIMARY);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
export type DropdownText = ReactNode | ((query: string) => ReactNode);
|
|
2
3
|
export type ItemState = {
|
|
3
4
|
selected: boolean;
|
|
4
5
|
active: boolean;
|
|
@@ -8,14 +9,21 @@ export type GetKeyProp<T> = [T] extends [string | number] ? {
|
|
|
8
9
|
} : {
|
|
9
10
|
getKey: (item: T) => string | number;
|
|
10
11
|
};
|
|
12
|
+
export type LoadItemsResult<T> = {
|
|
13
|
+
items: T[];
|
|
14
|
+
hasMore?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type LoadItems<T> = (query: string, page: number) => Promise<LoadItemsResult<T>>;
|
|
11
17
|
export type ItemsSource<T> = {
|
|
12
18
|
items: T[];
|
|
13
19
|
loadItems?: never;
|
|
14
20
|
debounceMs?: never;
|
|
21
|
+
minChars?: never;
|
|
15
22
|
} | {
|
|
16
23
|
items?: never;
|
|
17
|
-
loadItems:
|
|
24
|
+
loadItems: LoadItems<T>;
|
|
18
25
|
debounceMs?: number;
|
|
26
|
+
minChars?: number;
|
|
19
27
|
};
|
|
20
28
|
export type DropdownItemDescriptor = {
|
|
21
29
|
key: string | number;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import { type LoadItems } from './types';
|
|
1
2
|
type Args<T> = {
|
|
2
|
-
loadItems?:
|
|
3
|
+
loadItems?: LoadItems<T>;
|
|
3
4
|
query: string;
|
|
4
5
|
isOpen: boolean;
|
|
5
6
|
debounceMs?: number;
|
|
7
|
+
minChars?: number;
|
|
6
8
|
};
|
|
7
|
-
|
|
9
|
+
type Result<T> = {
|
|
8
10
|
items: T[];
|
|
9
11
|
loading: boolean;
|
|
12
|
+
loadingMore: boolean;
|
|
13
|
+
loadMore: () => void;
|
|
10
14
|
};
|
|
15
|
+
export declare function useAsyncItems<T>({ loadItems, query, isOpen, debounceMs, minChars, }: Args<T>): Result<T>;
|
|
11
16
|
export {};
|
|
@@ -1,46 +1,78 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { DEFAULT_DEBOUNCE_MS } from './constants';
|
|
3
3
|
import { useDebouncedValue } from './useDebouncedValue';
|
|
4
4
|
export function useAsyncItems({
|
|
5
5
|
loadItems,
|
|
6
6
|
query,
|
|
7
7
|
isOpen,
|
|
8
|
-
debounceMs = DEFAULT_DEBOUNCE_MS
|
|
8
|
+
debounceMs = DEFAULT_DEBOUNCE_MS,
|
|
9
|
+
minChars = 0
|
|
9
10
|
}) {
|
|
10
11
|
const [items, setItems] = useState([]);
|
|
11
12
|
const [loading, setLoading] = useState(false);
|
|
13
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
12
14
|
const debouncedQuery = useDebouncedValue(query, debounceMs);
|
|
13
15
|
const loadItemsRef = useRef(loadItems);
|
|
14
16
|
loadItemsRef.current = loadItems;
|
|
15
|
-
const
|
|
17
|
+
const sessionRef = useRef(0);
|
|
18
|
+
const pageRef = useRef(0);
|
|
19
|
+
const queryRef = useRef('');
|
|
20
|
+
const loadingRef = useRef(false);
|
|
21
|
+
const hasMoreRef = useRef(false);
|
|
16
22
|
useEffect(() => {
|
|
17
23
|
const loader = loadItemsRef.current;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
const belowMin = debouncedQuery.trim().length < minChars;
|
|
25
|
+
sessionRef.current += 1;
|
|
26
|
+
pageRef.current = 0;
|
|
27
|
+
loadingRef.current = false;
|
|
28
|
+
hasMoreRef.current = false;
|
|
29
|
+
setLoadingMore(false);
|
|
30
|
+
if (!loader || !isOpen || belowMin) {
|
|
23
31
|
setLoading(false);
|
|
32
|
+
if (!loader || belowMin) setItems([]);
|
|
24
33
|
return undefined;
|
|
25
34
|
}
|
|
26
|
-
|
|
35
|
+
const session = sessionRef.current;
|
|
36
|
+
queryRef.current = debouncedQuery;
|
|
37
|
+
loadingRef.current = true;
|
|
27
38
|
setLoading(true);
|
|
28
|
-
Promise.resolve(loader(debouncedQuery)).then(result => {
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
Promise.resolve(loader(debouncedQuery, 0)).then(result => {
|
|
40
|
+
if (session !== sessionRef.current) return;
|
|
41
|
+
setItems(result.items);
|
|
42
|
+
hasMoreRef.current = result.hasMore ?? false;
|
|
43
|
+
loadingRef.current = false;
|
|
32
44
|
setLoading(false);
|
|
33
45
|
}).catch(() => {
|
|
34
|
-
if (
|
|
46
|
+
if (session !== sessionRef.current) return;
|
|
47
|
+
loadingRef.current = false;
|
|
35
48
|
setLoading(false);
|
|
36
49
|
});
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
return undefined;
|
|
51
|
+
}, [debouncedQuery, isOpen, minChars]);
|
|
52
|
+
const loadMore = useCallback(() => {
|
|
53
|
+
const loader = loadItemsRef.current;
|
|
54
|
+
if (!loader || loadingRef.current || !hasMoreRef.current) return;
|
|
55
|
+
const session = sessionRef.current;
|
|
56
|
+
const nextPage = pageRef.current + 1;
|
|
57
|
+
loadingRef.current = true;
|
|
58
|
+
setLoadingMore(true);
|
|
59
|
+
Promise.resolve(loader(queryRef.current, nextPage)).then(result => {
|
|
60
|
+
if (session !== sessionRef.current) return;
|
|
61
|
+
pageRef.current = nextPage;
|
|
62
|
+
setItems(prev => [...prev, ...result.items]);
|
|
63
|
+
hasMoreRef.current = result.hasMore ?? false;
|
|
64
|
+
loadingRef.current = false;
|
|
65
|
+
setLoadingMore(false);
|
|
66
|
+
}).catch(() => {
|
|
67
|
+
if (session !== sessionRef.current) return;
|
|
68
|
+
loadingRef.current = false;
|
|
69
|
+
setLoadingMore(false);
|
|
70
|
+
});
|
|
71
|
+
}, []);
|
|
42
72
|
return {
|
|
43
73
|
items,
|
|
44
|
-
loading
|
|
74
|
+
loading,
|
|
75
|
+
loadingMore,
|
|
76
|
+
loadMore
|
|
45
77
|
};
|
|
46
78
|
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
+
import { type LoadItems } from './types';
|
|
1
2
|
type Args<T> = {
|
|
2
3
|
items?: T[];
|
|
3
|
-
loadItems?:
|
|
4
|
+
loadItems?: LoadItems<T>;
|
|
4
5
|
localItems?: T[];
|
|
5
6
|
debounceMs?: number;
|
|
7
|
+
minChars?: number;
|
|
6
8
|
getLabel: (item: T) => string;
|
|
7
9
|
filter?: (item: T, query: string) => boolean;
|
|
8
10
|
query: string;
|
|
9
11
|
isOpen: boolean;
|
|
10
12
|
};
|
|
11
|
-
|
|
13
|
+
type Result<T> = {
|
|
12
14
|
sourceItems: T[];
|
|
13
15
|
filteredItems: T[];
|
|
14
16
|
loading: boolean;
|
|
17
|
+
loadingMore: boolean;
|
|
18
|
+
loadMore: () => void;
|
|
15
19
|
};
|
|
20
|
+
export declare function useDropdownItems<T>({ items, loadItems, localItems, debounceMs, minChars, getLabel, filter, query, isOpen, }: Args<T>): Result<T>;
|
|
16
21
|
export {};
|
|
@@ -7,6 +7,7 @@ export function useDropdownItems({
|
|
|
7
7
|
loadItems,
|
|
8
8
|
localItems,
|
|
9
9
|
debounceMs = DEFAULT_DEBOUNCE_MS,
|
|
10
|
+
minChars,
|
|
10
11
|
getLabel,
|
|
11
12
|
filter,
|
|
12
13
|
query,
|
|
@@ -15,12 +16,15 @@ export function useDropdownItems({
|
|
|
15
16
|
const isAsync = !!loadItems;
|
|
16
17
|
const {
|
|
17
18
|
items: loadedItems,
|
|
18
|
-
loading
|
|
19
|
+
loading,
|
|
20
|
+
loadingMore,
|
|
21
|
+
loadMore
|
|
19
22
|
} = useAsyncItems({
|
|
20
23
|
loadItems,
|
|
21
24
|
query,
|
|
22
25
|
isOpen,
|
|
23
|
-
debounceMs
|
|
26
|
+
debounceMs,
|
|
27
|
+
minChars
|
|
24
28
|
});
|
|
25
29
|
const sourceItems = useMemo(() => isAsync ? loadedItems : [...(items ?? []), ...(localItems ?? [])], [isAsync, loadedItems, items, localItems]);
|
|
26
30
|
const filteredItems = useMemo(() => {
|
|
@@ -31,6 +35,8 @@ export function useDropdownItems({
|
|
|
31
35
|
return {
|
|
32
36
|
sourceItems,
|
|
33
37
|
filteredItems,
|
|
34
|
-
loading
|
|
38
|
+
loading,
|
|
39
|
+
loadingMore,
|
|
40
|
+
loadMore
|
|
35
41
|
};
|
|
36
42
|
}
|
|
@@ -5,6 +5,7 @@ type Args = {
|
|
|
5
5
|
export declare function useDropdownPosition({ isOpen, onClickOutside }: Args): {
|
|
6
6
|
setReference: (node: import("@floating-ui/react-dom").ReferenceType | null) => void;
|
|
7
7
|
setFloating: (node: HTMLElement | null) => void;
|
|
8
|
+
floatingRef: import("react").MutableRefObject<HTMLElement | null>;
|
|
8
9
|
floatingStyles: import("react").CSSProperties;
|
|
9
10
|
};
|
|
10
11
|
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
type Args = {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
open: () => void;
|
|
5
|
+
onClose?: () => void;
|
|
6
|
+
autoFocus?: boolean;
|
|
7
|
+
inputRef: RefObject<HTMLInputElement>;
|
|
8
|
+
};
|
|
9
|
+
export declare function useFieldOpenEffects({ isOpen, open, onClose, autoFocus, inputRef, }: Args): void;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
export function useFieldOpenEffects({
|
|
3
|
+
isOpen,
|
|
4
|
+
open,
|
|
5
|
+
onClose,
|
|
6
|
+
autoFocus,
|
|
7
|
+
inputRef
|
|
8
|
+
}) {
|
|
9
|
+
const onCloseRef = useRef(onClose);
|
|
10
|
+
onCloseRef.current = onClose;
|
|
11
|
+
const wasOpenRef = useRef(isOpen);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
var _onCloseRef$current;
|
|
14
|
+
if (wasOpenRef.current && !isOpen) (_onCloseRef$current = onCloseRef.current) === null || _onCloseRef$current === void 0 || _onCloseRef$current.call(onCloseRef);
|
|
15
|
+
wasOpenRef.current = isOpen;
|
|
16
|
+
}, [isOpen]);
|
|
17
|
+
const didAutoFocusRef = useRef(false);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
var _inputRef$current;
|
|
20
|
+
if (!autoFocus || didAutoFocusRef.current) return;
|
|
21
|
+
didAutoFocusRef.current = true;
|
|
22
|
+
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
|
|
23
|
+
open();
|
|
24
|
+
}, [autoFocus, open, inputRef]);
|
|
25
|
+
}
|
|
@@ -7,10 +7,11 @@ type Args = {
|
|
|
7
7
|
setActiveIndex: Dispatch<SetStateAction<number>>;
|
|
8
8
|
onSelectIndex: (index: number) => void;
|
|
9
9
|
onEscape: () => void;
|
|
10
|
+
onReachEnd?: () => void;
|
|
10
11
|
hasExtraRow?: boolean;
|
|
11
12
|
onSelectExtra?: () => void;
|
|
12
13
|
isQueryEmpty?: boolean;
|
|
13
14
|
onBackspaceEmpty?: () => void;
|
|
14
15
|
};
|
|
15
|
-
export declare function useListNavigation({ isOpen, open, itemCount, activeIndex, setActiveIndex, onSelectIndex, onEscape, hasExtraRow, onSelectExtra, isQueryEmpty, onBackspaceEmpty, }: Args): (event: KeyboardEvent<HTMLInputElement>) => void;
|
|
16
|
+
export declare function useListNavigation({ isOpen, open, itemCount, activeIndex, setActiveIndex, onSelectIndex, onEscape, onReachEnd, hasExtraRow, onSelectExtra, isQueryEmpty, onBackspaceEmpty, }: Args): (event: KeyboardEvent<HTMLInputElement>) => void;
|
|
16
17
|
export {};
|
|
@@ -7,6 +7,7 @@ export function useListNavigation({
|
|
|
7
7
|
setActiveIndex,
|
|
8
8
|
onSelectIndex,
|
|
9
9
|
onEscape,
|
|
10
|
+
onReachEnd,
|
|
10
11
|
hasExtraRow,
|
|
11
12
|
onSelectExtra,
|
|
12
13
|
isQueryEmpty,
|
|
@@ -16,7 +17,15 @@ export function useListNavigation({
|
|
|
16
17
|
return useCallback(event => {
|
|
17
18
|
if (event.key === 'ArrowDown') {
|
|
18
19
|
event.preventDefault();
|
|
19
|
-
if (!isOpen)
|
|
20
|
+
if (!isOpen) {
|
|
21
|
+
open();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (total > 0) {
|
|
25
|
+
const next = activeIndex >= total - 1 ? 0 : activeIndex + 1;
|
|
26
|
+
setActiveIndex(next);
|
|
27
|
+
if (next > 0 && next >= total - 1) onReachEnd === null || onReachEnd === void 0 || onReachEnd();
|
|
28
|
+
}
|
|
20
29
|
return;
|
|
21
30
|
}
|
|
22
31
|
if (event.key === 'ArrowUp') {
|
|
@@ -41,5 +50,5 @@ export function useListNavigation({
|
|
|
41
50
|
if (event.key === 'Backspace' && isQueryEmpty && onBackspaceEmpty) {
|
|
42
51
|
onBackspaceEmpty();
|
|
43
52
|
}
|
|
44
|
-
}, [isOpen, open, total, activeIndex, setActiveIndex, onSelectIndex, onSelectExtra, onEscape, hasExtraRow, isQueryEmpty, onBackspaceEmpty]);
|
|
53
|
+
}, [isOpen, open, total, activeIndex, setActiveIndex, onSelectIndex, onSelectExtra, onEscape, onReachEnd, hasExtraRow, isQueryEmpty, onBackspaceEmpty]);
|
|
45
54
|
}
|