@snack-uikit/list 0.11.0 → 0.11.1

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 (125) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +22 -61
  3. package/dist/components/Items/AccordionItem/AccordionItem.d.ts +4 -2
  4. package/dist/components/Items/AccordionItem/AccordionItem.js +18 -16
  5. package/dist/components/Items/BaseItem/BaseItem.d.ts +8 -4
  6. package/dist/components/Items/BaseItem/BaseItem.js +20 -14
  7. package/dist/components/Items/BaseItem/styles.module.css +20 -119
  8. package/dist/components/Items/GroupItem/GroupItem.d.ts +5 -1
  9. package/dist/components/Items/GroupItem/GroupItem.js +1 -3
  10. package/dist/components/Items/GroupSelectItem/GroupSelectItem.d.ts +4 -0
  11. package/dist/components/Items/GroupSelectItem/GroupSelectItem.js +18 -0
  12. package/dist/components/Items/GroupSelectItem/index.d.ts +1 -0
  13. package/dist/components/Items/GroupSelectItem/index.js +1 -0
  14. package/dist/components/Items/NextListItem/NextListItem.d.ts +6 -2
  15. package/dist/components/Items/NextListItem/NextListItem.js +55 -45
  16. package/dist/components/Items/NextListItem/constants.d.ts +2 -0
  17. package/dist/components/Items/NextListItem/constants.js +10 -0
  18. package/dist/components/Items/PinGroupItem/PinGroupItem.js +7 -3
  19. package/dist/components/Items/PinGroupItem/styles.module.css +26 -14
  20. package/dist/components/Items/SearchItem/SearchItem.d.ts +3 -1
  21. package/dist/components/Items/SearchItem/SearchItem.js +5 -8
  22. package/dist/components/Items/hooks.d.ts +11 -10
  23. package/dist/components/Items/hooks.js +64 -46
  24. package/dist/components/Items/index.d.ts +1 -5
  25. package/dist/components/Items/index.js +1 -5
  26. package/dist/components/Items/styles.module.css +33 -26
  27. package/dist/components/Items/types.d.ts +68 -34
  28. package/dist/components/Items/utils.d.ts +34 -16
  29. package/dist/components/Items/utils.js +135 -37
  30. package/dist/components/Lists/Droplist/DropList.d.ts +1 -1
  31. package/dist/components/Lists/Droplist/DropList.js +78 -50
  32. package/dist/components/Lists/List/List.d.ts +5 -9
  33. package/dist/components/Lists/List/List.js +61 -54
  34. package/dist/components/Lists/ListPrivate/ListPrivate.d.ts +6 -20
  35. package/dist/components/Lists/ListPrivate/ListPrivate.js +9 -8
  36. package/dist/components/Lists/ListPrivate/styles.module.css +1 -0
  37. package/dist/components/Lists/contexts/CollapseProvider.d.ts +13 -1
  38. package/dist/components/Lists/contexts/CollapseProvider.js +2 -0
  39. package/dist/components/Lists/contexts/FocusListProvider.d.ts +9 -0
  40. package/dist/components/Lists/contexts/FocusListProvider.js +5 -0
  41. package/dist/components/Lists/contexts/NewListProvider.d.ts +32 -0
  42. package/dist/components/Lists/contexts/{ListProvider.js → NewListProvider.js} +10 -5
  43. package/dist/components/Lists/contexts/SelectionProvider.d.ts +25 -15
  44. package/dist/components/Lists/contexts/SelectionProvider.js +17 -13
  45. package/dist/components/Lists/contexts/index.d.ts +3 -2
  46. package/dist/components/Lists/contexts/index.js +3 -2
  47. package/dist/components/Lists/hooks.d.ts +10 -12
  48. package/dist/components/Lists/hooks.js +73 -40
  49. package/dist/components/Lists/index.d.ts +2 -1
  50. package/dist/components/Lists/index.js +1 -0
  51. package/dist/components/Lists/types.d.ts +12 -14
  52. package/dist/components/index.d.ts +2 -2
  53. package/dist/components/index.js +1 -1
  54. package/dist/helperComponents/ItemContent/ItemContent.d.ts +15 -0
  55. package/dist/helperComponents/ItemContent/ItemContent.js +23 -0
  56. package/dist/helperComponents/ItemContent/index.d.ts +1 -0
  57. package/dist/helperComponents/ItemContent/index.js +1 -0
  58. package/dist/helperComponents/ItemContent/styles.module.css +117 -0
  59. package/dist/helperComponents/ListEmptyState/ListEmptyState.d.ts +2 -2
  60. package/dist/helperComponents/ListEmptyState/ListEmptyState.js +13 -4
  61. package/dist/helperComponents/Separator/Separator.d.ts +8 -2
  62. package/dist/helperComponents/Separator/Separator.js +22 -2
  63. package/dist/helperComponents/Separator/constants.d.ts +2 -0
  64. package/dist/helperComponents/Separator/constants.js +5 -0
  65. package/dist/helperComponents/Separator/styles.module.css +20 -1
  66. package/dist/helperComponents/index.d.ts +1 -0
  67. package/dist/helperComponents/index.js +1 -0
  68. package/dist/index.d.ts +2 -2
  69. package/dist/index.js +2 -2
  70. package/dist/types.d.ts +1 -1
  71. package/package.json +13 -18
  72. package/src/components/Items/AccordionItem/AccordionItem.tsx +24 -20
  73. package/src/components/Items/BaseItem/BaseItem.tsx +91 -82
  74. package/src/components/Items/BaseItem/styles.module.scss +19 -77
  75. package/src/components/Items/GroupItem/GroupItem.tsx +5 -5
  76. package/src/components/Items/GroupSelectItem/GroupSelectItem.tsx +44 -0
  77. package/src/components/Items/GroupSelectItem/index.ts +1 -0
  78. package/src/components/Items/NextListItem/NextListItem.tsx +88 -85
  79. package/src/components/Items/NextListItem/constants.ts +12 -0
  80. package/src/components/Items/PinGroupItem/PinGroupItem.tsx +14 -4
  81. package/src/components/Items/PinGroupItem/styles.module.scss +14 -14
  82. package/src/components/Items/SearchItem/SearchItem.tsx +6 -8
  83. package/src/components/Items/hooks.tsx +86 -66
  84. package/src/components/Items/index.ts +1 -5
  85. package/src/components/Items/styles.module.scss +36 -25
  86. package/src/components/Items/types.ts +91 -42
  87. package/src/components/Items/utils.ts +199 -52
  88. package/src/components/Lists/Droplist/DropList.tsx +155 -98
  89. package/src/components/Lists/List/List.tsx +129 -104
  90. package/src/components/Lists/ListPrivate/ListPrivate.tsx +19 -20
  91. package/src/components/Lists/ListPrivate/styles.module.scss +1 -0
  92. package/src/components/Lists/contexts/CollapseProvider.tsx +22 -0
  93. package/src/components/Lists/contexts/FocusListProvider.tsx +15 -0
  94. package/src/components/Lists/contexts/NewListProvider.tsx +54 -0
  95. package/src/components/Lists/contexts/SelectionProvider.tsx +56 -39
  96. package/src/components/Lists/contexts/index.ts +3 -2
  97. package/src/components/Lists/hooks.ts +124 -75
  98. package/src/components/Lists/index.ts +2 -2
  99. package/src/components/Lists/types.ts +20 -24
  100. package/src/components/index.ts +17 -2
  101. package/src/helperComponents/ItemContent/ItemContent.tsx +63 -0
  102. package/src/helperComponents/ItemContent/index.ts +1 -0
  103. package/src/helperComponents/ItemContent/styles.module.scss +78 -0
  104. package/src/helperComponents/ListEmptyState/ListEmptyState.tsx +26 -12
  105. package/src/helperComponents/Separator/Separator.tsx +49 -3
  106. package/src/helperComponents/Separator/constants.ts +7 -0
  107. package/src/helperComponents/Separator/styles.module.scss +41 -1
  108. package/src/helperComponents/index.ts +1 -0
  109. package/src/index.ts +4 -2
  110. package/src/types.ts +1 -1
  111. package/dist/components/Items/NextListItem/hooks.d.ts +0 -19
  112. package/dist/components/Items/NextListItem/hooks.js +0 -80
  113. package/dist/components/Lists/contexts/ListProvider.d.ts +0 -17
  114. package/dist/components/Lists/contexts/ParentListProvider.d.ts +0 -18
  115. package/dist/components/Lists/contexts/ParentListProvider.js +0 -12
  116. package/dist/hooks.d.ts +0 -5
  117. package/dist/hooks.js +0 -15
  118. package/dist/utils.d.ts +0 -30
  119. package/dist/utils.js +0 -115
  120. package/src/components/Items/NextListItem/hooks.ts +0 -105
  121. package/src/components/Lists/contexts/CollapseProvider.ts +0 -9
  122. package/src/components/Lists/contexts/ListProvider.tsx +0 -28
  123. package/src/components/Lists/contexts/ParentListProvider.tsx +0 -35
  124. package/src/hooks.ts +0 -27
  125. package/src/utils.ts +0 -175
@@ -1,20 +1,24 @@
1
+ import cn from 'classnames';
2
+ import mergeRefs from 'merge-refs';
1
3
  import { cloneElement, isValidElement, KeyboardEvent, ReactNode, useCallback, useMemo, useRef } from 'react';
2
- import { useUncontrolledProp } from 'uncontrollable';
3
4
 
4
- import { Dropdown } from '@snack-uikit/dropdown';
5
+ import { Dropdown, DropdownProps } from '@snack-uikit/dropdown';
6
+ import { useValueControl } from '@snack-uikit/utils';
5
7
 
6
- import { extractItemIds, extractItemRefs, withCollapsedItems } from '../../../utils';
7
- import { addItemsIds, getSlicedItems, useItemsWithIds } from '../../Items';
8
- import { extractSelectionProps, ParentListContext, SelectionProvider } from '../contexts';
9
- import { useKeyboardNavigation } from '../hooks';
8
+ import { extractActiveItems, ItemId, kindFlattenItems, useCreateBaseItems } from '../../Items';
9
+ import { CollapseContext, FocusListContext, NewListContextProvider, SelectionProvider } from '../contexts';
10
+ import { useNewKeyboardNavigation } from '../hooks';
10
11
  import { ListPrivate } from '../ListPrivate';
12
+ import styles from '../styles.module.scss';
11
13
  import { DroplistProps } from '../types';
12
14
 
15
+ const DEFAULT_FALLBACK_PLACEMENTS: DropdownProps['fallbackPlacements'] = ['top', 'right', 'bottom', 'left'];
16
+
13
17
  export function Droplist({
14
18
  items: itemsProp,
15
19
  search,
16
- pinBottom,
17
- pinTop,
20
+ pinBottom: pinBottomProp = [],
21
+ pinTop: pinTopProp = [],
18
22
  footerActiveElementsRefs,
19
23
  children,
20
24
  trigger,
@@ -24,65 +28,103 @@ export function Droplist({
24
28
  open: openProp,
25
29
  onOpenChange,
26
30
  collapse = {},
31
+ triggerClassName,
32
+ selection,
33
+ contentRender,
34
+ size = 's',
35
+ marker = true,
36
+ className,
27
37
  ...props
28
38
  }: DroplistProps) {
29
39
  const hasSearch = useMemo(() => Boolean(search), [search]);
30
40
 
31
- const memorizedItems = useMemo(
32
- () => addItemsIds((pinTop ?? []).concat(itemsProp).concat(pinBottom ?? [])),
33
- [itemsProp, pinBottom, pinTop],
34
- );
35
-
36
- const [openCollapsedItems, setOpenCollapsedItems] = useUncontrolledProp<Array<number | string>>(
37
- collapse.value,
38
- collapse.defaultValue ?? [],
39
- collapse.onChange,
41
+ const [openCollapseItems = [], setOpenCollapsedItems] = useValueControl<ItemId[] | undefined>(collapse);
42
+ const toggleOpenCollapseItem = useCallback(
43
+ (id: ItemId) =>
44
+ setOpenCollapsedItems((items: ItemId[]) =>
45
+ items?.includes(id) ? items.filter(item => item !== id) : (items ?? []).concat([id]),
46
+ ),
47
+ [setOpenCollapsedItems],
40
48
  );
41
49
 
42
- const { search: searchItem, footerRefs } = useItemsWithIds({
43
- search: hasSearch,
44
- footerActiveElementsRefs,
50
+ const [open = false, setOpen] = useValueControl<boolean>({
51
+ value: openProp,
52
+ defaultValue: false,
53
+ onChange: onOpenChange,
45
54
  });
46
55
 
47
- const { items, itemRefs, ids, expandedIds } = useMemo(() => {
48
- const res = withCollapsedItems({
49
- items: memorizedItems,
50
- openCollapsedItems,
56
+ const { searchItem, footerItems } = useCreateBaseItems({ footerActiveElementsRefs });
57
+ /**
58
+ * Объект с пропсами всех вложенных айтемов; ключ id
59
+ */
60
+ const { flattenItems, focusFlattenItems, ...memorizedItems } = useMemo(() => {
61
+ const pinTop = kindFlattenItems({ items: pinTopProp, prefix: '~pinTop', parentId: '~main' });
62
+ const items = kindFlattenItems({ items: itemsProp, prefix: '~main', parentId: '~main' });
63
+ const pinBottom = kindFlattenItems({ items: pinBottomProp, prefix: '~pinBottom', parentId: '~main' });
64
+
65
+ const flattenItems = { ...pinTop.flattenItems, ...pinBottom.flattenItems, ...items.flattenItems };
66
+ const focusFlattenItems = {
67
+ ...pinTop.focusFlattenItems,
68
+ ...pinBottom.focusFlattenItems,
69
+ ...items.focusFlattenItems,
70
+ };
71
+
72
+ [...footerItems, searchItem].forEach(item => {
73
+ flattenItems[item.id] = item;
74
+ focusFlattenItems[item.id] = { ...item, originalId: item.id, items: [], key: item.id, allChildIds: [] };
51
75
  });
52
76
 
53
- const items = searchItem.concat(res.items).concat(footerRefs);
77
+ return { items, pinTop, pinBottom, flattenItems, focusFlattenItems };
78
+ }, [itemsProp, pinTopProp, pinBottomProp, searchItem, footerItems]);
79
+
80
+ const { ids, expandedIds } = useMemo(() => {
81
+ const { pinTop, items, pinBottom } = memorizedItems;
82
+
83
+ let ids: ItemId[] = [];
84
+ let expandedIds: ItemId[] = [];
85
+
86
+ if (hasSearch) {
87
+ ids.push(searchItem.id);
88
+ }
89
+
90
+ [pinTop, items, pinBottom].forEach(({ focusFlattenItems, focusCloseChildIds }) => {
91
+ const activeItems = extractActiveItems({
92
+ focusFlattenItems,
93
+ focusCloseChildIds,
94
+ openCollapseItems,
95
+ isSelectionMultiple: selection?.mode === 'multiple',
96
+ });
97
+
98
+ ids = ids.concat(activeItems.ids);
99
+ expandedIds = expandedIds.concat(activeItems.expandedIds);
100
+ });
101
+
102
+ footerItems.forEach(footerItem => {
103
+ ids.push(footerItem.id);
104
+ });
54
105
 
55
106
  return {
56
- items,
57
- ids: extractItemIds(searchItem).concat(res.ids).concat(extractItemIds(footerRefs)),
58
- itemRefs: extractItemRefs(searchItem).concat(res.itemRefs).concat(extractItemRefs(footerRefs)),
59
- expandedIds: res.expandedIds,
107
+ ids,
108
+ expandedIds,
60
109
  };
61
- }, [footerRefs, memorizedItems, openCollapsedItems, searchItem]);
62
-
63
- const slicedItems = useMemo(
64
- () => getSlicedItems({ items, hasSearch, pinTop, pinBottom, footerRefs }),
65
- [items, hasSearch, pinTop, pinBottom, footerRefs],
66
- );
110
+ }, [footerItems, hasSearch, memorizedItems, openCollapseItems, searchItem.id, selection?.mode]);
67
111
 
68
112
  const triggerElemRef = useRef<HTMLElement>(null);
69
- const listRef = useRef<HTMLUListElement>(null);
113
+ const listRef = useRef<HTMLElement>(null);
70
114
 
71
- const {
72
- activeFocusIndex,
73
- setActiveFocusIndex,
74
- openNestedIndex,
75
- handleListKeyDown,
76
- resetNestedIndex,
77
- resetActiveFocusIndex,
78
- } = useKeyboardNavigation({ ids, expandedIds, parentRef: triggerElemRefProp ?? triggerElemRef, itemRefs });
115
+ const { handleListKeyDownFactory, resetActiveItemId, activeItemId, forceUpdateActiveItemId } =
116
+ useNewKeyboardNavigation({
117
+ mainRef: triggerElemRef,
118
+ focusFlattenItems,
119
+ });
79
120
 
80
- const [open, setOpen] = useUncontrolledProp<boolean>(openProp, false, onOpenChange);
121
+ const handleListKeyDown = useCallback(
122
+ (e: KeyboardEvent<HTMLElement>) => handleListKeyDownFactory(ids, expandedIds)(e),
123
+ [handleListKeyDownFactory, ids, expandedIds],
124
+ );
81
125
 
82
126
  const handleOpenChange = (open: boolean) => {
83
- resetNestedIndex();
84
- resetActiveFocusIndex();
85
- setOpenCollapsedItems([]);
127
+ resetActiveItemId();
86
128
 
87
129
  setOpen(open);
88
130
  };
@@ -94,14 +136,18 @@ export function Droplist({
94
136
  setOpen(true);
95
137
 
96
138
  setTimeout(() => {
139
+ resetActiveItemId();
97
140
  listRef.current?.focus();
98
- setActiveFocusIndex(0);
99
141
  }, 0);
100
142
  }
101
143
 
144
+ if (e.key === 'ArrowUp') {
145
+ setOpen(false);
146
+ }
147
+
102
148
  cb?.(e);
103
149
  },
104
- [setActiveFocusIndex, setOpen],
150
+ [resetActiveItemId, setOpen],
105
151
  );
106
152
 
107
153
  const triggerElem: ReactNode = useMemo(() => {
@@ -124,54 +170,65 @@ export function Droplist({
124
170
  }, [onKeyDown, children]);
125
171
 
126
172
  return (
127
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
128
- // @ts-ignore
129
- <SelectionProvider {...extractSelectionProps(props)}>
130
- <ParentListContext.Provider
131
- value={{
132
- parentIds: ids,
133
- parentActiveFocusIndex: activeFocusIndex,
134
- parentExpandedIds: expandedIds,
135
- parentItemRefs: itemRefs,
136
- parentOpenNestedIndex: openNestedIndex,
137
- triggerRef: triggerElemRefProp ?? triggerElemRef,
138
- parentRef: listRef,
139
- parentResetNestedIndex: resetNestedIndex,
140
- parentResetActiveFocusIndex: resetActiveFocusIndex,
141
- openCollapsedItems,
142
- toggleOpenCollapsedItems: id =>
143
- setOpenCollapsedItems((items: Array<string | number> | undefined = []) =>
144
- items.includes(id) ? items.filter(item => item !== id) : items?.concat([id]),
145
- ),
146
- }}
147
- >
148
- <Dropdown
149
- content={
150
- <ListPrivate
151
- {...slicedItems}
152
- {...props}
153
- onKeyDown={handleListKeyDown}
154
- tabIndex={0}
155
- ref={listRef}
156
- search={search}
157
- limitedScrollHeight
158
- parent='droplist'
159
- />
160
- }
161
- trigger={trigger}
162
- placement={placement}
163
- widthStrategy={widthStrategy}
164
- {...(triggerElemRefProp
165
- ? {}
166
- : {
167
- triggerRef: triggerElemRef,
168
- })}
169
- open={open}
170
- onOpenChange={handleOpenChange}
173
+ <NewListContextProvider
174
+ flattenItems={flattenItems}
175
+ focusFlattenItems={focusFlattenItems}
176
+ contentRender={contentRender}
177
+ size={size}
178
+ marker={marker}
179
+ >
180
+ <SelectionProvider {...selection}>
181
+ <CollapseContext.Provider
182
+ value={{
183
+ openCollapseItems,
184
+ toggleOpenCollapseItem,
185
+ }}
171
186
  >
172
- {triggerElem}
173
- </Dropdown>
174
- </ParentListContext.Provider>
175
- </SelectionProvider>
187
+ <FocusListContext.Provider
188
+ value={{
189
+ activeItemId,
190
+ handleListKeyDownFactory,
191
+ forceUpdateActiveItemId,
192
+ }}
193
+ >
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}
226
+ >
227
+ {triggerElem}
228
+ </Dropdown>
229
+ </FocusListContext.Provider>
230
+ </CollapseContext.Provider>
231
+ </SelectionProvider>
232
+ </NewListContextProvider>
176
233
  );
177
234
  }
@@ -1,149 +1,174 @@
1
1
  import cn from 'classnames';
2
2
  import mergeRefs from 'merge-refs';
3
- import { FocusEvent, forwardRef, KeyboardEvent, useMemo, useRef } from 'react';
4
- import { useUncontrolledProp } from 'uncontrollable';
3
+ import { ForwardedRef, forwardRef, KeyboardEvent, useCallback, useMemo, useRef } from 'react';
4
+
5
+ import { useValueControl } from '@snack-uikit/utils';
5
6
 
6
7
  import { HiddenTabButton } from '../../../helperComponents';
7
- import { extractItemIds, extractItemRefs, withCollapsedItems } from '../../../utils';
8
- import { addItemsIds, getSlicedItems, useItemsWithIds } from '../../Items';
9
- import { extractSelectionProps, ParentListContext, SelectionProvider, useParentListContext } from '../contexts';
10
- import { useKeyboardNavigation } from '../hooks';
8
+ import { extractActiveItems, ItemId, kindFlattenItems, useCreateBaseItems } from '../../Items';
9
+ import { CollapseContext, FocusListContext, NewListContextProvider, SelectionProvider } from '../contexts';
10
+ import { useNewKeyboardNavigation } from '../hooks';
11
11
  import { ListPrivate } from '../ListPrivate';
12
12
  import styles from '../styles.module.scss';
13
13
  import { ListProps } from '../types';
14
14
 
15
- export const List = forwardRef<HTMLElement, ListProps>(
15
+ export const List = forwardRef(
16
16
  (
17
17
  {
18
- items: itemsProp,
18
+ items: itemsProp = [],
19
19
  search,
20
- pinBottom,
21
- pinTop,
20
+ pinBottom: pinBottomProp = [],
21
+ pinTop: pinTopProp = [],
22
22
  footerActiveElementsRefs,
23
23
  onKeyDown,
24
24
  tabIndex = 0,
25
25
  className,
26
26
  collapse = {},
27
+ selection,
28
+ contentRender,
29
+ size = 's',
30
+ marker = true,
27
31
  ...props
28
- },
29
- ref,
32
+ }: ListProps,
33
+ ref: ForwardedRef<HTMLElement>,
30
34
  ) => {
31
35
  const hasSearch = useMemo(() => Boolean(search), [search]);
32
36
 
33
- const memorizedItems = useMemo(
34
- () => addItemsIds((pinTop ?? []).concat(itemsProp).concat(pinBottom ?? [])),
35
- [itemsProp, pinBottom, pinTop],
37
+ const [openCollapseItems = [], setOpenCollapsedItems] = useValueControl<ItemId[] | undefined>(collapse);
38
+ const toggleOpenCollapseItem = useCallback(
39
+ (id: ItemId) =>
40
+ setOpenCollapsedItems((items: ItemId[]) =>
41
+ items?.includes(id) ? items.filter(item => item !== id) : (items ?? []).concat([id]),
42
+ ),
43
+ [setOpenCollapsedItems],
36
44
  );
37
45
 
38
- const [openCollapsedItems, setOpenCollapsedItems] = useUncontrolledProp<Array<number | string>>(
39
- collapse.value,
40
- collapse.defaultValue ?? [],
41
- collapse.onChange,
42
- );
46
+ const { searchItem, footerItems } = useCreateBaseItems({ footerActiveElementsRefs });
47
+
48
+ /**
49
+ * Объект с пропсами всех вложенных айтемов; ключ id
50
+ */
51
+ const { flattenItems, focusFlattenItems, ...memorizedItems } = useMemo(() => {
52
+ const pinTop = kindFlattenItems({ items: pinTopProp, prefix: '~pinTop', parentId: '~main' });
53
+ const items = kindFlattenItems({ items: itemsProp, prefix: '~main', parentId: '~main' });
54
+ const pinBottom = kindFlattenItems({ items: pinBottomProp, prefix: '~pinBottom', parentId: '~main' });
55
+
56
+ const flattenItems = { ...pinTop.flattenItems, ...pinBottom.flattenItems, ...items.flattenItems };
57
+ const focusFlattenItems = {
58
+ ...pinTop.focusFlattenItems,
59
+ ...pinBottom.focusFlattenItems,
60
+ ...items.focusFlattenItems,
61
+ };
43
62
 
44
- const { search: searchItem, footerRefs } = useItemsWithIds({
45
- search: hasSearch,
46
- footerActiveElementsRefs,
47
- });
63
+ [...footerItems, searchItem].forEach(item => {
64
+ flattenItems[item.id] = item;
65
+ focusFlattenItems[item.id] = { ...item, originalId: item.id, items: [], key: item.id, allChildIds: [] };
66
+ });
67
+
68
+ return { items, pinTop, pinBottom, flattenItems, focusFlattenItems };
69
+ }, [itemsProp, pinTopProp, pinBottomProp, searchItem, footerItems]);
70
+
71
+ const { ids, expandedIds } = useMemo(() => {
72
+ const { pinTop, items, pinBottom } = memorizedItems;
73
+
74
+ let ids: ItemId[] = [];
75
+ let expandedIds: ItemId[] = [];
76
+
77
+ if (hasSearch) {
78
+ ids.push(searchItem.id);
79
+ }
48
80
 
49
- const { items, itemRefs, ids, expandedIds } = useMemo(() => {
50
- const res = withCollapsedItems({
51
- items: memorizedItems,
52
- openCollapsedItems,
81
+ [pinTop, items, pinBottom].forEach(({ focusFlattenItems, focusCloseChildIds }) => {
82
+ const activeItems = extractActiveItems({
83
+ focusFlattenItems,
84
+ focusCloseChildIds,
85
+ openCollapseItems,
86
+ isSelectionMultiple: selection?.mode === 'multiple',
87
+ });
88
+
89
+ ids = ids.concat(activeItems.ids);
90
+ expandedIds = expandedIds.concat(activeItems.expandedIds);
53
91
  });
54
92
 
55
- const items = searchItem.concat(res.items).concat(footerRefs);
93
+ footerItems.forEach(footerItem => {
94
+ ids.push(footerItem.id);
95
+ });
56
96
 
57
97
  return {
58
- items,
59
- ids: extractItemIds(searchItem).concat(res.ids).concat(extractItemIds(footerRefs)),
60
- itemRefs: extractItemRefs(searchItem).concat(res.itemRefs).concat(extractItemRefs(footerRefs)),
61
- expandedIds: res.expandedIds,
98
+ ids,
99
+ expandedIds,
62
100
  };
63
- }, [footerRefs, memorizedItems, openCollapsedItems, searchItem]);
64
-
65
- const slicedItems = useMemo(
66
- () => getSlicedItems({ items, hasSearch, pinTop, pinBottom, footerRefs }),
67
- [items, hasSearch, pinTop, pinBottom, footerRefs],
68
- );
101
+ }, [footerItems, hasSearch, memorizedItems, openCollapseItems, searchItem.id, selection?.mode]);
69
102
 
70
103
  const listRef = useRef<HTMLElement>(null);
71
104
  const btnRef = useRef<HTMLButtonElement>(null);
72
105
 
73
- const { triggerRef } = useParentListContext();
74
-
75
- const {
76
- activeFocusIndex,
77
- openNestedIndex,
78
- handleListKeyDown,
79
- resetNestedIndex,
80
- resetActiveFocusIndex,
81
- setActiveFocusIndex,
82
- } = useKeyboardNavigation({
83
- ids,
84
- expandedIds,
85
- parentRef: listRef,
86
- btnRef,
87
- itemRefs,
88
- });
89
-
90
- const isActive = listRef.current === document.activeElement && activeFocusIndex === -1 && openNestedIndex === -1;
106
+ const { handleListKeyDownFactory, activeItemId, resetActiveItemId, forceUpdateActiveItemId } =
107
+ useNewKeyboardNavigation({
108
+ mainRef: listRef,
109
+ btnRef,
110
+ focusFlattenItems,
111
+ });
112
+
113
+ const handleListKeyDown = useCallback(
114
+ (e: KeyboardEvent<HTMLElement>) => handleListKeyDownFactory(ids, expandedIds)(e),
115
+ [handleListKeyDownFactory, ids, expandedIds],
116
+ );
117
+
118
+ const isActive = listRef.current === document.activeElement && activeItemId === undefined;
91
119
 
92
120
  const mergedHandlerKeyDown = (e: KeyboardEvent<HTMLElement>) => {
93
121
  onKeyDown?.(e);
94
122
  handleListKeyDown?.(e);
95
123
  };
96
124
 
97
- const handleOnFocus = (e: FocusEvent<HTMLElement>) => {
98
- if (
99
- e.relatedTarget === null ||
100
- (e.relatedTarget && itemRefs.every(({ current }) => current !== e.relatedTarget))
101
- ) {
102
- resetActiveFocusIndex();
103
- }
125
+ const handleOnFocus = () => {
126
+ resetActiveItemId();
104
127
  };
105
128
 
106
129
  return (
107
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
108
- // @ts-ignore
109
- <SelectionProvider {...extractSelectionProps(props)}>
110
- <ParentListContext.Provider
111
- value={{
112
- parentIds: ids,
113
- parentActiveFocusIndex: activeFocusIndex,
114
- parentExpandedIds: expandedIds,
115
- parentItemRefs: itemRefs,
116
- parentOpenNestedIndex: openNestedIndex,
117
- triggerRef: triggerRef ?? listRef,
118
- parentRef: listRef,
119
-
120
- parentSetActiveFocusIndex: setActiveFocusIndex,
121
- parentResetNestedIndex: resetNestedIndex,
122
- parentResetActiveFocusIndex: resetActiveFocusIndex,
123
- openCollapsedItems,
124
- toggleOpenCollapsedItems: id =>
125
- setOpenCollapsedItems((items: Array<string | number> | undefined = []) =>
126
- items.includes(id) ? items.filter(item => item !== id) : items?.concat([id]),
127
- ),
128
- }}
129
- >
130
- <div className={cn(styles.wrapper, className)} data-active={isActive || undefined}>
131
- <ListPrivate
132
- {...props}
133
- {...slicedItems}
134
- ref={mergeRefs(ref, listRef)}
135
- onFocus={handleOnFocus}
136
- onKeyDown={mergedHandlerKeyDown}
137
- tabIndex={tabIndex}
138
- search={search}
139
- nested={false}
140
- parent='list'
141
- />
142
-
143
- <HiddenTabButton ref={btnRef} listRef={listRef} tabIndex={tabIndex} />
144
- </div>
145
- </ParentListContext.Provider>
146
- </SelectionProvider>
130
+ <NewListContextProvider
131
+ flattenItems={flattenItems}
132
+ focusFlattenItems={focusFlattenItems}
133
+ contentRender={contentRender}
134
+ size={size}
135
+ marker={marker}
136
+ >
137
+ <SelectionProvider {...selection}>
138
+ <CollapseContext.Provider
139
+ value={{
140
+ openCollapseItems,
141
+ toggleOpenCollapseItem,
142
+ }}
143
+ >
144
+ <FocusListContext.Provider
145
+ value={{
146
+ activeItemId,
147
+ handleListKeyDownFactory,
148
+ forceUpdateActiveItemId,
149
+ }}
150
+ >
151
+ <div className={cn(styles.wrapper, className)} data-active={isActive || undefined}>
152
+ <ListPrivate
153
+ {...props}
154
+ items={memorizedItems.items.focusCloseChildIds}
155
+ pinTop={memorizedItems.pinTop.focusCloseChildIds}
156
+ pinBottom={memorizedItems.pinBottom.focusCloseChildIds}
157
+ searchItem={searchItem}
158
+ ref={mergeRefs(ref, listRef)}
159
+ onFocus={handleOnFocus}
160
+ onKeyDown={mergedHandlerKeyDown}
161
+ tabIndex={tabIndex}
162
+ search={search}
163
+ nested={false}
164
+ />
165
+
166
+ <HiddenTabButton ref={btnRef} listRef={listRef} tabIndex={tabIndex} />
167
+ </div>
168
+ </FocusListContext.Provider>
169
+ </CollapseContext.Provider>
170
+ </SelectionProvider>
171
+ </NewListContextProvider>
147
172
  );
148
173
  },
149
174
  );