@snack-uikit/list 0.12.0 → 0.13.0

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/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 0.13.0 (2024-05-13)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **FF-0000:** fix keyboard control; add listRef props ([bda03fe](https://github.com/cloud-ru-tech/snack-uikit/commit/bda03fee548908bdc07e1455cf5aa1a3c5b766f9))
12
+
13
+
14
+ ### Features
15
+
16
+ * **FF-0000:** add closeDroplistOnItemClick prop ([fabd3a4](https://github.com/cloud-ru-tech/snack-uikit/commit/fabd3a4fe5443a985269228f082f9cfb0f4c0182))
17
+
18
+
19
+
20
+
21
+
6
22
  # 0.12.0 (2024-05-08)
7
23
 
8
24
 
package/README.md CHANGED
@@ -58,6 +58,8 @@
58
58
  | items* | `Item[]` | - | Основные элементы списка |
59
59
  | children* | `ReactNode \| ({onKeyDown}) => ReactNode * Рендер функция принимает аргументы `onKeyDown` - хендлер ввода, для поддержки управления с клавиатуры` | - | Триггер для дроплиста |
60
60
  | triggerElemRef | `RefObject<HTMLElement>` | - | Ссылка на элемент-триггер для дроплиста |
61
+ | listRef | `RefObject<HTMLElement>` | - | Ссылка на элемент выпадающего списка |
62
+ | closeDroplistOnItemClick | `boolean` | - | Закрывать выпадающий список после клика на базовый айтем. Работает в режимах selection: 'none' \| 'single' |
61
63
  | triggerClassName | `string` | - | CSS-класс триггера |
62
64
  | open | `boolean` | - | Управляет состоянием показан/не показан. |
63
65
  | onOpenChange | `(isOpen: boolean) => void` | - | Колбек отображения компонента. Срабатывает при изменении состояния open. |
@@ -14,7 +14,7 @@ import cn from 'classnames';
14
14
  import { Checkbox, Switch } from '@snack-uikit/toggles';
15
15
  import { extractSupportProps } from '@snack-uikit/utils';
16
16
  import { ItemContent } from '../../../helperComponents';
17
- import { useCollapseLevelContext, useFocusListContext, useNewListContext, useSelectionContext, } from '../../Lists/contexts';
17
+ import { useCollapseLevelContext, useFocusListContext, useNewListContext, useOpenListContext, useSelectionContext, } from '../../Lists/contexts';
18
18
  import commonStyles from '../styles.module.css';
19
19
  import { isContentItem } from '../utils';
20
20
  import { CHECKBOX_SIZE_MAP } from './constants';
@@ -26,6 +26,7 @@ export function BaseItem(_a) {
26
26
  const { size = 's', marker, contentRender } = useNewListContext();
27
27
  const { level = 0 } = useCollapseLevelContext();
28
28
  const { forceUpdateActiveItemId } = useFocusListContext();
29
+ const { closeDroplist, closeDroplistOnItemClick } = useOpenListContext();
29
30
  const { value, onChange, mode, isSelectionSingle, isSelectionMultiple } = useSelectionContext();
30
31
  const isChecked = isSelectionSingle ? value === id : checkedProp !== null && checkedProp !== void 0 ? checkedProp : value === null || value === void 0 ? void 0 : value.includes(id !== null && id !== void 0 ? id : '');
31
32
  const handleChange = () => {
@@ -42,25 +43,6 @@ export function BaseItem(_a) {
42
43
  onMouseDown === null || onMouseDown === void 0 ? void 0 : onMouseDown(e);
43
44
  forceUpdateActiveItemId === null || forceUpdateActiveItemId === void 0 ? void 0 : forceUpdateActiveItemId('~drop-focus');
44
45
  };
45
- const handleItemKeyDown = (e) => {
46
- onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
47
- if (e.key === 'ArrowRight' && onOpenNestedList) {
48
- onOpenNestedList(e);
49
- e.preventDefault();
50
- e.stopPropagation();
51
- return;
52
- }
53
- if (e.code === 'Space' || e.key === 'Enter') {
54
- if (isSelectionMultiple && isParentNode && onSelect) {
55
- onSelect();
56
- }
57
- !isParentNode && handleChange();
58
- // TODO: should pass an event here?
59
- !isParentNode && (onClick === null || onClick === void 0 ? void 0 : onClick(e));
60
- e.stopPropagation();
61
- e.preventDefault();
62
- }
63
- };
64
46
  const handleItemFocus = (e) => {
65
47
  onFocus === null || onFocus === void 0 ? void 0 : onFocus(e);
66
48
  e.stopPropagation();
@@ -76,6 +58,28 @@ export function BaseItem(_a) {
76
58
  const handleItemClick = (e) => {
77
59
  if (!disabled) {
78
60
  onClick === null || onClick === void 0 ? void 0 : onClick(e);
61
+ if (!isSelectionMultiple && closeDroplistOnItemClick) {
62
+ closeDroplist();
63
+ }
64
+ }
65
+ };
66
+ const handleItemKeyDown = (e) => {
67
+ onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
68
+ if (e.key === 'ArrowRight' && onOpenNestedList) {
69
+ onOpenNestedList(e);
70
+ e.preventDefault();
71
+ e.stopPropagation();
72
+ return;
73
+ }
74
+ if (e.code === 'Space' || e.key === 'Enter' || e.key === ' ') {
75
+ if (isSelectionMultiple && isParentNode && onSelect) {
76
+ onSelect();
77
+ }
78
+ !isParentNode && handleChange();
79
+ // TODO: should pass an event here?
80
+ !isParentNode && (handleItemClick === null || handleItemClick === void 0 ? void 0 : handleItemClick(e));
81
+ e.stopPropagation();
82
+ e.preventDefault();
79
83
  }
80
84
  };
81
85
  const handleCheckboxClick = (e) => {
@@ -1,2 +1,2 @@
1
1
  import { DroplistProps } from '../types';
2
- export declare function Droplist({ items: itemsProp, search, pinBottom: pinBottomProp, pinTop: pinTopProp, footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse, triggerClassName, selection, contentRender, size, marker, className, ...props }: DroplistProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function Droplist({ items: itemsProp, search, pinBottom: pinBottomProp, pinTop: pinTopProp, footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse, triggerClassName, selection, contentRender, size, marker, closeDroplistOnItemClick, className, listRef: listRefProp, ...props }: DroplistProps): import("react/jsx-runtime").JSX.Element;
@@ -16,13 +16,13 @@ import { cloneElement, isValidElement, useCallback, useMemo, useRef } from 'reac
16
16
  import { Dropdown } from '@snack-uikit/dropdown';
17
17
  import { useValueControl } from '@snack-uikit/utils';
18
18
  import { extractActiveItems, kindFlattenItems, useCreateBaseItems } from '../../Items';
19
- import { CollapseContext, FocusListContext, NewListContextProvider, SelectionProvider } from '../contexts';
19
+ import { CollapseContext, FocusListContext, NewListContextProvider, OpenListContext, SelectionProvider, } from '../contexts';
20
20
  import { useNewKeyboardNavigation } from '../hooks';
21
21
  import { ListPrivate } from '../ListPrivate';
22
22
  import styles from '../styles.module.css';
23
23
  const DEFAULT_FALLBACK_PLACEMENTS = ['top', 'right', 'bottom', 'left'];
24
24
  export function Droplist(_a) {
25
- var { items: itemsProp, search, pinBottom: pinBottomProp = [], pinTop: pinTopProp = [], footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse = {}, triggerClassName, selection, contentRender, size = 's', marker = true, className } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs", "children", "trigger", "placement", "widthStrategy", "triggerElemRef", "open", "onOpenChange", "collapse", "triggerClassName", "selection", "contentRender", "size", "marker", "className"]);
25
+ var { items: itemsProp, search, pinBottom: pinBottomProp = [], pinTop: pinTopProp = [], footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse = {}, triggerClassName, selection, contentRender, size = 's', marker = true, closeDroplistOnItemClick = false, className, listRef: listRefProp } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs", "children", "trigger", "placement", "widthStrategy", "triggerElemRef", "open", "onOpenChange", "collapse", "triggerClassName", "selection", "contentRender", "size", "marker", "closeDroplistOnItemClick", "className", "listRef"]);
26
26
  const hasSearch = useMemo(() => Boolean(search), [search]);
27
27
  const [openCollapseItems = [], setOpenCollapsedItems] = useValueControl(collapse);
28
28
  const toggleOpenCollapseItem = useCallback((id) => setOpenCollapsedItems((items) => (items === null || items === void 0 ? void 0 : items.includes(id)) ? items.filter(item => item !== id) : (items !== null && items !== void 0 ? items : []).concat([id])), [setOpenCollapsedItems]);
@@ -75,14 +75,14 @@ export function Droplist(_a) {
75
75
  const triggerElemRef = useRef(null);
76
76
  const listRef = useRef(null);
77
77
  const { handleListKeyDownFactory, resetActiveItemId, activeItemId, forceUpdateActiveItemId } = useNewKeyboardNavigation({
78
- mainRef: triggerElemRef,
78
+ mainRef: triggerElemRefProp !== null && triggerElemRefProp !== void 0 ? triggerElemRefProp : triggerElemRef,
79
79
  focusFlattenItems,
80
80
  });
81
81
  const handleListKeyDown = useCallback((e) => handleListKeyDownFactory(ids, expandedIds)(e), [handleListKeyDownFactory, ids, expandedIds]);
82
- const handleOpenChange = (open) => {
82
+ const handleOpenChange = useCallback((open) => {
83
83
  resetActiveItemId();
84
84
  setOpen(open);
85
- };
85
+ }, [resetActiveItemId, setOpen]);
86
86
  const onKeyDown = useCallback((e, cb) => {
87
87
  if (e.key === 'ArrowDown') {
88
88
  e.preventDefault();
@@ -98,6 +98,7 @@ export function Droplist(_a) {
98
98
  }
99
99
  cb === null || cb === void 0 ? void 0 : cb(e);
100
100
  }, [resetActiveItemId, setOpen]);
101
+ const isValid = useMemo(() => isValidElement(children), [children]);
101
102
  const triggerElem = useMemo(() => {
102
103
  if (isValidElement(children)) {
103
104
  const props = typeof children.props === 'object' ? children.props : {};
@@ -118,11 +119,16 @@ export function Droplist(_a) {
118
119
  activeItemId,
119
120
  handleListKeyDownFactory,
120
121
  forceUpdateActiveItemId,
121
- }, children: _jsx(Dropdown, { content: _jsx("div", { className: cn(styles.wrapper, className), children: _jsx(ListPrivate, Object.assign({}, props, { items: memorizedItems.items.focusCloseChildIds, pinTop: memorizedItems.pinTop.focusCloseChildIds, pinBottom: memorizedItems.pinBottom.focusCloseChildIds, onKeyDown: handleListKeyDown, searchItem: searchItem, tabIndex: 0, ref: listRef, search: search, onFocus: e => {
122
- e.stopPropagation();
123
- forceUpdateActiveItemId === null || forceUpdateActiveItemId === void 0 ? void 0 : forceUpdateActiveItemId(ids[0]);
124
- }, limitedScrollHeight: true })) }), triggerClassName: triggerClassName, fallbackPlacements: DEFAULT_FALLBACK_PLACEMENTS, trigger: trigger, placement: placement, widthStrategy: widthStrategy,
125
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
126
- // @ts-expect-error
127
- triggerRef: triggerElemRefProp ? mergeRefs(triggerElemRef, triggerElemRefProp) : triggerElemRef, open: open, onOpenChange: handleOpenChange, children: triggerElem }) }) }) })) }));
122
+ }, children: _jsx(OpenListContext.Provider, { value: {
123
+ closeDroplistOnItemClick,
124
+ closeDroplist: () => {
125
+ var _a;
126
+ setOpen(false);
127
+ resetActiveItemId();
128
+ (_a = (triggerElemRefProp !== null && triggerElemRefProp !== void 0 ? triggerElemRefProp : triggerElemRef).current) === null || _a === void 0 ? void 0 : _a.focus();
129
+ },
130
+ }, children: _jsx(Dropdown, { content: _jsx("div", { className: cn(styles.wrapper, className), children: _jsx(ListPrivate, Object.assign({}, props, { items: memorizedItems.items.focusCloseChildIds, pinTop: memorizedItems.pinTop.focusCloseChildIds, pinBottom: memorizedItems.pinBottom.focusCloseChildIds, onKeyDown: handleListKeyDown, searchItem: searchItem, tabIndex: 0, ref: mergeRefs(listRef, listRefProp), search: search, onFocus: e => {
131
+ e.stopPropagation();
132
+ forceUpdateActiveItemId === null || forceUpdateActiveItemId === void 0 ? void 0 : forceUpdateActiveItemId(ids[0]);
133
+ }, limitedScrollHeight: true })) }), outsideClick: true, triggerClassName: triggerClassName, fallbackPlacements: DEFAULT_FALLBACK_PLACEMENTS, trigger: trigger, placement: placement, widthStrategy: widthStrategy, triggerRef: !triggerElemRefProp ? triggerElemRef : (isValid && triggerElemRefProp) || undefined, open: open, onOpenChange: handleOpenChange, children: triggerElem }) }) }) }) })) }));
128
134
  }
@@ -0,0 +1,7 @@
1
+ /// <reference types="react" />
2
+ export type OpenListContextType = {
3
+ closeDroplistOnItemClick?: boolean;
4
+ closeDroplist(): void;
5
+ };
6
+ export declare const OpenListContext: import("react").Context<OpenListContextType>;
7
+ export declare const useOpenListContext: () => OpenListContextType;
@@ -0,0 +1,6 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const OpenListContext = createContext({
3
+ closeDroplist: () => { },
4
+ closeDroplistOnItemClick: false,
5
+ });
6
+ export const useOpenListContext = () => useContext(OpenListContext);
@@ -3,3 +3,4 @@ export * from './CollapseProvider';
3
3
  export * from './NewListProvider';
4
4
  export * from './SelectionProvider';
5
5
  export * from './FocusListProvider';
6
+ export * from './OpenListProvider';
@@ -3,3 +3,4 @@ export * from './CollapseProvider';
3
3
  export * from './NewListProvider';
4
4
  export * from './SelectionProvider';
5
5
  export * from './FocusListProvider';
6
+ export * from './OpenListProvider';
@@ -38,9 +38,8 @@ export function useNewKeyboardNavigation({ mainRef, btnRef, focusFlattenItems, }
38
38
  setActiveItemId(undefined);
39
39
  (_e = mainRef === null || mainRef === void 0 ? void 0 : mainRef.current) === null || _e === void 0 ? void 0 : _e.focus();
40
40
  }
41
- return;
42
41
  }
43
- if (activeItemIdRef.current !== undefined) {
42
+ else if (activeItemIdRef.current !== undefined) {
44
43
  const activeIndex = ids.findIndex(id => id === activeItemIdRef.current);
45
44
  const nextId = Math.max(activeIndex - 1, 0);
46
45
  const itemId = ids[nextId];
@@ -44,6 +44,16 @@ export type ListProps = WithSupportProps<{
44
44
  export type DroplistProps = {
45
45
  /** Ссылка на элемент-триггер для дроплиста */
46
46
  triggerElemRef?: RefObject<HTMLElement>;
47
+ /** Ссылка на элемент выпадающего списка */
48
+ listRef?: RefObject<HTMLElement>;
49
+ /**
50
+ * Закрывать выпадающий список после клика на базовый айтем.
51
+ *
52
+ * Работает в режимах selection: 'none' | 'single'
53
+ *
54
+ * @default false
55
+ */
56
+ closeDroplistOnItemClick?: boolean;
47
57
  /** Триггер для дроплиста
48
58
  * @type ReactNode | ({onKeyDown}) => ReactNode
49
59
  *
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "List",
7
- "version": "0.12.0",
7
+ "version": "0.13.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -49,5 +49,5 @@
49
49
  "peerDependencies": {
50
50
  "@snack-uikit/locale": "*"
51
51
  },
52
- "gitHead": "a308f550fb12d114a27d9b7d6804c212a316a09d"
52
+ "gitHead": "d36fe553ce72b92deafee581e70090458827f8ac"
53
53
  }
@@ -9,6 +9,7 @@ import {
9
9
  useCollapseLevelContext,
10
10
  useFocusListContext,
11
11
  useNewListContext,
12
+ useOpenListContext,
12
13
  useSelectionContext,
13
14
  } from '../../Lists/contexts';
14
15
  import commonStyles from '../styles.module.scss';
@@ -56,6 +57,7 @@ export function BaseItem({
56
57
  const { size = 's', marker, contentRender } = useNewListContext();
57
58
  const { level = 0 } = useCollapseLevelContext();
58
59
  const { forceUpdateActiveItemId } = useFocusListContext();
60
+ const { closeDroplist, closeDroplistOnItemClick } = useOpenListContext();
59
61
  const { value, onChange, mode, isSelectionSingle, isSelectionMultiple } = useSelectionContext();
60
62
 
61
63
  const isChecked = isSelectionSingle ? value === id : checkedProp ?? value?.includes(id ?? '');
@@ -78,6 +80,29 @@ export function BaseItem({
78
80
  forceUpdateActiveItemId?.('~drop-focus');
79
81
  };
80
82
 
83
+ const handleItemFocus = (e: FocusEvent<HTMLElement>) => {
84
+ onFocus?.(e);
85
+ e.stopPropagation();
86
+ };
87
+
88
+ const handleCheckboxChange = () => {
89
+ if (isParentNode && onSelect) {
90
+ onSelect();
91
+ } else {
92
+ handleChange();
93
+ }
94
+ };
95
+
96
+ const handleItemClick = (e: MouseEvent<HTMLElement>) => {
97
+ if (!disabled) {
98
+ onClick?.(e);
99
+
100
+ if (!isSelectionMultiple && closeDroplistOnItemClick) {
101
+ closeDroplist();
102
+ }
103
+ }
104
+ };
105
+
81
106
  const handleItemKeyDown = (e: KeyboardEvent<HTMLElement>) => {
82
107
  onKeyDown?.(e);
83
108
 
@@ -90,39 +115,20 @@ export function BaseItem({
90
115
  return;
91
116
  }
92
117
 
93
- if (e.code === 'Space' || e.key === 'Enter') {
118
+ if (e.code === 'Space' || e.key === 'Enter' || e.key === ' ') {
94
119
  if (isSelectionMultiple && isParentNode && onSelect) {
95
120
  onSelect();
96
121
  }
97
122
 
98
123
  !isParentNode && handleChange();
99
124
  // TODO: should pass an event here?
100
- !isParentNode && onClick?.(e as unknown as MouseEvent<HTMLElement>);
125
+ !isParentNode && handleItemClick?.(e as unknown as MouseEvent<HTMLElement>);
101
126
 
102
127
  e.stopPropagation();
103
128
  e.preventDefault();
104
129
  }
105
130
  };
106
131
 
107
- const handleItemFocus = (e: FocusEvent<HTMLElement>) => {
108
- onFocus?.(e);
109
- e.stopPropagation();
110
- };
111
-
112
- const handleCheckboxChange = () => {
113
- if (isParentNode && onSelect) {
114
- onSelect();
115
- } else {
116
- handleChange();
117
- }
118
- };
119
-
120
- const handleItemClick = (e: MouseEvent<HTMLElement>) => {
121
- if (!disabled) {
122
- onClick?.(e);
123
- }
124
- };
125
-
126
132
  const handleCheckboxClick = (e: MouseEvent) => {
127
133
  e.stopPropagation();
128
134
  };
@@ -6,7 +6,13 @@ import { Dropdown, DropdownProps } from '@snack-uikit/dropdown';
6
6
  import { useValueControl } from '@snack-uikit/utils';
7
7
 
8
8
  import { extractActiveItems, ItemId, kindFlattenItems, useCreateBaseItems } from '../../Items';
9
- import { CollapseContext, FocusListContext, NewListContextProvider, SelectionProvider } from '../contexts';
9
+ import {
10
+ CollapseContext,
11
+ FocusListContext,
12
+ NewListContextProvider,
13
+ OpenListContext,
14
+ SelectionProvider,
15
+ } from '../contexts';
10
16
  import { useNewKeyboardNavigation } from '../hooks';
11
17
  import { ListPrivate } from '../ListPrivate';
12
18
  import styles from '../styles.module.scss';
@@ -33,7 +39,9 @@ export function Droplist({
33
39
  contentRender,
34
40
  size = 's',
35
41
  marker = true,
42
+ closeDroplistOnItemClick = false,
36
43
  className,
44
+ listRef: listRefProp,
37
45
  ...props
38
46
  }: DroplistProps) {
39
47
  const hasSearch = useMemo(() => Boolean(search), [search]);
@@ -114,7 +122,7 @@ export function Droplist({
114
122
 
115
123
  const { handleListKeyDownFactory, resetActiveItemId, activeItemId, forceUpdateActiveItemId } =
116
124
  useNewKeyboardNavigation({
117
- mainRef: triggerElemRef,
125
+ mainRef: triggerElemRefProp ?? triggerElemRef,
118
126
  focusFlattenItems,
119
127
  });
120
128
 
@@ -123,11 +131,14 @@ export function Droplist({
123
131
  [handleListKeyDownFactory, ids, expandedIds],
124
132
  );
125
133
 
126
- const handleOpenChange = (open: boolean) => {
127
- resetActiveItemId();
134
+ const handleOpenChange = useCallback(
135
+ (open: boolean) => {
136
+ resetActiveItemId();
128
137
 
129
- setOpen(open);
130
- };
138
+ setOpen(open);
139
+ },
140
+ [resetActiveItemId, setOpen],
141
+ );
131
142
 
132
143
  const onKeyDown = useCallback(
133
144
  (e: KeyboardEvent<HTMLElement>, cb?: (e: KeyboardEvent<HTMLElement>) => void) => {
@@ -150,6 +161,8 @@ export function Droplist({
150
161
  [resetActiveItemId, setOpen],
151
162
  );
152
163
 
164
+ const isValid = useMemo(() => isValidElement(children), [children]);
165
+
153
166
  const triggerElem: ReactNode = useMemo(() => {
154
167
  if (isValidElement(children)) {
155
168
  const props = typeof children.props === 'object' ? children.props : {};
@@ -191,41 +204,51 @@ export function Droplist({
191
204
  forceUpdateActiveItemId,
192
205
  }}
193
206
  >
194
- <Dropdown
195
- content={
196
- <div className={cn(styles.wrapper, className)}>
197
- <ListPrivate
198
- {...props}
199
- items={memorizedItems.items.focusCloseChildIds}
200
- pinTop={memorizedItems.pinTop.focusCloseChildIds}
201
- pinBottom={memorizedItems.pinBottom.focusCloseChildIds}
202
- onKeyDown={handleListKeyDown}
203
- searchItem={searchItem}
204
- tabIndex={0}
205
- ref={listRef}
206
- search={search}
207
- onFocus={e => {
208
- e.stopPropagation();
209
-
210
- forceUpdateActiveItemId?.(ids[0]);
211
- }}
212
- limitedScrollHeight
213
- />
214
- </div>
215
- }
216
- triggerClassName={triggerClassName}
217
- fallbackPlacements={DEFAULT_FALLBACK_PLACEMENTS}
218
- trigger={trigger}
219
- placement={placement}
220
- widthStrategy={widthStrategy}
221
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
222
- // @ts-expect-error
223
- triggerRef={triggerElemRefProp ? mergeRefs(triggerElemRef, triggerElemRefProp) : triggerElemRef}
224
- open={open}
225
- onOpenChange={handleOpenChange}
207
+ <OpenListContext.Provider
208
+ value={{
209
+ closeDroplistOnItemClick,
210
+ closeDroplist: () => {
211
+ setOpen(false);
212
+ resetActiveItemId();
213
+ (triggerElemRefProp ?? triggerElemRef).current?.focus();
214
+ },
215
+ }}
226
216
  >
227
- {triggerElem}
228
- </Dropdown>
217
+ <Dropdown
218
+ content={
219
+ <div className={cn(styles.wrapper, className)}>
220
+ <ListPrivate
221
+ {...props}
222
+ items={memorizedItems.items.focusCloseChildIds}
223
+ pinTop={memorizedItems.pinTop.focusCloseChildIds}
224
+ pinBottom={memorizedItems.pinBottom.focusCloseChildIds}
225
+ onKeyDown={handleListKeyDown}
226
+ searchItem={searchItem}
227
+ tabIndex={0}
228
+ ref={mergeRefs(listRef, listRefProp)}
229
+ search={search}
230
+ onFocus={e => {
231
+ e.stopPropagation();
232
+
233
+ forceUpdateActiveItemId?.(ids[0]);
234
+ }}
235
+ limitedScrollHeight
236
+ />
237
+ </div>
238
+ }
239
+ outsideClick
240
+ triggerClassName={triggerClassName}
241
+ fallbackPlacements={DEFAULT_FALLBACK_PLACEMENTS}
242
+ trigger={trigger}
243
+ placement={placement}
244
+ widthStrategy={widthStrategy}
245
+ triggerRef={!triggerElemRefProp ? triggerElemRef : (isValid && triggerElemRefProp) || undefined}
246
+ open={open}
247
+ onOpenChange={handleOpenChange}
248
+ >
249
+ {triggerElem}
250
+ </Dropdown>
251
+ </OpenListContext.Provider>
229
252
  </FocusListContext.Provider>
230
253
  </CollapseContext.Provider>
231
254
  </SelectionProvider>
@@ -0,0 +1,13 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ export type OpenListContextType = {
4
+ closeDroplistOnItemClick?: boolean;
5
+ closeDroplist(): void;
6
+ };
7
+
8
+ export const OpenListContext = createContext<OpenListContextType>({
9
+ closeDroplist: () => {},
10
+ closeDroplistOnItemClick: false,
11
+ });
12
+
13
+ export const useOpenListContext = () => useContext(OpenListContext);
@@ -3,3 +3,4 @@ export * from './CollapseProvider';
3
3
  export * from './NewListProvider';
4
4
  export * from './SelectionProvider';
5
5
  export * from './FocusListProvider';
6
+ export * from './OpenListProvider';
@@ -59,10 +59,7 @@ export function useNewKeyboardNavigation<T extends HTMLElement>({
59
59
  setActiveItemId(undefined);
60
60
  mainRef?.current?.focus();
61
61
  }
62
- return;
63
- }
64
-
65
- if (activeItemIdRef.current !== undefined) {
62
+ } else if (activeItemIdRef.current !== undefined) {
66
63
  const activeIndex = ids.findIndex(id => id === activeItemIdRef.current);
67
64
  const nextId = Math.max(activeIndex - 1, 0);
68
65
  const itemId = ids[nextId];
@@ -56,6 +56,16 @@ export type ListProps = WithSupportProps<
56
56
  export type DroplistProps = {
57
57
  /** Ссылка на элемент-триггер для дроплиста */
58
58
  triggerElemRef?: RefObject<HTMLElement>;
59
+ /** Ссылка на элемент выпадающего списка */
60
+ listRef?: RefObject<HTMLElement>;
61
+ /**
62
+ * Закрывать выпадающий список после клика на базовый айтем.
63
+ *
64
+ * Работает в режимах selection: 'none' | 'single'
65
+ *
66
+ * @default false
67
+ */
68
+ closeDroplistOnItemClick?: boolean;
59
69
 
60
70
  /** Триггер для дроплиста
61
71
  * @type ReactNode | ({onKeyDown}) => ReactNode