@pushwoosh/dumb-components 1.1.140 → 1.1.142

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.
Files changed (47) hide show
  1. package/Combobox/Combobox.d.ts +4 -0
  2. package/Combobox/Combobox.js +272 -0
  3. package/Combobox/index.d.ts +2 -0
  4. package/Combobox/index.js +1 -0
  5. package/Combobox/types.d.ts +40 -0
  6. package/Combobox/types.js +1 -0
  7. package/ComboboxMulti/ComboboxMulti.d.ts +3 -0
  8. package/ComboboxMulti/ComboboxMulti.js +203 -0
  9. package/ComboboxMulti/index.d.ts +2 -0
  10. package/ComboboxMulti/index.js +1 -0
  11. package/ComboboxMulti/types.d.ts +34 -0
  12. package/ComboboxMulti/types.js +1 -0
  13. package/hooks/useLayer.js +41 -14
  14. package/hooks/useOnClickOutside.js +6 -5
  15. package/index.d.ts +2 -0
  16. package/index.js +2 -0
  17. package/package.json +2 -2
  18. package/shared/dropdownField/FieldInput.d.ts +5 -0
  19. package/shared/dropdownField/FieldInput.js +43 -0
  20. package/shared/dropdownField/FieldShell.d.ts +30 -0
  21. package/shared/dropdownField/FieldShell.js +110 -0
  22. package/shared/dropdownField/SuggestionsDropdown.d.ts +16 -0
  23. package/shared/dropdownField/SuggestionsDropdown.js +68 -0
  24. package/shared/dropdownField/constants.d.ts +6 -0
  25. package/shared/dropdownField/constants.js +6 -0
  26. package/shared/dropdownField/index.d.ts +13 -0
  27. package/shared/dropdownField/index.js +12 -0
  28. package/shared/dropdownField/shared.d.ts +4 -0
  29. package/shared/dropdownField/shared.js +18 -0
  30. package/shared/dropdownField/styles.d.ts +24 -0
  31. package/shared/dropdownField/styles.js +81 -0
  32. package/shared/dropdownField/types.d.ts +27 -0
  33. package/shared/dropdownField/types.js +1 -0
  34. package/shared/dropdownField/useActiveIndex.d.ts +2 -0
  35. package/shared/dropdownField/useActiveIndex.js +12 -0
  36. package/shared/dropdownField/useAsyncItems.d.ts +11 -0
  37. package/shared/dropdownField/useAsyncItems.js +46 -0
  38. package/shared/dropdownField/useDebouncedValue.d.ts +1 -0
  39. package/shared/dropdownField/useDebouncedValue.js +13 -0
  40. package/shared/dropdownField/useDropdownItems.d.ts +16 -0
  41. package/shared/dropdownField/useDropdownItems.js +36 -0
  42. package/shared/dropdownField/useDropdownPosition.d.ts +11 -0
  43. package/shared/dropdownField/useDropdownPosition.js +41 -0
  44. package/shared/dropdownField/useFieldOpenEffects.d.ts +10 -0
  45. package/shared/dropdownField/useFieldOpenEffects.js +25 -0
  46. package/shared/dropdownField/useListNavigation.d.ts +16 -0
  47. package/shared/dropdownField/useListNavigation.js +45 -0
@@ -0,0 +1,4 @@
1
+ import { type ReactElement } from 'react';
2
+ import { type FreeComboboxProps, type SelectComboboxProps } from './types';
3
+ export declare function Combobox(props: FreeComboboxProps): ReactElement;
4
+ export declare function Combobox<T>(props: SelectComboboxProps<T>): ReactElement;
@@ -0,0 +1,272 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useId, useMemo, useRef, useState } from 'react';
3
+ import { DEFAULT_DEBOUNCE_MS, FieldInput, FieldShell, defaultGetKey, useActiveIndex, useDropdownItems, useFieldOpenEffects, useListNavigation, useShellMouseDown } from '../shared/dropdownField';
4
+ function FreeCombobox({
5
+ items,
6
+ loadItems,
7
+ debounceMs = DEFAULT_DEBOUNCE_MS,
8
+ value,
9
+ onChange,
10
+ onClose,
11
+ autoFocus,
12
+ getLabel: getLabelProp,
13
+ filter,
14
+ renderItem,
15
+ renderEmpty,
16
+ placeholder,
17
+ disabled,
18
+ loading,
19
+ clearable,
20
+ autosize,
21
+ $isErrored,
22
+ width,
23
+ minWidth
24
+ }) {
25
+ const inputRef = useRef(null);
26
+ const baseId = useId();
27
+ const listboxId = `${baseId}-list`;
28
+ const getLabel = useMemo(() => getLabelProp ?? (item => item), [getLabelProp]);
29
+ const [isOpen, setIsOpen] = useState(false);
30
+ const {
31
+ filteredItems,
32
+ loading: asyncLoading
33
+ } = useDropdownItems({
34
+ items,
35
+ loadItems,
36
+ debounceMs,
37
+ getLabel,
38
+ filter,
39
+ query: value,
40
+ isOpen
41
+ });
42
+ const [activeIndex, setActiveIndex] = useActiveIndex(filteredItems.length, -1);
43
+ const open = useCallback(() => setIsOpen(true), []);
44
+ const close = useCallback(() => {
45
+ setIsOpen(false);
46
+ setActiveIndex(-1);
47
+ }, [setActiveIndex]);
48
+ const pick = useCallback(item => {
49
+ onChange(getLabel(item));
50
+ setIsOpen(false);
51
+ setActiveIndex(-1);
52
+ }, [onChange, getLabel, setActiveIndex]);
53
+ const onInputKeyDown = useListNavigation({
54
+ isOpen,
55
+ open,
56
+ itemCount: filteredItems.length,
57
+ activeIndex,
58
+ setActiveIndex,
59
+ onSelectIndex: index => pick(filteredItems[index]),
60
+ onEscape: close
61
+ });
62
+ const onShellMouseDown = useShellMouseDown(inputRef, isOpen, open);
63
+ useFieldOpenEffects({
64
+ isOpen,
65
+ open,
66
+ onClose,
67
+ autoFocus,
68
+ inputRef
69
+ });
70
+ const dropdownItems = useMemo(() => filteredItems.map((item, idx) => {
71
+ const selected = getLabel(item) === value;
72
+ const state = {
73
+ active: idx === activeIndex,
74
+ selected
75
+ };
76
+ return {
77
+ key: item,
78
+ id: `${baseId}-opt-${item}`,
79
+ label: renderItem ? renderItem(item, state) : getLabel(item),
80
+ active: state.active,
81
+ selected,
82
+ onSelect: () => pick(item)
83
+ };
84
+ }), [filteredItems, activeIndex, value, getLabel, renderItem, pick, baseId]);
85
+ const dropdownVisible = isOpen && filteredItems.length > 0;
86
+ const activeDescendant = activeIndex >= 0 && activeIndex < filteredItems.length ? `${baseId}-opt-${filteredItems[activeIndex]}` : undefined;
87
+ const onInputChange = event => {
88
+ onChange(event.target.value);
89
+ if (!isOpen) open();
90
+ };
91
+ return _jsx(FieldShell, {
92
+ isOpen: isOpen,
93
+ dropdownOpen: dropdownVisible,
94
+ isErrored: $isErrored,
95
+ disabled: disabled,
96
+ loading: loading || asyncLoading,
97
+ width: width,
98
+ minWidth: minWidth,
99
+ autosize: autosize,
100
+ onShellMouseDown: onShellMouseDown,
101
+ onClose: close,
102
+ showClear: !!(clearable && value),
103
+ onClear: () => {
104
+ var _inputRef$current;
105
+ onChange('');
106
+ (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
107
+ },
108
+ dropdownItems: dropdownItems,
109
+ emptyText: renderEmpty ? renderEmpty(value) : undefined,
110
+ listboxId: listboxId,
111
+ children: _jsx(FieldInput, {
112
+ ref: inputRef,
113
+ autosize: autosize,
114
+ value: value,
115
+ onChange: onInputChange,
116
+ onKeyDown: onInputKeyDown,
117
+ onFocus: open,
118
+ placeholder: placeholder,
119
+ disabled: disabled,
120
+ role: "combobox",
121
+ "aria-expanded": dropdownVisible,
122
+ "aria-controls": listboxId,
123
+ "aria-autocomplete": "list",
124
+ "aria-activedescendant": activeDescendant
125
+ })
126
+ });
127
+ }
128
+ function SelectCombobox({
129
+ items,
130
+ loadItems,
131
+ debounceMs = DEFAULT_DEBOUNCE_MS,
132
+ value,
133
+ onChange,
134
+ onClose,
135
+ autoFocus,
136
+ getLabel,
137
+ getKey: getKeyProp,
138
+ filter,
139
+ renderItem,
140
+ renderEmpty,
141
+ placeholder,
142
+ disabled,
143
+ loading,
144
+ clearable,
145
+ autosize,
146
+ $isErrored,
147
+ width,
148
+ minWidth
149
+ }) {
150
+ const inputRef = useRef(null);
151
+ const baseId = useId();
152
+ const listboxId = `${baseId}-list`;
153
+ const optionId = key => `${baseId}-opt-${key}`;
154
+ const getKey = getKeyProp ?? defaultGetKey;
155
+ const [isOpen, setIsOpen] = useState(false);
156
+ const [query, setQuery] = useState('');
157
+ const {
158
+ sourceItems,
159
+ filteredItems,
160
+ loading: asyncLoading
161
+ } = useDropdownItems({
162
+ items,
163
+ loadItems,
164
+ debounceMs,
165
+ getLabel,
166
+ filter,
167
+ query,
168
+ isOpen
169
+ });
170
+ const [activeIndex, setActiveIndex] = useActiveIndex(filteredItems.length, 0);
171
+ const hasValue = value !== null && value !== undefined;
172
+ const valueLabel = hasValue ? getLabel(value) : '';
173
+ const selectedKey = hasValue ? getKey(value) : null;
174
+ const inputValue = isOpen ? query : valueLabel;
175
+ const open = useCallback(() => {
176
+ if (isOpen) return;
177
+ setIsOpen(true);
178
+ setQuery('');
179
+ const idx = sourceItems.findIndex(it => getKey(it) === selectedKey);
180
+ setActiveIndex(idx >= 0 ? idx : 0);
181
+ }, [isOpen, sourceItems, getKey, selectedKey, setActiveIndex]);
182
+ const close = useCallback(() => {
183
+ setIsOpen(false);
184
+ setQuery('');
185
+ }, []);
186
+ const commit = useCallback(item => {
187
+ var _inputRef$current2;
188
+ onChange(item);
189
+ setIsOpen(false);
190
+ setQuery('');
191
+ setActiveIndex(0);
192
+ (_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 || _inputRef$current2.blur();
193
+ }, [onChange, setActiveIndex]);
194
+ const onInputKeyDown = useListNavigation({
195
+ isOpen,
196
+ open,
197
+ itemCount: filteredItems.length,
198
+ activeIndex,
199
+ setActiveIndex,
200
+ onSelectIndex: index => commit(filteredItems[index]),
201
+ onEscape: close
202
+ });
203
+ const onShellMouseDown = useShellMouseDown(inputRef, isOpen, open);
204
+ useFieldOpenEffects({
205
+ isOpen,
206
+ open,
207
+ onClose,
208
+ autoFocus,
209
+ inputRef
210
+ });
211
+ const dropdownItems = useMemo(() => filteredItems.map((item, idx) => {
212
+ const key = getKey(item);
213
+ const selected = key === selectedKey;
214
+ const state = {
215
+ active: idx === activeIndex,
216
+ selected
217
+ };
218
+ return {
219
+ key,
220
+ id: `${baseId}-opt-${key}`,
221
+ label: renderItem ? renderItem(item, state) : getLabel(item),
222
+ active: state.active,
223
+ selected,
224
+ onSelect: () => commit(item)
225
+ };
226
+ }), [filteredItems, activeIndex, selectedKey, getLabel, getKey, renderItem, commit, baseId]);
227
+ const activeDescendant = activeIndex >= 0 && activeIndex < filteredItems.length ? optionId(getKey(filteredItems[activeIndex])) : undefined;
228
+ return _jsx(FieldShell, {
229
+ isOpen: isOpen,
230
+ dropdownOpen: isOpen,
231
+ isErrored: $isErrored,
232
+ disabled: disabled,
233
+ loading: loading || asyncLoading,
234
+ width: width,
235
+ minWidth: minWidth,
236
+ autosize: autosize,
237
+ onShellMouseDown: onShellMouseDown,
238
+ onClose: close,
239
+ showClear: !!(clearable && hasValue),
240
+ onClear: () => {
241
+ var _inputRef$current3;
242
+ onChange(null);
243
+ (_inputRef$current3 = inputRef.current) === null || _inputRef$current3 === void 0 || _inputRef$current3.focus();
244
+ },
245
+ dropdownItems: dropdownItems,
246
+ emptyText: renderEmpty ? renderEmpty(query) : undefined,
247
+ listboxId: listboxId,
248
+ children: _jsx(FieldInput, {
249
+ ref: inputRef,
250
+ autosize: autosize,
251
+ value: inputValue,
252
+ onChange: event => setQuery(event.target.value),
253
+ onKeyDown: onInputKeyDown,
254
+ onFocus: open,
255
+ placeholder: placeholder,
256
+ disabled: disabled,
257
+ role: "combobox",
258
+ "aria-expanded": isOpen,
259
+ "aria-controls": listboxId,
260
+ "aria-autocomplete": "list",
261
+ "aria-activedescendant": activeDescendant
262
+ })
263
+ });
264
+ }
265
+ export function Combobox(props) {
266
+ if (props.select) return _jsx(SelectCombobox, {
267
+ ...props
268
+ });
269
+ return _jsx(FreeCombobox, {
270
+ ...props
271
+ });
272
+ }
@@ -0,0 +1,2 @@
1
+ export { Combobox } from './Combobox';
2
+ export type { ComboboxProps, FreeComboboxProps, SelectComboboxProps, } from './types';
@@ -0,0 +1 @@
1
+ export { Combobox } from './Combobox';
@@ -0,0 +1,40 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { GetKeyProp, ItemState } from '../shared/dropdownField';
3
+ type ComboboxSource<T> = {
4
+ items?: T[];
5
+ loadItems?: never;
6
+ debounceMs?: never;
7
+ } | {
8
+ items?: never;
9
+ loadItems: (query: string) => Promise<T[]>;
10
+ debounceMs?: number;
11
+ };
12
+ type CommonProps<T> = {
13
+ filter?: (item: T, query: string) => boolean;
14
+ renderItem?: (item: T, state: ItemState) => ReactNode;
15
+ renderEmpty?: (query: string) => ReactNode;
16
+ onClose?: () => void;
17
+ autoFocus?: boolean;
18
+ placeholder?: string;
19
+ disabled?: boolean;
20
+ loading?: boolean;
21
+ clearable?: boolean;
22
+ autosize?: boolean;
23
+ $isErrored?: boolean;
24
+ width?: string;
25
+ minWidth?: string;
26
+ };
27
+ export type FreeComboboxProps = ComboboxSource<string> & CommonProps<string> & {
28
+ select?: false;
29
+ value: string;
30
+ onChange: (value: string) => void;
31
+ getLabel?: (item: string) => string;
32
+ };
33
+ export type SelectComboboxProps<T> = ComboboxSource<T> & GetKeyProp<T> & CommonProps<T> & {
34
+ select: true;
35
+ value: T | null;
36
+ onChange: (value: T | null) => void;
37
+ getLabel: (item: T) => string;
38
+ };
39
+ export type ComboboxProps<T> = FreeComboboxProps | SelectComboboxProps<T>;
40
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { type ReactElement } from 'react';
2
+ import { type ComboboxMultiProps } from './types';
3
+ export declare function ComboboxMulti<T>({ items, loadItems, debounceMs, value, onChange, onClose, autoFocus, getLabel, getKey: getKeyProp, filter, renderItem, renderEmpty, creatable, onCreate, canCreate, renderCreateLabel, maxValues, placeholder, disabled, loading, clearable, autosize, $isErrored, width, minWidth, }: ComboboxMultiProps<T>): ReactElement;
@@ -0,0 +1,203 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useId, useMemo, useRef, useState } from 'react';
3
+ import { Chip } from '../Chip';
4
+ import { DEFAULT_DEBOUNCE_MS, FieldInput, FieldShell, defaultGetKey, useActiveIndex, useDropdownItems, useFieldOpenEffects, useListNavigation, useShellMouseDown } from '../shared/dropdownField';
5
+ function defaultCanCreate(query, items, getLabel) {
6
+ const trimmed = query.trim();
7
+ if (!trimmed) return false;
8
+ const lower = trimmed.toLowerCase();
9
+ return !items.some(item => getLabel(item).toLowerCase() === lower);
10
+ }
11
+ export function ComboboxMulti({
12
+ items,
13
+ loadItems,
14
+ debounceMs = DEFAULT_DEBOUNCE_MS,
15
+ value,
16
+ onChange,
17
+ onClose,
18
+ autoFocus,
19
+ getLabel,
20
+ getKey: getKeyProp,
21
+ filter,
22
+ renderItem,
23
+ renderEmpty,
24
+ creatable,
25
+ onCreate,
26
+ canCreate,
27
+ renderCreateLabel,
28
+ maxValues,
29
+ placeholder,
30
+ disabled,
31
+ loading,
32
+ clearable,
33
+ autosize,
34
+ $isErrored,
35
+ width,
36
+ minWidth
37
+ }) {
38
+ const inputRef = useRef(null);
39
+ const isAsync = !!loadItems;
40
+ const getKey = getKeyProp ?? defaultGetKey;
41
+ const baseId = useId();
42
+ const listboxId = `${baseId}-list`;
43
+ const createId = `${baseId}-create`;
44
+ const optionId = key => `${baseId}-opt-${key}`;
45
+ const [isOpen, setIsOpen] = useState(false);
46
+ const [query, setQuery] = useState('');
47
+ const [createdItems, setCreatedItems] = useState([]);
48
+ const {
49
+ sourceItems,
50
+ filteredItems,
51
+ loading: asyncLoading
52
+ } = useDropdownItems({
53
+ items,
54
+ loadItems,
55
+ localItems: createdItems,
56
+ debounceMs,
57
+ getLabel,
58
+ filter,
59
+ query,
60
+ isOpen
61
+ });
62
+ const selectedKeys = useMemo(() => {
63
+ const set = new Set();
64
+ value.forEach(item => set.add(getKey(item)));
65
+ return set;
66
+ }, [value, getKey]);
67
+ const visibleItems = useMemo(() => filteredItems.filter(item => !selectedKeys.has(getKey(item))), [filteredItems, selectedKeys, getKey]);
68
+ const atMax = maxValues !== undefined && value.length >= maxValues;
69
+ const canCreateNow = !!(creatable && query.trim() && !atMax && (canCreate ? canCreate(query, sourceItems) : defaultCanCreate(query, sourceItems, getLabel)));
70
+ const totalRows = visibleItems.length + (canCreateNow ? 1 : 0);
71
+ const [activeIndex, setActiveIndex] = useActiveIndex(totalRows, 0);
72
+ const open = useCallback(() => setIsOpen(true), []);
73
+ const close = useCallback(() => {
74
+ setIsOpen(false);
75
+ setQuery('');
76
+ }, []);
77
+ const toggle = useCallback(item => {
78
+ var _inputRef$current;
79
+ const key = getKey(item);
80
+ if (selectedKeys.has(key)) {
81
+ onChange(value.filter(v => getKey(v) !== key));
82
+ return;
83
+ }
84
+ if (maxValues !== undefined && value.length >= maxValues) return;
85
+ onChange([...value, item]);
86
+ setIsOpen(true);
87
+ (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
88
+ }, [value, getKey, selectedKeys, onChange, maxValues]);
89
+ const create = useCallback(() => {
90
+ const trimmed = query.trim();
91
+ if (!trimmed || atMax) return;
92
+ const item = onCreate ? onCreate(trimmed) : trimmed;
93
+ if (!isAsync) setCreatedItems(prev => [...prev, item]);
94
+ onChange([...value, item]);
95
+ setQuery('');
96
+ setActiveIndex(0);
97
+ }, [query, atMax, onCreate, isAsync, onChange, value, setActiveIndex]);
98
+ const onInputKeyDown = useListNavigation({
99
+ isOpen,
100
+ open,
101
+ itemCount: visibleItems.length,
102
+ activeIndex,
103
+ setActiveIndex,
104
+ onSelectIndex: index => toggle(visibleItems[index]),
105
+ onEscape: close,
106
+ hasExtraRow: canCreateNow,
107
+ onSelectExtra: create,
108
+ isQueryEmpty: !query && value.length > 0,
109
+ onBackspaceEmpty: () => onChange(value.slice(0, -1))
110
+ });
111
+ const onShellMouseDown = useShellMouseDown(inputRef, isOpen, open);
112
+ useFieldOpenEffects({
113
+ isOpen,
114
+ open,
115
+ onClose,
116
+ autoFocus,
117
+ inputRef
118
+ });
119
+ const toggleOpen = useCallback(() => {
120
+ if (isOpen) {
121
+ var _inputRef$current2;
122
+ close();
123
+ (_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 || _inputRef$current2.blur();
124
+ } else {
125
+ var _inputRef$current3;
126
+ open();
127
+ (_inputRef$current3 = inputRef.current) === null || _inputRef$current3 === void 0 || _inputRef$current3.focus();
128
+ }
129
+ }, [isOpen, open, close]);
130
+ const itemOffset = canCreateNow ? 1 : 0;
131
+ const dropdownItems = useMemo(() => visibleItems.map((item, idx) => {
132
+ const key = getKey(item);
133
+ const state = {
134
+ active: idx + itemOffset === activeIndex,
135
+ selected: false
136
+ };
137
+ return {
138
+ key,
139
+ id: `${baseId}-opt-${key}`,
140
+ label: renderItem ? renderItem(item, state) : getLabel(item),
141
+ active: state.active,
142
+ selected: state.selected,
143
+ onSelect: () => toggle(item)
144
+ };
145
+ }), [visibleItems, itemOffset, activeIndex, renderItem, getLabel, getKey, toggle, baseId]);
146
+ const createActive = canCreateNow && activeIndex === 0;
147
+ const activeItem = visibleItems[activeIndex - itemOffset];
148
+ let activeDescendant;
149
+ if (createActive) activeDescendant = createId;else if (activeItem !== undefined) activeDescendant = optionId(getKey(activeItem));
150
+ let createLabel;
151
+ if (canCreateNow) {
152
+ createLabel = renderCreateLabel ? renderCreateLabel(query.trim()) : `Create "${query.trim()}"`;
153
+ }
154
+ const hasTags = value.length > 0;
155
+ return _jsxs(FieldShell, {
156
+ isOpen: isOpen,
157
+ dropdownOpen: isOpen,
158
+ isErrored: $isErrored,
159
+ disabled: disabled,
160
+ loading: loading || asyncLoading,
161
+ width: width,
162
+ minWidth: minWidth,
163
+ autosize: autosize,
164
+ tightLeft: hasTags,
165
+ onShellMouseDown: onShellMouseDown,
166
+ onClose: close,
167
+ showClear: !!(clearable && hasTags),
168
+ onClear: () => onChange([]),
169
+ clearLabel: "Clear all",
170
+ showChevron: true,
171
+ onToggle: toggleOpen,
172
+ dropdownItems: dropdownItems,
173
+ emptyText: renderEmpty ? renderEmpty(query) : undefined,
174
+ createLabel: createLabel,
175
+ createActive: createActive,
176
+ onCreate: create,
177
+ listboxId: listboxId,
178
+ createId: createId,
179
+ children: [value.map(item => {
180
+ const key = getKey(item);
181
+ return _jsx(Chip, {
182
+ "$readOnly": disabled,
183
+ "$onClose": disabled ? undefined : () => onChange(value.filter(v => getKey(v) !== key)),
184
+ "$maxWidth": "100%",
185
+ children: getLabel(item)
186
+ }, key);
187
+ }), _jsx(FieldInput, {
188
+ ref: inputRef,
189
+ autosize: autosize,
190
+ value: query,
191
+ onChange: e => setQuery(e.target.value),
192
+ onKeyDown: onInputKeyDown,
193
+ onFocus: open,
194
+ placeholder: hasTags ? undefined : placeholder,
195
+ disabled: disabled,
196
+ role: "combobox",
197
+ "aria-expanded": isOpen,
198
+ "aria-controls": listboxId,
199
+ "aria-autocomplete": "list",
200
+ "aria-activedescendant": activeDescendant
201
+ })]
202
+ });
203
+ }
@@ -0,0 +1,2 @@
1
+ export { ComboboxMulti } from './ComboboxMulti';
2
+ export type { ComboboxMultiProps } from './types';
@@ -0,0 +1 @@
1
+ export { ComboboxMulti } from './ComboboxMulti';
@@ -0,0 +1,34 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { GetKeyProp, ItemState, ItemsSource } from '../shared/dropdownField';
3
+ type CreatableProp<T> = [T] extends [string] ? {
4
+ creatable?: boolean;
5
+ onCreate?: (query: string) => T;
6
+ } : {
7
+ creatable?: false;
8
+ onCreate?: never;
9
+ } | {
10
+ creatable: true;
11
+ onCreate: (query: string) => T;
12
+ };
13
+ export type ComboboxMultiProps<T> = ItemsSource<T> & GetKeyProp<T> & CreatableProp<T> & {
14
+ value: T[];
15
+ onChange: (value: T[]) => void;
16
+ onClose?: () => void;
17
+ autoFocus?: boolean;
18
+ getLabel: (item: T) => string;
19
+ filter?: (item: T, query: string) => boolean;
20
+ renderItem?: (item: T, state: ItemState) => ReactNode;
21
+ renderEmpty?: (query: string) => ReactNode;
22
+ canCreate?: (query: string, items: T[]) => boolean;
23
+ renderCreateLabel?: (query: string) => ReactNode;
24
+ maxValues?: number;
25
+ placeholder?: string;
26
+ disabled?: boolean;
27
+ loading?: boolean;
28
+ clearable?: boolean;
29
+ autosize?: boolean;
30
+ $isErrored?: boolean;
31
+ width?: string;
32
+ minWidth?: string;
33
+ };
34
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/hooks/useLayer.js CHANGED
@@ -1,38 +1,65 @@
1
1
  import { useEffect, useId, useLayoutEffect, useRef, useState } from 'react';
2
2
  const BASE_Z_INDEX = 10000;
3
3
  const Z_INDEX_STEP = 100;
4
- const stack = [];
5
- const subscribers = new Set();
6
- let keydownAttached = false;
4
+ const GLOBAL_KEY = '__pushwoosh_dumb_components_layer_state__';
5
+ function createState() {
6
+ return {
7
+ stack: [],
8
+ subscribers: new Set(),
9
+ keydownHandler: null
10
+ };
11
+ }
12
+ function isLayerState(value) {
13
+ if (typeof value !== 'object' || value === null) {
14
+ return false;
15
+ }
16
+ const candidate = value;
17
+ return Array.isArray(candidate.stack) && candidate.subscribers instanceof Set;
18
+ }
19
+ function getState() {
20
+ const globalScope = window;
21
+ if (!isLayerState(globalScope[GLOBAL_KEY])) {
22
+ globalScope[GLOBAL_KEY] = createState();
23
+ }
24
+ return globalScope[GLOBAL_KEY];
25
+ }
7
26
  function notify() {
8
- subscribers.forEach(subscriber => subscriber());
27
+ getState().subscribers.forEach(subscriber => subscriber());
9
28
  }
10
29
  function handleKeydown(event) {
11
30
  var _top$close;
12
31
  if (event.key !== 'Escape') {
13
32
  return;
14
33
  }
34
+ const {
35
+ stack
36
+ } = getState();
15
37
  const top = stack[stack.length - 1];
16
38
  top === null || top === void 0 || (_top$close = top.close) === null || _top$close === void 0 || _top$close.call(top);
17
39
  }
18
40
  function ensureKeydownListener() {
19
- if (!keydownAttached && stack.length > 0) {
20
- window.addEventListener('keydown', handleKeydown);
21
- keydownAttached = true;
41
+ const state = getState();
42
+ if (!state.keydownHandler && state.stack.length > 0) {
43
+ state.keydownHandler = handleKeydown;
44
+ window.addEventListener('keydown', state.keydownHandler);
22
45
  }
23
46
  }
24
47
  function teardownKeydownListener() {
25
- if (keydownAttached && stack.length === 0) {
26
- window.removeEventListener('keydown', handleKeydown);
27
- keydownAttached = false;
48
+ const state = getState();
49
+ if (state.keydownHandler && state.stack.length === 0) {
50
+ window.removeEventListener('keydown', state.keydownHandler);
51
+ state.keydownHandler = null;
28
52
  }
29
53
  }
30
54
  function registerLayer(entry) {
31
- stack.push(entry);
55
+ getState().stack.push(entry);
32
56
  ensureKeydownListener();
33
57
  notify();
34
58
  }
35
59
  function unregisterLayer(id) {
60
+ const {
61
+ stack
62
+ } = getState();
36
63
  const index = stack.findIndex(entry => entry.id === id);
37
64
  if (index !== -1) {
38
65
  stack.splice(index, 1);
@@ -41,7 +68,7 @@ function unregisterLayer(id) {
41
68
  notify();
42
69
  }
43
70
  function getDepth(id) {
44
- return stack.findIndex(entry => entry.id === id);
71
+ return getState().stack.findIndex(entry => entry.id === id);
45
72
  }
46
73
  export function useLayer({
47
74
  isOpen,
@@ -54,9 +81,9 @@ export function useLayer({
54
81
  const [, forceRender] = useState(0);
55
82
  useEffect(() => {
56
83
  const subscriber = () => forceRender(value => value + 1);
57
- subscribers.add(subscriber);
84
+ getState().subscribers.add(subscriber);
58
85
  return () => {
59
- subscribers.delete(subscriber);
86
+ getState().subscribers.delete(subscriber);
60
87
  };
61
88
  }, []);
62
89
  useLayoutEffect(() => {